add: developer pass

This commit is contained in:
trisua 2025-07-18 14:52:00 -04:00
parent 636ecce9f4
commit 02f3d08926
14 changed files with 355 additions and 101 deletions

View file

@ -194,22 +194,30 @@ pub struct StripeConfig {
///
/// <https://docs.stripe.com/no-code/customer-portal>
pub billing_portal_url: String,
/// The text representation of the price of supporter. (like `$4 USD`)
pub supporter_price_text: String,
/// The text representation of prices. (like `$4 USD`)
pub price_texts: StripePriceTexts,
/// Product IDs from the Stripe dashboard.
///
/// These are checked when we receive a webhook to ensure we provide the correct product.
pub product_ids: StripeProductIds,
}
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
pub struct StripePriceTexts {
pub supporter: String,
pub dev_pass: String,
}
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
pub struct StripePaymentLinks {
pub supporter: String,
pub dev_pass: String,
}
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
pub struct StripeProductIds {
pub supporter: String,
pub dev_pass: String,
}
/// Manuals config (search help, etc)

View file

@ -1,5 +1,5 @@
use oiseau::cache::Cache;
use crate::model::apps::{AppDataQuery, AppDataQueryResult, AppDataSelectMode, AppDataSelectQuery};
use crate::model::apps::{AppDataQuery, AppDataQueryResult, AppDataSelectMode};
use crate::model::{apps::AppData, permissions::FinePermission, Error, Result};
use crate::{auto_method, DataManager};
use oiseau::{PostgresRow, execute, get, query_row, query_rows, params};
@ -51,13 +51,7 @@ impl DataManager {
Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
};
let query_str = query.to_string().replace(
"%q%",
&match query.query {
AppDataSelectQuery::KeyIs(_) => format!("k = $1"),
AppDataSelectQuery::LikeJson(_, _) => format!("v LIKE $1"),
},
);
let query_str = query.to_string().replace("%q%", &query.query.selector());
let res = match query.mode {
AppDataSelectMode::One(_) => AppDataQueryResult::One(
@ -98,13 +92,7 @@ impl DataManager {
let query_str = query
.to_string()
.replace(
"%q%",
&match query.query {
AppDataSelectQuery::KeyIs(_) => format!("k = $1"),
AppDataSelectQuery::LikeJson(_, _) => format!("v LIKE $1"),
},
)
.replace("%q%", &query.query.selector())
.replace("SELECT * FROM", "SELECT id FROM");
if let Err(e) = execute!(

View file

@ -3,7 +3,7 @@ use crate::model::{
apps::{AppQuota, ThirdPartyApp},
auth::User,
oauth::AppScope,
permissions::FinePermission,
permissions::{FinePermission, SecondaryPermission},
Error, Result,
};
use crate::{auto_method, DataManager};
@ -72,10 +72,15 @@ impl DataManager {
// check number of apps
let owner = self.get_user_by_id(data.owner).await?;
if !owner.permissions.check(FinePermission::SUPPORTER) {
let apps = self.get_apps_by_owner(data.owner).await?;
if !owner
.secondary_permissions
.check(SecondaryPermission::DEVELOPER_PASS)
{
let apps = self
.get_table_row_count_where("apps", &format!("owner = {}", owner.id))
.await? as usize;
if apps.len() >= Self::MAXIMUM_FREE_APPS {
if apps >= Self::MAXIMUM_FREE_APPS {
return Err(Error::MiscError(
"You already have the maximum number of apps you can have".to_string(),
));

View file

@ -85,7 +85,7 @@ impl DataManager {
let res = query_row!(
&conn,
&format!("SELECT COUNT(*)::int FROM {} {}", table, r#where),
&format!("SELECT COUNT(*)::int FROM {} WHERE {}", table, r#where),
params![],
|x| Ok(x.get::<usize, i32>(0))
);

View file

@ -3,6 +3,7 @@ use super::common::NAME_REGEX;
use oiseau::cache::Cache;
use crate::model::communities::{CommunityContext, CommunityJoinAccess, CommunityMembership};
use crate::model::communities_permissions::CommunityPermission;
use crate::model::permissions::SecondaryPermission;
use crate::model::{
Error, Result,
auth::User,
@ -255,7 +256,11 @@ impl DataManager {
// check is_forge
// only supporters can CREATE forge communities... anybody can contribute to them
if data.is_forge && !owner.permissions.check(FinePermission::SUPPORTER) {
if data.is_forge
&& !owner
.secondary_permissions
.check(SecondaryPermission::DEVELOPER_PASS)
{
return Err(Error::RequiresSupporter);
}

View file

@ -147,6 +147,8 @@ impl AppData {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum AppDataSelectQuery {
KeyIs(String),
KeyLike(String),
ValueLike(String),
LikeJson(String, String),
}
@ -154,11 +156,24 @@ impl Display for AppDataSelectQuery {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&match self {
Self::KeyIs(k) => k.to_owned(),
Self::KeyLike(k) => k.to_owned(),
Self::ValueLike(v) => v.to_owned(),
Self::LikeJson(k, v) => format!("%\"{k}\":\"{v}\"%"),
})
}
}
impl AppDataSelectQuery {
pub fn selector(&self) -> String {
match self {
AppDataSelectQuery::KeyIs(_) => format!("k = $1"),
AppDataSelectQuery::KeyLike(_) => format!("k LIKE $1"),
AppDataSelectQuery::ValueLike(_) => format!("v LIKE $1"),
AppDataSelectQuery::LikeJson(_, _) => format!("v LIKE $1"),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum AppDataSelectMode {
/// Select a single row (with offset).
@ -179,14 +194,14 @@ impl Display for AppDataSelectMode {
Self::One(offset) => format!("LIMIT 1 OFFSET {offset}"),
Self::Many(limit, offset) => {
format!(
"LIMIT {} OFFSET {offset}",
if *limit > 1024 { 1024 } else { *limit }
"ORDER BY k DESC LIMIT {} OFFSET {offset}",
if *limit > 24 { 24 } else { *limit }
)
}
Self::ManyJson(order_by_top_level_key, limit, offset) => {
format!(
"ORDER BY v::jsonb->>'{order_by_top_level_key}' DESC LIMIT {} OFFSET {offset}",
if *limit > 1024 { 1024 } else { *limit }
if *limit > 24 { 24 } else { *limit }
)
}
})