add: community topic endpoints
This commit is contained in:
parent
12fa80c7c0
commit
ef029c59b3
7 changed files with 201 additions and 28 deletions
|
@ -3,12 +3,14 @@ use axum::{
|
||||||
extract::Path,
|
extract::Path,
|
||||||
response::{IntoResponse, Redirect},
|
response::{IntoResponse, Redirect},
|
||||||
};
|
};
|
||||||
use crate::cookie::CookieJar;
|
use crate::{cookie::CookieJar, routes::api::v1::AddTopic};
|
||||||
use tetratto_core::model::{
|
use tetratto_core::model::{
|
||||||
auth::Notification,
|
auth::Notification,
|
||||||
communities::{Community, CommunityMembership},
|
communities::{Community, CommunityMembership, ForumTopic},
|
||||||
communities_permissions::CommunityPermission,
|
communities_permissions::CommunityPermission,
|
||||||
oauth, ApiReturn, Error,
|
oauth,
|
||||||
|
permissions::FinePermission,
|
||||||
|
ApiReturn, Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -269,7 +271,7 @@ pub async fn get_membership(
|
||||||
Err(e) => return Json(e.into()),
|
Err(e) => return Json(e.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if user.id != community.owner {
|
if user.id != community.owner && !user.permissions.check(FinePermission::MANAGE_MEMBERSHIPS) {
|
||||||
// only the owner can select community memberships
|
// only the owner can select community memberships
|
||||||
return Json(Error::NotAllowed.into());
|
return Json(Error::NotAllowed.into());
|
||||||
}
|
}
|
||||||
|
@ -523,3 +525,117 @@ pub async fn get_communities_request(
|
||||||
payload: Some(communities),
|
payload: Some(communities),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn add_topic_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
Path(id): Path<usize>,
|
||||||
|
Json(req): Json<AddTopic>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManage) {
|
||||||
|
Some(ua) => ua,
|
||||||
|
None => return Json(Error::NotAllowed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut community = match data.get_community_by_id(id).await {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !community.is_forum {
|
||||||
|
return Json(Error::DoesNotSupportField("community".to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (id, topic) = ForumTopic::new(req.title, req.description, req.color);
|
||||||
|
community.topics.insert(id, topic);
|
||||||
|
|
||||||
|
match data
|
||||||
|
.update_community_topics(id, &user, community.topics)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Community updated".to_string(),
|
||||||
|
payload: (),
|
||||||
|
}),
|
||||||
|
Err(e) => Json(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_topic_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
Path((id, topic_id)): Path<(usize, usize)>,
|
||||||
|
Json(req): Json<AddTopic>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManage) {
|
||||||
|
Some(ua) => ua,
|
||||||
|
None => return Json(Error::NotAllowed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut community = match data.get_community_by_id(id).await {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !community.is_forum {
|
||||||
|
return Json(Error::DoesNotSupportField("community".to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let topic = ForumTopic {
|
||||||
|
title: req.title,
|
||||||
|
description: req.description,
|
||||||
|
color: req.color,
|
||||||
|
};
|
||||||
|
|
||||||
|
community.topics.insert(topic_id, topic);
|
||||||
|
|
||||||
|
match data
|
||||||
|
.update_community_topics(id, &user, community.topics)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Community updated".to_string(),
|
||||||
|
payload: (),
|
||||||
|
}),
|
||||||
|
Err(e) => Json(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_topic_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
Path((id, topic_id)): Path<(usize, usize)>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::CommunityManage) {
|
||||||
|
Some(ua) => ua,
|
||||||
|
None => return Json(Error::NotAllowed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut community = match data.get_community_by_id(id).await {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !community.is_forum {
|
||||||
|
return Json(Error::DoesNotSupportField("community".to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
community.topics.remove(&topic_id);
|
||||||
|
|
||||||
|
match data
|
||||||
|
.update_community_topics(id, &user, community.topics)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Community updated".to_string(),
|
||||||
|
payload: (),
|
||||||
|
}),
|
||||||
|
Err(e) => Json(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -131,6 +131,18 @@ pub fn routes() -> Router {
|
||||||
"/communities/{id}/supports_titles",
|
"/communities/{id}/supports_titles",
|
||||||
get(communities::communities::supports_titles_request),
|
get(communities::communities::supports_titles_request),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/communities/{id}/topics",
|
||||||
|
post(communities::communities::add_topic_request),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/communities/{id}/topics/{id}",
|
||||||
|
post(communities::communities::update_topic_request),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/communities/{id}/topics/{id}",
|
||||||
|
delete(communities::communities::delete_topic_request),
|
||||||
|
)
|
||||||
// posts
|
// posts
|
||||||
.route("/posts", post(communities::posts::create_request))
|
.route("/posts", post(communities::posts::create_request))
|
||||||
.route("/posts/{id}", delete(communities::posts::delete_request))
|
.route("/posts/{id}", delete(communities::posts::delete_request))
|
||||||
|
@ -778,6 +790,13 @@ pub struct UpdateCommunityJoinAccess {
|
||||||
pub access: CommunityJoinAccess,
|
pub access: CommunityJoinAccess,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct AddTopic {
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
pub color: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct CreatePostPoll {
|
pub struct CreatePostPoll {
|
||||||
pub option_a: String,
|
pub option_a: String,
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
use super::common::NAME_REGEX;
|
use super::common::NAME_REGEX;
|
||||||
|
|
||||||
use oiseau::cache::Cache;
|
use oiseau::cache::Cache;
|
||||||
use crate::model::communities::{CommunityContext, CommunityJoinAccess, CommunityMembership};
|
use crate::{
|
||||||
use crate::model::communities_permissions::CommunityPermission;
|
auto_method, DataManager,
|
||||||
use crate::model::permissions::SecondaryPermission;
|
model::{
|
||||||
use crate::model::{
|
Error, Result,
|
||||||
Error, Result,
|
auth::User,
|
||||||
auth::User,
|
communities::{
|
||||||
communities::Community,
|
CommunityReadAccess, CommunityWriteAccess, ForumTopic, Community, CommunityContext,
|
||||||
communities::{CommunityReadAccess, CommunityWriteAccess},
|
CommunityJoinAccess, CommunityMembership,
|
||||||
permissions::FinePermission,
|
},
|
||||||
|
permissions::{FinePermission, SecondaryPermission},
|
||||||
|
communities_permissions::CommunityPermission,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use pathbufd::PathBufD;
|
use pathbufd::PathBufD;
|
||||||
use std::fs::{exists, remove_file};
|
use std::{
|
||||||
use crate::{auto_method, DataManager};
|
fs::{exists, remove_file},
|
||||||
|
collections::HashMap,
|
||||||
|
};
|
||||||
|
|
||||||
use oiseau::{PostgresRow, execute, get, query_row, query_rows, params};
|
use oiseau::{PostgresRow, execute, get, query_row, query_rows, params};
|
||||||
|
|
||||||
|
@ -29,14 +33,13 @@ impl DataManager {
|
||||||
read_access: serde_json::from_str(&get!(x->5(String))).unwrap(),
|
read_access: serde_json::from_str(&get!(x->5(String))).unwrap(),
|
||||||
write_access: serde_json::from_str(&get!(x->6(String))).unwrap(),
|
write_access: serde_json::from_str(&get!(x->6(String))).unwrap(),
|
||||||
join_access: serde_json::from_str(&get!(x->7(String))).unwrap(),
|
join_access: serde_json::from_str(&get!(x->7(String))).unwrap(),
|
||||||
// likes
|
|
||||||
likes: get!(x->8(i32)) as isize,
|
likes: get!(x->8(i32)) as isize,
|
||||||
dislikes: get!(x->9(i32)) as isize,
|
dislikes: get!(x->9(i32)) as isize,
|
||||||
// ...
|
|
||||||
member_count: get!(x->10(i32)) as usize,
|
member_count: get!(x->10(i32)) as usize,
|
||||||
is_forge: get!(x->11(i32)) as i8 == 1,
|
is_forge: get!(x->11(i32)) as i8 == 1,
|
||||||
post_count: get!(x->12(i32)) as usize,
|
post_count: get!(x->12(i32)) as usize,
|
||||||
is_forum: get!(x->13(i32)) as i8 == 1,
|
is_forum: get!(x->13(i32)) as i8 == 1,
|
||||||
|
topics: serde_json::from_str(&get!(x->14(String))).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,7 +285,7 @@ impl DataManager {
|
||||||
|
|
||||||
let res = execute!(
|
let res = execute!(
|
||||||
&conn,
|
&conn,
|
||||||
"INSERT INTO communities VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)",
|
"INSERT INTO communities VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)",
|
||||||
params![
|
params![
|
||||||
&(data.id as i64),
|
&(data.id as i64),
|
||||||
&(data.created as i64),
|
&(data.created as i64),
|
||||||
|
@ -298,6 +301,7 @@ impl DataManager {
|
||||||
&{ if data.is_forge { 1 } else { 0 } },
|
&{ if data.is_forge { 1 } else { 0 } },
|
||||||
&0_i32,
|
&0_i32,
|
||||||
&{ if data.is_forum { 1 } else { 0 } },
|
&{ if data.is_forum { 1 } else { 0 } },
|
||||||
|
&serde_json::to_string(&data.topics).unwrap().as_str(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -532,6 +536,7 @@ impl DataManager {
|
||||||
auto_method!(update_community_read_access(CommunityReadAccess)@get_community_by_id_no_void:FinePermission::MANAGE_COMMUNITIES; -> "UPDATE communities SET read_access = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_community);
|
auto_method!(update_community_read_access(CommunityReadAccess)@get_community_by_id_no_void:FinePermission::MANAGE_COMMUNITIES; -> "UPDATE communities SET read_access = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_community);
|
||||||
auto_method!(update_community_write_access(CommunityWriteAccess)@get_community_by_id_no_void:FinePermission::MANAGE_COMMUNITIES; -> "UPDATE communities SET write_access = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_community);
|
auto_method!(update_community_write_access(CommunityWriteAccess)@get_community_by_id_no_void:FinePermission::MANAGE_COMMUNITIES; -> "UPDATE communities SET write_access = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_community);
|
||||||
auto_method!(update_community_join_access(CommunityJoinAccess)@get_community_by_id_no_void:FinePermission::MANAGE_COMMUNITIES; -> "UPDATE communities SET join_access = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_community);
|
auto_method!(update_community_join_access(CommunityJoinAccess)@get_community_by_id_no_void:FinePermission::MANAGE_COMMUNITIES; -> "UPDATE communities SET join_access = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_community);
|
||||||
|
auto_method!(update_community_topics(HashMap<usize, ForumTopic>)@get_community_by_id_no_void:FinePermission::MANAGE_COMMUNITIES; -> "UPDATE communities SET topics = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_community);
|
||||||
|
|
||||||
auto_method!(incr_community_likes()@get_community_by_id_no_void -> "UPDATE communities SET likes = likes + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_community --incr);
|
auto_method!(incr_community_likes()@get_community_by_id_no_void -> "UPDATE communities SET likes = likes + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_community --incr);
|
||||||
auto_method!(incr_community_dislikes()@get_community_by_id_no_void -> "UPDATE communities SET dislikes = dislikes + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_community --incr);
|
auto_method!(incr_community_dislikes()@get_community_by_id_no_void -> "UPDATE communities SET dislikes = dislikes + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_community --incr);
|
||||||
|
|
|
@ -7,9 +7,11 @@ CREATE TABLE IF NOT EXISTS communities (
|
||||||
read_access TEXT NOT NULL,
|
read_access TEXT NOT NULL,
|
||||||
write_access TEXT NOT NULL,
|
write_access TEXT NOT NULL,
|
||||||
join_access TEXT NOT NULL,
|
join_access TEXT NOT NULL,
|
||||||
-- likes
|
|
||||||
likes INT NOT NULL,
|
likes INT NOT NULL,
|
||||||
dislikes INT NOT NULL,
|
dislikes INT NOT NULL,
|
||||||
-- counts
|
member_count INT NOT NULL,
|
||||||
member_count INT NOT NULL
|
is_forge INT NOT NULL,
|
||||||
|
post_count INT NOT NULL,
|
||||||
|
is_forum INT NOT NULL,
|
||||||
|
topics TEXT NOT NULL
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,3 +17,7 @@ ADD COLUMN IF NOT EXISTS replying_to BIGINT DEFAULT 0;
|
||||||
-- communities is_forum
|
-- communities is_forum
|
||||||
ALTER TABLE communities
|
ALTER TABLE communities
|
||||||
ADD COLUMN IF NOT EXISTS is_forum INT DEFAULT 0;
|
ADD COLUMN IF NOT EXISTS is_forum INT DEFAULT 0;
|
||||||
|
|
||||||
|
-- communities topics
|
||||||
|
ALTER TABLE communities
|
||||||
|
ADD COLUMN IF NOT EXISTS topics TEXT DEFAULT '{}';
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
|
use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
|
||||||
use super::communities_permissions::CommunityPermission;
|
use super::communities_permissions::CommunityPermission;
|
||||||
|
@ -19,14 +21,20 @@ pub struct Community {
|
||||||
pub write_access: CommunityWriteAccess,
|
pub write_access: CommunityWriteAccess,
|
||||||
/// Who can join the community.
|
/// Who can join the community.
|
||||||
pub join_access: CommunityJoinAccess,
|
pub join_access: CommunityJoinAccess,
|
||||||
// likes
|
|
||||||
pub likes: isize,
|
pub likes: isize,
|
||||||
pub dislikes: isize,
|
pub dislikes: isize,
|
||||||
// ...
|
|
||||||
pub member_count: usize,
|
pub member_count: usize,
|
||||||
pub is_forge: bool,
|
pub is_forge: bool,
|
||||||
pub post_count: usize,
|
pub post_count: usize,
|
||||||
pub is_forum: bool,
|
pub is_forum: bool,
|
||||||
|
/// The topics of a community if the community has `is_forum` enabled.
|
||||||
|
///
|
||||||
|
/// Since topics are given a unique ID (the key of the hashmap), a removal of a topic
|
||||||
|
/// should be done through a specific DELETE endpoint which ALSO deletes all posts
|
||||||
|
/// within the topic.
|
||||||
|
///
|
||||||
|
/// Communities should be limited to 10 topics per community.
|
||||||
|
pub topics: HashMap<usize, ForumTopic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Community {
|
impl Community {
|
||||||
|
@ -50,6 +58,7 @@ impl Community {
|
||||||
is_forge: false,
|
is_forge: false,
|
||||||
post_count: 0,
|
post_count: 0,
|
||||||
is_forum: false,
|
is_forum: false,
|
||||||
|
topics: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +80,7 @@ impl Community {
|
||||||
is_forge: false,
|
is_forge: false,
|
||||||
post_count: 0,
|
post_count: 0,
|
||||||
is_forum: false,
|
is_forum: false,
|
||||||
|
topics: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -521,10 +531,25 @@ impl PollVote {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct ForumTopic {
|
pub struct ForumTopic {
|
||||||
pub id: usize,
|
|
||||||
pub created: usize,
|
|
||||||
pub owner: usize,
|
|
||||||
pub community: usize,
|
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
pub color: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ForumTopic {
|
||||||
|
/// Create a new [`ForumTopic`].
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * ID for [`Community`] hashmap
|
||||||
|
/// * [`ForumTopic`]
|
||||||
|
pub fn new(title: String, description: String, color: String) -> (usize, Self) {
|
||||||
|
(
|
||||||
|
Snowflake::new().to_string().parse::<usize>().unwrap(),
|
||||||
|
Self {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ pub enum Error {
|
||||||
RequiresSupporter,
|
RequiresSupporter,
|
||||||
DrawingsDisabled,
|
DrawingsDisabled,
|
||||||
AppHitStorageLimit,
|
AppHitStorageLimit,
|
||||||
|
DoesNotSupportField(String),
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +79,7 @@ impl Display for Error {
|
||||||
Self::RequiresSupporter => "Only site supporters can do this".to_string(),
|
Self::RequiresSupporter => "Only site supporters can do this".to_string(),
|
||||||
Self::DrawingsDisabled => "You are not allowed to submit drawings there".to_string(),
|
Self::DrawingsDisabled => "You are not allowed to submit drawings there".to_string(),
|
||||||
Self::AppHitStorageLimit => "This app has already hit its storage limit, or will do so if this data is processed.".to_string(),
|
Self::AppHitStorageLimit => "This app has already hit its storage limit, or will do so if this data is processed.".to_string(),
|
||||||
|
Self::DoesNotSupportField(name) => format!("{name} does not support this field"),
|
||||||
_ => format!("An unknown error as occurred: ({:?})", self),
|
_ => format!("An unknown error as occurred: ({:?})", self),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue