diff --git a/crates/app/src/public/css/style.css b/crates/app/src/public/css/style.css index a3145db..4266a74 100644 --- a/crates/app/src/public/css/style.css +++ b/crates/app/src/public/css/style.css @@ -556,6 +556,10 @@ input[type="checkbox"]:checked { background-image: url("/icons/check.svg"); } +label { + cursor: pointer; +} + /* pillmenu */ .pillmenu { display: flex; diff --git a/crates/app/src/public/html/components.lisp b/crates/app/src/public/html/components.lisp index f88a872..f61433e 100644 --- a/crates/app/src/public/html/components.lisp +++ b/crates/app/src/public/html/components.lisp @@ -89,7 +89,7 @@ (text "{%- endif %}")) (text "{%- endif %} {%- endmacro %} {% macro full_username(user) -%} {% if user and user.username -%}") (div - ("class" "flex items_center") + ("class" "flex flex_wrap items_center") (a ("href" "/@{{ user.username }}") ("class" "flush flex gap_1") @@ -119,6 +119,12 @@ ("style" "color: var(--color-primary);") ("class" "flex items_center") (text "{{ icon \"star\" }}")) + (text "{%- endif %} {% if user.checkouts|length > 0 -%}") + (span + ("title" "Donator") + ("style" "color: var(--color-primary);") + ("class" "flex items_center") + (text "{{ icon \"hand-heart\" }}")) (text "{%- endif %} {% if user.permissions|has_staff_badge -%}") (span ("title" "Staff") diff --git a/crates/app/src/public/html/economy/edit.lisp b/crates/app/src/public/html/economy/edit.lisp index 356eeb3..328133f 100644 --- a/crates/app/src/public/html/economy/edit.lisp +++ b/crates/app/src/public/html/economy/edit.lisp @@ -92,7 +92,7 @@ (div ("class" "flex flex_col gap_1") (label - ("for" "title") + ("for" "price") (str (text "economy:label.price"))) (input ("type" "number") @@ -130,7 +130,7 @@ (div ("class" "flex flex_col gap_1") (label - ("for" "title") + ("for" "stock") (str (text "economy:label.stock"))) (input ("type" "number") diff --git a/crates/app/src/routes/api/v1/auth/connections/stripe.rs b/crates/app/src/routes/api/v1/auth/connections/stripe.rs index 58aa1e8..14ca49a 100644 --- a/crates/app/src/routes/api/v1/auth/connections/stripe.rs +++ b/crates/app/src/routes/api/v1/auth/connections/stripe.rs @@ -7,7 +7,7 @@ use axum::{ }; use tetratto_core::model::{ auth::{Notification, User}, - economy::{CoinTransfer, CoinTransferMethod}, + economy::{CoinTransfer, CoinTransferMethod, CoinTransferSource}, moderation::AuditLogEntry, permissions::{FinePermission, SecondaryPermission}, ApiReturn, Error, @@ -635,6 +635,7 @@ pub async fn handle_stupid_fucking_checkout_success_session( user.id, 100, CoinTransferMethod::Transfer, + CoinTransferSource::Purchase, ), true, ) @@ -651,6 +652,7 @@ pub async fn handle_stupid_fucking_checkout_success_session( user.id, 400, CoinTransferMethod::Transfer, + CoinTransferSource::Purchase, ), true, ) diff --git a/crates/app/src/routes/api/v1/transfers.rs b/crates/app/src/routes/api/v1/transfers.rs index be26656..0995006 100644 --- a/crates/app/src/routes/api/v1/transfers.rs +++ b/crates/app/src/routes/api/v1/transfers.rs @@ -1,7 +1,7 @@ use crate::{get_user_from_token, State, cookie::CookieJar}; use axum::{response::IntoResponse, Extension, Json}; use tetratto_core::model::{ - economy::{CoinTransfer, CoinTransferMethod}, + economy::{CoinTransfer, CoinTransferMethod, CoinTransferSource}, oauth, requests::{ActionData, ActionRequest, ActionType}, ApiReturn, Error, @@ -29,6 +29,7 @@ pub async fn create_request( }, req.amount, CoinTransferMethod::Transfer, // this endpoint is ONLY for regular transfers; products export a buy endpoint for the other method + CoinTransferSource::General, ), true, ) diff --git a/crates/core/src/database/auth.rs b/crates/core/src/database/auth.rs index 1a37e3a..bdc887d 100644 --- a/crates/core/src/database/auth.rs +++ b/crates/core/src/database/auth.rs @@ -538,6 +538,61 @@ impl DataManager { return Err(Error::DatabaseError(e.to_string())); } + // delete transfers + let res = execute!( + &conn, + "DELETE FROM transfers WHERE sender = $1 OR receiver = $2", + &[&(id as i64)] + ); + + if let Err(e) = res { + return Err(Error::DatabaseError(e.to_string())); + } + + // delete products + let res = execute!( + &conn, + "DELETE FROM products WHERE owner = $1", + &[&(id as i64)] + ); + + if let Err(e) = res { + return Err(Error::DatabaseError(e.to_string())); + } + + // delete domains + let res = execute!( + &conn, + "DELETE FROM domains WHERE owner = $1", + &[&(id as i64)] + ); + + if let Err(e) = res { + return Err(Error::DatabaseError(e.to_string())); + } + + // delete services + let res = execute!( + &conn, + "DELETE FROM services WHERE owner = $1", + &[&(id as i64)] + ); + + if let Err(e) = res { + return Err(Error::DatabaseError(e.to_string())); + } + + // delete letters + let res = execute!( + &conn, + "DELETE FROM letters WHERE owner = $1", + &[&(id as i64)] + ); + + if let Err(e) = res { + return Err(Error::DatabaseError(e.to_string())); + } + // delete user follows... individually since it requires updating user counts for follow in self.get_userfollows_by_receiver_all(id).await? { self.delete_userfollow(follow.id, &user, true).await?; diff --git a/crates/core/src/database/drivers/sql/create_transfers.sql b/crates/core/src/database/drivers/sql/create_transfers.sql index d747c78..ea157e4 100644 --- a/crates/core/src/database/drivers/sql/create_transfers.sql +++ b/crates/core/src/database/drivers/sql/create_transfers.sql @@ -5,5 +5,6 @@ CREATE TABLE IF NOT EXISTS transfers ( receiver BIGINT NOT NULL, amount INT NOT NULL, is_pending INT NOT NULL, - method TEXT NOT NULL + method TEXT NOT NULL, + source TEXT NOT NULL ) diff --git a/crates/core/src/database/drivers/sql/version_migrations.sql b/crates/core/src/database/drivers/sql/version_migrations.sql index e4f30d5..33f4725 100644 --- a/crates/core/src/database/drivers/sql/version_migrations.sql +++ b/crates/core/src/database/drivers/sql/version_migrations.sql @@ -49,3 +49,7 @@ ADD COLUMN IF NOT EXISTS checkouts TEXT DEFAULT '[]'; -- products single_use ALTER TABLE products ADD COLUMN IF NOT EXISTS single_use INT DEFAULT 1; + +-- transfers source +ALTER TABLE transfers +ADD COLUMN IF NOT EXISTS source TEXT DEFAULT '"General"'; diff --git a/crates/core/src/database/products.rs b/crates/core/src/database/products.rs index 9137bc3..4c2f6f8 100644 --- a/crates/core/src/database/products.rs +++ b/crates/core/src/database/products.rs @@ -1,6 +1,8 @@ use crate::model::{ auth::User, - economy::{CoinTransfer, CoinTransferMethod, Product, ProductFulfillmentMethod}, + economy::{ + CoinTransfer, CoinTransferMethod, CoinTransferSource, Product, ProductFulfillmentMethod, + }, mail::Letter, permissions::FinePermission, Error, Result, @@ -154,6 +156,7 @@ impl DataManager { product.owner, product.price, CoinTransferMethod::Purchase(product.id), + CoinTransferSource::Sale, ); if !product.stock.is_negative() { diff --git a/crates/core/src/database/transfers.rs b/crates/core/src/database/transfers.rs index 0226c6b..ecbde84 100644 --- a/crates/core/src/database/transfers.rs +++ b/crates/core/src/database/transfers.rs @@ -18,6 +18,7 @@ impl DataManager { amount: get!(x->4(i32)), is_pending: get!(x->5(i32)) as i8 == 1, method: serde_json::from_str(&get!(x->6(String))).unwrap(), + source: serde_json::from_str(&get!(x->7(String))).unwrap(), } } @@ -175,7 +176,7 @@ impl DataManager { let res = execute!( &conn, - "INSERT INTO transfers VALUES ($1, $2, $3, $4, $5, $6, $7)", + "INSERT INTO transfers VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", params![ &(data.id as i64), &(data.created as i64), @@ -184,6 +185,7 @@ impl DataManager { &data.amount, &{ if data.is_pending { 1 } else { 0 } }, &serde_json::to_string(&data.method).unwrap(), + &serde_json::to_string(&data.source).unwrap(), ] ); diff --git a/crates/core/src/model/economy.rs b/crates/core/src/model/economy.rs index 15cf435..389cdb7 100644 --- a/crates/core/src/model/economy.rs +++ b/crates/core/src/model/economy.rs @@ -58,6 +58,16 @@ pub enum CoinTransferMethod { Purchase(usize), } +#[derive(Serialize, Deserialize)] +pub enum CoinTransferSource { + /// An unknown source, such as a transfer request. + General, + /// A product sale. + Sale, + /// A purchase of coins through Stripe. + Purchase, +} + #[derive(Serialize, Deserialize)] pub struct CoinTransfer { pub id: usize, @@ -67,11 +77,18 @@ pub struct CoinTransfer { pub amount: i32, pub is_pending: bool, pub method: CoinTransferMethod, + pub source: CoinTransferSource, } impl CoinTransfer { /// Create a new [`CoinTransfer`]. - pub fn new(sender: usize, receiver: usize, amount: i32, method: CoinTransferMethod) -> Self { + pub fn new( + sender: usize, + receiver: usize, + amount: i32, + method: CoinTransferMethod, + source: CoinTransferSource, + ) -> Self { Self { id: Snowflake::new().to_string().parse::().unwrap(), created: unix_epoch_timestamp(), @@ -80,6 +97,7 @@ impl CoinTransfer { amount, is_pending: false, method, + source, } }