feat: init

This commit is contained in:
zhenyi
2026-06-07 11:30:56 +08:00
commit 563381c1ca
361 changed files with 41327 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
pub mod workspace;
pub mod workspace_audit_logs;
pub mod workspace_billing;
pub mod workspace_custom_branding;
pub mod workspace_domains;
pub mod workspace_integrations;
pub mod workspace_invitations;
pub mod workspace_members;
pub mod workspace_pending_approvals;
pub mod workspace_queries;
pub mod workspace_settings;
pub mod workspace_stats;
pub mod workspace_webhooks;
pub use workspace::Workspace;
pub use workspace_audit_logs::WorkspaceAuditLog;
pub use workspace_billing::WorkspaceBilling;
pub use workspace_custom_branding::WorkspaceCustomBranding;
pub use workspace_domains::WorkspaceDomain;
pub use workspace_integrations::WorkspaceIntegration;
pub use workspace_invitations::WorkspaceInvitation;
pub use workspace_members::WorkspaceMember;
pub use workspace_pending_approvals::WorkspacePendingApproval;
pub use workspace_settings::WorkspaceSettings;
pub use workspace_stats::WorkspaceStats;
pub use workspace_webhooks::WorkspaceWebhook;
+22
View File
@@ -0,0 +1,22 @@
use crate::models::common::{Status, Visibility};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct Workspace {
pub id: Uuid,
pub owner_id: Uuid,
pub name: String,
pub description: Option<String>,
pub avatar_url: Option<String>,
pub visibility: Visibility,
pub plan: String,
pub status: Status,
pub default_role: String,
pub is_personal: bool,
pub archived_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub deleted_at: Option<DateTime<Utc>>,
}
+18
View File
@@ -0,0 +1,18 @@
use crate::models::common::{JsonValue, TargetType};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct WorkspaceAuditLog {
pub id: Uuid,
pub workspace_id: Uuid,
pub actor_id: Option<Uuid>,
pub action: String,
pub target_type: Option<TargetType>,
pub target_id: Option<Uuid>,
pub ip_address: Option<String>,
pub user_agent: Option<String>,
pub metadata: Option<JsonValue>,
pub created_at: DateTime<Utc>,
}
+21
View File
@@ -0,0 +1,21 @@
use crate::models::common::Status;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct WorkspaceBilling {
pub workspace_id: Uuid,
pub customer_id: Option<String>,
pub subscription_id: Option<String>,
pub plan: String,
pub billing_email: Option<String>,
pub status: Status,
pub seats: i32,
pub trial_ends_at: Option<DateTime<Utc>>,
pub current_period_start: Option<DateTime<Utc>>,
pub current_period_end: Option<DateTime<Utc>>,
pub canceled_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
@@ -0,0 +1,17 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct WorkspaceCustomBranding {
pub workspace_id: Uuid,
pub logo_url: Option<String>,
pub favicon_url: Option<String>,
pub primary_color: Option<String>,
pub accent_color: Option<String>,
pub custom_css: Option<String>,
pub support_url: Option<String>,
pub enabled: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
+16
View File
@@ -0,0 +1,16 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct WorkspaceDomain {
pub id: Uuid,
pub workspace_id: Uuid,
pub domain: String,
pub verification_token_hash: Option<String>,
pub is_primary: bool,
pub is_verified: bool,
pub verified_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
@@ -0,0 +1,20 @@
use crate::models::common::Provider;
use crate::models::json_types::{TypedJson, WorkspaceIntegrationConfig};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct WorkspaceIntegration {
pub id: Uuid,
pub workspace_id: Uuid,
pub provider: Provider,
pub name: String,
pub config: Option<TypedJson<WorkspaceIntegrationConfig>>,
pub secret_ciphertext: Option<String>,
pub enabled: bool,
pub installed_by: Uuid,
pub last_used_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
@@ -0,0 +1,19 @@
use crate::models::common::Role;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow, utoipa::ToSchema)]
pub struct WorkspaceInvitation {
pub id: Uuid,
pub workspace_id: Uuid,
pub email: String,
pub role: Role,
pub token_hash: String,
pub invited_by: Uuid,
pub accepted_by: Option<Uuid>,
pub accepted_at: Option<DateTime<Utc>>,
pub revoked_at: Option<DateTime<Utc>>,
pub expires_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
}
+18
View File
@@ -0,0 +1,18 @@
use crate::models::common::{Role, Status};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct WorkspaceMember {
pub id: Uuid,
pub workspace_id: Uuid,
pub user_id: Uuid,
pub role: Role,
pub status: Status,
pub invited_by: Option<Uuid>,
pub joined_at: Option<DateTime<Utc>>,
pub last_active_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
@@ -0,0 +1,19 @@
use crate::models::common::{RequestType, Status};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct WorkspacePendingApproval {
pub id: Uuid,
pub workspace_id: Uuid,
pub requester_id: Uuid,
pub request_type: RequestType,
pub status: Status,
pub reason: Option<String>,
pub reviewed_by: Option<Uuid>,
pub reviewed_at: Option<DateTime<Utc>>,
pub expires_at: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
+102
View File
@@ -0,0 +1,102 @@
//! SQL query methods for the `Workspace` entity.
use sqlx::PgPool;
use uuid::Uuid;
use super::workspace::Workspace;
use crate::models::common::{Role, Visibility};
impl Workspace {
/// Find a non-deleted workspace by primary key.
pub async fn find_by_name(pool: &PgPool, name: &str) -> Result<Option<Self>, sqlx::Error> {
sqlx::query_as::<_, Workspace>(
"SELECT id, owner_id, name, description, avatar_url, visibility, plan, status, \
default_role, is_personal, archived_at, created_at, updated_at, deleted_at \
FROM workspace WHERE name = $1 AND deleted_at IS NULL",
)
.bind(name)
.fetch_optional(pool)
.await
}
pub async fn find_by_id(pool: &PgPool, id: Uuid) -> Result<Option<Self>, sqlx::Error> {
sqlx::query_as::<_, Workspace>(
r#"SELECT id, owner_id, name, description, avatar_url, visibility, plan, status,
default_role, is_personal, archived_at, created_at, updated_at, deleted_at
FROM workspace WHERE id = $1 AND deleted_at IS NULL"#,
)
.bind(id)
.fetch_optional(pool)
.await
}
/// Count non-deleted workspaces owned by a user.
pub async fn count_owned(pool: &PgPool, owner_id: Uuid) -> Result<i64, sqlx::Error> {
sqlx::query_scalar(
r#"SELECT COUNT(*) FROM workspace WHERE owner_id = $1 AND deleted_at IS NULL"#,
)
.bind(owner_id)
.fetch_one(pool)
.await
}
/// Check if a user is an active member of a workspace.
pub async fn is_member(
pool: &PgPool,
workspace_id: Uuid,
user_id: Uuid,
) -> Result<bool, sqlx::Error> {
sqlx::query_scalar::<_, bool>(
r#"SELECT EXISTS(
SELECT 1 FROM workspace_member
WHERE workspace_id = $1 AND user_id = $2 AND status = 'active'
)"#,
)
.bind(workspace_id)
.bind(user_id)
.fetch_one(pool)
.await
}
/// Get the role of a user in a workspace.
/// Returns `Role::Owner` if the user is the workspace owner but has no explicit member row.
pub async fn user_role(
pool: &PgPool,
workspace_id: Uuid,
user_id: Uuid,
owner_id: Uuid,
) -> Result<Option<Role>, sqlx::Error> {
if owner_id == user_id {
return Ok(Some(Role::Owner));
}
let role_str: Option<String> = sqlx::query_scalar(
r#"SELECT role FROM workspace_member
WHERE workspace_id = $1 AND user_id = $2 AND status = 'active'"#,
)
.bind(workspace_id)
.bind(user_id)
.fetch_optional(pool)
.await?;
Ok(role_str.and_then(|r| r.parse::<Role>().ok()))
}
/// Check if the user can read the workspace.
/// Readable if: owner, active member, or workspace is public/internal.
pub async fn is_readable(
pool: &PgPool,
ws: &Workspace,
user_id: Uuid,
) -> Result<bool, sqlx::Error> {
if ws.owner_id == user_id {
return Ok(true);
}
if Self::is_member(pool, ws.id, user_id).await? {
return Ok(true);
}
Ok(matches!(
ws.visibility,
Visibility::Public | Visibility::Internal
))
}
}
+18
View File
@@ -0,0 +1,18 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct WorkspaceSettings {
pub workspace_id: Uuid,
pub allow_public_repos: bool,
pub allow_member_invites: bool,
pub require_two_factor: bool,
pub default_repo_visibility: String,
pub default_branch_name: String,
pub issue_tracking_enabled: bool,
pub pull_requests_enabled: bool,
pub wiki_enabled: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
+17
View File
@@ -0,0 +1,17 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct WorkspaceStats {
pub workspace_id: Uuid,
pub members_count: i64,
pub repos_count: i64,
pub issues_count: i64,
pub pull_requests_count: i64,
pub storage_bytes: i64,
pub bandwidth_bytes: i64,
pub build_minutes_used: i64,
pub last_activity_at: Option<DateTime<Utc>>,
pub updated_at: DateTime<Utc>,
}
+19
View File
@@ -0,0 +1,19 @@
use crate::models::common::EventType;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct WorkspaceWebhook {
pub id: Uuid,
pub workspace_id: Uuid,
pub url: String,
pub secret_ciphertext: Option<String>,
pub events: Vec<EventType>,
pub active: bool,
pub last_delivery_status: Option<String>,
pub last_delivery_at: Option<DateTime<Utc>>,
pub created_by: Uuid,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}