add: post comments

add: user follow api, user block api
This commit is contained in:
trisua 2025-03-25 22:52:47 -04:00
parent 559ce19932
commit 8580e34be2
18 changed files with 296 additions and 49 deletions

View file

@ -14,3 +14,4 @@ version = "1.0.0"
"auth:action.register" = "Register" "auth:action.register" = "Register"
"auth:action.logout" = "Logout" "auth:action.logout" = "Logout"
"auth:link.my_profile" = "My profile" "auth:link.my_profile" = "My profile"
"auth:link.settings" = "Settings"

View file

@ -38,6 +38,11 @@
<span>{{ text "auth:link.my_profile" }}</span> <span>{{ text "auth:link.my_profile" }}</span>
</a> </a>
<a href="/settings">
{{ icon "settings" }}
<span>{{ text "auth:link.settings" }}</span>
</a>
<div class="title"></div> <div class="title"></div>
<button class="red" onclick="trigger('me::logout')"> <button class="red" onclick="trigger('me::logout')">
{{ icon "log-out" }} {{ icon "log-out" }}

View file

@ -1,4 +1,5 @@
pub mod images; pub mod images;
pub mod social;
use super::AuthProps; use super::AuthProps;
use crate::{ use crate::{

View file

@ -0,0 +1,93 @@
use crate::{
State, get_user_from_token,
model::{ApiReturn, Error},
};
use axum::{Extension, Json, extract::Path, response::IntoResponse};
use axum_extra::extract::CookieJar;
use tetratto_core::model::auth::{UserBlock, UserFollow};
/// Toggle following on the given user.
pub async fn follow_request(
jar: CookieJar,
Path(id): Path<usize>,
Extension(data): Extension<State>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
if let Ok(userfollow) = data.get_userfollow_by_initiator_receiver(user.id, id).await {
// delete
match data.delete_userfollow(userfollow.id, user).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "User unfollowed".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
}
} else {
// create
match data.create_userfollow(UserFollow::new(user.id, id)).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "User followed".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
}
}
}
/// Toggle blocking on the given user.
pub async fn block_request(
jar: CookieJar,
Path(id): Path<usize>,
Extension(data): Extension<State>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
if let Ok(userblock) = data.get_userblock_by_initiator_receiver(user.id, id).await {
// delete
match data.delete_userblock(userblock.id, user).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "User unblocked".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
}
} else {
// create
match data.create_userblock(UserBlock::new(user.id, id)).await {
Ok(_) => {
if let Ok(userfollow) = data.get_userfollow_by_initiator_receiver(user.id, id).await
{
// automatically unfollow
match data.delete_userfollow(userfollow.id, user).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "User unfollowed".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
}
} else {
// not following user, don't do anything else
Json(ApiReturn {
ok: true,
message: "User blocked".to_string(),
payload: (),
})
}
}
Err(e) => return Json(e.into()),
}
}
}

View file

