Hard caps on creating projects/orgs/collections (#4430)

* implement backend limits on project creation

* implement collection, org creation hard caps

* Fix limit api

* Fix clippy

* Fix limits

* Update sqlx queries

* Address PR comments on user limit structure

* sqlx prepare and clippy

* fix test maybe
This commit is contained in:
aecsocket
2025-09-28 11:01:00 +01:00
committed by GitHub
parent 3f55711f9e
commit f466470d06
503 changed files with 14260 additions and 11 deletions

View File

@@ -30,6 +30,7 @@ pub mod shared_instance_item;
pub mod team_item;
pub mod thread_item;
pub mod user_item;
pub mod user_limits;
pub mod user_subscription_item;
pub mod users_compliance;
pub mod users_notifications_preferences_item;

View File

@@ -0,0 +1,125 @@
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use crate::database::models::DBUserId;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DBUserLimits {
pub user_id: Option<DBUserId>,
pub projects: u64,
pub organizations: u64,
pub collections: u64,
}
impl DBUserLimits {
pub async fn upsert(&self, pool: &PgPool) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO user_limits (user_id, projects, organizations, collections)
VALUES ($1, $2, $3, $4)
ON CONFLICT (user_id) DO UPDATE
SET projects = EXCLUDED.projects,
organizations = EXCLUDED.organizations,
collections = EXCLUDED.collections",
self.user_id.map(|id| id.0),
self.projects as i64,
self.organizations as i64,
self.collections as i64
)
.execute(pool)
.await?;
Ok(())
}
pub async fn get_defaults(pool: &PgPool) -> Result<Self, sqlx::Error> {
let row = sqlx::query!(
"SELECT projects, organizations, collections
FROM user_limits
WHERE user_id IS NULL"
)
.fetch_one(pool)
.await?;
Ok(Self {
user_id: None,
projects: row.projects as u64,
organizations: row.organizations as u64,
collections: row.collections as u64,
})
}
pub async fn get(
user_id: DBUserId,
pool: &PgPool,
) -> Result<Self, sqlx::Error> {
let row = sqlx::query!(
"SELECT projects, organizations, collections
FROM user_limits
WHERE user_id = $1",
user_id as DBUserId
)
.fetch_optional(pool)
.await?;
if let Some(row) = row {
Ok(Self {
user_id: Some(user_id),
projects: row.projects as u64,
organizations: row.organizations as u64,
collections: row.collections as u64,
})
} else {
Self::get_defaults(pool).await
}
}
}
// impl UserLimits {
// pub async fn get(user: &User, pool: &PgPool) -> Result<Self, sqlx::Error> {
// let current = sqlx::query!(
// "SELECT
// (SELECT COUNT(*) FROM mods m
// JOIN teams t ON m.team_id = t.id
// JOIN team_members tm ON t.id = tm.team_id
// WHERE tm.user_id = $1) as projects,
// (SELECT COUNT(*) FROM organizations o
// JOIN teams t ON o.team_id = t.id
// JOIN team_members tm ON t.id = tm.team_id
// WHERE tm.user_id = $1) as organizations,
// (SELECT COUNT(*) FROM collections
// WHERE user_id = $1) as collections",
// DBUserId::from(user.id) as DBUserId,
// )
// .fetch_one(pool)
// .await?;
// let current = UserLimitCount {
// projects: current.projects.map_or(0, |x| x as u64),
// organizations: current.organizations.map_or(0, |x| x as u64),
// collections: current.collections.map_or(0, |x| x as u64),
// };
// if user.role.is_admin() {
// Ok(Self {
// current,
// max: UserLimitCount {
// projects: u64::MAX,
// organizations: u64::MAX,
// collections: u64::MAX,
// },
// })
// } else {
// // TODO: global config for max
// Ok(Self {
// current,
// max: UserLimitCount {
// projects: user.max_projects.unwrap_or(256),
// organizations: user.max_organizations.unwrap_or(16),
// collections: user.max_collections.unwrap_or(64),
// },
// })
// }
// }
// }