backend signup

This commit is contained in:
2025-10-29 15:09:16 +01:00
parent abe3fc777c
commit 1cc0aafc1a
21 changed files with 673 additions and 211 deletions
+91 -9
View File
@@ -1,11 +1,18 @@
use axum::{async_trait, extract::FromRequestParts, http::request::Parts};
use diesel::{pg::PgConnection, prelude::*, sql_types::Text};
use diesel::{
dsl::{exists, select},
pg::PgConnection,
prelude::*,
sql_types::Text,
};
use serde_json::json;
use uuid::Uuid;
use crate::{
db::PgPool,
error::{AppError, AppResult},
models::Tenant,
jobs::{enqueue_job, JOB_PROVISION_TENANT},
models::{Tenant, TenantStatus},
schema::tenants::dsl,
state::AppState,
};
@@ -50,17 +57,72 @@ impl TenantService {
Ok(self.get_by_slug(slug)?.id)
}
pub fn create_tenant(
&self,
slug: &str,
storage_root: Option<&str>,
quickwit_index: Option<&str>,
status: TenantStatus,
initial_members: &[Uuid],
created_by: Option<Uuid>,
) -> AppResult<Tenant> {
let slug = slug.trim();
if slug.is_empty() {
return Err(AppError::bad_request("tenant slug must not be empty"));
}
let mut conn = self.pool.get().map_err(|err| {
tracing::error!(error = ?err, "database pool error");
AppError::internal("database pool error")
})?;
let exists: bool =
select(exists(dsl::tenants.filter(dsl::slug.eq(slug)))).get_result(&mut conn)?;
if exists {
return Err(AppError::conflict(format!(
"tenant '{}' already exists",
slug
)));
}
let id = Uuid::new_v4();
let storage_root = normalize_storage_root(storage_root, id);
let quickwit_index = normalize_quickwit_index(quickwit_index, id);
diesel::insert_into(dsl::tenants)
.values((
dsl::id.eq(id),
dsl::slug.eq(slug),
dsl::storage_root.eq(Some(storage_root.clone())),
dsl::quickwit_index.eq(Some(quickwit_index.clone())),
dsl::config.eq(json!({})),
dsl::status.eq(status),
dsl::created_by.eq(created_by),
))
.execute(&mut conn)?;
if status == TenantStatus::Creating {
let payload = json!({
"members": initial_members,
});
enqueue_job(&mut conn, id, JOB_PROVISION_TENANT, payload, None).map_err(|err| {
tracing::error!(error = ?err, tenant_id = %id, "failed to enqueue tenant provisioning job");
AppError::internal("failed to enqueue tenant provisioning job")
})?;
}
TenantRepository::get_by_id(&mut conn, id)
}
fn load<F>(&self, loader: F) -> AppResult<Tenant>
where
F: FnOnce(&mut PgConnection) -> AppResult<Tenant>,
{
let mut conn = self
.pool
.get()
.map_err(|err| {
tracing::error!(error = ?err, "database pool error");
AppError::internal("database pool error")
})?;
let mut conn = self.pool.get().map_err(|err| {
tracing::error!(error = ?err, "database pool error");
AppError::internal("database pool error")
})?;
let tenant = loader(&mut conn)?;
Ok(tenant)
}
@@ -92,3 +154,23 @@ impl FromRequestParts<AppState> for TenantContext {
Ok(Self { tenant })
}
}
fn normalize_storage_root(raw: Option<&str>, tenant_id: Uuid) -> String {
match raw.map(str::trim) {
Some(root) if !root.is_empty() => {
let mut owned = root.to_owned();
if !owned.ends_with('/') {
owned.push('/');
}
owned
}
_ => format!("tenants/{tenant_id}/"),
}
}
fn normalize_quickwit_index(raw: Option<&str>, tenant_id: Uuid) -> String {
match raw.map(str::trim) {
Some(value) if !value.is_empty() => value.to_owned(),
_ => format!("documents-{tenant_id}"),
}
}