add: user follows, user blocks, ip bans
TODO: implement user following API endpoints TODO: implement user blocking API endpoints TODO: don't allow blocked users to interact with the users who blocked them
This commit is contained in:
parent
81005a6e1c
commit
559ce19932
25 changed files with 628 additions and 127 deletions
|
@ -42,6 +42,11 @@ pub async fn register_request(
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
// check for ip ban
|
||||||
|
if let Ok(_) = data.get_ipban_by_ip(&real_ip).await {
|
||||||
|
return (None, Json(Error::NotAllowed.into()));
|
||||||
|
}
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
let mut user = User::new(props.username, props.password);
|
let mut user = User::new(props.username, props.password);
|
||||||
let (initial_token, t) = User::create_token(&real_ip);
|
let (initial_token, t) = User::create_token(&real_ip);
|
||||||
|
@ -90,6 +95,11 @@ pub async fn login_request(
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
// check for ip ban
|
||||||
|
if let Ok(_) = data.get_ipban_by_ip(&real_ip).await {
|
||||||
|
return (None, Json(Error::NotAllowed.into()));
|
||||||
|
}
|
||||||
|
|
||||||
// verify password
|
// verify password
|
||||||
let user = match data.get_user_by_username(&props.username).await {
|
let user = match data.get_user_by_username(&props.username).await {
|
||||||
Ok(ua) => ua,
|
Ok(ua) => ua,
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
use axum::{Extension, Json, extract::Path, response::IntoResponse};
|
use axum::{Extension, Json, extract::Path, response::IntoResponse};
|
||||||
use axum_extra::extract::CookieJar;
|
use axum_extra::extract::CookieJar;
|
||||||
use tetratto_core::model::{ApiReturn, Error, journal::JournalPage};
|
use tetratto_core::model::{ApiReturn, Error, journal::Journal};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
State, get_user_from_token,
|
State, get_user_from_token,
|
||||||
routes::api::v1::{
|
routes::api::v1::{
|
||||||
CreateJournalPage, UpdateJournalPagePrompt, UpdateJournalPageReadAccess,
|
CreateJournal, UpdateJournalPrompt, UpdateJournalReadAccess, UpdateJournalTitle,
|
||||||
UpdateJournalPageTitle, UpdateJournalPageWriteAccess,
|
UpdateJournalWriteAccess,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn create_request(
|
pub async fn create_request(
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
Extension(data): Extension<State>,
|
Extension(data): Extension<State>,
|
||||||
Json(req): Json<CreateJournalPage>,
|
Json(req): Json<CreateJournal>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let data = &(data.read().await).0;
|
let data = &(data.read().await).0;
|
||||||
let user = match get_user_from_token!(jar, data) {
|
let user = match get_user_from_token!(jar, data) {
|
||||||
|
@ -22,7 +22,7 @@ pub async fn create_request(
|
||||||
};
|
};
|
||||||
|
|
||||||
match data
|
match data
|
||||||
.create_page(JournalPage::new(req.title, req.prompt, user.id))
|
.create_page(Journal::new(req.title, req.prompt, user.id))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => Json(ApiReturn {
|
Ok(_) => Json(ApiReturn {
|
||||||
|
@ -59,7 +59,7 @@ pub async fn update_title_request(
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
Extension(data): Extension<State>,
|
Extension(data): Extension<State>,
|
||||||
Path(id): Path<usize>,
|
Path(id): Path<usize>,
|
||||||
Json(req): Json<UpdateJournalPageTitle>,
|
Json(req): Json<UpdateJournalTitle>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let data = &(data.read().await).0;
|
let data = &(data.read().await).0;
|
||||||
let user = match get_user_from_token!(jar, data) {
|
let user = match get_user_from_token!(jar, data) {
|
||||||
|
@ -81,7 +81,7 @@ pub async fn update_prompt_request(
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
Extension(data): Extension<State>,
|
Extension(data): Extension<State>,
|
||||||
Path(id): Path<usize>,
|
Path(id): Path<usize>,
|
||||||
Json(req): Json<UpdateJournalPagePrompt>,
|
Json(req): Json<UpdateJournalPrompt>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let data = &(data.read().await).0;
|
let data = &(data.read().await).0;
|
||||||
let user = match get_user_from_token!(jar, data) {
|
let user = match get_user_from_token!(jar, data) {
|
||||||
|
@ -103,7 +103,7 @@ pub async fn update_read_access_request(
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
Extension(data): Extension<State>,
|
Extension(data): Extension<State>,
|
||||||
Path(id): Path<usize>,
|
Path(id): Path<usize>,
|
||||||
Json(req): Json<UpdateJournalPageReadAccess>,
|
Json(req): Json<UpdateJournalReadAccess>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let data = &(data.read().await).0;
|
let data = &(data.read().await).0;
|
||||||
let user = match get_user_from_token!(jar, data) {
|
let user = match get_user_from_token!(jar, data) {
|
||||||
|
@ -125,7 +125,7 @@ pub async fn update_write_access_request(
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
Extension(data): Extension<State>,
|
Extension(data): Extension<State>,
|
||||||
Path(id): Path<usize>,
|
Path(id): Path<usize>,
|
||||||
Json(req): Json<UpdateJournalPageWriteAccess>,
|
Json(req): Json<UpdateJournalWriteAccess>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let data = &(data.read().await).0;
|
let data = &(data.read().await).0;
|
||||||
let user = match get_user_from_token!(jar, data) {
|
let user = match get_user_from_token!(jar, data) {
|
|
@ -1,2 +1,2 @@
|
||||||
pub mod entries;
|
pub mod journals;
|
||||||
pub mod pages;
|
pub mod posts;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use axum::{Extension, Json, extract::Path, response::IntoResponse};
|
use axum::{Extension, Json, extract::Path, response::IntoResponse};
|
||||||
use axum_extra::extract::CookieJar;
|
use axum_extra::extract::CookieJar;
|
||||||
use tetratto_core::model::{ApiReturn, Error, journal::JournalEntry};
|
use tetratto_core::model::{ApiReturn, Error, journal::JournalPost};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
State, get_user_from_token,
|
State, get_user_from_token,
|
||||||
|
@ -19,7 +19,7 @@ pub async fn create_request(
|
||||||
};
|
};
|
||||||
|
|
||||||
match data
|
match data
|
||||||
.create_entry(JournalEntry::new(req.content, req.journal, user.id))
|
.create_entry(JournalPost::new(req.content, req.journal, user.id))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => Json(ApiReturn {
|
Ok(_) => Json(ApiReturn {
|
||||||
|
@ -64,7 +64,7 @@ pub async fn update_content_request(
|
||||||
None => return Json(Error::NotAllowed.into()),
|
None => return Json(Error::NotAllowed.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
match data.update_entry_content(id, user, req.content).await {
|
match data.update_post_content(id, user, req.content).await {
|
||||||
Ok(_) => Json(ApiReturn {
|
Ok(_) => Json(ApiReturn {
|
||||||
ok: true,
|
ok: true,
|
||||||
message: "Entry updated".to_string(),
|
message: "Entry updated".to_string(),
|
||||||
|
@ -86,7 +86,7 @@ pub async fn update_context_request(
|
||||||
None => return Json(Error::NotAllowed.into()),
|
None => return Json(Error::NotAllowed.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
match data.update_entry_context(id, user, req.context).await {
|
match data.update_post_context(id, user, req.context).await {
|
||||||
Ok(_) => Json(ApiReturn {
|
Ok(_) => Json(ApiReturn {
|
||||||
ok: true,
|
ok: true,
|
||||||
message: "Entry updated".to_string(),
|
message: "Entry updated".to_string(),
|
|
@ -8,7 +8,7 @@ use axum::{
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tetratto_core::model::{
|
use tetratto_core::model::{
|
||||||
journal::{JournalEntryContext, JournalPageReadAccess, JournalPageWriteAccess},
|
journal::{JournalPostContext, JournalReadAccess, JournalWriteAccess},
|
||||||
reactions::AssetType,
|
reactions::AssetType,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,35 +18,35 @@ pub fn routes() -> Router {
|
||||||
.route("/reactions", post(reactions::create_request))
|
.route("/reactions", post(reactions::create_request))
|
||||||
.route("/reactions/{id}", get(reactions::get_request))
|
.route("/reactions/{id}", get(reactions::get_request))
|
||||||
.route("/reactions/{id}", delete(reactions::delete_request))
|
.route("/reactions/{id}", delete(reactions::delete_request))
|
||||||
// journal pages
|
// journal journals
|
||||||
.route("/pages", post(journal::pages::create_request))
|
.route("/journals", post(journal::journals::create_request))
|
||||||
.route("/pages/{id}", delete(journal::pages::delete_request))
|
.route("/journals/{id}", delete(journal::journals::delete_request))
|
||||||
.route(
|
.route(
|
||||||
"/pages/{id}/title",
|
"/journals/{id}/title",
|
||||||
post(journal::pages::update_title_request),
|
post(journal::journals::update_title_request),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/pages/{id}/prompt",
|
"/journals/{id}/prompt",
|
||||||
post(journal::pages::update_prompt_request),
|
post(journal::journals::update_prompt_request),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/pages/{id}/access/read",
|
"/journals/{id}/access/read",
|
||||||
post(journal::pages::update_read_access_request),
|
post(journal::journals::update_read_access_request),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/pages/{id}/access/write",
|
"/journals/{id}/access/write",
|
||||||
post(journal::pages::update_write_access_request),
|
post(journal::journals::update_write_access_request),
|
||||||
)
|
)
|
||||||
// journal entries
|
// journal posts
|
||||||
.route("/entries", post(journal::entries::create_request))
|
.route("/posts", post(journal::posts::create_request))
|
||||||
.route("/entries/{id}", delete(journal::entries::delete_request))
|
.route("/posts/{id}", delete(journal::posts::delete_request))
|
||||||
.route(
|
.route(
|
||||||
"/entries/{id}/content",
|
"/posts/{id}/content",
|
||||||
post(journal::entries::update_content_request),
|
post(journal::posts::update_content_request),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/entries/{id}/context",
|
"/posts/{id}/context",
|
||||||
post(journal::entries::update_context_request),
|
post(journal::posts::update_context_request),
|
||||||
)
|
)
|
||||||
// auth
|
// auth
|
||||||
// global
|
// global
|
||||||
|
@ -79,29 +79,29 @@ pub struct AuthProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct CreateJournalPage {
|
pub struct CreateJournal {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub prompt: String,
|
pub prompt: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct UpdateJournalPageTitle {
|
pub struct UpdateJournalTitle {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct UpdateJournalPagePrompt {
|
pub struct UpdateJournalPrompt {
|
||||||
pub prompt: String,
|
pub prompt: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct UpdateJournalPageReadAccess {
|
pub struct UpdateJournalReadAccess {
|
||||||
pub access: JournalPageReadAccess,
|
pub access: JournalReadAccess,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct UpdateJournalPageWriteAccess {
|
pub struct UpdateJournalWriteAccess {
|
||||||
pub access: JournalPageWriteAccess,
|
pub access: JournalWriteAccess,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -117,7 +117,7 @@ pub struct UpdateJournalEntryContent {
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct UpdateJournalEntryContext {
|
pub struct UpdateJournalEntryContext {
|
||||||
pub context: JournalEntryContext,
|
pub context: JournalPostContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
|
@ -29,10 +29,14 @@ impl DataManager {
|
||||||
settings: serde_json::from_str(&get!(x->5(String)).to_string()).unwrap(),
|
settings: serde_json::from_str(&get!(x->5(String)).to_string()).unwrap(),
|
||||||
tokens: serde_json::from_str(&get!(x->6(String)).to_string()).unwrap(),
|
tokens: serde_json::from_str(&get!(x->6(String)).to_string()).unwrap(),
|
||||||
permissions: FinePermission::from_bits(get!(x->7(u32))).unwrap(),
|
permissions: FinePermission::from_bits(get!(x->7(u32))).unwrap(),
|
||||||
|
// counts
|
||||||
|
notification_count: get!(x->8(i64)) as usize,
|
||||||
|
follower_count: get!(x->9(i64)) as usize,
|
||||||
|
following_count: get!(x->10(i64)) as usize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto_method!(get_user_by_id(&str)@get_user_from_row -> "SELECT * FROM users WHERE id = $1" --name="user" --returns=User --cache-key-tmpl="atto.user:{}");
|
auto_method!(get_user_by_id(usize)@get_user_from_row -> "SELECT * FROM users WHERE id = $1" --name="user" --returns=User --cache-key-tmpl="atto.user:{}");
|
||||||
auto_method!(get_user_by_username(&str)@get_user_from_row -> "SELECT * FROM users WHERE username = $1" --name="user" --returns=User --cache-key-tmpl="atto.user:{}");
|
auto_method!(get_user_by_username(&str)@get_user_from_row -> "SELECT * FROM users WHERE username = $1" --name="user" --returns=User --cache-key-tmpl="atto.user:{}");
|
||||||
|
|
||||||
/// Get a user given just their auth token.
|
/// Get a user given just their auth token.
|
||||||
|
@ -87,7 +91,7 @@ impl DataManager {
|
||||||
|
|
||||||
let res = execute!(
|
let res = execute!(
|
||||||
&conn,
|
&conn,
|
||||||
"INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
"INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
|
||||||
&[
|
&[
|
||||||
&data.id.to_string().as_str(),
|
&data.id.to_string().as_str(),
|
||||||
&data.created.to_string().as_str(),
|
&data.created.to_string().as_str(),
|
||||||
|
@ -97,6 +101,8 @@ impl DataManager {
|
||||||
&serde_json::to_string(&data.settings).unwrap().as_str(),
|
&serde_json::to_string(&data.settings).unwrap().as_str(),
|
||||||
&serde_json::to_string(&data.tokens).unwrap().as_str(),
|
&serde_json::to_string(&data.tokens).unwrap().as_str(),
|
||||||
&(FinePermission::DEFAULT.bits()).to_string().as_str(),
|
&(FinePermission::DEFAULT.bits()).to_string().as_str(),
|
||||||
|
&0.to_string().as_str(),
|
||||||
|
&0.to_string().as_str(),
|
||||||
&0.to_string().as_str()
|
&0.to_string().as_str()
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -114,7 +120,7 @@ impl DataManager {
|
||||||
/// * `id` - the ID of the user
|
/// * `id` - the ID of the user
|
||||||
/// * `password` - the current password of the user
|
/// * `password` - the current password of the user
|
||||||
/// * `force` - if we should delete even if the given password is incorrect
|
/// * `force` - if we should delete even if the given password is incorrect
|
||||||
pub async fn delete_user(&self, id: &str, password: &str, force: bool) -> Result<()> {
|
pub async fn delete_user(&self, id: usize, password: &str, force: bool) -> Result<()> {
|
||||||
let user = self.get_user_by_id(id).await?;
|
let user = self.get_user_by_id(id).await?;
|
||||||
|
|
||||||
if (hash_salted(password.to_string(), user.salt) != user.password) && !force {
|
if (hash_salted(password.to_string(), user.salt) != user.password) && !force {
|
||||||
|
@ -140,6 +146,12 @@ impl DataManager {
|
||||||
|
|
||||||
auto_method!(update_user_tokens(Vec<Token>) -> "UPDATE users SET tokens = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.user:{}");
|
auto_method!(update_user_tokens(Vec<Token>) -> "UPDATE users SET tokens = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.user:{}");
|
||||||
|
|
||||||
auto_method!(incr_user_notifications() -> "UPDATE users SET notification_count = notification_count + 1 WHERE id = $1" --cache-key-tmpl="atto.user:{}" --reactions-key-tmpl="atto.user.notification_count:{}" --incr);
|
auto_method!(incr_user_notifications() -> "UPDATE users SET notification_count = notification_count + 1 WHERE id = $1" --cache-key-tmpl="atto.user:{}" --incr);
|
||||||
auto_method!(decr_user_notifications() -> "UPDATE users SET notification_count = notification_count - 1 WHERE id = $1" --cache-key-tmpl="atto.user:{}" --reactions-key-tmpl="atto.user.notification_count:{}" --decr);
|
auto_method!(decr_user_notifications() -> "UPDATE users SET notification_count = notification_count - 1 WHERE id = $1" --cache-key-tmpl="atto.user:{}" --decr);
|
||||||
|
|
||||||
|
auto_method!(incr_user_follower_count() -> "UPDATE users SET follower_count = follower_count + 1 WHERE id = $1" --cache-key-tmpl="atto.user:{}" --incr);
|
||||||
|
auto_method!(decr_user_follower_count() -> "UPDATE users SET follower_count = follower_count - 1 WHERE id = $1" --cache-key-tmpl="atto.user:{}" --decr);
|
||||||
|
|
||||||
|
auto_method!(incr_user_following_count() -> "UPDATE users SET following_count = following_count + 1 WHERE id = $1" --cache-key-tmpl="atto.user:{}" --incr);
|
||||||
|
auto_method!(decr_user_following_count() -> "UPDATE users SET following_count = following_count - 1 WHERE id = $1" --cache-key-tmpl="atto.user:{}" --decr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,9 @@ impl DataManager {
|
||||||
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();
|
||||||
|
execute!(&conn, common::CREATE_TABLE_USERFOLLOWS).unwrap();
|
||||||
|
execute!(&conn, common::CREATE_TABLE_USERBLOCKS).unwrap();
|
||||||
|
execute!(&conn, common::CREATE_TABLE_IPBANS).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -358,7 +361,7 @@ macro_rules! auto_method {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
($name:ident() -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal --reactions-key-tmpl=$reactions_key_tmpl:literal --incr) => {
|
($name:ident() -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal --incr) => {
|
||||||
pub async fn $name(&self, id: usize) -> Result<()> {
|
pub async fn $name(&self, id: usize) -> Result<()> {
|
||||||
let conn = match self.connect().await {
|
let conn = match self.connect().await {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
|
@ -372,13 +375,12 @@ macro_rules! auto_method {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.2.remove(format!($cache_key_tmpl, id)).await;
|
self.2.remove(format!($cache_key_tmpl, id)).await;
|
||||||
self.2.remove(format!($reactions_key_tmpl, id)).await;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
($name:ident() -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal --reactions-key-tmpl=$reactions_key_tmpl:literal --decr) => {
|
($name:ident() -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal --decr) => {
|
||||||
pub async fn $name(&self, id: usize) -> Result<()> {
|
pub async fn $name(&self, id: usize) -> Result<()> {
|
||||||
let conn = match self.connect().await {
|
let conn = match self.connect().await {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
|
@ -392,7 +394,6 @@ macro_rules! auto_method {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.2.remove(format!($cache_key_tmpl, id)).await;
|
self.2.remove(format!($cache_key_tmpl, id)).await;
|
||||||
self.2.remove(format!($reactions_key_tmpl, id)).await;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,3 +4,6 @@ pub const CREATE_TABLE_ENTRIES: &str = include_str!("./sql/create_entries.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");
|
||||||
|
pub const CREATE_TABLE_USERFOLLOWS: &str = include_str!("./sql/create_userfollows.sql");
|
||||||
|
pub const CREATE_TABLE_USERBLOCKS: &str = include_str!("./sql/create_userblocks.sql");
|
||||||
|
pub const CREATE_TABLE_IPBANS: &str = include_str!("./sql/create_ipbans.sql");
|
||||||
|
|
6
crates/core/src/database/drivers/sql/create_ipbans.sql
Normal file
6
crates/core/src/database/drivers/sql/create_ipbans.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS ipbans (
|
||||||
|
ip TEXT NOT NULL,
|
||||||
|
created INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
reason TEXT NOT NULL,
|
||||||
|
moderator TEXT NOT NULL
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS userblocks (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
created INTEGER NOT NULL,
|
||||||
|
initiator INTEGER NOT NULL,
|
||||||
|
receiver INTEGER NOT NULL
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS userfollows (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
created INTEGER NOT NULL,
|
||||||
|
initiator INTEGER NOT NULL,
|
||||||
|
receiver INTEGER NOT NULL
|
||||||
|
)
|
|
@ -8,5 +8,7 @@ CREATE TABLE IF NOT EXISTS users (
|
||||||
tokens TEXT NOT NULL,
|
tokens TEXT NOT NULL,
|
||||||
permissions INTEGER NOT NULL,
|
permissions INTEGER NOT NULL,
|
||||||
-- counts
|
-- counts
|
||||||
notification_count INTEGER NOT NULL
|
notification_count INTEGER NOT NULL,
|
||||||
|
follower_count INTEGER NOT NULL,
|
||||||
|
following_count INTEGER NOT NULL
|
||||||
)
|
)
|
||||||
|
|
90
crates/core/src/database/ipbans.rs
Normal file
90
crates/core/src/database/ipbans.rs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
use super::*;
|
||||||
|
use crate::cache::Cache;
|
||||||
|
use crate::model::{Error, Result, auth::IpBan, auth::User, permissions::FinePermission};
|
||||||
|
use crate::{auto_method, execute, get, query_row};
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
use rusqlite::Row;
|
||||||
|
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
|
use tokio_postgres::Row;
|
||||||
|
|
||||||
|
impl DataManager {
|
||||||
|
/// Get a [`IpBan`] from an SQL row.
|
||||||
|
pub(crate) fn get_ipban_from_row(
|
||||||
|
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
||||||
|
#[cfg(feature = "postgres")] x: &Row,
|
||||||
|
) -> IpBan {
|
||||||
|
IpBan {
|
||||||
|
ip: get!(x->0(String)),
|
||||||
|
created: get!(x->1(i64)) as usize,
|
||||||
|
reason: get!(x->2(String)),
|
||||||
|
moderator: get!(x->3(i64)) as usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto_method!(get_ipban_by_ip(&str)@get_ipban_from_row -> "SELECT * FROM ipbans WHERE ip = $1" --name="ip ban" --returns=IpBan --cache-key-tmpl="atto.ipban:{}");
|
||||||
|
|
||||||
|
/// Create a new user block in the database.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `data` - a mock [`IpBan`] object to insert
|
||||||
|
pub async fn create_ipban(&self, data: IpBan) -> Result<()> {
|
||||||
|
let user = self.get_user_by_id(data.moderator).await?;
|
||||||
|
|
||||||
|
// ONLY moderators can create ip bans
|
||||||
|
if !user.permissions.check(FinePermission::MANAGE_BANS) {
|
||||||
|
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,
|
||||||
|
"INSERT INTO ipbans VALUES ($1, $2, $3, $4)",
|
||||||
|
&[
|
||||||
|
&data.ip.as_str(),
|
||||||
|
&data.created.to_string().as_str(),
|
||||||
|
&data.reason.as_str(),
|
||||||
|
&data.moderator.to_string().as_str()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// return
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_ipban(&self, id: usize, user: User) -> Result<()> {
|
||||||
|
// ONLY moderators can manage ip bans
|
||||||
|
if !user.permissions.check(FinePermission::MANAGE_BANS) {
|
||||||
|
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 ipbans WHERE id = $1",
|
||||||
|
&[&id.to_string()]
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.2.remove(format!("atto.ipban:{}", id)).await;
|
||||||
|
|
||||||
|
// return
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::cache::Cache;
|
use crate::cache::Cache;
|
||||||
use crate::model::journal::JournalPageMembership;
|
use crate::model::journal::JournalMembership;
|
||||||
use crate::model::journal_permissions::JournalPermission;
|
use crate::model::journal_permissions::JournalPermission;
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
Error, Result,
|
Error, Result,
|
||||||
auth::User,
|
auth::User,
|
||||||
journal::JournalPage,
|
journal::Journal,
|
||||||
journal::{JournalPageReadAccess, JournalPageWriteAccess},
|
journal::{JournalReadAccess, JournalWriteAccess},
|
||||||
permissions::FinePermission,
|
permissions::FinePermission,
|
||||||
};
|
};
|
||||||
use crate::{auto_method, execute, get, query_row};
|
use crate::{auto_method, execute, get, query_row};
|
||||||
|
@ -18,12 +18,12 @@ use rusqlite::Row;
|
||||||
use tokio_postgres::Row;
|
use tokio_postgres::Row;
|
||||||
|
|
||||||
impl DataManager {
|
impl DataManager {
|
||||||
/// Get a [`JournalPage`] from an SQL row.
|
/// Get a [`Journal`] from an SQL row.
|
||||||
pub(crate) fn get_page_from_row(
|
pub(crate) fn get_page_from_row(
|
||||||
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
||||||
#[cfg(feature = "postgres")] x: &Row,
|
#[cfg(feature = "postgres")] x: &Row,
|
||||||
) -> JournalPage {
|
) -> Journal {
|
||||||
JournalPage {
|
Journal {
|
||||||
id: get!(x->0(i64)) as usize,
|
id: get!(x->0(i64)) as usize,
|
||||||
created: get!(x->1(i64)) as usize,
|
created: get!(x->1(i64)) as usize,
|
||||||
title: get!(x->2(String)),
|
title: get!(x->2(String)),
|
||||||
|
@ -37,13 +37,13 @@ impl DataManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto_method!(get_page_by_id()@get_page_from_row -> "SELECT * FROM pages WHERE id = $1" --name="journal page" --returns=JournalPage --cache-key-tmpl="atto.page:{}");
|
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:{}");
|
||||||
|
|
||||||
/// Create a new journal page in the database.
|
/// Create a new journal page in the database.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `data` - a mock [`JournalPage`] object to insert
|
/// * `data` - a mock [`Journal`] object to insert
|
||||||
pub async fn create_page(&self, data: JournalPage) -> Result<()> {
|
pub async fn create_page(&self, data: Journal) -> Result<()> {
|
||||||
// check values
|
// check values
|
||||||
if data.title.len() < 2 {
|
if data.title.len() < 2 {
|
||||||
return Err(Error::DataTooShort("title".to_string()));
|
return Err(Error::DataTooShort("title".to_string()));
|
||||||
|
@ -82,7 +82,7 @@ impl DataManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add journal page owner as admin
|
// add journal page owner as admin
|
||||||
self.create_membership(JournalPageMembership::new(
|
self.create_membership(JournalMembership::new(
|
||||||
data.owner,
|
data.owner,
|
||||||
data.id,
|
data.id,
|
||||||
JournalPermission::ADMINISTRATOR,
|
JournalPermission::ADMINISTRATOR,
|
||||||
|
@ -97,11 +97,11 @@ impl DataManager {
|
||||||
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 FROM pages WHERE id = $1" --cache-key-tmpl="atto.page:{}");
|
||||||
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 pages SET title = $1 WHERE id = $2" --cache-key-tmpl="atto.page:{}");
|
||||||
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 pages SET prompt = $1 WHERE id = $2" --cache-key-tmpl="atto.page:{}");
|
||||||
auto_method!(update_page_read_access(JournalPageReadAccess)@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 pages SET read_access = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.page:{}");
|
||||||
auto_method!(update_page_write_access(JournalPageWriteAccess)@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 pages SET write_access = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.page:{}");
|
||||||
|
|
||||||
auto_method!(incr_page_likes() -> "UPDATE pages SET likes = likes + 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --reactions-key-tmpl="atto.entry.likes:{}" --incr);
|
auto_method!(incr_page_likes() -> "UPDATE pages SET likes = likes + 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --incr);
|
||||||
auto_method!(incr_page_dislikes() -> "UPDATE pages SET likes = dislikes + 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --reactions-key-tmpl="atto.entry.dislikes:{}" --incr);
|
auto_method!(incr_page_dislikes() -> "UPDATE pages SET likes = dislikes + 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --incr);
|
||||||
auto_method!(decr_page_likes() -> "UPDATE pages SET likes = likes - 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --reactions-key-tmpl="atto.entry.likes:{}" --decr);
|
auto_method!(decr_page_likes() -> "UPDATE pages SET likes = likes - 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --decr);
|
||||||
auto_method!(decr_page_dislikes() -> "UPDATE pages SET likes = dislikes - 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --reactions-key-tmpl="atto.entry.dislikes:{}" --decr);
|
auto_method!(decr_page_dislikes() -> "UPDATE pages SET likes = dislikes - 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --decr);
|
||||||
}
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::cache::Cache;
|
use crate::cache::Cache;
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
Error, Result, auth::User, journal::JournalPageMembership,
|
Error, Result, auth::User, journal::JournalMembership, journal_permissions::JournalPermission,
|
||||||
journal_permissions::JournalPermission, permissions::FinePermission,
|
permissions::FinePermission,
|
||||||
};
|
};
|
||||||
use crate::{auto_method, execute, get, query_row};
|
use crate::{auto_method, execute, get, query_row};
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@ impl DataManager {
|
||||||
pub(crate) fn get_membership_from_row(
|
pub(crate) fn get_membership_from_row(
|
||||||
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
||||||
#[cfg(feature = "postgres")] x: &Row,
|
#[cfg(feature = "postgres")] x: &Row,
|
||||||
) -> JournalPageMembership {
|
) -> JournalMembership {
|
||||||
JournalPageMembership {
|
JournalMembership {
|
||||||
id: get!(x->0(i64)) as usize,
|
id: get!(x->0(i64)) as usize,
|
||||||
created: get!(x->1(i64)) as usize,
|
created: get!(x->1(i64)) as usize,
|
||||||
owner: get!(x->2(i64)) as usize,
|
owner: get!(x->2(i64)) as usize,
|
||||||
|
@ -27,14 +27,14 @@ impl DataManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto_method!(get_membership_by_id()@get_membership_from_row -> "SELECT * FROM memberships WHERE id = $1" --name="journal membership" --returns=JournalPageMembership --cache-key-tmpl="atto.membership:{}");
|
auto_method!(get_membership_by_id()@get_membership_from_row -> "SELECT * FROM memberships WHERE id = $1" --name="journal membership" --returns=JournalMembership --cache-key-tmpl="atto.membership:{}");
|
||||||
|
|
||||||
/// Get a journal page membership by `owner` and `journal`.
|
/// Get a journal membership by `owner` and `journal`.
|
||||||
pub async fn get_membership_by_owner_journal(
|
pub async fn get_membership_by_owner_journal(
|
||||||
&self,
|
&self,
|
||||||
owner: usize,
|
owner: usize,
|
||||||
journal: usize,
|
journal: usize,
|
||||||
) -> Result<JournalPageMembership> {
|
) -> Result<JournalMembership> {
|
||||||
let conn = match self.connect().await {
|
let conn = match self.connect().await {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
@ -54,11 +54,11 @@ impl DataManager {
|
||||||
Ok(res.unwrap())
|
Ok(res.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new journal page membership in the database.
|
/// Create a new journal membership in the database.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `data` - a mock [`JournalPageMembership`] object to insert
|
/// * `data` - a mock [`JournalMembership`] object to insert
|
||||||
pub async fn create_membership(&self, data: JournalPageMembership) -> Result<()> {
|
pub async fn create_membership(&self, data: JournalMembership) -> Result<()> {
|
||||||
let conn = match self.connect().await {
|
let conn = match self.connect().await {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
mod auth;
|
mod auth;
|
||||||
mod common;
|
mod common;
|
||||||
mod drivers;
|
mod drivers;
|
||||||
mod entries;
|
mod ipbans;
|
||||||
|
mod journals;
|
||||||
mod memberships;
|
mod memberships;
|
||||||
mod notifications;
|
mod notifications;
|
||||||
mod pages;
|
mod posts;
|
||||||
mod reactions;
|
mod reactions;
|
||||||
|
mod userblocks;
|
||||||
|
mod userfollows;
|
||||||
|
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "sqlite")]
|
||||||
pub use drivers::sqlite::*;
|
pub use drivers::sqlite::*;
|
||||||
|
|
|
@ -10,7 +10,7 @@ use rusqlite::Row;
|
||||||
use tokio_postgres::Row;
|
use tokio_postgres::Row;
|
||||||
|
|
||||||
impl DataManager {
|
impl DataManager {
|
||||||
/// Get a [`Reaction`] from an SQL row.
|
/// Get a [`Notification`] from an SQL row.
|
||||||
pub(crate) fn get_notification_from_row(
|
pub(crate) fn get_notification_from_row(
|
||||||
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
||||||
#[cfg(feature = "postgres")] x: &Row,
|
#[cfg(feature = "postgres")] x: &Row,
|
||||||
|
@ -41,7 +41,7 @@ impl DataManager {
|
||||||
);
|
);
|
||||||
|
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
return Err(Error::GeneralNotFound("reactions".to_string()));
|
return Err(Error::GeneralNotFound("notification".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(res.unwrap())
|
Ok(res.unwrap())
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::cache::Cache;
|
use crate::cache::Cache;
|
||||||
use crate::model::journal::JournalEntryContext;
|
use crate::model::journal::JournalPostContext;
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
Error, Result, auth::User, journal::JournalEntry, journal::JournalPageWriteAccess,
|
Error, Result, auth::User, journal::JournalPost, journal::JournalWriteAccess,
|
||||||
permissions::FinePermission,
|
permissions::FinePermission,
|
||||||
};
|
};
|
||||||
use crate::{auto_method, execute, get, query_row};
|
use crate::{auto_method, execute, get, query_row};
|
||||||
|
@ -15,11 +15,11 @@ use tokio_postgres::Row;
|
||||||
|
|
||||||
impl DataManager {
|
impl DataManager {
|
||||||
/// Get a [`JournalEntry`] from an SQL row.
|
/// Get a [`JournalEntry`] from an SQL row.
|
||||||
pub(crate) fn get_entry_from_row(
|
pub(crate) fn get_post_from_row(
|
||||||
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
||||||
#[cfg(feature = "postgres")] x: &Row,
|
#[cfg(feature = "postgres")] x: &Row,
|
||||||
) -> JournalEntry {
|
) -> JournalPost {
|
||||||
JournalEntry {
|
JournalPost {
|
||||||
id: get!(x->0(i64)) as usize,
|
id: get!(x->0(i64)) as usize,
|
||||||
created: get!(x->1(i64)) as usize,
|
created: get!(x->1(i64)) as usize,
|
||||||
content: get!(x->2(String)),
|
content: get!(x->2(String)),
|
||||||
|
@ -32,13 +32,13 @@ impl DataManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto_method!(get_entry_by_id()@get_entry_from_row -> "SELECT * FROM entries WHERE id = $1" --name="journal entry" --returns=JournalEntry --cache-key-tmpl="atto.entry:{}");
|
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:{}");
|
||||||
|
|
||||||
/// 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: JournalEntry) -> Result<()> {
|
pub async fn create_entry(&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()));
|
||||||
|
@ -53,12 +53,12 @@ impl DataManager {
|
||||||
};
|
};
|
||||||
|
|
||||||
match page.write_access {
|
match page.write_access {
|
||||||
JournalPageWriteAccess::Owner => {
|
JournalWriteAccess::Owner => {
|
||||||
if data.owner != page.owner {
|
if data.owner != page.owner {
|
||||||
return Err(Error::NotAllowed);
|
return Err(Error::NotAllowed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JournalPageWriteAccess::Joined => {
|
JournalWriteAccess::Joined => {
|
||||||
if let Err(_) = self
|
if let Err(_) = self
|
||||||
.get_membership_by_owner_journal(data.owner, page.id)
|
.get_membership_by_owner_journal(data.owner, page.id)
|
||||||
.await
|
.await
|
||||||
|
@ -95,12 +95,12 @@ impl DataManager {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
auto_method!(delete_entry()@get_entry_by_id:MANAGE_JOURNAL_ENTRIES -> "DELETE FROM entries WHERE id = $1" --cache-key-tmpl="atto.entry:{}");
|
auto_method!(delete_entry()@get_post_by_id:MANAGE_JOURNAL_ENTRIES -> "DELETE FROM entries WHERE id = $1" --cache-key-tmpl="atto.entry:{}");
|
||||||
auto_method!(update_entry_content(String)@get_entry_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 entries SET content = $1 WHERE id = $2" --cache-key-tmpl="atto.entry:{}");
|
||||||
auto_method!(update_entry_context(JournalEntryContext)@get_entry_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 entries SET context = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.entry:{}");
|
||||||
|
|
||||||
auto_method!(incr_entry_likes() -> "UPDATE entries SET likes = likes + 1 WHERE id = $1" --cache-key-tmpl="atto.entry:{}" --reactions-key-tmpl="atto.entry.likes:{}" --incr);
|
auto_method!(incr_post_likes() -> "UPDATE entries SET likes = likes + 1 WHERE id = $1" --cache-key-tmpl="atto.entry:{}" --incr);
|
||||||
auto_method!(incr_entry_dislikes() -> "UPDATE entries SET likes = dislikes + 1 WHERE id = $1" --cache-key-tmpl="atto.entry:{}" --reactions-key-tmpl="atto.entry.dislikes:{}" --incr);
|
auto_method!(incr_post_dislikes() -> "UPDATE entries SET likes = dislikes + 1 WHERE id = $1" --cache-key-tmpl="atto.entry:{}" --incr);
|
||||||
auto_method!(decr_entry_likes() -> "UPDATE entries SET likes = likes - 1 WHERE id = $1" --cache-key-tmpl="atto.entry:{}" --reactions-key-tmpl="atto.entry.likes:{}" --decr);
|
auto_method!(decr_post_likes() -> "UPDATE entries SET likes = likes - 1 WHERE id = $1" --cache-key-tmpl="atto.entry:{}" --decr);
|
||||||
auto_method!(decr_entry_dislikes() -> "UPDATE entries SET likes = dislikes - 1 WHERE id = $1" --cache-key-tmpl="atto.entry:{}" --reactions-key-tmpl="atto.entry.dislikes:{}" --decr);
|
auto_method!(decr_post_dislikes() -> "UPDATE entries SET likes = dislikes - 1 WHERE id = $1" --cache-key-tmpl="atto.entry:{}" --decr);
|
||||||
}
|
}
|
|
@ -51,13 +51,13 @@ impl DataManager {
|
||||||
);
|
);
|
||||||
|
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
return Err(Error::GeneralNotFound("reactions".to_string()));
|
return Err(Error::GeneralNotFound("reaction".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(res.unwrap())
|
Ok(res.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new journal page membership in the database.
|
/// Create a new journal membership in the database.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `data` - a mock [`Reaction`] object to insert
|
/// * `data` - a mock [`Reaction`] object to insert
|
||||||
|
@ -86,13 +86,13 @@ impl DataManager {
|
||||||
|
|
||||||
// incr corresponding
|
// incr corresponding
|
||||||
match data.asset_type {
|
match data.asset_type {
|
||||||
AssetType::JournalPage => {
|
AssetType::Journal => {
|
||||||
if let Err(e) = self.incr_page_likes(data.id).await {
|
if let Err(e) = self.incr_page_likes(data.id).await {
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssetType::JournalEntry => {
|
AssetType::JournalEntry => {
|
||||||
if let Err(e) = self.incr_entry_likes(data.id).await {
|
if let Err(e) = self.incr_post_likes(data.id).await {
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,13 +130,13 @@ impl DataManager {
|
||||||
|
|
||||||
// decr corresponding
|
// decr corresponding
|
||||||
match reaction.asset_type {
|
match reaction.asset_type {
|
||||||
AssetType::JournalPage => {
|
AssetType::Journal => {
|
||||||
if let Err(e) = self.decr_page_likes(reaction.asset).await {
|
if let Err(e) = self.decr_page_likes(reaction.asset).await {
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AssetType::JournalEntry => {
|
AssetType::JournalEntry => {
|
||||||
if let Err(e) = self.decr_entry_likes(reaction.asset).await {
|
if let Err(e) = self.decr_post_likes(reaction.asset).await {
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
137
crates/core/src/database/userblocks.rs
Normal file
137
crates/core/src/database/userblocks.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
use super::*;
|
||||||
|
use crate::cache::Cache;
|
||||||
|
use crate::model::{Error, Result, auth::User, auth::UserBlock, permissions::FinePermission};
|
||||||
|
use crate::{auto_method, execute, get, query_row};
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
use rusqlite::Row;
|
||||||
|
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
|
use tokio_postgres::Row;
|
||||||
|
|
||||||
|
impl DataManager {
|
||||||
|
/// Get a [`UserBlock`] from an SQL row.
|
||||||
|
pub(crate) fn get_userblock_from_row(
|
||||||
|
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
||||||
|
#[cfg(feature = "postgres")] x: &Row,
|
||||||
|
) -> UserBlock {
|
||||||
|
UserBlock {
|
||||||
|
id: get!(x->0(i64)) as usize,
|
||||||
|
created: get!(x->1(i64)) as usize,
|
||||||
|
initiator: get!(x->2(i64)) as usize,
|
||||||
|
receiver: get!(x->3(i64)) as usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto_method!(get_userblock_by_id()@get_userblock_from_row -> "SELECT * FROM userblocks WHERE id = $1" --name="user block" --returns=UserBlock --cache-key-tmpl="atto.userblock:{}");
|
||||||
|
|
||||||
|
/// Get a user block by `initiator` and `receiver` (in that order).
|
||||||
|
pub async fn get_userblock_by_initiator_receiver(
|
||||||
|
&self,
|
||||||
|
initiator: usize,
|
||||||
|
receiver: usize,
|
||||||
|
) -> Result<UserBlock> {
|
||||||
|
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 userblocks WHERE initator = $1 AND receiver = $2",
|
||||||
|
&[&(initiator as i64), &(receiver as i64)],
|
||||||
|
|x| { Ok(Self::get_userblock_from_row(x)) }
|
||||||
|
);
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
return Err(Error::GeneralNotFound("user block".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a user block by `receiver` and `initiator` (in that order).
|
||||||
|
pub async fn get_userblock_by_receiver_initiator(
|
||||||
|
&self,
|
||||||
|
receiver: usize,
|
||||||
|
initiator: usize,
|
||||||
|
) -> Result<UserBlock> {
|
||||||
|
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 userblocks WHERE receiver = $1 AND initiator = $2",
|
||||||
|
&[&(receiver as i64), &(initiator as i64)],
|
||||||
|
|x| { Ok(Self::get_userblock_from_row(x)) }
|
||||||
|
);
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
return Err(Error::GeneralNotFound("user block".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new user block in the database.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `data` - a mock [`UserBlock`] object to insert
|
||||||
|
pub async fn create_userblock(&self, data: UserBlock) -> Result<()> {
|
||||||
|
let conn = match self.connect().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = execute!(
|
||||||
|
&conn,
|
||||||
|
"INSERT INTO userblocks VALUES ($1, $2, $3, $4)",
|
||||||
|
&[
|
||||||
|
&data.id.to_string().as_str(),
|
||||||
|
&data.created.to_string().as_str(),
|
||||||
|
&data.initiator.to_string().as_str(),
|
||||||
|
&data.receiver.to_string().as_str()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// return
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_userblock(&self, id: usize, user: User) -> Result<()> {
|
||||||
|
let block = self.get_userblock_by_id(id).await?;
|
||||||
|
|
||||||
|
if user.id != block.initiator {
|
||||||
|
// only the initiator (or moderators) can delete user blocks!
|
||||||
|
if !user.permissions.check(FinePermission::MANAGE_FOLLOWS) {
|
||||||
|
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 userblocks WHERE id = $1",
|
||||||
|
&[&id.to_string()]
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.2.remove(format!("atto.userblock:{}", id)).await;
|
||||||
|
|
||||||
|
// return
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
152
crates/core/src/database/userfollows.rs
Normal file
152
crates/core/src/database/userfollows.rs
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
use super::*;
|
||||||
|
use crate::cache::Cache;
|
||||||
|
use crate::model::{Error, Result, auth::User, auth::UserFollow, permissions::FinePermission};
|
||||||
|
use crate::{auto_method, execute, get, query_row};
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
use rusqlite::Row;
|
||||||
|
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
|
use tokio_postgres::Row;
|
||||||
|
|
||||||
|
impl DataManager {
|
||||||
|
/// Get a [`UserFollow`] from an SQL row.
|
||||||
|
pub(crate) fn get_userfollow_from_row(
|
||||||
|
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
||||||
|
#[cfg(feature = "postgres")] x: &Row,
|
||||||
|
) -> UserFollow {
|
||||||
|
UserFollow {
|
||||||
|
id: get!(x->0(i64)) as usize,
|
||||||
|
created: get!(x->1(i64)) as usize,
|
||||||
|
initiator: get!(x->2(i64)) as usize,
|
||||||
|
receiver: get!(x->3(i64)) as usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto_method!(get_userfollow_by_id()@get_userfollow_from_row -> "SELECT * FROM userfollows WHERE id = $1" --name="user follow" --returns=UserFollow --cache-key-tmpl="atto.userfollow:{}");
|
||||||
|
|
||||||
|
/// Get a user follow by `initiator` and `receiver` (in that order).
|
||||||
|
pub async fn get_userfollow_by_initiator_receiver(
|
||||||
|
&self,
|
||||||
|
initiator: usize,
|
||||||
|
receiver: usize,
|
||||||
|
) -> Result<UserFollow> {
|
||||||
|
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 userfollows WHERE initator = $1 AND receiver = $2",
|
||||||
|
&[&(initiator as i64), &(receiver as i64)],
|
||||||
|
|x| { Ok(Self::get_userfollow_from_row(x)) }
|
||||||
|
);
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
return Err(Error::GeneralNotFound("user follow".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a user follow by `receiver` and `initiator` (in that order).
|
||||||
|
pub async fn get_userfollow_by_receiver_initiator(
|
||||||
|
&self,
|
||||||
|
receiver: usize,
|
||||||
|
initiator: usize,
|
||||||
|
) -> Result<UserFollow> {
|
||||||
|
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 userfollows WHERE receiver = $1 AND initiator = $2",
|
||||||
|
&[&(receiver as i64), &(initiator as i64)],
|
||||||
|
|x| { Ok(Self::get_userfollow_from_row(x)) }
|
||||||
|
);
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
return Err(Error::GeneralNotFound("user follow".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new user follow in the database.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `data` - a mock [`UserFollow`] object to insert
|
||||||
|
pub async fn create_userfollow(&self, data: UserFollow) -> Result<()> {
|
||||||
|
let conn = match self.connect().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = execute!(
|
||||||
|
&conn,
|
||||||
|
"INSERT INTO userfollows VALUES ($1, $2, $3, $4)",
|
||||||
|
&[
|
||||||
|
&data.id.to_string().as_str(),
|
||||||
|
&data.created.to_string().as_str(),
|
||||||
|
&data.initiator.to_string().as_str(),
|
||||||
|
&data.receiver.to_string().as_str()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// incr counts
|
||||||
|
self.incr_user_following_count(data.initiator)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.incr_user_follower_count(data.receiver).await.unwrap();
|
||||||
|
|
||||||
|
// return
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_userfollow(&self, id: usize, user: User) -> Result<()> {
|
||||||
|
let follow = self.get_userfollow_by_id(id).await?;
|
||||||
|
|
||||||
|
if (user.id != follow.initiator) && (user.id != follow.receiver) {
|
||||||
|
if !user.permissions.check(FinePermission::MANAGE_FOLLOWS) {
|
||||||
|
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 userfollows WHERE id = $1",
|
||||||
|
&[&id.to_string()]
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.2.remove(format!("atto.userfollow:{}", id)).await;
|
||||||
|
|
||||||
|
// decr counts
|
||||||
|
self.incr_user_following_count(follow.initiator)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.incr_user_follower_count(follow.receiver)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// return
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,9 @@ pub struct User {
|
||||||
pub settings: UserSettings,
|
pub settings: UserSettings,
|
||||||
pub tokens: Vec<Token>,
|
pub tokens: Vec<Token>,
|
||||||
pub permissions: FinePermission,
|
pub permissions: FinePermission,
|
||||||
|
pub notification_count: usize,
|
||||||
|
pub follower_count: usize,
|
||||||
|
pub following_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -48,6 +51,9 @@ impl User {
|
||||||
settings: UserSettings::default(),
|
settings: UserSettings::default(),
|
||||||
tokens: Vec::new(),
|
tokens: Vec::new(),
|
||||||
permissions: FinePermission::DEFAULT,
|
permissions: FinePermission::DEFAULT,
|
||||||
|
notification_count: 0,
|
||||||
|
follower_count: 0,
|
||||||
|
following_count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,3 +103,69 @@ impl Notification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct UserFollow {
|
||||||
|
pub id: usize,
|
||||||
|
pub created: usize,
|
||||||
|
pub initiator: usize,
|
||||||
|
pub receiver: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserFollow {
|
||||||
|
/// Create a new [`UserFollow`].
|
||||||
|
pub fn new(initiator: usize, receiver: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
id: AlmostSnowflake::new(1234567890)
|
||||||
|
.to_string()
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap(),
|
||||||
|
created: unix_epoch_timestamp() as usize,
|
||||||
|
initiator,
|
||||||
|
receiver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct UserBlock {
|
||||||
|
pub id: usize,
|
||||||
|
pub created: usize,
|
||||||
|
pub initiator: usize,
|
||||||
|
pub receiver: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserBlock {
|
||||||
|
/// Create a new [`UserBlock`].
|
||||||
|
pub fn new(initiator: usize, receiver: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
id: AlmostSnowflake::new(1234567890)
|
||||||
|
.to_string()
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap(),
|
||||||
|
created: unix_epoch_timestamp() as usize,
|
||||||
|
initiator,
|
||||||
|
receiver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct IpBan {
|
||||||
|
pub ip: String,
|
||||||
|
pub created: usize,
|
||||||
|
pub reason: String,
|
||||||
|
pub moderator: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IpBan {
|
||||||
|
/// Create a new [`IpBan`].
|
||||||
|
pub fn new(ip: String, moderator: usize, reason: String) -> Self {
|
||||||
|
Self {
|
||||||
|
ip,
|
||||||
|
created: unix_epoch_timestamp() as usize,
|
||||||
|
reason,
|
||||||
|
moderator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use tetratto_shared::{snow::AlmostSnowflake, unix_epoch_timestamp};
|
||||||
use super::journal_permissions::JournalPermission;
|
use super::journal_permissions::JournalPermission;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct JournalPage {
|
pub struct Journal {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
pub created: usize,
|
pub created: usize,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
@ -12,18 +12,18 @@ pub struct JournalPage {
|
||||||
/// The ID of the owner of the journal page.
|
/// The ID of the owner of the journal page.
|
||||||
pub owner: usize,
|
pub owner: usize,
|
||||||
/// Who can read the journal page.
|
/// Who can read the journal page.
|
||||||
pub read_access: JournalPageReadAccess,
|
pub read_access: JournalReadAccess,
|
||||||
/// Who can write to the journal page (create journal entries belonging to it).
|
/// Who can write to the journal page (create journal entries belonging to it).
|
||||||
///
|
///
|
||||||
/// The owner of the journal page (and moderators) are the ***only*** people
|
/// The owner of the journal page (and moderators) are the ***only*** people
|
||||||
/// capable of removing entries.
|
/// capable of removing entries.
|
||||||
pub write_access: JournalPageWriteAccess,
|
pub write_access: JournalWriteAccess,
|
||||||
pub likes: isize,
|
pub likes: isize,
|
||||||
pub dislikes: isize,
|
pub dislikes: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JournalPage {
|
impl Journal {
|
||||||
/// Create a new [`JournalPage`].
|
/// Create a new [`Journal`].
|
||||||
pub fn new(title: String, prompt: String, owner: usize) -> Self {
|
pub fn new(title: String, prompt: String, owner: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: AlmostSnowflake::new(1234567890)
|
id: AlmostSnowflake::new(1234567890)
|
||||||
|
@ -34,17 +34,17 @@ impl JournalPage {
|
||||||
title,
|
title,
|
||||||
prompt,
|
prompt,
|
||||||
owner,
|
owner,
|
||||||
read_access: JournalPageReadAccess::default(),
|
read_access: JournalReadAccess::default(),
|
||||||
write_access: JournalPageWriteAccess::default(),
|
write_access: JournalWriteAccess::default(),
|
||||||
likes: 0,
|
likes: 0,
|
||||||
dislikes: 0,
|
dislikes: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Who can read a [`JournalPage`].
|
/// Who can read a [`Journal`].
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub enum JournalPageReadAccess {
|
pub enum JournalReadAccess {
|
||||||
/// Everybody can view the journal page from the owner's profile.
|
/// Everybody can view the journal page from the owner's profile.
|
||||||
Everybody,
|
Everybody,
|
||||||
/// Only people with the link to the journal page.
|
/// Only people with the link to the journal page.
|
||||||
|
@ -53,15 +53,15 @@ pub enum JournalPageReadAccess {
|
||||||
Private,
|
Private,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for JournalPageReadAccess {
|
impl Default for JournalReadAccess {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Everybody
|
Self::Everybody
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Who can write to a [`JournalPage`].
|
/// Who can write to a [`Journal`].
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub enum JournalPageWriteAccess {
|
pub enum JournalWriteAccess {
|
||||||
/// Everybody (authenticated + anonymous users).
|
/// Everybody (authenticated + anonymous users).
|
||||||
Everybody,
|
Everybody,
|
||||||
/// Authenticated users only.
|
/// Authenticated users only.
|
||||||
|
@ -74,14 +74,14 @@ pub enum JournalPageWriteAccess {
|
||||||
Owner,
|
Owner,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for JournalPageWriteAccess {
|
impl Default for JournalWriteAccess {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Authenticated
|
Self::Authenticated
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct JournalPageMembership {
|
pub struct JournalMembership {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
pub created: usize,
|
pub created: usize,
|
||||||
pub owner: usize,
|
pub owner: usize,
|
||||||
|
@ -89,7 +89,7 @@ pub struct JournalPageMembership {
|
||||||
pub role: JournalPermission,
|
pub role: JournalPermission,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JournalPageMembership {
|
impl JournalMembership {
|
||||||
pub fn new(owner: usize, journal: usize, role: JournalPermission) -> Self {
|
pub fn new(owner: usize, journal: usize, role: JournalPermission) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: AlmostSnowflake::new(1234567890)
|
id: AlmostSnowflake::new(1234567890)
|
||||||
|
@ -105,11 +105,11 @@ impl JournalPageMembership {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct JournalEntryContext {
|
pub struct JournalPostContext {
|
||||||
pub comments_enabled: bool,
|
pub comments_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for JournalEntryContext {
|
impl Default for JournalPostContext {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
comments_enabled: true,
|
comments_enabled: true,
|
||||||
|
@ -118,21 +118,21 @@ impl Default for JournalEntryContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct JournalEntry {
|
pub struct JournalPost {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
pub created: usize,
|
pub created: usize,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
/// The ID of the owner of this entry.
|
/// The ID of the owner of this entry.
|
||||||
pub owner: usize,
|
pub owner: usize,
|
||||||
/// The ID of the [`JournalPage`] this entry belongs to.
|
/// The ID of the [`Journal`] this entry belongs to.
|
||||||
pub journal: usize,
|
pub journal: usize,
|
||||||
/// Extra information about the journal entry.
|
/// Extra information about the journal entry.
|
||||||
pub context: JournalEntryContext,
|
pub context: JournalPostContext,
|
||||||
pub likes: isize,
|
pub likes: isize,
|
||||||
pub dislikes: isize,
|
pub dislikes: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JournalEntry {
|
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, owner: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -144,7 +144,7 @@ impl JournalEntry {
|
||||||
content,
|
content,
|
||||||
owner,
|
owner,
|
||||||
journal,
|
journal,
|
||||||
context: JournalEntryContext::default(),
|
context: JournalPostContext::default(),
|
||||||
likes: 0,
|
likes: 0,
|
||||||
dislikes: 0,
|
dislikes: 0,
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ bitflags! {
|
||||||
const VIEW_AUDIT_LOG = 1 << 10;
|
const VIEW_AUDIT_LOG = 1 << 10;
|
||||||
const MANAGE_MEMBERSHIPS = 1 << 11;
|
const MANAGE_MEMBERSHIPS = 1 << 11;
|
||||||
const MANAGE_REACTIONS = 1 << 12;
|
const MANAGE_REACTIONS = 1 << 12;
|
||||||
|
const MANAGE_FOLLOWS = 1 << 13;
|
||||||
|
|
||||||
const _ = !0;
|
const _ = !0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use tetratto_shared::{snow::AlmostSnowflake, unix_epoch_timestamp};
|
||||||
/// All of the items which support reactions.
|
/// All of the items which support reactions.
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub enum AssetType {
|
pub enum AssetType {
|
||||||
JournalPage,
|
Journal,
|
||||||
JournalEntry,
|
JournalEntry,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue