add: reactions
This commit is contained in:
parent
c46eb3b807
commit
382e3bc7a6
18 changed files with 357 additions and 11 deletions
|
@ -43,7 +43,7 @@
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
trigger("atto::toast", [
|
trigger("atto::toast", [
|
||||||
res.ok ? "sucesss" : "error",
|
res.ok ? "success" : "error",
|
||||||
res.message,
|
res.message,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
trigger("atto::toast", [
|
trigger("atto::toast", [
|
||||||
res.ok ? "sucesss" : "error",
|
res.ok ? "success" : "error",
|
||||||
res.message,
|
res.message,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
trigger("atto::toast", [
|
trigger("atto::toast", [
|
||||||
res.ok ? "sucesss" : "error",
|
res.ok ? "success" : "error",
|
||||||
res.message,
|
res.message,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,23 @@
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod journal;
|
pub mod journal;
|
||||||
|
pub mod reactions;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
routing::{delete, get, post},
|
routing::{delete, get, post},
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tetratto_core::model::journal::{
|
use tetratto_core::model::{
|
||||||
JournalEntryContext, JournalPageReadAccess, JournalPageWriteAccess,
|
journal::{JournalEntryContext, JournalPageReadAccess, JournalPageWriteAccess},
|
||||||
|
reactions::AssetType,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn routes() -> Router {
|
pub fn routes() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
|
// reactions
|
||||||
|
.route("/reactions", post(reactions::create_request))
|
||||||
|
.route("/reactions/{id}", get(reactions::get_request))
|
||||||
|
.route("/reactions/{id}", delete(reactions::delete_request))
|
||||||
// journal pages
|
// journal pages
|
||||||
.route("/pages", post(journal::pages::create_request))
|
.route("/pages", post(journal::pages::create_request))
|
||||||
.route("/pages/{id}", delete(journal::pages::delete_request))
|
.route("/pages/{id}", delete(journal::pages::delete_request))
|
||||||
|
@ -113,3 +119,9 @@ pub struct UpdateJournalEntryContent {
|
||||||
pub struct UpdateJournalEntryContext {
|
pub struct UpdateJournalEntryContext {
|
||||||
pub context: JournalEntryContext,
|
pub context: JournalEntryContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct CreateReaction {
|
||||||
|
pub asset: usize,
|
||||||
|
pub asset_type: AssetType,
|
||||||
|
}
|
||||||
|
|
76
crates/app/src/routes/api/v1/reactions.rs
Normal file
76
crates/app/src/routes/api/v1/reactions.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use axum::{Extension, Json, extract::Path, response::IntoResponse};
|
||||||
|
use axum_extra::extract::CookieJar;
|
||||||
|
use tetratto_core::model::{ApiReturn, Error, reactions::Reaction};
|
||||||
|
|
||||||
|
use crate::{State, get_user_from_token, routes::api::v1::CreateReaction};
|
||||||
|
|
||||||
|
pub async fn get_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
Path(id): Path<usize>,
|
||||||
|
) -> 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()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match data.get_reaction_by_owner_asset(user.id, id).await {
|
||||||
|
Ok(r) => Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Reaction exists".to_string(),
|
||||||
|
payload: Some(r),
|
||||||
|
}),
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
Json(req): Json<CreateReaction>,
|
||||||
|
) -> 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()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match data
|
||||||
|
.create_reaction(Reaction::new(user.id, req.asset, req.asset_type))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Reaction created".to_string(),
|
||||||
|
payload: (),
|
||||||
|
}),
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
Path(id): Path<usize>,
|
||||||
|
) -> 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()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let reaction = match data.get_reaction_by_owner_asset(user.id, id).await {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match data.delete_reaction(reaction.id, user).await {
|
||||||
|
Ok(_) => Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Reaction deleted".to_string(),
|
||||||
|
payload: (),
|
||||||
|
}),
|
||||||
|
Err(e) => return Json(e.into()),
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ impl DataManager {
|
||||||
execute!(&conn, common::CREATE_TABLE_PAGES, []).unwrap();
|
execute!(&conn, common::CREATE_TABLE_PAGES, []).unwrap();
|
||||||
execute!(&conn, common::CREATE_TABLE_ENTRIES, []).unwrap();
|
execute!(&conn, common::CREATE_TABLE_ENTRIES, []).unwrap();
|
||||||
execute!(&conn, common::CREATE_TABLE_MEMBERSHIPS, []).unwrap();
|
execute!(&conn, common::CREATE_TABLE_MEMBERSHIPS, []).unwrap();
|
||||||
|
execute!(&conn, common::CREATE_TABLE_REACTIONS, []).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -110,9 +111,9 @@ macro_rules! auto_method {
|
||||||
|
|
||||||
($name:ident()@$select_fn:ident:$permission:ident -> $query:literal) => {
|
($name:ident()@$select_fn:ident:$permission:ident -> $query:literal) => {
|
||||||
pub async fn $name(&self, id: usize, user: User) -> Result<()> {
|
pub async fn $name(&self, id: usize, user: User) -> Result<()> {
|
||||||
let page = self.$select_fn(id).await?;
|
let y = self.$select_fn(id).await?;
|
||||||
|
|
||||||
if user.id != page.owner {
|
if user.id != y.owner {
|
||||||
if !user.permissions.check(FinePermission::$permission) {
|
if !user.permissions.check(FinePermission::$permission) {
|
||||||
return Err(Error::NotAllowed);
|
return Err(Error::NotAllowed);
|
||||||
}
|
}
|
||||||
|
@ -135,9 +136,9 @@ macro_rules! auto_method {
|
||||||
|
|
||||||
($name:ident()@$select_fn:ident:$permission:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal) => {
|
($name:ident()@$select_fn:ident:$permission:ident -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal) => {
|
||||||
pub async fn $name(&self, id: usize, user: User) -> Result<()> {
|
pub async fn $name(&self, id: usize, user: User) -> Result<()> {
|
||||||
let page = self.$select_fn(id).await?;
|
let y = self.$select_fn(id).await?;
|
||||||
|
|
||||||
if user.id != page.owner {
|
if user.id != y.owner {
|
||||||
if !user.permissions.check(FinePermission::$permission) {
|
if !user.permissions.check(FinePermission::$permission) {
|
||||||
return Err(Error::NotAllowed);
|
return Err(Error::NotAllowed);
|
||||||
}
|
}
|
||||||
|
@ -351,4 +352,44 @@ macro_rules! auto_method {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
($name:ident() -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal --reactions-key-tmpl=$reactions_key_tmpl:literal --incr) => {
|
||||||
|
pub async fn $name(&self, id: usize) -> Result<()> {
|
||||||
|
let conn = match self.connect().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = execute!(&conn, $query, &[&id.to_string()]);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.2.remove(format!($cache_key_tmpl, id)).await;
|
||||||
|
self.2.remove(format!($reactions_key_tmpl, id)).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($name:ident() -> $query:literal --cache-key-tmpl=$cache_key_tmpl:literal --reactions-key-tmpl=$reactions_key_tmpl:literal --decr) => {
|
||||||
|
pub async fn $name(&self, id: usize) -> Result<()> {
|
||||||
|
let conn = match self.connect().await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = execute!(&conn, $query, &[&id.to_string()]);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.2.remove(format!($cache_key_tmpl, id)).await;
|
||||||
|
self.2.remove(format!($reactions_key_tmpl, id)).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,3 +2,4 @@ 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_PAGES: &str = include_str!("./sql/create_pages.sql");
|
||||||
pub const CREATE_TABLE_ENTRIES: &str = include_str!("./sql/create_entries.sql");
|
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");
|
||||||
|
|
|
@ -4,5 +4,8 @@ CREATE TABLE IF NOT EXISTS entries (
|
||||||
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,
|
||||||
|
-- likes
|
||||||
|
likes INTEGER NOT NULL,
|
||||||
|
dislikes INTEGER NOT NULL
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,5 +5,8 @@ CREATE TABLE IF NOT EXISTS pages (
|
||||||
prompt TEXT NOT NULL,
|
prompt TEXT NOT NULL,
|
||||||
owner INTEGER NOT NULL,
|
owner INTEGER NOT NULL,
|
||||||
read_access TEXT NOT NULL,
|
read_access TEXT NOT NULL,
|
||||||
write_access TEXT NOT NULL
|
write_access TEXT NOT NULL,
|
||||||
|
-- likes
|
||||||
|
likes INTEGER NOT NULL,
|
||||||
|
dislikes INTEGER NOT NULL
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS reactions (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
created INTEGER NOT NULL,
|
||||||
|
owner INTEGER NOT NULL,
|
||||||
|
asset INTEGER NOT NULL,
|
||||||
|
asset_type TEXT NOT NULL
|
||||||
|
)
|
|
@ -26,6 +26,9 @@ impl DataManager {
|
||||||
owner: get!(x->3(u64)) as usize,
|
owner: get!(x->3(u64)) as usize,
|
||||||
journal: get!(x->4(u64)) as usize,
|
journal: get!(x->4(u64)) as usize,
|
||||||
context: serde_json::from_str(&get!(x->5(String))).unwrap(),
|
context: serde_json::from_str(&get!(x->5(String))).unwrap(),
|
||||||
|
// likes
|
||||||
|
likes: get!(x->6(i64)) as isize,
|
||||||
|
dislikes: get!(x->7(i64)) as isize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,4 +98,9 @@ impl DataManager {
|
||||||
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_entry_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_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_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_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!(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_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!(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_entry_dislikes() -> "UPDATE entries SET likes = dislikes - 1 WHERE id = $1" --cache-key-tmpl="atto.entry:{}" --reactions-key-tmpl="atto.entry.dislikes:{}" --decr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ mod drivers;
|
||||||
mod entries;
|
mod entries;
|
||||||
mod memberships;
|
mod memberships;
|
||||||
mod pages;
|
mod pages;
|
||||||
|
mod reactions;
|
||||||
|
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "sqlite")]
|
||||||
pub use drivers::sqlite::*;
|
pub use drivers::sqlite::*;
|
||||||
|
|
|
@ -31,6 +31,9 @@ impl DataManager {
|
||||||
owner: get!(x->4(u64)) as usize,
|
owner: get!(x->4(u64)) as usize,
|
||||||
read_access: serde_json::from_str(&get!(x->5(String)).to_string()).unwrap(),
|
read_access: serde_json::from_str(&get!(x->5(String)).to_string()).unwrap(),
|
||||||
write_access: serde_json::from_str(&get!(x->6(String)).to_string()).unwrap(),
|
write_access: serde_json::from_str(&get!(x->6(String)).to_string()).unwrap(),
|
||||||
|
// likes
|
||||||
|
likes: get!(x->6(i64)) as isize,
|
||||||
|
dislikes: get!(x->7(i64)) as isize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,4 +99,9 @@ impl DataManager {
|
||||||
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(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_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(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!(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_dislikes() -> "UPDATE pages SET likes = dislikes + 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --reactions-key-tmpl="atto.entry.dislikes:{}" --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_dislikes() -> "UPDATE pages SET likes = dislikes - 1 WHERE id = $1" --cache-key-tmpl="atto.pages:{}" --reactions-key-tmpl="atto.entry.dislikes:{}" --decr);
|
||||||
}
|
}
|
||||||
|
|
142
crates/core/src/database/reactions.rs
Normal file
142
crates/core/src/database/reactions.rs
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
use super::*;
|
||||||
|
use crate::cache::Cache;
|
||||||
|
use crate::model::reactions::AssetType;
|
||||||
|
use crate::model::{Error, Result, auth::User, permissions::FinePermission, reactions::Reaction};
|
||||||
|
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 [`Reaction`] from an SQL row.
|
||||||
|
pub(crate) fn get_reaction_from_row(
|
||||||
|
#[cfg(feature = "sqlite")] x: &Row<'_>,
|
||||||
|
#[cfg(feature = "postgres")] x: &Row,
|
||||||
|
) -> Reaction {
|
||||||
|
Reaction {
|
||||||
|
id: get!(x->0(u64)) as usize,
|
||||||
|
created: get!(x->1(u64)) as usize,
|
||||||
|
owner: get!(x->2(u64)) as usize,
|
||||||
|
asset: get!(x->3(u64)) as usize,
|
||||||
|
asset_type: serde_json::from_str(&get!(x->4(String))).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto_method!(get_reaction_by_id()@get_reaction_from_row -> "SELECT * FROM reactions WHERE id = $1" --name="reaction" --returns=Reaction --cache-key-tmpl="atto.reaction:{}");
|
||||||
|
|
||||||
|
/// Get a reaction by `owner` and `asset`.
|
||||||
|
pub async fn get_reaction_by_owner_asset(
|
||||||
|
&self,
|
||||||
|
owner: usize,
|
||||||
|
asset: usize,
|
||||||
|
) -> Result<Reaction> {
|
||||||
|
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 reactions WHERE owner = $1 AND asset = $2",
|
||||||
|
&[&owner, &asset],
|
||||||
|
|x| { Ok(Self::get_reaction_from_row(x)) }
|
||||||
|
);
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
return Err(Error::GeneralNotFound("reactions".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new journal page membership in the database.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `data` - a mock [`Reaction`] object to insert
|
||||||
|
pub async fn create_reaction(&self, data: Reaction) -> 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 reactions VALUES ($1, $2, $3, $4, $5",
|
||||||
|
&[
|
||||||
|
&data.id.to_string().as_str(),
|
||||||
|
&data.created.to_string().as_str(),
|
||||||
|
&data.owner.to_string().as_str(),
|
||||||
|
&data.asset.to_string().as_str(),
|
||||||
|
&serde_json::to_string(&data.asset_type).unwrap().as_str(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// incr corresponding
|
||||||
|
match data.asset_type {
|
||||||
|
AssetType::JournalPage => {
|
||||||
|
if let Err(e) = self.incr_page_likes(data.id).await {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssetType::JournalEntry => {
|
||||||
|
if let Err(e) = self.incr_entry_likes(data.id).await {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// return
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_reaction(&self, id: usize, user: User) -> Result<()> {
|
||||||
|
let reaction = self.get_reaction_by_id(id).await?;
|
||||||
|
|
||||||
|
if user.id != reaction.owner {
|
||||||
|
if !user.permissions.check(FinePermission::MANAGE_REACTIONS) {
|
||||||
|
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 reactions WHERE id = $1",
|
||||||
|
&[&id.to_string()]
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
return Err(Error::DatabaseError(e.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.2.remove(format!("atto.reaction:{}", id)).await;
|
||||||
|
|
||||||
|
// decr corresponding
|
||||||
|
match reaction.asset_type {
|
||||||
|
AssetType::JournalPage => {
|
||||||
|
if let Err(e) = self.decr_page_likes(reaction.asset).await {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssetType::JournalEntry => {
|
||||||
|
if let Err(e) = self.decr_entry_likes(reaction.asset).await {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// return
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,8 @@ pub struct JournalPage {
|
||||||
/// 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: JournalPageWriteAccess,
|
||||||
|
pub likes: isize,
|
||||||
|
pub dislikes: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JournalPage {
|
impl JournalPage {
|
||||||
|
@ -34,6 +36,8 @@ impl JournalPage {
|
||||||
owner,
|
owner,
|
||||||
read_access: JournalPageReadAccess::default(),
|
read_access: JournalPageReadAccess::default(),
|
||||||
write_access: JournalPageWriteAccess::default(),
|
write_access: JournalPageWriteAccess::default(),
|
||||||
|
likes: 0,
|
||||||
|
dislikes: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,6 +128,8 @@ pub struct JournalEntry {
|
||||||
pub journal: usize,
|
pub journal: usize,
|
||||||
/// Extra information about the journal entry.
|
/// Extra information about the journal entry.
|
||||||
pub context: JournalEntryContext,
|
pub context: JournalEntryContext,
|
||||||
|
pub likes: isize,
|
||||||
|
pub dislikes: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JournalEntry {
|
impl JournalEntry {
|
||||||
|
@ -139,6 +145,8 @@ impl JournalEntry {
|
||||||
owner,
|
owner,
|
||||||
journal,
|
journal,
|
||||||
context: JournalEntryContext::default(),
|
context: JournalEntryContext::default(),
|
||||||
|
likes: 0,
|
||||||
|
dislikes: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ pub mod auth;
|
||||||
pub mod journal;
|
pub mod journal;
|
||||||
pub mod journal_permissions;
|
pub mod journal_permissions;
|
||||||
pub mod permissions;
|
pub mod permissions;
|
||||||
|
pub mod reactions;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ bitflags! {
|
||||||
const VIEW_REPORTS = 1 << 9;
|
const VIEW_REPORTS = 1 << 9;
|
||||||
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 _ = !0;
|
const _ = !0;
|
||||||
}
|
}
|
||||||
|
|
34
crates/core/src/model/reactions.rs
Normal file
34
crates/core/src/model/reactions.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tetratto_shared::{snow::AlmostSnowflake, unix_epoch_timestamp};
|
||||||
|
|
||||||
|
/// All of the items which support reactions.
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum AssetType {
|
||||||
|
JournalPage,
|
||||||
|
JournalEntry,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Reaction {
|
||||||
|
pub id: usize,
|
||||||
|
pub created: usize,
|
||||||
|
pub owner: usize,
|
||||||
|
pub asset: usize,
|
||||||
|
pub asset_type: AssetType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reaction {
|
||||||
|
/// Create a new [`Reaction`].
|
||||||
|
pub fn new(owner: usize, asset: usize, asset_type: AssetType) -> Self {
|
||||||
|
Self {
|
||||||
|
id: AlmostSnowflake::new(1234567890)
|
||||||
|
.to_string()
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap(),
|
||||||
|
created: unix_epoch_timestamp() as usize,
|
||||||
|
owner,
|
||||||
|
asset,
|
||||||
|
asset_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue