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) => {
|
||||
trigger("atto::toast", [
|
||||
res.ok ? "sucesss" : "error",
|
||||
res.ok ? "success" : "error",
|
||||
res.message,
|
||||
]);
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger("atto::toast", [
|
||||
res.ok ? "sucesss" : "error",
|
||||
res.ok ? "success" : "error",
|
||||
res.message,
|
||||
]);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
trigger("atto::toast", [
|
||||
res.ok ? "sucesss" : "error",
|
||||
res.ok ? "success" : "error",
|
||||
res.message,
|
||||
]);
|
||||
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
pub mod auth;
|
||||
pub mod journal;
|
||||
pub mod reactions;
|
||||
|
||||
use axum::{
|
||||
Router,
|
||||
routing::{delete, get, post},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tetratto_core::model::journal::{
|
||||
JournalEntryContext, JournalPageReadAccess, JournalPageWriteAccess,
|
||||
use tetratto_core::model::{
|
||||
journal::{JournalEntryContext, JournalPageReadAccess, JournalPageWriteAccess},
|
||||
reactions::AssetType,
|
||||
};
|
||||
|
||||
pub fn routes() -> Router {
|
||||
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
|
||||
.route("/pages", post(journal::pages::create_request))
|
||||
.route("/pages/{id}", delete(journal::pages::delete_request))
|
||||
|
@ -113,3 +119,9 @@ pub struct UpdateJournalEntryContent {
|
|||
pub struct UpdateJournalEntryContext {
|
||||
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_ENTRIES, []).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_MEMBERSHIPS, []).unwrap();
|
||||
execute!(&conn, common::CREATE_TABLE_REACTIONS, []).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -110,9 +111,9 @@ macro_rules! auto_method {
|
|||
|
||||
($name:ident()@$select_fn:ident:$permission:ident -> $query:literal) => {
|
||||
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) {
|
||||
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) => {
|
||||
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) {
|
||||
return Err(Error::NotAllowed);
|
||||
}
|
||||
|
@ -351,4 +352,44 @@ macro_rules! auto_method {
|
|||
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_ENTRIES: &str = include_str!("./sql/create_entries.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,
|
||||
owner 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,
|
||||
owner INTEGER 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,
|
||||
journal: get!(x->4(u64)) as usize,
|
||||
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!(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!(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 memberships;
|
||||
mod pages;
|
||||
mod reactions;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub use drivers::sqlite::*;
|
||||
|
|
|
@ -31,6 +31,9 @@ impl DataManager {
|
|||
owner: get!(x->4(u64)) as usize,
|
||||
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(),
|
||||
// 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_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!(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
|
||||
/// capable of removing entries.
|
||||
pub write_access: JournalPageWriteAccess,
|
||||
pub likes: isize,
|
||||
pub dislikes: isize,
|
||||
}
|
||||
|
||||
impl JournalPage {
|
||||
|
@ -34,6 +36,8 @@ impl JournalPage {
|
|||
owner,
|
||||
read_access: JournalPageReadAccess::default(),
|
||||
write_access: JournalPageWriteAccess::default(),
|
||||
likes: 0,
|
||||
dislikes: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +128,8 @@ pub struct JournalEntry {
|
|||
pub journal: usize,
|
||||
/// Extra information about the journal entry.
|
||||
pub context: JournalEntryContext,
|
||||
pub likes: isize,
|
||||
pub dislikes: isize,
|
||||
}
|
||||
|
||||
impl JournalEntry {
|
||||
|
@ -139,6 +145,8 @@ impl JournalEntry {
|
|||
owner,
|
||||
journal,
|
||||
context: JournalEntryContext::default(),
|
||||
likes: 0,
|
||||
dislikes: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ pub mod auth;
|
|||
pub mod journal;
|
||||
pub mod journal_permissions;
|
||||
pub mod permissions;
|
||||
pub mod reactions;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ bitflags! {
|
|||
const VIEW_REPORTS = 1 << 9;
|
||||
const VIEW_AUDIT_LOG = 1 << 10;
|
||||
const MANAGE_MEMBERSHIPS = 1 << 11;
|
||||
const MANAGE_REACTIONS = 1 << 12;
|
||||
|
||||
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