remove: marketplace

This commit is contained in:
trisua 2025-08-05 23:50:45 -04:00
parent 2407e6b213
commit b5f841a990
27 changed files with 22 additions and 1067 deletions

View file

@ -1,8 +1,7 @@
use super::common::NAME_REGEX;
use oiseau::cache::Cache;
use crate::model::auth::{
Achievement, AchievementName, AchievementRarity, Notification, StripeSellerData,
UserConnections, ACHIEVEMENTS,
Achievement, AchievementName, AchievementRarity, Notification, UserConnections, ACHIEVEMENTS,
};
use crate::model::moderation::AuditLogEntry;
use crate::model::oauth::AuthGrant;
@ -125,11 +124,10 @@ impl DataManager {
awaiting_purchase: get!(x->24(i32)) as i8 == 1,
was_purchased: get!(x->25(i32)) as i8 == 1,
browser_session: get!(x->26(String)),
seller_data: serde_json::from_str(&get!(x->27(String)).to_string()).unwrap(),
ban_reason: get!(x->28(String)),
channel_mutes: serde_json::from_str(&get!(x->29(String)).to_string()).unwrap(),
is_deactivated: get!(x->30(i32)) as i8 == 1,
ban_expire: get!(x->31(i64)) as usize,
ban_reason: get!(x->27(String)),
channel_mutes: serde_json::from_str(&get!(x->28(String)).to_string()).unwrap(),
is_deactivated: get!(x->29(i32)) as i8 == 1,
ban_expire: get!(x->30(i64)) as usize,
}
}
@ -286,7 +284,7 @@ impl DataManager {
let res = execute!(
&conn,
"INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32)",
"INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31)",
params![
&(data.id as i64),
&(data.created as i64),
@ -315,7 +313,6 @@ impl DataManager {
&if data.awaiting_purchase { 1_i32 } else { 0_i32 },
&if data.was_purchased { 1_i32 } else { 0_i32 },
&data.browser_session,
&serde_json::to_string(&data.seller_data).unwrap(),
&data.ban_reason,
&serde_json::to_string(&data.channel_mutes).unwrap(),
&if data.is_deactivated { 1_i32 } else { 0_i32 },
@ -1058,7 +1055,6 @@ impl DataManager {
auto_method!(update_user_achievements(Vec<Achievement>)@get_user_by_id -> "UPDATE users SET achievements = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
auto_method!(update_user_invite_code(i64)@get_user_by_id -> "UPDATE users SET invite_code = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
auto_method!(update_user_browser_session(&str)@get_user_by_id -> "UPDATE users SET browser_session = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
auto_method!(update_user_seller_data(StripeSellerData)@get_user_by_id -> "UPDATE users SET seller_data = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
auto_method!(update_user_ban_reason(&str)@get_user_by_id -> "UPDATE users SET ban_reason = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
auto_method!(update_user_channel_mutes(Vec<usize>)@get_user_by_id -> "UPDATE users SET channel_mutes = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
auto_method!(update_user_ban_expire(i64)@get_user_by_id -> "UPDATE users SET ban_expire = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);

View file

@ -42,7 +42,6 @@ impl DataManager {
execute!(&conn, common::CREATE_TABLE_INVITE_CODES).unwrap();
execute!(&conn, common::CREATE_TABLE_DOMAINS).unwrap();
execute!(&conn, common::CREATE_TABLE_SERVICES).unwrap();
execute!(&conn, common::CREATE_TABLE_PRODUCTS).unwrap();
execute!(&conn, common::CREATE_TABLE_APP_DATA).unwrap();
execute!(&conn, common::CREATE_TABLE_LETTERS).unwrap();

View file

@ -30,6 +30,5 @@ pub const CREATE_TABLE_MESSAGE_REACTIONS: &str = include_str!("./sql/create_mess
pub const CREATE_TABLE_INVITE_CODES: &str = include_str!("./sql/create_invite_codes.sql");
pub const CREATE_TABLE_DOMAINS: &str = include_str!("./sql/create_domains.sql");
pub const CREATE_TABLE_SERVICES: &str = include_str!("./sql/create_services.sql");
pub const CREATE_TABLE_PRODUCTS: &str = include_str!("./sql/create_products.sql");
pub const CREATE_TABLE_APP_DATA: &str = include_str!("./sql/create_app_data.sql");
pub const CREATE_TABLE_LETTERS: &str = include_str!("./sql/create_letters.sql");

View file

@ -1,12 +0,0 @@
CREATE TABLE IF NOT EXISTS products (
id BIGINT NOT NULL PRIMARY KEY,
created BIGINT NOT NULL,
owner BIGINT NOT NULL,
name TEXT NOT NULL,
description TEXT NOT NULL,
likes INT NOT NULL,
dislikes INT NOT NULL,
product_type TEXT NOT NULL,
price TEXT NOT NULL,
uploads TEXT NOT NULL
)

View file

@ -26,7 +26,6 @@ CREATE TABLE IF NOT EXISTS users (
awaiting_purchase INT NOT NULL,
was_purchased INT NOT NULL,
browser_session TEXT NOT NULL,
seller_data TEXT NOT NULL,
ban_reason TEXT NOT NULL,
channel_mutes TEXT NOT NULL,
is_deactivated INT NOT NULL,

View file

@ -29,3 +29,7 @@ ADD COLUMN IF NOT EXISTS topic BIGINT DEFAULT 0;
-- users ban_expire
ALTER TABLE users
ADD COLUMN IF NOT EXISTS ban_expire BIGINT DEFAULT 0;
-- remove users seller_data
ALTER TABLE users
DROP COLUMN IF EXISTS seller_data;

View file

@ -23,7 +23,6 @@ mod notifications;
mod polls;
mod pollvotes;
mod posts;
mod products;
mod questions;
mod reactions;
mod reports;

View file

@ -1,175 +0,0 @@
use crate::model::{
auth::User,
permissions::{FinePermission, SecondaryPermission},
products::{Product, ProductPrice},
Error, Result,
};
use crate::{auto_method, DataManager};
use oiseau::{cache::Cache, execute, get, params, query_rows, PostgresRow};
impl DataManager {
/// Get a [`Product`] from an SQL row.
pub(crate) fn get_product_from_row(x: &PostgresRow) -> Product {
Product {
id: get!(x->0(i64)) as usize,
created: get!(x->1(i64)) as usize,
owner: get!(x->2(i64)) as usize,
name: get!(x->3(String)),
description: get!(x->4(String)),
likes: get!(x->5(i32)) as isize,
dislikes: get!(x->6(i32)) as isize,
product_type: serde_json::from_str(&get!(x->7(String))).unwrap(),
price: serde_json::from_str(&get!(x->8(String))).unwrap(),
uploads: serde_json::from_str(&get!(x->9(String))).unwrap(),
}
}
auto_method!(get_product_by_id(usize as i64)@get_product_from_row -> "SELECT * FROM products WHERE id = $1" --name="product" --returns=Product --cache-key-tmpl="atto.product:{}");
/// Get all products by user.
///
/// # Arguments
/// * `id` - the ID of the user to fetch products for
/// * `batch`
/// * `page`
pub async fn get_products_by_user(
&self,
id: usize,
batch: usize,
page: usize,
) -> Result<Vec<Product>> {
let conn = match self.0.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = query_rows!(
&conn,
"SELECT * FROM products WHERE owner = $1 ORDER BY created DESC LIMIT {} OFFSET {}",
&[&(id as i64), &(batch as i64), &((page * batch) as i64)],
|x| { Self::get_product_from_row(x) }
);
if res.is_err() {
return Err(Error::GeneralNotFound("product".to_string()));
}
Ok(res.unwrap())
}
/// Get all products by user.
///
/// # Arguments
/// * `id` - the ID of the user to fetch products for
pub async fn get_products_by_user_all(&self, id: usize) -> Result<Vec<Product>> {
let conn = match self.0.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = query_rows!(
&conn,
"SELECT * FROM products WHERE owner = $1 ORDER BY created DESC",
&[&(id as i64)],
|x| { Self::get_product_from_row(x) }
);
if res.is_err() {
return Err(Error::GeneralNotFound("product".to_string()));
}
Ok(res.unwrap())
}
const MAXIMUM_FREE_PRODUCTS: usize = 15;
/// Create a new product in the database.
///
/// # Arguments
/// * `data` - a mock [`Product`] object to insert
pub async fn create_product(&self, data: Product) -> Result<Product> {
// check values
if data.name.trim().len() < 2 {
return Err(Error::DataTooShort("name".to_string()));
} else if data.name.len() > 128 {
return Err(Error::DataTooLong("name".to_string()));
}
// check number of products
let owner = self.get_user_by_id(data.owner).await?;
if !owner.permissions.check(FinePermission::SUPPORTER) {
let products = self
.get_table_row_count_where("products", &format!("owner = {}", owner.id))
.await? as usize;
if products >= Self::MAXIMUM_FREE_PRODUCTS {
return Err(Error::MiscError(
"You already have the maximum number of products you can have".to_string(),
));
}
}
// ...
let conn = match self.0.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = execute!(
&conn,
"INSERT INTO products VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
params![
&(data.id as i64),
&(data.created as i64),
&(data.owner as i64),
&data.name,
&data.description,
&0_i32,
&0_i32,
&serde_json::to_string(&data.product_type).unwrap(),
&serde_json::to_string(&data.price).unwrap(),
&serde_json::to_string(&data.uploads).unwrap(),
]
);
if let Err(e) = res {
return Err(Error::DatabaseError(e.to_string()));
}
Ok(data)
}
pub async fn delete_product(&self, id: usize, user: &User) -> Result<()> {
let product = self.get_product_by_id(id).await?;
// check user permission
if user.id != product.owner
&& !user
.secondary_permissions
.check(SecondaryPermission::MANAGE_PRODUCTS)
{
return Err(Error::NotAllowed);
}
// ...
let conn = match self.0.connect().await {
Ok(c) => c,
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let res = execute!(&conn, "DELETE FROM products WHERE id = $1", &[&(id as i64)]);
if let Err(e) = res {
return Err(Error::DatabaseError(e.to_string()));
}
// ...
self.0.1.remove(format!("atto.product:{}", id)).await;
Ok(())
}
auto_method!(update_product_name(&str)@get_product_by_id:FinePermission::MANAGE_USERS; -> "UPDATE products SET name = $1 WHERE id = $2" --cache-key-tmpl="atto.product:{}");
auto_method!(update_product_description(&str)@get_product_by_id:FinePermission::MANAGE_USERS; -> "UPDATE products SET description = $1 WHERE id = $2" --cache-key-tmpl="atto.product:{}");
auto_method!(update_product_price(ProductPrice)@get_product_by_id:FinePermission::MANAGE_USERS; -> "UPDATE products SET price = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.product:{}");
}

View file

@ -79,9 +79,6 @@ pub struct User {
/// view pages which require authentication (all `$` routes).
#[serde(default)]
pub browser_session: String,
/// Stripe connected account information (for Tetratto marketplace).
#[serde(default)]
pub seller_data: StripeSellerData,
/// The reason the user was banned.
#[serde(default)]
pub ban_reason: String,
@ -355,14 +352,6 @@ pub struct UserSettings {
pub forum_signature: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct StripeSellerData {
#[serde(default)]
pub account_id: Option<String>,
#[serde(default)]
pub completed_onboarding: bool,
}
fn mime_avif() -> String {
"image/avif".to_string()
}
@ -407,7 +396,6 @@ impl User {
awaiting_purchase: false,
was_purchased: false,
browser_session: String::new(),
seller_data: StripeSellerData::default(),
ban_reason: String::new(),
channel_mutes: Vec::new(),
is_deactivated: false,

View file

@ -11,7 +11,6 @@ pub mod mail;
pub mod moderation;
pub mod oauth;
pub mod permissions;
pub mod products;
pub mod reactions;
pub mod requests;
pub mod socket;

View file

@ -1,88 +0,0 @@
use std::fmt::Display;
use serde::{Serialize, Deserialize};
use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Product {
pub id: usize,
pub created: usize,
pub owner: usize,
pub name: String,
pub description: String,
pub likes: isize,
pub dislikes: isize,
pub product_type: ProductType,
pub price: ProductPrice,
/// Optional uploads to accompany the product title and description. Maximum of 4.
pub uploads: Vec<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ProductType {
/// Text + images.
Data,
/// When a commission product is purchased, the creator will receive a request
/// prompting them to respond with text + images.
///
/// This is the only product type which does not immediately return data to the
/// customer, as seller input is required.
///
/// If the request is deleted, the purchase should be immediately refunded.
///
/// Commissions are paid beforehand to prevent theft. This means it is vital
/// that refunds are enforced.
Commission,
}
/// A currency.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Currency {
USD,
EUR,
GBP,
}
impl Display for Currency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Currency::USD => "$",
Currency::EUR => "",
Currency::GBP => "£",
})
}
}
/// Price in USD. `(dollars, cents)`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProductPrice(u64, u64, Currency);
impl Display for ProductPrice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&format!("{}{}.{}", self.2, self.0, self.1))
}
}
impl Product {
/// Create a new [`Product`].
pub fn new(
owner: usize,
name: String,
description: String,
price: ProductPrice,
r#type: ProductType,
) -> Self {
Self {
id: Snowflake::new().to_string().parse::<usize>().unwrap(),
created: unix_epoch_timestamp(),
owner,
name,
description,
likes: 0,
dislikes: 0,
product_type: r#type,
price,
uploads: Vec::new(),
}
}
}