add: allow free users to create 2 invites

This commit is contained in:
trisua 2025-06-22 13:50:12 -04:00
parent 626c6711ef
commit 2f83497f98
4 changed files with 66 additions and 19 deletions

View file

@ -536,7 +536,7 @@
(icon (text "plus")) (icon (text "plus"))
(str (text "settings:label.generate_invite"))) (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 (div
("class" "card flex flex-col gap-2") ("class" "card flex flex-col gap-2")
(text "{% if code[1].is_used -%}") (text "{% if code[1].is_used -%}")
@ -669,7 +669,12 @@
(li (li
(text "Create infinite journals")) (text "Create infinite journals"))
(li (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 (a
("href" "{{ config.stripe.payment_link }}?client_reference_id={{ user.id }}") ("href" "{{ config.stripe.payment_link }}?client_reference_id={{ user.id }}")
("class" "button") ("class" "button")

View file

@ -18,7 +18,7 @@ use axum::{
}; };
use axum_extra::extract::CookieJar; use axum_extra::extract::CookieJar;
use serde::Deserialize; use serde::Deserialize;
use tetratto_core::model::{addr::RemoteAddr, permissions::FinePermission}; use tetratto_core::model::addr::RemoteAddr;
use tetratto_shared::hash::hash; use tetratto_shared::hash::hash;
use cf_turnstile::{SiteVerifyRequest, TurnstileClient}; 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 { // let owner = match data.get_user_by_id(invite_code.owner).await {
Ok(u) => u, // Ok(u) => u,
Err(e) => return (None, Json(e.into())), // Err(e) => return (None, Json(e.into())),
}; // };
if !owner.permissions.check(FinePermission::SUPPORTER) { // if !owner.permissions.check(FinePermission::SUPPORTER) {
return ( // return (
None, // None,
Json( // Json(
Error::MiscError("Invite code owner must be an active supporter".to_string()) // Error::MiscError("Invite code owner must be an active supporter".to_string())
.into(), // .into(),
), // ),
); // );
} // }
user.invite_code = invite_code.id; user.invite_code = invite_code.id;

View file

@ -420,6 +420,17 @@ impl DataManager {
return Err(Error::DatabaseError(e.to_string())); 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 // delete message reactions
let res = execute!( let res = execute!(
&conn, &conn,
@ -479,6 +490,16 @@ impl DataManager {
self.delete_poll(poll.id, &user).await?; 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(()) Ok(())
} }

View file

@ -1,4 +1,5 @@
use oiseau::{cache::Cache, query_rows}; use oiseau::{cache::Cache, query_rows};
use tetratto_shared::unix_epoch_timestamp;
use crate::model::{ use crate::model::{
Error, Result, Error, Result,
auth::{User, InviteCode}, auth::{User, InviteCode},
@ -67,7 +68,9 @@ impl DataManager {
Ok(out) 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. /// Create a new invite_code in the database.
/// ///
@ -75,8 +78,26 @@ impl DataManager {
/// * `data` - a mock [`InviteCode`] object to insert /// * `data` - a mock [`InviteCode`] object to insert
pub async fn create_invite_code(&self, data: InviteCode, user: &User) -> Result<InviteCode> { pub async fn create_invite_code(&self, data: InviteCode, user: &User) -> Result<InviteCode> {
if !user.permissions.check(FinePermission::SUPPORTER) { if !user.permissions.check(FinePermission::SUPPORTER) {
return Err(Error::RequiresSupporter); // check account creation date
} else { 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 // check count
if self.get_invite_codes_by_owner(user.id).await?.len() if self.get_invite_codes_by_owner(user.id).await?.len()
>= Self::MAXIMUM_SUPPORTER_INVITE_CODES >= Self::MAXIMUM_SUPPORTER_INVITE_CODES