add: store coin transfer source

This commit is contained in:
trisua 2025-08-08 16:01:23 -04:00
parent a08552338b
commit 98426d0989
11 changed files with 105 additions and 9 deletions

View file

@ -556,6 +556,10 @@ input[type="checkbox"]:checked {
background-image: url("/icons/check.svg"); background-image: url("/icons/check.svg");
} }
label {
cursor: pointer;
}
/* pillmenu */ /* pillmenu */
.pillmenu { .pillmenu {
display: flex; display: flex;

View file

@ -89,7 +89,7 @@
(text "{%- endif %}")) (text "{%- endif %}"))
(text "{%- endif %} {%- endmacro %} {% macro full_username(user) -%} {% if user and user.username -%}") (text "{%- endif %} {%- endmacro %} {% macro full_username(user) -%} {% if user and user.username -%}")
(div (div
("class" "flex items_center") ("class" "flex flex_wrap items_center")
(a (a
("href" "/@{{ user.username }}") ("href" "/@{{ user.username }}")
("class" "flush flex gap_1") ("class" "flush flex gap_1")
@ -119,6 +119,12 @@
("style" "color: var(--color-primary);") ("style" "color: var(--color-primary);")
("class" "flex items_center") ("class" "flex items_center")
(text "{{ icon \"star\" }}")) (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 -%}") (text "{%- endif %} {% if user.permissions|has_staff_badge -%}")
(span (span
("title" "Staff") ("title" "Staff")

View file

@ -92,7 +92,7 @@
(div (div
("class" "flex flex_col gap_1") ("class" "flex flex_col gap_1")
(label (label
("for" "title") ("for" "price")
(str (text "economy:label.price"))) (str (text "economy:label.price")))
(input (input
("type" "number") ("type" "number")
@ -130,7 +130,7 @@
(div (div
("class" "flex flex_col gap_1") ("class" "flex flex_col gap_1")
(label (label
("for" "title") ("for" "stock")
(str (text "economy:label.stock"))) (str (text "economy:label.stock")))
(input (input
("type" "number") ("type" "number")

View file

@ -7,7 +7,7 @@ use axum::{
}; };
use tetratto_core::model::{ use tetratto_core::model::{
auth::{Notification, User}, auth::{Notification, User},
economy::{CoinTransfer, CoinTransferMethod}, economy::{CoinTransfer, CoinTransferMethod, CoinTransferSource},
moderation::AuditLogEntry, moderation::AuditLogEntry,
permissions::{FinePermission, SecondaryPermission}, permissions::{FinePermission, SecondaryPermission},
ApiReturn, Error, ApiReturn, Error,
@ -635,6 +635,7 @@ pub async fn handle_stupid_fucking_checkout_success_session(
user.id, user.id,
100, 100,
CoinTransferMethod::Transfer, CoinTransferMethod::Transfer,
CoinTransferSource::Purchase,
), ),
true, true,
) )
@ -651,6 +652,7 @@ pub async fn handle_stupid_fucking_checkout_success_session(
user.id, user.id,
400, 400,
CoinTransferMethod::Transfer, CoinTransferMethod::Transfer,
CoinTransferSource::Purchase,
), ),
true, true,
) )

View file

@ -1,7 +1,7 @@
use crate::{get_user_from_token, State, cookie::CookieJar}; use crate::{get_user_from_token, State, cookie::CookieJar};
use axum::{response::IntoResponse, Extension, Json}; use axum::{response::IntoResponse, Extension, Json};
use tetratto_core::model::{ use tetratto_core::model::{
economy::{CoinTransfer, CoinTransferMethod}, economy::{CoinTransfer, CoinTransferMethod, CoinTransferSource},
oauth, oauth,
requests::{ActionData, ActionRequest, ActionType}, requests::{ActionData, ActionRequest, ActionType},
ApiReturn, Error, ApiReturn, Error,
@ -29,6 +29,7 @@ pub async fn create_request(
}, },
req.amount, req.amount,
CoinTransferMethod::Transfer, // this endpoint is ONLY for regular transfers; products export a buy endpoint for the other method CoinTransferMethod::Transfer, // this endpoint is ONLY for regular transfers; products export a buy endpoint for the other method
CoinTransferSource::General,
), ),
true, true,
) )

View file

@ -538,6 +538,61 @@ impl DataManager {
return Err(Error::DatabaseError(e.to_string())); 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 // delete user follows... individually since it requires updating user counts
for follow in self.get_userfollows_by_receiver_all(id).await? { for follow in self.get_userfollows_by_receiver_all(id).await? {
self.delete_userfollow(follow.id, &user, true).await?; self.delete_userfollow(follow.id, &user, true).await?;

View file

@ -5,5 +5,6 @@ CREATE TABLE IF NOT EXISTS transfers (
receiver BIGINT NOT NULL, receiver BIGINT NOT NULL,
amount INT NOT NULL, amount INT NOT NULL,
is_pending INT NOT NULL, is_pending INT NOT NULL,
method TEXT NOT NULL method TEXT NOT NULL,
source TEXT NOT NULL
) )

View file

@ -49,3 +49,7 @@ ADD COLUMN IF NOT EXISTS checkouts TEXT DEFAULT '[]';
-- products single_use -- products single_use
ALTER TABLE products ALTER TABLE products
ADD COLUMN IF NOT EXISTS single_use INT DEFAULT 1; ADD COLUMN IF NOT EXISTS single_use INT DEFAULT 1;
-- transfers source
ALTER TABLE transfers
ADD COLUMN IF NOT EXISTS source TEXT DEFAULT '"General"';

View file

@ -1,6 +1,8 @@
use crate::model::{ use crate::model::{
auth::User, auth::User,
economy::{CoinTransfer, CoinTransferMethod, Product, ProductFulfillmentMethod}, economy::{
CoinTransfer, CoinTransferMethod, CoinTransferSource, Product, ProductFulfillmentMethod,
},
mail::Letter, mail::Letter,
permissions::FinePermission, permissions::FinePermission,
Error, Result, Error, Result,
@ -154,6 +156,7 @@ impl DataManager {
product.owner, product.owner,
product.price, product.price,
CoinTransferMethod::Purchase(product.id), CoinTransferMethod::Purchase(product.id),
CoinTransferSource::Sale,
); );
if !product.stock.is_negative() { if !product.stock.is_negative() {

View file

@ -18,6 +18,7 @@ impl DataManager {
amount: get!(x->4(i32)), amount: get!(x->4(i32)),
is_pending: get!(x->5(i32)) as i8 == 1, is_pending: get!(x->5(i32)) as i8 == 1,
method: serde_json::from_str(&get!(x->6(String))).unwrap(), 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!( let res = execute!(
&conn, &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![ params![
&(data.id as i64), &(data.id as i64),
&(data.created as i64), &(data.created as i64),
@ -184,6 +185,7 @@ impl DataManager {
&data.amount, &data.amount,
&{ if data.is_pending { 1 } else { 0 } }, &{ if data.is_pending { 1 } else { 0 } },
&serde_json::to_string(&data.method).unwrap(), &serde_json::to_string(&data.method).unwrap(),
&serde_json::to_string(&data.source).unwrap(),
] ]
); );

View file

@ -58,6 +58,16 @@ pub enum CoinTransferMethod {
Purchase(usize), 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)] #[derive(Serialize, Deserialize)]
pub struct CoinTransfer { pub struct CoinTransfer {
pub id: usize, pub id: usize,
@ -67,11 +77,18 @@ pub struct CoinTransfer {
pub amount: i32, pub amount: i32,
pub is_pending: bool, pub is_pending: bool,
pub method: CoinTransferMethod, pub method: CoinTransferMethod,
pub source: CoinTransferSource,
} }
impl CoinTransfer { impl CoinTransfer {
/// Create a new [`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 { Self {
id: Snowflake::new().to_string().parse::<usize>().unwrap(), id: Snowflake::new().to_string().parse::<usize>().unwrap(),
created: unix_epoch_timestamp(), created: unix_epoch_timestamp(),
@ -80,6 +97,7 @@ impl CoinTransfer {
amount, amount,
is_pending: false, is_pending: false,
method, method,
source,
} }
} }