refactor: complete v0.8 core architecture

This commit is contained in:
thakares committed 2026-08-21 13:56:36 +05:30
1 parent f138a645f8
commit d7e0ac7679
98 files changed
+8413 -3044

No files matched your search

+85 -36
View File
@@ -1,12 +1,13 @@
//! Cross-tenant slug transfer business logic.
//!
//! Copies URL/page content between tenant content DBs, then updates global_slugs
//! ownership. Handlers own admin auth and HTTP mapping.
//! Copies URL/page content between tenant content DBs, then updates v0.8 slug
//! databases ownership. Handlers own admin auth and HTTP mapping.
use crate::db::tenant::TenantOpenMode;
use crate::identity::TenantId;
use crate::state::{AppState, UserDbs};
use crate::utils::lock_db;
use chrono::Utc;
use rusqlite::OptionalExtension;
#[derive(Debug)]
pub enum TransferError {
@@ -34,31 +35,21 @@ pub struct SlugTransferRequest {
pub struct SlugTransferResult {
pub old_owner_user_id: i64,
pub new_owner_user_id: i64,
pub old_owner_tenant_id: TenantId,
pub new_owner_tenant_id: TenantId,
pub target_type: String,
pub new_target_id: String,
}
/// Look up slug ownership in `global_slugs`.
pub fn lookup_slug(state: &AppState, slug: &str) -> Result<(i64, String, String), TransferError> {
let system_conn = lock_db(&state.system_db, "system_db")
.map_err(|e| TransferError::Internal(e.to_string()))?;
let mut stmt = system_conn
.prepare("SELECT owner_user_id, target_type, target_id FROM global_slugs WHERE slug = ?1;")
.map_err(|e| TransferError::Internal(e.to_string()))?;
let row_opt = stmt
.query_row([slug], |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
))
})
.optional()
.map_err(|e| TransferError::Internal(e.to_string()))?;
match row_opt {
Some(r) => Ok(r),
None => Err(TransferError::NotFound("Slug not found")),
/// Look up slug ownership in v0.8 slug databases (`global_urls.db` / `global_landing_pages.db`).
pub fn lookup_slug(
state: &AppState,
slug: &str,
) -> Result<crate::db::slugs::ResolvedSlugInfo, TransferError> {
match state.lookup_slug(slug) {
Ok(Some(info)) => Ok(info),
Ok(None) => Err(TransferError::NotFound("Slug not found")),
Err(e) => Err(TransferError::Internal(e.to_string())),
}
}
@@ -158,25 +149,68 @@ fn copy_content(
}
}
/// Perform a full slug transfer (content + registry + quotas + history).
/// Perform a full slug transfer (content + v0.8 slug registry + quotas + history).
pub fn transfer_slug(
state: &AppState,
req: &SlugTransferRequest,
admin_username: &str,
) -> Result<SlugTransferResult, TransferError> {
let (old_owner_user_id, target_type, _target_id) = lookup_slug(state, &req.slug)?;
let slug_info = lookup_slug(state, &req.slug)?;
let target_type = slug_info.target_type.as_str().to_string();
if old_owner_user_id == req.new_owner_user_id {
let old_owner_tenant_id = TenantId::parse(&slug_info.owner_tenant_id).map_err(|_| {
TransferError::Internal(format!(
"Invalid owner tenant ID '{}' on slug '{}'",
slug_info.owner_tenant_id, req.slug
))
})?;
// Look up old owner user row in users.db
let (old_owner_user_id, new_owner_tenant_id) = {
let users_conn = lock_db(&state.users_db, "users_db")
.map_err(|e| TransferError::Internal(e.to_string()))?;
let old_user = crate::db::users::get_user_by_tenant_id(&users_conn, old_owner_tenant_id)
.map_err(|e| TransferError::Internal(e.to_string()))?
.ok_or_else(|| {
TransferError::Internal(format!(
"Current owner user for tenant {old_owner_tenant_id} not found"
))
})?;
let new_user = crate::db::users::get_user_by_id(&users_conn, req.new_owner_user_id)
.map_err(|e| TransferError::Internal(e.to_string()))?
.ok_or_else(|| {
TransferError::BadRequest(format!(
"Target user ID {} not found",
req.new_owner_user_id
))
})?;
if new_user.account_type == "admin" {
return Err(TransferError::BadRequest(
"Target user cannot be an Admin account (must be a tenant account)".into(),
));
}
let new_tid = new_user.tenant_id.ok_or_else(|| {
TransferError::BadRequest("Target user has no TenantId allocated".into())
})?;
(old_user.id, new_tid)
};
if old_owner_tenant_id == new_owner_tenant_id {
return Err(TransferError::BadRequest(
"New owner must be different from the current owner".into(),
));
}
let old_dbs = state
.get_user_dbs(old_owner_user_id)
.open_tenant(old_owner_tenant_id, TenantOpenMode::CoreJob)
.map_err(|_| TransferError::Internal("Failed to load current owner's database".into()))?;
let new_dbs = state
.get_user_dbs(req.new_owner_user_id)
.open_tenant(new_owner_tenant_id, TenantOpenMode::Provision)
.map_err(|_| TransferError::Internal("Failed to load new owner's database".into()))?;
let new_target_id = copy_content(
@@ -188,16 +222,29 @@ pub fn transfer_slug(
req.new_owner_user_id,
)?;
// Update authoritative v0.8 slug databases
{
let urls_conn = lock_db(&state.db.global_urls, "global_urls")
.map_err(|e| TransferError::Internal(e.to_string()))?;
let pages_conn = lock_db(&state.db.global_landing_pages, "global_landing_pages")
.map_err(|e| TransferError::Internal(e.to_string()))?;
crate::db::slugs::transfer_slug_owner(
&urls_conn,
&pages_conn,
&req.slug,
&new_owner_tenant_id,
&new_target_id,
)
.map_err(|e| TransferError::Internal(format!("Failed to update slug registry: {e}")))?;
}
// Write audit event and history
{
let system_conn = lock_db(&state.system_db, "system_db")
.map_err(|e| TransferError::Internal(e.to_string()))?;
let now = Utc::now().to_rfc3339();
let _ = system_conn.execute(
"UPDATE global_slugs SET owner_user_id = ?1, target_id = ?2, updated_at = ?3 WHERE slug = ?4;",
rusqlite::params![req.new_owner_user_id, new_target_id, now, req.slug],
);
let _ = system_conn.execute(
"INSERT INTO slug_history (slug, old_owner_user_id, new_owner_user_id, action, timestamp, admin_username)
VALUES (?1, ?2, ?3, 'transferred', ?4, ?5);",
@@ -228,8 +275,8 @@ pub fn transfer_slug(
"slug",
&req.slug,
Some(&format!(
"From owner {} to owner {}",
old_owner_user_id, req.new_owner_user_id
"From tenant {} (user {}) to tenant {} (user {})",
old_owner_tenant_id, old_owner_user_id, new_owner_tenant_id, req.new_owner_user_id
)),
);
}
@@ -237,6 +284,8 @@ pub fn transfer_slug(
Ok(SlugTransferResult {
old_owner_user_id,
new_owner_user_id: req.new_owner_user_id,
old_owner_tenant_id,
new_owner_tenant_id,
target_type,
new_target_id,
})