@ -19,7 +19,12 @@ pub async fn create_request(
}; };
match data match data
.create_entry(JournalPost::new(req.content, req.journal, user.id)) .create_post(JournalPost::new(
req.content,
req.journal,
req.replying_to,
user.id,
))
.await .await
{ {
Ok(_) => Json(ApiReturn { Ok(_) => Json(ApiReturn {
@ -42,7 +47,7 @@ pub async fn delete_request(
None => return Json(Error::NotAllowed.into()), None => return Json(Error::NotAllowed.into()),
}; };
match data.delete_entry(id, user).await { match data.delete_post(id, user).await {
Ok(_) => Json(ApiReturn { Ok(_) => Json(ApiReturn {
ok: true, ok: true,
message: "Entry deleted".to_string(), message: "Entry deleted".to_string(),

View file

@ -70,6 +70,14 @@ pub fn routes() -> Router {
"/auth/profile/{id}/banner", "/auth/profile/{id}/banner",
get(auth::images::banner_request), get(auth::images::banner_request),
) )
.route(
"/auth/profile/{id}/follow",
post(auth::social::follow_request),
)
.route(
"/auth/profile/{id}/block",
post(auth::social::block_request),
)
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -108,6 +116,8 @@ pub struct UpdateJournalWriteAccess {
pub struct CreateJournalEntry { pub struct CreateJournalEntry {
pub content: String, pub content: String,
pub journal: usize, pub journal: usize,
#[serde(default)]
pub replying_to: Option<usize>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]

View file

@ -132,7 +132,7 @@ impl DataManager {
Err(e) => return Err(Error::DatabaseConnection(e.to_string())), Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
}; };
let res = execute!(&conn, "DELETE FROM users WHERE id = $1", &[&id]); let res = execute!(&conn, "DELETE FROM users WHERE id = $1", &[&(id as i64)]);
if let Err(e) = res { if let Err(e) = res {
return Err(Error::DatabaseError(e.to_string())); return Err(Error::DatabaseError(e.to_string()));

View file

@ -14,8 +14,8 @@ impl DataManager {
}; };
execute!(&conn, common::CREATE_TABLE_USERS).unwrap(); execute!(&conn, common::CREATE_TABLE_USERS).unwrap();
execute!(&conn, common::CREATE_TABLE_PAGES).unwrap(); execute!(&conn, common::CREATE_TABLE_JOURNALS).unwrap();
execute!(&conn, common::CREATE_TABLE_ENTRIES).unwrap(); execute!(&conn, common::CREATE_TABLE_POSTS).unwrap();
execute!(&conn, common::CREATE_TABLE_MEMBERSHIPS).unwrap(); execute!(&conn, common::CREATE_TABLE_MEMBERSHIPS).unwrap();
execute!(&conn, common::CREATE_TABLE_REACTIONS).unwrap(); execute!(&conn, common::CREATE_TABLE_REACTIONS).unwrap();
execute!(&conn, common::CREATE_TABLE_NOTIFICATIONS).unwrap(); execute!(&conn, common::CREATE_TABLE_NOTIFICATIONS).unwrap();
@ -99,7 +99,9 @@ macro_rules! auto_method {
Err(e) => return Err(Error::DatabaseConnection(e.to_string())), Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
}; };
let res = query_row!(&conn, $query, &[&selector], |x| { Ok(Self::$select_fn(x)) }); let res = query_row!(&conn, $query, &[&selector.to_string()], |x| {
Ok(Self::$select_fn(x))
});
if res.is_err() { if res.is_err() {
return Err(Error::GeneralNotFound($name_.to_string())); return Err(Error::GeneralNotFound($name_.to_string()));

View file

@ -1,6 +1,6 @@
pub const CREATE_TABLE_USERS: &str = include_str!("./sql/create_users.sql"); pub const CREATE_TABLE_USERS: &str = include_str!("./sql/create_users.sql");
pub const CREATE_TABLE_PAGES: &str = include_str!("./sql/create_pages.sql"); pub const CREATE_TABLE_JOURNALS: &str = include_str!("./sql/create_journals.sql");
pub const CREATE_TABLE_ENTRIES: &str = include_str!("./sql/create_entries.sql"); pub const CREATE_TABLE_POSTS: &str = include_str!("./sql/create_posts.sql");
pub const CREATE_TABLE_MEMBERSHIPS: &str = include_str!("./sql/create_memberships.sql"); pub const CREATE_TABLE_MEMBERSHIPS: &str = include_str!("./sql/create_memberships.sql");
pub const CREATE_TABLE_REACTIONS: &str = include_str!("./sql/create_reactions.sql"); pub const CREATE_TABLE_REACTIONS: &str = include_str!("./sql/create_reactions.sql");
pub const CREATE_TABLE_NOTIFICATIONS: &str = include_str!("./sql/create_notifications.sql"); pub const CREATE_TABLE_NOTIFICATIONS: &str = include_str!("./sql/create_notifications.sql");

View file

@ -61,7 +61,7 @@ impl DataManager {
#[cfg(feature = "postgres")] #[cfg(feature = "postgres")]
#[macro_export] #[macro_export]
macro_rules! get { macro_rules! get {
($row:ident->$idx:literal($t:tt)) => { ($row:ident->$idx:literal($t:ty)) => {
$row.get::<usize, Option<$t>>($idx).unwrap() $row.get::<usize, Option<$t>>($idx).unwrap()
}; };
} }

View file

@ -1,4 +1,4 @@
CREATE TABLE IF NOT EXISTS pages ( CREATE TABLE IF NOT EXISTS journals (
id INTEGER NOT NULL PRIMARY KEY, id INTEGER NOT NULL PRIMARY KEY,
created INTEGER NOT NULL, created INTEGER NOT NULL,
title TEXT NOT NULL, title TEXT NOT NULL,

View file

@ -1,11 +1,14 @@
CREATE TABLE IF NOT EXISTS entries ( CREATE TABLE IF NOT EXISTS posts (
id INTEGER NOT NULL PRIMARY KEY, id INTEGER NOT NULL PRIMARY KEY,
created INTEGER NOT NULL, created INTEGER NOT NULL,
content TEXT NOT NULL, content TEXT NOT NULL,
owner INTEGER NOT NULL, owner INTEGER NOT NULL,
journal INTEGER NOT NULL, journal INTEGER NOT NULL,
context TEXT NOT NULL, context TEXT NOT NULL,
replying_to INTEGER, -- the ID of the post this is a comment on... NULL means it isn't a reply
-- likes -- likes
likes INTEGER NOT NULL, likes INTEGER NOT NULL,
dislikes INTEGER NOT NULL dislikes INTEGER NOT NULL,
-- other counts
comment_count INTEGER NOT NULL
) )

View file

@ -44,7 +44,7 @@ impl DataManager {
#[macro_export] #[macro_export]
macro_rules! get { macro_rules! get {
($row:ident->$idx:literal($t:tt)) => { ($row:ident->$idx:literal($t:ty)) => {
$row.get::<usize, $t>($idx).unwrap() $row.get::<usize, $t>($idx).unwrap()
}; };
} }

View file

@ -37,7 +37,7 @@ impl DataManager {
} }
} }
auto_method!(get_page_by_id()@get_page_from_row -> "SELECT * FROM pages WHERE id = $1" --name="journal page" --returns=Journal --cache-key-tmpl="atto.page:{}"); auto_method!(get_page_by_id()@get_page_from_row -> "SELECT * FROM journals WHERE id = $1" --name="journal" --returns=Journal --cache-key-tmpl="atto.journal:{}");
/// Create a new journal page in the database. /// Create a new journal page in the database.
/// ///
@ -65,7 +65,7 @@ impl DataManager {
let res = execute!( let res = execute!(
&conn, &conn,
"INSERT INTO pages VALUES ($1, $2, $3, $4, $5, $6, $7)", "INSERT INTO journals VALUES ($1, $2, $3, $4, $5, $6, $7)",
&[ &[
&data.id.to_string().as_str(), &data.id.to_string().as_str(),
&data.created.to_string().as_str(), &data.created.to_string().as_str(),
@ -94,14 +94,14 @@ impl DataManager {
Ok(()) Ok(())
} }
auto_method!(delete_page()@get_page_by_id:MANAGE_JOURNAL_PAGES -> "DELETE FROM pages WHERE id = $1" --cache-key-tmpl="atto.page:{}"); auto_method!(delete_page()@get_page_by_id:MANAGE_JOURNAL_PAGES -> "DELETE journals pages WHERE id = $1" --cache-key-tmpl="atto.journal:{}");
auto_method!(update_page_title(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET title = $1 WHERE id = $2" --cache-key-tmpl="atto.page:{}"); auto_method!(update_page_title(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE journals SET title = $1 WHERE id = $2" --cache-key-tmpl="atto.journal:{}");
auto_method!(update_page_prompt(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET prompt = $1 WHERE id = $2" --cache-key-tmpl="atto.page:{}"); auto_method!(update_page_prompt(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE journals SET prompt = $1 WHERE id = $2" --cache-key-tmpl="atto.journal:{}");
auto_method!(update_page_read_access(JournalReadAccess)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET read_access = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.page:{}"); auto_method!(update_page_read_access(JournalReadAccess)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE journals SET read_access = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.journal:{}");
auto_method!(update_page_write_access(JournalWriteAccess)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET write_access = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.page:{}"); auto_method!(update_page_write_access(JournalWriteAccess)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE journals SET write_access = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.journal:{}");
auto_method!(incr_page_likes() -> "UPDATE pages SET likes = likes + 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --incr); auto_method!(incr_page_likes() -> "UPDATE journals SET likes = likes + 1 WHERE id = $1" --cache-key-tmpl="atto.journal:{}" --incr);
auto_method!(incr_page_dislikes() -> "UPDATE pages SET likes = dislikes + 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --incr); auto_method!(incr_page_dislikes() -> "UPDATE journals SET likes = dislikes + 1 WHERE id = $1" --cache-key-tmpl="atto.journal:{}" --incr);
auto_method!(decr_page_likes() -> "UPDATE pages SET likes = likes - 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --decr); auto_method!(decr_page_likes() -> "UPDATE journals SET likes = likes - 1 WHERE id = $1" --cache-key-tmpl="atto.journal:{}" --decr);
auto_method!(decr_page_dislikes() -> "UPDATE pages SET likes = dislikes - 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --decr); auto_method!(decr_page_dislikes() -> "UPDATE journals SET likes = dislikes - 1 WHERE id = $1" --cache-key-tmpl="atto.journal:{}" --decr);
} }

View file

@ -83,5 +83,67 @@ impl DataManager {
Ok(()) Ok(())
} }
auto_method!(delete_membership()@get_membership_by_id:MANAGE_MEMBERSHIPS -> "DELETE FROM memberships WHERE id = $1" --cache-key-tmpl="atto.membership:{}"); /// Delete a membership given its `id`
pub async fn delete_membership(&self, id: usize, user: User) -> Result<()> {
let y = self.get_membership_by_id(id).await?;
if user.id != y.owner {
// pull other user's membership status
if let Ok(z) = self.get_membership_by_id(user.id).await {
// somebody with MANAGE_ROLES _and_ a higher role number can remove us
if (!z.role.check(JournalPermission::MANAGE_ROLES) | (z.role < y.role))
&& !z.role.check(JournalPermission::ADMINISTRATOR)
{
return Err(Error::NotAllowed);
}
} else if !user.permissions.check(FinePermission::MANAGE_MEMBERSHIPS) {
return Err(Error::NotAllowed);
}
}
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = execute!(
&conn,
"DELETE FROM memberships WHERE id = $1",
&[&id.to_string()]
);
if let Err(e) = res {
return Err(Error::DatabaseError(e.to_string()));
}
self.2.remove(format!("atto.membership:{}", id)).await;
Ok(())
}
/// Update a membership's role given its `id`
pub async fn update_membership_role(
&self,
id: usize,
new_role: JournalPermission,
) -> Result<()> {
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = execute!(
&conn,
"UPDATE memberships SET role = $1 WHERE id = $2",
&[&(new_role.bits()).to_string(), &id.to_string()]
);
if let Err(e) = res {
return Err(Error::DatabaseError(e.to_string()));
}
self.2.remove(format!("atto.membership:{}", id)).await;
Ok(())
}
} }

View file

@ -26,19 +26,57 @@ impl DataManager {
owner: get!(x->3(i64)) as usize, owner: get!(x->3(i64)) as usize,
journal: get!(x->4(i64)) as usize, journal: get!(x->4(i64)) as usize,
context: serde_json::from_str(&get!(x->5(String))).unwrap(), context: serde_json::from_str(&get!(x->5(String))).unwrap(),
replying_to: if let Some(id) = get!(x->6(Option<i64>)) {
Some(id as usize)
} else {
None
},
// likes // likes
likes: get!(x->6(i64)) as isize, likes: get!(x->7(i64)) as isize,
dislikes: get!(x->7(i64)) as isize, dislikes: get!(x->8(i64)) as isize,
// other counts
comment_count: get!(x->9(i64)) as usize,
} }
} }
auto_method!(get_post_by_id()@get_post_from_row -> "SELECT * FROM entries WHERE id = $1" --name="journal entry" --returns=JournalPost --cache-key-tmpl="atto.entry:{}"); auto_method!(get_post_by_id()@get_post_from_row -> "SELECT * FROM posts WHERE id = $1" --name="post" --returns=JournalPost --cache-key-tmpl="atto.post:{}");
/// Get all posts which are comments on the given post by ID.
///
/// # Arguments
/// * `id` - the ID of the post the requested posts are commenting on
/// * `batch` - the limit of posts in each page
/// * `page` - the page number
pub async fn get_post_comments(
&self,
id: usize,
batch: usize,
page: usize,
) -> Result<JournalPost> {
let conn = match self.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = query_row!(
&conn,
"SELECT * FROM posts WHERE replying_to = $1 LIMIT $2 OFFSET $3",
&[&(id as i64), &(batch as i64), &((page * batch) as i64)],
|x| { Ok(Self::get_post_from_row(x)) }
);
if res.is_err() {
return Err(Error::GeneralNotFound("post".to_string()));
}
Ok(res.unwrap())
}
/// Create a new journal entry in the database. /// Create a new journal entry in the database.
/// ///
/// # Arguments /// # Arguments
/// * `data` - a mock [`JournalEntry`] object to insert /// * `data` - a mock [`JournalEntry`] object to insert
pub async fn create_entry(&self, data: JournalPost) -> Result<()> { pub async fn create_post(&self, data: JournalPost) -> Result<()> {
// check values // check values
if data.content.len() < 2 { if data.content.len() < 2 {
return Err(Error::DataTooShort("content".to_string())); return Err(Error::DataTooShort("content".to_string()));
@ -77,14 +115,22 @@ impl DataManager {
let res = execute!( let res = execute!(
&conn, &conn,
"INSERT INTO entries VALUES ($1, $2, $3, $4, $5", "INSERT INTO posts VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
&[ &[
&data.id.to_string().as_str(), &Some(data.id.to_string()),
&data.created.to_string().as_str(), &Some(data.created.to_string()),
&data.content.as_str(), &Some(data.content),
&data.owner.to_string().as_str(), &Some(data.owner.to_string()),
&data.journal.to_string().as_str(), &Some(data.journal.to_string()),
&serde_json::to_string(&data.context).unwrap().as_str(), &Some(serde_json::to_string(&data.context).unwrap()),
&if let Some(id) = data.replying_to {
Some(id.to_string())
} else {
None
},
&Some(0.to_string()),
&Some(0.to_string()),
&Some(0.to_string())
] ]
); );
@ -92,15 +138,24 @@ impl DataManager {
return Err(Error::DatabaseError(e.to_string())); return Err(Error::DatabaseError(e.to_string()));
} }
// incr comment count
if let Some(id) = data.replying_to {
self.incr_post_comments(id).await.unwrap();
}
// return
Ok(()) Ok(())
} }
auto_method!(delete_entry()@get_post_by_id:MANAGE_JOURNAL_ENTRIES -> "DELETE FROM entries WHERE id = $1" --cache-key-tmpl="atto.entry:{}"); auto_method!(delete_post()@get_post_by_id:MANAGE_JOURNAL_ENTRIES -> "DELETE FROM posts WHERE id = $1" --cache-key-tmpl="atto.post:{}");
auto_method!(update_post_content(String)@get_post_by_id:MANAGE_JOURNAL_ENTRIES -> "UPDATE entries SET content = $1 WHERE id = $2" --cache-key-tmpl="atto.entry:{}"); auto_method!(update_post_content(String)@get_post_by_id:MANAGE_JOURNAL_ENTRIES -> "UPDATE posts SET content = $1 WHERE id = $2" --cache-key-tmpl="atto.post:{}");
auto_method!(update_post_context(JournalPostContext)@get_post_by_id:MANAGE_JOURNAL_ENTRIES -> "UPDATE entries SET context = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.entry:{}"); auto_method!(update_post_context(JournalPostContext)@get_post_by_id:MANAGE_JOURNAL_ENTRIES -> "UPDATE posts SET context = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.post:{}");
auto_method!(incr_post_likes() -> "UPDATE entries SET likes = likes + 1 WHERE id = $1" --cache-key-tmpl="atto.entry:{}" --incr); auto_method!(incr_post_likes() -> "UPDATE posts SET likes = likes + 1 WHERE id = $1" --cache-key-tmpl="atto.post:{}" --incr);
auto_method!(incr_post_dislikes() -> "UPDATE entries SET likes = dislikes + 1 WHERE id = $1" --cache-key-tmpl="atto.entry:{}" --incr); auto_method!(incr_post_dislikes() -> "UPDATE posts SET likes = dislikes + 1 WHERE id = $1" --cache-key-tmpl="atto.post:{}" --incr);
auto_method!(decr_post_likes() -> "UPDATE entries SET likes = likes - 1 WHERE id = $1" --cache-key-tmpl="atto.entry:{}" --decr); auto_method!(decr_post_likes() -> "UPDATE posts SET likes = likes - 1 WHERE id = $1" --cache-key-tmpl="atto.post:{}" --decr);
auto_method!(decr_post_dislikes() -> "UPDATE entries SET likes = dislikes - 1 WHERE id = $1" --cache-key-tmpl="atto.entry:{}" --decr); auto_method!(decr_post_dislikes() -> "UPDATE posts SET likes = dislikes - 1 WHERE id = $1" --cache-key-tmpl="atto.post:{}" --decr);
auto_method!(incr_post_comments() -> "UPDATE posts SET comment_count = comment_count + 1 WHERE id = $1" --cache-key-tmpl="atto.post:{}" --incr);
auto_method!(decr_post_comments() -> "UPDATE posts SET comment_count = comment_count - 1 WHERE id = $1" --cache-key-tmpl="atto.post:{}" --decr);
} }

View file

@ -62,10 +62,8 @@ impl Default for JournalReadAccess {
/// Who can write to a [`Journal`]. /// Who can write to a [`Journal`].
#[derive(Serialize, Deserialize, PartialEq, Eq)] #[derive(Serialize, Deserialize, PartialEq, Eq)]
pub enum JournalWriteAccess { pub enum JournalWriteAccess {
/// Everybody (authenticated + anonymous users). /// Everybody (authenticated users only still).
Everybody, Everybody,
/// Authenticated users only.
Authenticated,
/// Only people who joined the journal page can write to it. /// Only people who joined the journal page can write to it.
/// ///
/// Memberships can be managed by the owner of the journal page. /// Memberships can be managed by the owner of the journal page.
@ -76,7 +74,7 @@ pub enum JournalWriteAccess {
impl Default for JournalWriteAccess { impl Default for JournalWriteAccess {
fn default() -> Self { fn default() -> Self {
Self::Authenticated Self::Joined
} }
} }
@ -128,13 +126,16 @@ pub struct JournalPost {
pub journal: usize, pub journal: usize,
/// Extra information about the journal entry. /// Extra information about the journal entry.
pub context: JournalPostContext, pub context: JournalPostContext,
/// The ID of the post this post is a comment on.
pub replying_to: Option<usize>,
pub likes: isize, pub likes: isize,
pub dislikes: isize, pub dislikes: isize,
pub comment_count: usize,
} }
impl JournalPost { impl JournalPost {
/// Create a new [`JournalEntry`]. /// Create a new [`JournalEntry`].
pub fn new(content: String, journal: usize, owner: usize) -> Self { pub fn new(content: String, journal: usize, replying_to: Option<usize>, owner: usize) -> Self {
Self { Self {
id: AlmostSnowflake::new(1234567890) id: AlmostSnowflake::new(1234567890)
.to_string() .to_string()
@ -145,8 +146,10 @@ impl JournalPost {
owner, owner,
journal, journal,
context: JournalPostContext::default(), context: JournalPostContext::default(),
replying_to,
likes: 0, likes: 0,
dislikes: 0, dislikes: 0,
comment_count: 0,
} }
} }
} }

View file

@ -11,6 +11,8 @@ bitflags! {
const DEFAULT = 1 << 0; const DEFAULT = 1 << 0;
const ADMINISTRATOR = 1 << 1; const ADMINISTRATOR = 1 << 1;
const MEMBER = 1 << 2; const MEMBER = 1 << 2;
const MANAGE_POSTS = 1 << 3;
const MANAGE_ROLES = 1 << 4;
const _ = !0; const _ = !0;
} }
@ -93,9 +95,14 @@ impl JournalPermission {
} }
/// Check if the given [`JournalPermission`] qualifies as "Member" status. /// Check if the given [`JournalPermission`] qualifies as "Member" status.
pub fn check_helper(self) -> bool { pub fn check_member(self) -> bool {
self.check(JournalPermission::MEMBER) self.check(JournalPermission::MEMBER)
} }
/// Check if the given [`JournalPermission`] qualifies as "Moderator" status.
pub fn check_moderator(self) -> bool {
self.check(JournalPermission::MANAGE_POSTS)
}
} }
impl Default for JournalPermission { impl Default for JournalPermission {