remove: marketplace
This commit is contained in:
parent
2407e6b213
commit
b5f841a990
27 changed files with 22 additions and 1067 deletions
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -23,7 +23,6 @@ mod notifications;
|
|||
mod polls;
|
||||
mod pollvotes;
|
||||
mod posts;
|
||||
mod products;
|
||||
mod questions;
|
||||
mod reactions;
|
||||
mod reports;
|
||||
|
|
|
@ -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:{}");
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue