add: forges ui
TODO: forges tickets feed, posts open/closed state
This commit is contained in:
parent
5b1db42c51
commit
a6140f7c8c
40 changed files with 1664 additions and 1270 deletions
|
@ -12,12 +12,12 @@ default = ["sqlite", "redis"]
|
|||
[dependencies]
|
||||
pathbufd = "0.1.4"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
toml = "0.8.22"
|
||||
toml = "0.8.23"
|
||||
tetratto-shared = { path = "../shared" }
|
||||
tetratto-l10n = { path = "../l10n" }
|
||||
serde_json = "1.0.140"
|
||||
totp-rs = { version = "5.7.0", features = ["qr", "gen_secret"] }
|
||||
reqwest = { version = "0.12.18", features = ["json"] }
|
||||
reqwest = { version = "0.12.19", features = ["json"] }
|
||||
bitflags = "2.9.1"
|
||||
async-recursion = "1.1.1"
|
||||
md-5 = "0.10.6"
|
||||
|
@ -25,4 +25,4 @@ base16ct = { version = "0.2.0", features = ["alloc"] }
|
|||
base64 = "0.22.1"
|
||||
emojis = "0.6.4"
|
||||
regex = "1.11.1"
|
||||
oiseau = { version = "0.1.0", default-features = false }
|
||||
oiseau = { version = "0.1.2", default-features = false }
|
||||
|
|
|
@ -10,7 +10,10 @@ use crate::model::{
|
|||
};
|
||||
use pathbufd::PathBufD;
|
||||
use std::fs::{exists, remove_file};
|
||||
use tetratto_shared::hash::{hash_salted, salt};
|
||||
use tetratto_shared::{
|
||||
hash::{hash_salted, salt},
|
||||
unix_epoch_timestamp,
|
||||
};
|
||||
use crate::{auto_method, DataManager};
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
|
@ -18,8 +21,6 @@ use oiseau::SqliteRow;
|
|||
|
||||
#[cfg(feature = "postgres")]
|
||||
use oiseau::PostgresRow;
|
||||
#[cfg(feature = "postgres")]
|
||||
use tetratto_shared::unix_epoch_timestamp;
|
||||
|
||||
use oiseau::{execute, get, query_row, params};
|
||||
|
||||
|
|
|
@ -40,8 +40,10 @@ impl DataManager {
|
|||
// likes
|
||||
likes: get!(x->8(i32)) as isize,
|
||||
dislikes: get!(x->9(i32)) as isize,
|
||||
// counts
|
||||
// ...
|
||||
member_count: get!(x->10(i32)) as usize,
|
||||
is_forge: get!(x->11(i32)) as i8 == 1,
|
||||
post_count: get!(x->12(i32)) as usize,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,7 +53,10 @@ impl DataManager {
|
|||
}
|
||||
|
||||
if let Some(cached) = self.0.1.get(format!("atto.community:{}", id)).await {
|
||||
return Ok(serde_json::from_str(&cached).unwrap());
|
||||
match serde_json::from_str(&cached) {
|
||||
Ok(c) => return Ok(c),
|
||||
Err(_) => self.0.1.remove(format!("atto.community:{}", id)).await,
|
||||
};
|
||||
}
|
||||
|
||||
let conn = match self.0.connect().await {
|
||||
|
@ -89,7 +94,10 @@ impl DataManager {
|
|||
}
|
||||
|
||||
if let Some(cached) = self.0.1.get(format!("atto.community:{}", id)).await {
|
||||
return Ok(serde_json::from_str(&cached).unwrap());
|
||||
match serde_json::from_str(&cached) {
|
||||
Ok(c) => return Ok(c),
|
||||
Err(_) => self.0.1.remove(format!("atto.community:{}", id)).await,
|
||||
};
|
||||
}
|
||||
|
||||
let conn = match self.0.connect().await {
|
||||
|
@ -218,7 +226,7 @@ impl DataManager {
|
|||
return Err(Error::MiscError("This title cannot be used".to_string()));
|
||||
}
|
||||
|
||||
let regex = regex::RegexBuilder::new(r"[^\w_\-\.!]+")
|
||||
let regex = regex::RegexBuilder::new(NAME_REGEX)
|
||||
.multi_line(true)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
@ -259,6 +267,12 @@ impl DataManager {
|
|||
}
|
||||
}
|
||||
|
||||
// check is_forge
|
||||
// only supporters can CREATE forge communities... anybody can contribute to them
|
||||
if data.is_forge && !owner.permissions.check(FinePermission::SUPPORTER) {
|
||||
return Err(Error::RequiresSupporter);
|
||||
}
|
||||
|
||||
// make sure community doesn't already exist with title
|
||||
if self
|
||||
.get_community_by_title_no_void(&data.title.to_lowercase())
|
||||
|
@ -276,7 +290,7 @@ impl DataManager {
|
|||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO communities VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
|
||||
"INSERT INTO communities VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
|
@ -288,7 +302,9 @@ impl DataManager {
|
|||
&serde_json::to_string(&data.join_access).unwrap().as_str(),
|
||||
&0_i32,
|
||||
&0_i32,
|
||||
&1_i32
|
||||
&1_i32,
|
||||
&{ if data.is_forge { 1 } else { 0 } },
|
||||
&0_i32,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -532,4 +548,7 @@ impl DataManager {
|
|||
|
||||
auto_method!(incr_community_member_count()@get_community_by_id_no_void -> "UPDATE communities SET member_count = member_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_community --incr);
|
||||
auto_method!(decr_community_member_count()@get_community_by_id_no_void -> "UPDATE communities SET member_count = member_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_community --decr=member_count);
|
||||
|
||||
auto_method!(incr_community_post_count()@get_community_by_id_no_void -> "UPDATE communities SET post_count = post_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_community --incr);
|
||||
auto_method!(decr_community_post_count()@get_community_by_id_no_void -> "UPDATE communities SET post_count = post_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_community --decr=post_count);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::model::{
|
|||
communities_permissions::CommunityPermission, channels::Message,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use tetratto_shared::unix_epoch_timestamp;
|
||||
use crate::{auto_method, DataManager};
|
||||
|
||||
#[cfg(feature = "redis")]
|
||||
|
@ -18,8 +19,6 @@ use oiseau::SqliteRow;
|
|||
|
||||
#[cfg(feature = "postgres")]
|
||||
use oiseau::PostgresRow;
|
||||
#[cfg(feature = "postgres")]
|
||||
use tetratto_shared::unix_epoch_timestamp;
|
||||
|
||||
use oiseau::{execute, get, query_rows, params};
|
||||
|
||||
|
|
|
@ -872,7 +872,7 @@ impl DataManager {
|
|||
|
||||
let res = query_row!(
|
||||
&conn,
|
||||
"SELECT * FROM posts WHERE context LIKE $1 AND owner = $2 LIMIT 1",
|
||||
"SELECT * FROM posts WHERE context LIKE $1 AND owner = $2 AND is_deleted = 0 LIMIT 1",
|
||||
params![&format!("%\"answering\":{question}%"), &(owner as i64),],
|
||||
|x| { Ok(Self::get_post_from_row(x)) }
|
||||
);
|
||||
|
@ -1494,6 +1494,9 @@ impl DataManager {
|
|||
// increase user post count
|
||||
self.incr_user_post_count(data.owner).await?;
|
||||
|
||||
// increase community post count
|
||||
self.incr_community_post_count(data.community).await?;
|
||||
|
||||
// return
|
||||
Ok(data.id)
|
||||
}
|
||||
|
@ -1546,6 +1549,13 @@ impl DataManager {
|
|||
self.decr_user_post_count(y.owner).await?;
|
||||
}
|
||||
|
||||
// decr community post count
|
||||
let community = self.get_community_by_id_no_void(y.community).await?;
|
||||
|
||||
if community.post_count > 0 {
|
||||
self.decr_community_post_count(y.community).await?;
|
||||
}
|
||||
|
||||
// decr question answer count
|
||||
if y.context.answering != 0 {
|
||||
let question = self.get_question_by_id(y.context.answering).await?;
|
||||
|
@ -1622,12 +1632,19 @@ impl DataManager {
|
|||
self.decr_user_post_count(y.owner).await?;
|
||||
}
|
||||
|
||||
// decr community post count
|
||||
let community = self.get_community_by_id_no_void(y.community).await?;
|
||||
|
||||
if community.post_count > 0 {
|
||||
self.decr_community_post_count(y.community).await?;
|
||||
}
|
||||
|
||||
// decr question answer count
|
||||
if y.context.answering != 0 {
|
||||
let question = self.get_question_by_id(y.context.answering).await?;
|
||||
|
||||
if question.is_global {
|
||||
self.incr_question_answer_count(y.context.answering).await?;
|
||||
self.decr_question_answer_count(y.context.answering).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1644,12 +1661,15 @@ impl DataManager {
|
|||
// incr user post count
|
||||
self.incr_user_post_count(y.owner).await?;
|
||||
|
||||
// incr community post count
|
||||
self.incr_community_post_count(y.community).await?;
|
||||
|
||||
// incr question answer count
|
||||
if y.context.answering != 0 {
|
||||
let question = self.get_question_by_id(y.context.answering).await?;
|
||||
|
||||
if question.is_global {
|
||||
self.decr_question_answer_count(y.context.answering).await?;
|
||||
self.incr_question_answer_count(y.context.answering).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,10 @@ pub struct Community {
|
|||
// likes
|
||||
pub likes: isize,
|
||||
pub dislikes: isize,
|
||||
// counts
|
||||
// ...
|
||||
pub member_count: usize,
|
||||
pub is_forge: bool,
|
||||
pub post_count: usize,
|
||||
}
|
||||
|
||||
impl Community {
|
||||
|
@ -44,6 +46,8 @@ impl Community {
|
|||
likes: 0,
|
||||
dislikes: 0,
|
||||
member_count: 0,
|
||||
is_forge: false,
|
||||
post_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,6 +66,8 @@ impl Community {
|
|||
likes: 0,
|
||||
dislikes: 0,
|
||||
member_count: 0,
|
||||
is_forge: false,
|
||||
post_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,9 +90,6 @@ pub struct CommunityContext {
|
|||
/// `enable_titles` is required for this setting to work.
|
||||
#[serde(default)]
|
||||
pub require_titles: bool,
|
||||
/// The community's layout in the UI.
|
||||
#[serde(default)]
|
||||
pub layout: CommunityLayout,
|
||||
}
|
||||
|
||||
/// Who can read a [`Community`].
|
||||
|
@ -140,21 +143,6 @@ impl Default for CommunityJoinAccess {
|
|||
}
|
||||
}
|
||||
|
||||
/// The layout of the [`Community`]'s UI.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum CommunityLayout {
|
||||
/// The classic timeline-like layout.
|
||||
Classic,
|
||||
/// A GitHub-esque bug tracker layout.
|
||||
BugTracker,
|
||||
}
|
||||
|
||||
impl Default for CommunityLayout {
|
||||
fn default() -> Self {
|
||||
Self::Classic
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CommunityMembership {
|
||||
pub id: usize,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue