From b5f841a990d8524d9b6228f265a1bb076a7bea51 Mon Sep 17 00:00:00 2001 From: trisua Date: Tue, 5 Aug 2025 23:50:45 -0400 Subject: [PATCH] remove: marketplace --- crates/app/src/assets.rs | 6 - crates/app/src/langs/en-US.toml | 6 - crates/app/src/public/html/auth/base.lisp | 2 +- crates/app/src/public/html/auth/login.lisp | 3 +- crates/app/src/public/html/auth/register.lisp | 3 +- .../public/html/auth/seller_connection.lisp | 25 -- crates/app/src/public/html/components.lisp | 2 +- crates/app/src/public/html/macros.lisp | 14 -- .../src/public/html/marketplace/seller.lisp | 79 ------ crates/app/src/public/js/me.js | 57 ----- .../routes/api/v1/auth/connections/stripe.rs | 148 +---------- crates/app/src/routes/api/v1/auth/profile.rs | 29 +-- crates/app/src/routes/api/v1/mod.rs | 48 ---- crates/app/src/routes/api/v1/products.rs | 234 ------------------ crates/app/src/routes/pages/marketplace.rs | 107 -------- crates/app/src/routes/pages/mod.rs | 14 -- crates/core/src/database/auth.rs | 16 +- crates/core/src/database/common.rs | 1 - crates/core/src/database/drivers/common.rs | 1 - .../database/drivers/sql/create_products.sql | 12 - .../src/database/drivers/sql/create_users.sql | 1 - .../drivers/sql/version_migrations.sql | 4 + crates/core/src/database/mod.rs | 1 - crates/core/src/database/products.rs | 175 ------------- crates/core/src/model/auth.rs | 12 - crates/core/src/model/mod.rs | 1 - crates/core/src/model/products.rs | 88 ------- 27 files changed, 22 insertions(+), 1067 deletions(-) delete mode 100644 crates/app/src/public/html/auth/seller_connection.lisp delete mode 100644 crates/app/src/public/html/marketplace/seller.lisp delete mode 100644 crates/app/src/routes/api/v1/products.rs delete mode 100644 crates/app/src/routes/pages/marketplace.rs delete mode 100644 crates/core/src/database/drivers/sql/create_products.sql delete mode 100644 crates/core/src/database/products.rs delete mode 100644 crates/core/src/model/products.rs diff --git a/crates/app/src/assets.rs b/crates/app/src/assets.rs index 092889f..c8db3be 100644 --- a/crates/app/src/assets.rs +++ b/crates/app/src/assets.rs @@ -55,7 +55,6 @@ pub const AUTH_BASE: &str = include_str!("./public/html/auth/base.lisp"); pub const AUTH_LOGIN: &str = include_str!("./public/html/auth/login.lisp"); pub const AUTH_REGISTER: &str = include_str!("./public/html/auth/register.lisp"); pub const AUTH_CONNECTION: &str = include_str!("./public/html/auth/connection.lisp"); -pub const AUTH_SELLER_CONNECTION: &str = include_str!("./public/html/auth/seller_connection.lisp"); pub const PROFILE_BASE: &str = include_str!("./public/html/profile/base.lisp"); pub const PROFILE_POSTS: &str = include_str!("./public/html/profile/posts.lisp"); @@ -143,8 +142,6 @@ pub const LITTLEWEB_SERVICE: &str = include_str!("./public/html/littleweb/servic pub const LITTLEWEB_DOMAIN: &str = include_str!("./public/html/littleweb/domain.lisp"); pub const LITTLEWEB_BROWSER: &str = include_str!("./public/html/littleweb/browser.lisp"); -pub const MARKETPLACE_SELLER: &str = include_str!("./public/html/marketplace/seller.lisp"); - pub const MAIL_RECEIVED: &str = include_str!("./public/html/mail/received.lisp"); pub const MAIL_SENT: &str = include_str!("./public/html/mail/sent.lisp"); pub const MAIL_COMPOSE: &str = include_str!("./public/html/mail/compose.lisp"); @@ -297,7 +294,6 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD { write_template!(html_path->"auth/login.html"(crate::assets::AUTH_LOGIN) --config=config --lisp plugins); write_template!(html_path->"auth/register.html"(crate::assets::AUTH_REGISTER) --config=config --lisp plugins); write_template!(html_path->"auth/connection.html"(crate::assets::AUTH_CONNECTION) --config=config --lisp plugins); - write_template!(html_path->"auth/seller_connection.html"(crate::assets::AUTH_SELLER_CONNECTION) --config=config --lisp plugins); write_template!(html_path->"profile/base.html"(crate::assets::PROFILE_BASE) -d "profile" --config=config --lisp plugins); write_template!(html_path->"profile/posts.html"(crate::assets::PROFILE_POSTS) --config=config --lisp plugins); @@ -378,8 +374,6 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD { write_template!(html_path->"littleweb/domain.html"(crate::assets::LITTLEWEB_DOMAIN) --config=config --lisp plugins); write_template!(html_path->"littleweb/browser.html"(crate::assets::LITTLEWEB_BROWSER) --config=config --lisp plugins); - write_template!(html_path->"marketplace/seller.html"(crate::assets::MARKETPLACE_SELLER) -d "marketplace" --config=config --lisp plugins); - write_template!(html_path->"mail/received.html"(crate::assets::MAIL_RECEIVED) -d "mail" --config=config --lisp plugins); write_template!(html_path->"mail/sent.html"(crate::assets::MAIL_SENT) --config=config --lisp plugins); write_template!(html_path->"mail/compose.html"(crate::assets::MAIL_COMPOSE) --config=config --lisp plugins); diff --git a/crates/app/src/langs/en-US.toml b/crates/app/src/langs/en-US.toml index f995deb..523d7f7 100644 --- a/crates/app/src/langs/en-US.toml +++ b/crates/app/src/langs/en-US.toml @@ -316,12 +316,6 @@ version = "1.0.0" "littleweb:action.rename" = "Rename" "littleweb:action.add" = "Add" -"marketplace:label.products" = "Products" -"marketplace:label.status" = "Status" -"marketplace:action.get_started" = "Get started" -"marketplace:action.finsh_setting_up_account" = "Finish setting up my account" -"marketplace:action.open_seller_dashboard" = "Open seller dashboard" - "mail:label.received" = "Received" "mail:label.sent" = "Sent" "mail:label.compose" = "Compose" diff --git a/crates/app/src/public/html/auth/base.lisp b/crates/app/src/public/html/auth/base.lisp index 4940089..e9a8b9b 100644 --- a/crates/app/src/public/html/auth/base.lisp +++ b/crates/app/src/public/html/auth/base.lisp @@ -3,7 +3,7 @@ ("class" "flex flex_col gap_2") ("style" "max-width: 48ch") (h2 - ("class" "w_full text-center") + ("class" "w_full text_center") ; block for title (text "{% block title %}{% endblock %}")) (div diff --git a/crates/app/src/public/html/auth/login.lisp b/crates/app/src/public/html/auth/login.lisp index 9c1e2e4..c2e3cf0 100644 --- a/crates/app/src/public/html/auth/login.lisp +++ b/crates/app/src/public/html/auth/login.lisp @@ -113,10 +113,9 @@ (text "{% endblock %} {% block footer %}") (span - ("class" "small w_full text-center") + ("class" "small w_full text_center") (text "Or, ") (a ("href" "/auth/register") (text "register"))) - (text "{% endblock %}") diff --git a/crates/app/src/public/html/auth/register.lisp b/crates/app/src/public/html/auth/register.lisp index 4beb2fb..0fd9ae9 100644 --- a/crates/app/src/public/html/auth/register.lisp +++ b/crates/app/src/public/html/auth/register.lisp @@ -170,10 +170,9 @@ (text "{% endblock %} {% block footer %}") (span - ("class" "small w_full text-center") + ("class" "small w_full text_center") (text "Or, ") (a ("href" "/auth/login") (text "login"))) - (text "{% endblock %}") diff --git a/crates/app/src/public/html/auth/seller_connection.lisp b/crates/app/src/public/html/auth/seller_connection.lisp deleted file mode 100644 index c13b498..0000000 --- a/crates/app/src/public/html/auth/seller_connection.lisp +++ /dev/null @@ -1,25 +0,0 @@ -(text "{% extends \"auth/base.html\" %} {% block head %}") -(title - (text "Connection")) - -(text "{% endblock %} {% block title %}Connection{% endblock %} {% block content %}") -(div - ("class" "w_full flex_col gap_2") - ("id" "status") - (b - (text "Working..."))) - -(text "{% if connection_type == \"refresh\" %}") -(script - ("defer" "true") - (text "setTimeout(async () => { - trigger(\"seller::onboarding\"); - }, 1000);")) -(text "{% elif connection_type == \"return\" %}") -(script - ("defer" "true") - (text "setTimeout(async () => { - document.getElementById(\"status\").innerHTML = - `Account updated. You can now close this tab.`; - }, 1000);")) -(text "{%- endif %} {% endblock %}") diff --git a/crates/app/src/public/html/components.lisp b/crates/app/src/public/html/components.lisp index d2b2bf6..3870cc4 100644 --- a/crates/app/src/public/html/components.lisp +++ b/crates/app/src/public/html/components.lisp @@ -2668,7 +2668,7 @@ (td (a ("href" "/community/{{ community.title }}/topic/{{ post.topic }}") - ("class" "flex gap_1 items_center") + ("class" "flex gap_1 items_center w_content") (text "{{ self::community_avatar(id=post.community, community=community) }}") (span (text "{% if community.context.display_name -%} {{ community.context.display_name }} {% else %} {{ community.title }} {%- endif %}")))) diff --git a/crates/app/src/public/html/macros.lisp b/crates/app/src/public/html/macros.lisp index 6efc6eb..cd35481 100644 --- a/crates/app/src/public/html/macros.lisp +++ b/crates/app/src/public/html/macros.lisp @@ -376,17 +376,3 @@ (span (text "{{ text \"settings:tab.connections\" }}"))) (text "{%- endmacro %}") - -(text "{% macro seller_settings_nav_options() -%}") -(a - ("data-tab-button" "account") - ("class" "active") - ("href" "#/account") - (icon (text "smile")) - (span (str (text "settings:tab.account")))) -(a - ("data-tab-button" "products") - ("href" "#/products") - (icon (text "package")) - (span (str (text "marketplace:label.products")))) -(text "{%- endmacro %}") diff --git a/crates/app/src/public/html/marketplace/seller.lisp b/crates/app/src/public/html/marketplace/seller.lisp deleted file mode 100644 index c16bb70..0000000 --- a/crates/app/src/public/html/marketplace/seller.lisp +++ /dev/null @@ -1,79 +0,0 @@ -(text "{% extends \"root.html\" %} {% block head %}") -(title - (text "Seller settings - {{ config.name }}")) -(text "{% endblock %} {% block body %} {{ macros::nav() }}") -(main - ("class" "flex flex_col gap_2") - - ; nav - (div - ("class" "mobile_nav mobile") - ; primary nav - (div - ("class" "dropdown") - ("style" "width: max-content") - (button - ("class" "raised small") - ("onclick" "trigger('atto::hooks::dropdown', [event])") - ("exclude" "dropdown") - (icon (text "sliders-horizontal")) - (span ("class" "current_tab_text") (text "account"))) - (div - ("class" "inner left") - (text "{{ macros::seller_settings_nav_options() }}")))) - - ; nav desktop - (div - ("class" "desktop pillmenu") - (text "{{ macros::seller_settings_nav_options() }}")) - - ; ... - (div - ("class" "card w_full lowered flex flex_col gap_2") - ("data-tab" "account") - (div - ("class" "card_nest w_full") - (div - ("class" "card small flex items_center gap_2") - (div - ("class" "notification") - ("style" "width: 46px") - (icon (text "stripe"))) - - (b (str (text "marketplace:label.status")))) - - (div - ("class" "card") - (text "{% if user.seller_data.account_id -%}") - (text "{% if user.seller_data.completed_onboarding -%}") - ; completed onboarding + has stripe account linked - (button - ("onclick" "trigger('seller::login')") - (icon (text "arrow-right")) - (str (text "marketplace:action.open_seller_dashboard"))) - (text "{% else %}") - ; not completed onboarding - (p (text "You've not finished setting up your Stripe account.")) - (p (text "Please complete onboarding to accept payments.")) - - (button - ("onclick" "trigger('seller::onboarding')") - (icon (text "arrow-right")) - (str (text "marketplace:action.finsh_setting_up_account"))) - (text "{%- endif %}") - (text "{% else %}") - ; doesn't have a stripe account linked - (button - ("onclick" "trigger('seller::register')") - (icon (text "arrow-right")) - (str (text "marketplace:action.get_started"))) - (text "{%- endif %}")))) - - (div - ("class" "card w_full lowered hidden flex flex_col gap_2") - ("data-tab" "products") - (div - ("class" "card w_full flex flex_wrap gap_2") - ))) - -(text "{% endblock %}") diff --git a/crates/app/src/public/js/me.js b/crates/app/src/public/js/me.js index 9b8ad1d..e1f7def 100644 --- a/crates/app/src/public/js/me.js +++ b/crates/app/src/public/js/me.js @@ -1205,60 +1205,3 @@ ]); }); })(); - -(() => { - const self = reg_ns("seller"); - - self.define("register", async () => { - await trigger("atto::debounce", ["seller::register"]); - - if ( - !(await trigger("atto::confirm", [ - "Are you sure you want to do this?", - ])) - ) { - return; - } - - const res = await ( - await fetch("/api/v1/service_hooks/stripe/seller/register", { - method: "POST", - }) - ).json(); - - trigger("atto::toast", [res.ok ? "success" : "error", res.message]); - self.onboarding(); - }); - - self.define("onboarding", async () => { - await trigger("atto::debounce", ["seller::onboarding"]); - - const res = await ( - await fetch("/api/v1/service_hooks/stripe/seller/onboarding", { - method: "POST", - }) - ).json(); - - trigger("atto::toast", [res.ok ? "success" : "error", res.message]); - - if (res.ok) { - window.location.href = res.payload; - } - }); - - self.define("login", async () => { - await trigger("atto::debounce", ["seller::login"]); - - const res = await ( - await fetch("/api/v1/service_hooks/stripe/seller/login", { - method: "POST", - }) - ).json(); - - trigger("atto::toast", [res.ok ? "success" : "error", res.message]); - - if (res.ok) { - window.location.href = res.payload; - } - }); -})(); 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 837d2d3..992deac 100644 --- a/crates/app/src/routes/api/v1/auth/connections/stripe.rs +++ b/crates/app/src/routes/api/v1/auth/connections/stripe.rs @@ -1,7 +1,5 @@ -use std::{str::FromStr, time::Duration}; - +use std::time::Duration; use axum::{http::HeaderMap, response::IntoResponse, Extension, Json}; -use crate::cookie::CookieJar; use tetratto_core::model::{ auth::{Notification, User}, moderation::AuditLogEntry, @@ -9,7 +7,7 @@ use tetratto_core::model::{ ApiReturn, Error, }; use stripe::{EventObject, EventType}; -use crate::{get_user_from_token, State}; +use crate::State; pub async fn stripe_webhook( Extension(data): Extension, @@ -471,145 +469,3 @@ pub async fn stripe_webhook( payload: (), }) } - -pub async fn onboarding_account_link_request( - jar: CookieJar, - Extension(data): Extension, -) -> impl IntoResponse { - let data = &(data.read().await); - let user = match get_user_from_token!(jar, data.0) { - Some(ua) => ua, - None => return Json(Error::NotAllowed.into()), - }; - - if user.seller_data.account_id.is_some() { - return Json(Error::NotAllowed.into()); - } - - let client = match data.3 { - Some(ref c) => c, - None => return Json(Error::Unknown.into()), - }; - - match stripe::AccountLink::create( - &client, - stripe::CreateAccountLink { - account: match user.seller_data.account_id { - Some(id) => stripe::AccountId::from_str(&id).unwrap(), - None => return Json(Error::NotAllowed.into()), - }, - type_: stripe::AccountLinkType::AccountOnboarding, - collect: None, - expand: &[], - refresh_url: Some(&format!( - "{}/auth/connections_link/seller/refresh", - data.0.0.0.host - )), - return_url: Some(&format!( - "{}/auth/connections_link/seller/return", - data.0.0.0.host - )), - collection_options: None, - }, - ) - .await - { - Ok(x) => Json(ApiReturn { - ok: true, - message: "Acceptable".to_string(), - payload: Some(x.url), - }), - Err(e) => Json(Error::MiscError(e.to_string()).into()), - } -} - -pub async fn create_seller_account_request( - jar: CookieJar, - Extension(data): Extension, -) -> impl IntoResponse { - let data = &(data.read().await); - let mut user = match get_user_from_token!(jar, data.0) { - Some(ua) => ua, - None => return Json(Error::NotAllowed.into()), - }; - - if user.seller_data.account_id.is_some() { - return Json(Error::NotAllowed.into()); - } - - let client = match data.3 { - Some(ref c) => c, - None => return Json(Error::Unknown.into()), - }; - - let account = match stripe::Account::create( - &client, - stripe::CreateAccount { - type_: Some(stripe::AccountType::Express), - capabilities: Some(stripe::CreateAccountCapabilities { - card_payments: Some(stripe::CreateAccountCapabilitiesCardPayments { - requested: Some(true), - }), - transfers: Some(stripe::CreateAccountCapabilitiesTransfers { - requested: Some(true), - }), - ..Default::default() - }), - ..Default::default() - }, - ) - .await - { - Ok(a) => a, - Err(e) => return Json(Error::MiscError(e.to_string()).into()), - }; - - user.seller_data.account_id = Some(account.id.to_string()); - match data - .0 - .update_user_seller_data(user.id, user.seller_data) - .await - { - Ok(_) => Json(ApiReturn { - ok: true, - message: "Acceptable".to_string(), - payload: (), - }), - Err(e) => return Json(e.into()), - } -} - -pub async fn login_link_request( - jar: CookieJar, - Extension(data): Extension, -) -> impl IntoResponse { - let data = &(data.read().await); - let user = match get_user_from_token!(jar, data.0) { - Some(ua) => ua, - None => return Json(Error::NotAllowed.into()), - }; - - if user.seller_data.account_id.is_none() | !user.seller_data.completed_onboarding { - return Json(Error::NotAllowed.into()); - } - - let client = match data.3 { - Some(ref c) => c, - None => return Json(Error::Unknown.into()), - }; - - match stripe::LoginLink::create( - &client, - &stripe::AccountId::from_str(&user.seller_data.account_id.unwrap()).unwrap(), - &data.0.0.0.host, - ) - .await - { - Ok(x) => Json(ApiReturn { - ok: true, - message: "Acceptable".to_string(), - payload: Some(x.url), - }), - Err(e) => Json(Error::MiscError(e.to_string()).into()), - } -} diff --git a/crates/app/src/routes/api/v1/auth/profile.rs b/crates/app/src/routes/api/v1/auth/profile.rs index a4ec514..969cbce 100644 --- a/crates/app/src/routes/api/v1/auth/profile.rs +++ b/crates/app/src/routes/api/v1/auth/profile.rs @@ -1,4 +1,4 @@ -use std::{str::FromStr, time::Duration}; +use std::time::Duration; use crate::{ get_user_from_token, model::{ApiReturn, Error}, @@ -572,28 +572,11 @@ pub async fn delete_user_request( .delete_user(id, &req.password, user.permissions.check_manager()) .await { - Ok(ua) => { - // delete stripe user - if let Some(stripe_id) = ua.seller_data.account_id - && let Some(ref client) = data.3 - { - if let Err(e) = stripe::Account::delete( - &client, - &stripe::AccountId::from_str(&stripe_id).unwrap(), - ) - .await - { - return Json(Error::MiscError(e.to_string()).into()); - } - } - - // ... - Json(ApiReturn { - ok: true, - message: "User deleted".to_string(), - payload: (), - }) - } + Ok(_) => Json(ApiReturn { + ok: true, + message: "User deleted".to_string(), + payload: (), + }), Err(e) => Json(e.into()), } } diff --git a/crates/app/src/routes/api/v1/mod.rs b/crates/app/src/routes/api/v1/mod.rs index 694ad29..5539bf4 100644 --- a/crates/app/src/routes/api/v1/mod.rs +++ b/crates/app/src/routes/api/v1/mod.rs @@ -8,7 +8,6 @@ pub mod journals; pub mod letters; pub mod notes; pub mod notifications; -pub mod products; pub mod reactions; pub mod reports; pub mod requests; @@ -34,7 +33,6 @@ use tetratto_core::model::{ littleweb::{DomainData, DomainTld, ServiceFsEntry}, oauth::AppScope, permissions::{FinePermission, SecondaryPermission}, - products::{ProductPrice, ProductType}, reactions::AssetType, stacks::{StackMode, StackPrivacy, StackSort}, }; @@ -566,18 +564,6 @@ pub fn routes() -> Router { "/service_hooks/stripe", post(auth::connections::stripe::stripe_webhook), ) - .route( - "/service_hooks/stripe/seller/register", - post(auth::connections::stripe::create_seller_account_request), - ) - .route( - "/service_hooks/stripe/seller/onboarding", - post(auth::connections::stripe::onboarding_account_link_request), - ) - .route( - "/service_hooks/stripe/seller/login", - post(auth::connections::stripe::login_link_request), - ) // channels .route("/channels", post(channels::channels::create_request)) .route( @@ -716,17 +702,6 @@ pub fn routes() -> Router { .route("/domains/{id}", get(domains::get_request)) .route("/domains/{id}", delete(domains::delete_request)) .route("/domains/{id}/data", post(domains::update_data_request)) - // products - .route("/products", get(products::list_request)) - .route("/products", post(products::create_request)) - .route("/products/{id}", get(products::get_request)) - .route("/products/{id}", delete(products::delete_request)) - .route("/products/{id}/name", post(products::update_name_request)) - .route( - "/products/{id}/description", - post(products::update_description_request), - ) - .route("/products/{id}/price", post(products::update_price_request)) // letters .route("/letters", post(letters::create_request)) .route("/letters/{id}", get(letters::get_request)) @@ -1207,29 +1182,6 @@ pub struct UpdateDomainData { pub data: Vec<(String, DomainData)>, } -#[derive(Deserialize)] -pub struct CreateProduct { - pub name: String, - pub description: String, - pub product_type: ProductType, - pub price: ProductPrice, -} - -#[derive(Deserialize)] -pub struct UpdateProductName { - pub name: String, -} - -#[derive(Deserialize)] -pub struct UpdateProductDescription { - pub description: String, -} - -#[derive(Deserialize)] -pub struct UpdateProductPrice { - pub price: ProductPrice, -} - #[derive(Deserialize)] pub struct UpdateUploadAlt { pub alt: String, diff --git a/crates/app/src/routes/api/v1/products.rs b/crates/app/src/routes/api/v1/products.rs deleted file mode 100644 index 4d53814..0000000 --- a/crates/app/src/routes/api/v1/products.rs +++ /dev/null @@ -1,234 +0,0 @@ -use crate::{ - get_user_from_token, - image::{save_webp_buffer, JsonMultipart}, - routes::{ - api::v1::{ - communities::posts::MAXIMUM_FILE_SIZE, CreateProduct, UpdateProductDescription, - UpdateProductName, UpdateProductPrice, - }, - pages::PaginatedQuery, - }, - State, -}; -use axum::{ - extract::{Path, Query}, - response::IntoResponse, - Extension, Json, -}; -use crate::cookie::CookieJar; -use tetratto_core::model::{ - oauth, - products::Product, - uploads::{MediaType, MediaUpload}, - ApiReturn, Error, -}; - -pub async fn get_request( - Path(id): Path, - Extension(data): Extension, -) -> impl IntoResponse { - let data = &(data.read().await).0; - match data.get_product_by_id(id).await { - Ok(x) => Json(ApiReturn { - ok: true, - message: "Success".to_string(), - payload: Some(x), - }), - Err(e) => return Json(e.into()), - } -} - -pub async fn list_request( - jar: CookieJar, - Extension(data): Extension, - Query(props): Query, -) -> impl IntoResponse { - let data = &(data.read().await).0; - let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadProducts) { - Some(ua) => ua, - None => return Json(Error::NotAllowed.into()), - }; - - match data.get_products_by_user(user.id, 12, props.page).await { - Ok(x) => Json(ApiReturn { - ok: true, - message: "Success".to_string(), - payload: Some(x), - }), - Err(e) => Json(e.into()), - } -} - -pub async fn create_request( - jar: CookieJar, - Extension(data): Extension, - JsonMultipart(uploads, req): JsonMultipart, -) -> impl IntoResponse { - let data = &(data.read().await).0; - let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateProducts) { - Some(ua) => ua, - None => return Json(Error::NotAllowed.into()), - }; - - if uploads.len() > 4 { - return Json( - Error::MiscError("Too many uploads. Please use a maximum of 4".to_string()).into(), - ); - } - - let mut product = Product::new( - user.id, - req.name, - req.description, - req.price, - req.product_type, - ); - - // check sizes - for img in &uploads { - if img.len() > MAXIMUM_FILE_SIZE { - return Json(Error::FileTooLarge.into()); - } - } - - // create uploads - for _ in 0..uploads.len() { - product.uploads.push( - match data - .create_upload(MediaUpload::new(MediaType::Webp, product.owner)) - .await - { - Ok(u) => u.id, - Err(e) => return Json(e.into()), - }, - ); - } - - let product_uploads = product.uploads.clone(); - match data.create_product(product).await { - Ok(x) => { - // store uploads - for (i, upload_id) in product_uploads.iter().enumerate() { - let image = match uploads.get(i) { - Some(img) => img, - None => { - if let Err(e) = data.delete_upload(*upload_id).await { - return Json(e.into()); - } - - continue; - } - }; - - let upload = match data.get_upload_by_id(*upload_id).await { - Ok(u) => u, - Err(e) => return Json(e.into()), - }; - - if let Err(e) = - save_webp_buffer(&upload.path(&data.0.0).to_string(), image.to_vec(), None) - { - return Json(Error::MiscError(e.to_string()).into()); - } - } - - // ... - Json(ApiReturn { - ok: true, - message: "Product created".to_string(), - payload: x.id.to_string(), - }) - } - Err(e) => Json(e.into()), - } -} - -pub async fn update_name_request( - jar: CookieJar, - Extension(data): Extension, - Path(id): Path, - Json(req): Json, -) -> impl IntoResponse { - let data = &(data.read().await).0; - let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageProducts) { - Some(ua) => ua, - None => return Json(Error::NotAllowed.into()), - }; - - match data.update_product_name(id, &user, &req.name).await { - Ok(_) => Json(ApiReturn { - ok: true, - message: "Product updated".to_string(), - payload: (), - }), - Err(e) => Json(e.into()), - } -} - -pub async fn update_description_request( - jar: CookieJar, - Extension(data): Extension, - Path(id): Path, - Json(req): Json, -) -> impl IntoResponse { - let data = &(data.read().await).0; - let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageProducts) { - Some(ua) => ua, - None => return Json(Error::NotAllowed.into()), - }; - - match data - .update_product_description(id, &user, &req.description) - .await - { - Ok(_) => Json(ApiReturn { - ok: true, - message: "Product updated".to_string(), - payload: (), - }), - Err(e) => Json(e.into()), - } -} - -pub async fn update_price_request( - jar: CookieJar, - Extension(data): Extension, - Path(id): Path, - Json(req): Json, -) -> impl IntoResponse { - let data = &(data.read().await).0; - let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageProducts) { - Some(ua) => ua, - None => return Json(Error::NotAllowed.into()), - }; - - match data.update_product_price(id, &user, req.price).await { - Ok(_) => Json(ApiReturn { - ok: true, - message: "Product updated".to_string(), - payload: (), - }), - Err(e) => Json(e.into()), - } -} - -pub async fn delete_request( - jar: CookieJar, - Extension(data): Extension, - Path(id): Path, -) -> impl IntoResponse { - let data = &(data.read().await).0; - let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageProducts) { - Some(ua) => ua, - None => return Json(Error::NotAllowed.into()), - }; - - match data.delete_product(id, &user).await { - Ok(_) => Json(ApiReturn { - ok: true, - message: "Product deleted".to_string(), - payload: (), - }), - Err(e) => Json(e.into()), - } -} diff --git a/crates/app/src/routes/pages/marketplace.rs b/crates/app/src/routes/pages/marketplace.rs deleted file mode 100644 index 8d5a3be..0000000 --- a/crates/app/src/routes/pages/marketplace.rs +++ /dev/null @@ -1,107 +0,0 @@ -use super::render_error; -use crate::{ - assets::initial_context, get_lang, get_user_from_token, State, routes::pages::PaginatedQuery, -}; -use axum::{ - extract::Query, - response::{Html, IntoResponse}, - Extension, -}; -use crate::cookie::CookieJar; -use tetratto_core::model::Error; - -/// `/settings/seller` -pub async fn seller_settings_request( - jar: CookieJar, - Extension(data): Extension, - Query(props): Query, -) -> impl IntoResponse { - let data = data.read().await; - let user = match get_user_from_token!(jar, data.0) { - Some(ua) => ua, - None => { - return Err(Html( - render_error(Error::NotAllowed, &jar, &data, &None).await, - )); - } - }; - - let products = match data.0.get_products_by_user(user.id, 12, props.page).await { - Ok(x) => x, - Err(e) => return Err(Html(render_error(e, &jar, &data, &None).await)), - }; - - let lang = get_lang!(jar, data.0); - let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await; - - context.insert("list", &products); - context.insert("page", &props.page); - - // return - Ok(Html( - data.1.render("marketplace/seller.html", &context).unwrap(), - )) -} - -pub async fn connection_return_request( - jar: CookieJar, - Extension(data): Extension, -) -> impl IntoResponse { - let data = data.read().await; - let mut user = match get_user_from_token!(jar, data.0) { - Some(ua) => ua, - None => { - return Err(Html( - render_error(Error::NotAllowed, &jar, &data, &None).await, - )); - } - }; - - // update user - user.seller_data.completed_onboarding = true; - if let Err(e) = data - .0 - .update_user_seller_data(user.id, user.seller_data.clone()) - .await - { - return Err(Html(render_error(e, &jar, &data, &None).await)); - } - - // ... - let lang = get_lang!(jar, data.0); - let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await; - context.insert("connection_type", "return"); - - // return - Ok(Html( - data.1 - .render("auth/seller_connection.html", &context) - .unwrap(), - )) -} - -pub async fn connection_reload_request( - jar: CookieJar, - Extension(data): Extension, -) -> impl IntoResponse { - let data = data.read().await; - let user = match get_user_from_token!(jar, data.0) { - Some(ua) => ua, - None => { - return Err(Html( - render_error(Error::NotAllowed, &jar, &data, &None).await, - )); - } - }; - - let lang = get_lang!(jar, data.0); - let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await; - context.insert("connection_type", "reload"); - - // return - Ok(Html( - data.1 - .render("auth/seller_connection.html", &context) - .unwrap(), - )) -} diff --git a/crates/app/src/routes/pages/mod.rs b/crates/app/src/routes/pages/mod.rs index 01f24d6..93cfc9a 100644 --- a/crates/app/src/routes/pages/mod.rs +++ b/crates/app/src/routes/pages/mod.rs @@ -6,7 +6,6 @@ pub mod forge; pub mod journals; pub mod littleweb; pub mod mail; -pub mod marketplace; pub mod misc; pub mod mod_panel; pub mod profile; @@ -77,14 +76,6 @@ pub fn routes() -> Router { "/auth/connections_link/app/{id}", get(developer::connection_callback_request), ) - .route( - "/auth/connections_link/seller/reload", - get(marketplace::connection_reload_request), - ) - .route( - "/auth/connections_link/seller/return", - get(marketplace::connection_return_request), - ) // profile .route("/settings", get(profile::settings_request)) .route("/@{username}", get(profile::posts_request)) @@ -163,11 +154,6 @@ pub fn routes() -> Router { .route("/domains/{id}", get(littleweb::domain_request)) .route("/net", get(littleweb::browser_home_request)) .route("/net/{*uri}", get(littleweb::browser_request)) - // marketplace - .route( - "/settings/seller", - get(marketplace::seller_settings_request), - ) // mail .route("/mail", get(mail::received_request)) .route("/mail/sent", get(mail::sent_request)) diff --git a/crates/core/src/database/auth.rs b/crates/core/src/database/auth.rs index e0aef74..05698ff 100644 --- a/crates/core/src/database/auth.rs +++ b/crates/core/src/database/auth.rs @@ -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)@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)@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); diff --git a/crates/core/src/database/common.rs b/crates/core/src/database/common.rs index 5e10783..71e792d 100644 --- a/crates/core/src/database/common.rs +++ b/crates/core/src/database/common.rs @@ -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(); diff --git a/crates/core/src/database/drivers/common.rs b/crates/core/src/database/drivers/common.rs index bccbfb9..4881179 100644 --- a/crates/core/src/database/drivers/common.rs +++ b/crates/core/src/database/drivers/common.rs @@ -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"); diff --git a/crates/core/src/database/drivers/sql/create_products.sql b/crates/core/src/database/drivers/sql/create_products.sql deleted file mode 100644 index 4a972aa..0000000 --- a/crates/core/src/database/drivers/sql/create_products.sql +++ /dev/null @@ -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 -) diff --git a/crates/core/src/database/drivers/sql/create_users.sql b/crates/core/src/database/drivers/sql/create_users.sql index 06e86ef..e68a27c 100644 --- a/crates/core/src/database/drivers/sql/create_users.sql +++ b/crates/core/src/database/drivers/sql/create_users.sql @@ -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, diff --git a/crates/core/src/database/drivers/sql/version_migrations.sql b/crates/core/src/database/drivers/sql/version_migrations.sql index 5c6c51e..5f3148c 100644 --- a/crates/core/src/database/drivers/sql/version_migrations.sql +++ b/crates/core/src/database/drivers/sql/version_migrations.sql @@ -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; diff --git a/crates/core/src/database/mod.rs b/crates/core/src/database/mod.rs index 218bcd6..5650d7d 100644 --- a/crates/core/src/database/mod.rs +++ b/crates/core/src/database/mod.rs @@ -23,7 +23,6 @@ mod notifications; mod polls; mod pollvotes; mod posts; -mod products; mod questions; mod reactions; mod reports; diff --git a/crates/core/src/database/products.rs b/crates/core/src/database/products.rs deleted file mode 100644 index b5e32a5..0000000 --- a/crates/core/src/database/products.rs +++ /dev/null @@ -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> { - 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> { - 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 { - // 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:{}"); -} diff --git a/crates/core/src/model/auth.rs b/crates/core/src/model/auth.rs index 7622f1a..ef19586 100644 --- a/crates/core/src/model/auth.rs +++ b/crates/core/src/model/auth.rs @@ -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, - #[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, diff --git a/crates/core/src/model/mod.rs b/crates/core/src/model/mod.rs index 5bf8c52..b78dd37 100644 --- a/crates/core/src/model/mod.rs +++ b/crates/core/src/model/mod.rs @@ -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; diff --git a/crates/core/src/model/products.rs b/crates/core/src/model/products.rs deleted file mode 100644 index 2b90ca5..0000000 --- a/crates/core/src/model/products.rs +++ /dev/null @@ -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, -} - -#[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::().unwrap(), - created: unix_epoch_timestamp(), - owner, - name, - description, - likes: 0, - dislikes: 0, - product_type: r#type, - price, - uploads: Vec::new(), - } - } -}