From 2f83497f98f3d7ec96fba8401c1426bbca408bc9 Mon Sep 17 00:00:00 2001 From: trisua Date: Sun, 22 Jun 2025 13:50:12 -0400 Subject: [PATCH] add: allow free users to create 2 invites --- .../app/src/public/html/profile/settings.lisp | 9 ++++-- crates/app/src/routes/api/v1/auth/mod.rs | 28 +++++++++---------- crates/core/src/database/auth.rs | 21 ++++++++++++++ crates/core/src/database/invite_codes.rs | 27 ++++++++++++++++-- 4 files changed, 66 insertions(+), 19 deletions(-) diff --git a/crates/app/src/public/html/profile/settings.lisp b/crates/app/src/public/html/profile/settings.lisp index 3c76c1d..598b0ec 100644 --- a/crates/app/src/public/html/profile/settings.lisp +++ b/crates/app/src/public/html/profile/settings.lisp @@ -536,7 +536,7 @@ (icon (text "plus")) (str (text "settings:label.generate_invite"))) - (text "{{ components::supporter_ad(body=\"Become a supporter to generate invite codes!\") }} {% for code in invites %}") + (text "{{ components::supporter_ad(body=\"Become a supporter to generate up to 24 invite codes! You can currently have 2 maximum.\") }} {% for code in invites %}") (div ("class" "card flex flex-col gap-2") (text "{% if code[1].is_used -%}") @@ -669,7 +669,12 @@ (li (text "Create infinite journals")) (li - (text "Create infinite notes in each journal"))) + (text "Create infinite notes in each journal")) + + (text "{% if config.security.enable_invite_codes -%}") + (li + (text "Create up to 24 invite codes")) + (text "{%- endif %}")) (a ("href" "{{ config.stripe.payment_link }}?client_reference_id={{ user.id }}") ("class" "button") diff --git a/crates/app/src/routes/api/v1/auth/mod.rs b/crates/app/src/routes/api/v1/auth/mod.rs index f5d17f1..cff7d20 100644 --- a/crates/app/src/routes/api/v1/auth/mod.rs +++ b/crates/app/src/routes/api/v1/auth/mod.rs @@ -18,7 +18,7 @@ use axum::{ }; use axum_extra::extract::CookieJar; use serde::Deserialize; -use tetratto_core::model::{addr::RemoteAddr, permissions::FinePermission}; +use tetratto_core::model::addr::RemoteAddr; use tetratto_shared::hash::hash; use cf_turnstile::{SiteVerifyRequest, TurnstileClient}; @@ -107,20 +107,20 @@ pub async fn register_request( ); } - let owner = match data.get_user_by_id(invite_code.owner).await { - Ok(u) => u, - Err(e) => return (None, Json(e.into())), - }; + // let owner = match data.get_user_by_id(invite_code.owner).await { + // Ok(u) => u, + // Err(e) => return (None, Json(e.into())), + // }; - if !owner.permissions.check(FinePermission::SUPPORTER) { - return ( - None, - Json( - Error::MiscError("Invite code owner must be an active supporter".to_string()) - .into(), - ), - ); - } + // if !owner.permissions.check(FinePermission::SUPPORTER) { + // return ( + // None, + // Json( + // Error::MiscError("Invite code owner must be an active supporter".to_string()) + // .into(), + // ), + // ); + // } user.invite_code = invite_code.id; diff --git a/crates/core/src/database/auth.rs b/crates/core/src/database/auth.rs index 927b7fb..49f2713 100644 --- a/crates/core/src/database/auth.rs +++ b/crates/core/src/database/auth.rs @@ -420,6 +420,17 @@ impl DataManager { return Err(Error::DatabaseError(e.to_string())); } + // delete invite codes + let res = execute!( + &conn, + "DELETE FROM invite_codes WHERE owner = $1", + &[&(id as i64)] + ); + + if let Err(e) = res { + return Err(Error::DatabaseError(e.to_string())); + } + // delete message reactions let res = execute!( &conn, @@ -479,6 +490,16 @@ impl DataManager { self.delete_poll(poll.id, &user).await?; } + // free up invite code + if self.0.0.security.enable_invite_codes { + if user.invite_code != 0 && self.get_invite_code_by_id(user.invite_code).await.is_ok() { + // we're checking if the code is ok because the owner might've deleted their account, + // deleting all of their invite codes as well + self.update_invite_code_is_used(user.invite_code, false) + .await?; + } + } + // ... Ok(()) } diff --git a/crates/core/src/database/invite_codes.rs b/crates/core/src/database/invite_codes.rs index 672e5a9..386149a 100644 --- a/crates/core/src/database/invite_codes.rs +++ b/crates/core/src/database/invite_codes.rs @@ -1,4 +1,5 @@ use oiseau::{cache::Cache, query_rows}; +use tetratto_shared::unix_epoch_timestamp; use crate::model::{ Error, Result, auth::{User, InviteCode}, @@ -67,7 +68,9 @@ impl DataManager { Ok(out) } - const MAXIMUM_SUPPORTER_INVITE_CODES: usize = 15; + const MAXIMUM_FREE_INVITE_CODES: usize = 2; + const MAXIMUM_SUPPORTER_INVITE_CODES: usize = 24; + const MINIMUM_ACCOUNT_AGE_FOR_FREE_INVITE_CODES: usize = 2_629_800_000; // 1mo /// Create a new invite_code in the database. /// @@ -75,8 +78,26 @@ impl DataManager { /// * `data` - a mock [`InviteCode`] object to insert pub async fn create_invite_code(&self, data: InviteCode, user: &User) -> Result { if !user.permissions.check(FinePermission::SUPPORTER) { - return Err(Error::RequiresSupporter); - } else { + // check account creation date + if unix_epoch_timestamp() - user.created + < Self::MINIMUM_ACCOUNT_AGE_FOR_FREE_INVITE_CODES + { + return Err(Error::MiscError( + "Your account is too young to do this".to_string(), + )); + } + + // our account is old enough, but we need to make sure we don't already have + // 2 invite codes + if self.get_invite_codes_by_owner(user.id).await?.len() + >= Self::MAXIMUM_FREE_INVITE_CODES + { + return Err(Error::MiscError( + "You already have the maximum number of invite codes you can create" + .to_string(), + )); + } + } else if !user.permissions.check(FinePermission::MANAGE_INVITES) { // check count if self.get_invite_codes_by_owner(user.id).await?.len() >= Self::MAXIMUM_SUPPORTER_INVITE_CODES