backend signup
This commit is contained in:
+37
-149
@@ -2,59 +2,28 @@ use std::env;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use argon2::{
|
||||
password_hash::{PasswordHasher, SaltString},
|
||||
Argon2,
|
||||
};
|
||||
use diesel::{dsl::exists, prelude::*, select};
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::{Client, Method, StatusCode};
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
|
||||
use backend::{
|
||||
auth::password,
|
||||
config::AppConfig,
|
||||
db::{self, PgPool},
|
||||
documents::search::ensure_quickwit_index,
|
||||
jobs::{enqueue_job, JOB_ANALYZE_DOCUMENT},
|
||||
models::{DocumentAsset, DocumentAssetObject, NewUser, NewUserMembership, Tenant, User},
|
||||
models::{
|
||||
DocumentAsset, DocumentAssetObject, NewUser, NewUserMembership, Tenant, TenantStatus, User,
|
||||
},
|
||||
s3,
|
||||
schema::{
|
||||
document_asset_objects, document_assets, documents, tenants, user_memberships, users,
|
||||
},
|
||||
storage::{ObjectStorage, S3Storage, TenantStorage},
|
||||
tenants::TenantService,
|
||||
utils::tracing::init_tracing,
|
||||
};
|
||||
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
static QUICKWIT_INDEX_TEMPLATE: Lazy<serde_json::Value> = Lazy::new(|| {
|
||||
json!({
|
||||
"version": "0.8",
|
||||
"index_id": "documents",
|
||||
"doc_mapping": {
|
||||
"tokenizers": [
|
||||
{
|
||||
"name": "substring",
|
||||
"type": "ngram",
|
||||
"min_gram": 2,
|
||||
"max_gram": 20,
|
||||
"prefix_only": false
|
||||
}
|
||||
],
|
||||
"field_mappings": [
|
||||
{ "name": "tenant_id", "type": "text", "stored": true },
|
||||
{ "name": "document_id", "type": "text", "stored": true },
|
||||
{ "name": "version_id", "type": "text", "stored": true },
|
||||
{ "name": "title", "type": "text", "tokenizer": "substring", "stored": true },
|
||||
{ "name": "text", "type": "text", "tokenizer": "substring", "record": "position" }
|
||||
]
|
||||
},
|
||||
"search_settings": {
|
||||
"default_search_fields": ["title", "text"]
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Command {
|
||||
CreateUser {
|
||||
@@ -218,7 +187,7 @@ fn create_user(pool: &PgPool, username: &str, password: &str) -> Result<()> {
|
||||
bail!("user '{}' already exists", username);
|
||||
}
|
||||
|
||||
let password_hash = hash_password(password)?;
|
||||
let password_hash = password::hash_password(password).map_err(|err| anyhow!(err))?;
|
||||
let new_user = NewUser {
|
||||
id: Uuid::new_v4(),
|
||||
username: username.to_string(),
|
||||
@@ -239,7 +208,7 @@ fn set_password(pool: &PgPool, username: &str, password: &str) -> Result<()> {
|
||||
}
|
||||
|
||||
let mut conn = pool.get().context("failed to get database connection")?;
|
||||
let password_hash = hash_password(password)?;
|
||||
let password_hash = password::hash_password(password).map_err(|err| anyhow!(err))?;
|
||||
|
||||
let updated = diesel::update(users::table.filter(users::username.eq(username)))
|
||||
.set(users::password_hash.eq(password_hash))
|
||||
@@ -253,14 +222,6 @@ fn set_password(pool: &PgPool, username: &str, password: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hash_password(password: &str) -> Result<String> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let hash = Argon2::default()
|
||||
.hash_password(password.as_bytes(), &salt)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
Ok(hash.to_string())
|
||||
}
|
||||
|
||||
fn list_users(pool: &PgPool) -> Result<()> {
|
||||
let mut conn = pool.get().context("failed to get database connection")?;
|
||||
|
||||
@@ -315,46 +276,28 @@ fn create_tenant(
|
||||
storage_root_arg: Option<String>,
|
||||
quickwit_index_arg: Option<String>,
|
||||
) -> Result<()> {
|
||||
if slug.trim().is_empty() {
|
||||
bail!("tenant slug must not be empty");
|
||||
}
|
||||
let service = TenantService::new(pool.clone());
|
||||
let tenant = service
|
||||
.create_tenant(
|
||||
slug,
|
||||
storage_root_arg.as_deref(),
|
||||
quickwit_index_arg.as_deref(),
|
||||
TenantStatus::Creating,
|
||||
&[],
|
||||
None,
|
||||
)
|
||||
.map_err(|err| anyhow!(format!("{err:?}")))?;
|
||||
|
||||
let mut conn = pool.get().context("failed to get database connection")?;
|
||||
let exists: bool =
|
||||
select(exists(tenants::table.filter(tenants::slug.eq(slug)))).get_result(&mut conn)?;
|
||||
if exists {
|
||||
bail!("tenant '{}' already exists", slug);
|
||||
}
|
||||
|
||||
let id = Uuid::new_v4();
|
||||
let storage_root = storage_root_arg
|
||||
.map(|mut s| {
|
||||
if s.is_empty() {
|
||||
format!("tenants/{}/", id)
|
||||
} else {
|
||||
if !s.ends_with('/') {
|
||||
s.push('/');
|
||||
}
|
||||
s
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| format!("tenants/{}/", id));
|
||||
let quickwit_index = quickwit_index_arg.unwrap_or_else(|| format!("documents-{}", id));
|
||||
|
||||
diesel::insert_into(tenants::table)
|
||||
.values((
|
||||
tenants::id.eq(id),
|
||||
tenants::slug.eq(slug),
|
||||
tenants::storage_root.eq(Some(storage_root.clone())),
|
||||
tenants::quickwit_index.eq(Some(quickwit_index.clone())),
|
||||
tenants::status.eq("active"),
|
||||
tenants::config.eq(serde_json::json!({})),
|
||||
))
|
||||
.execute(&mut conn)?;
|
||||
let storage_root = tenant.storage_root.as_deref().unwrap_or("<none>");
|
||||
let quickwit_index = tenant.quickwit_index.as_deref().unwrap_or("<none>");
|
||||
|
||||
println!(
|
||||
"created tenant '{}' with id {}, storage_root '{}', quickwit_index '{}'",
|
||||
slug, id, storage_root, quickwit_index
|
||||
"created tenant '{}' with id {}, storage_root '{}', quickwit_index '{}', status '{}'",
|
||||
tenant.slug,
|
||||
tenant.id,
|
||||
storage_root,
|
||||
quickwit_index,
|
||||
tenant.status.as_str()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -605,63 +548,19 @@ async fn quickwit_index(
|
||||
|
||||
match method {
|
||||
Method::POST => {
|
||||
let payload = render_index_template(&index_id);
|
||||
let response = client
|
||||
.post(format!("{}/api/v1/indexes", base_endpoint))
|
||||
.header("content-type", "application/json")
|
||||
.body(payload)
|
||||
.send()
|
||||
ensure_quickwit_index(&client, base_endpoint, &index_id)
|
||||
.await
|
||||
.context("failed to send create index request")?;
|
||||
.context("failed to ensure quickwit index")?;
|
||||
|
||||
match response.status() {
|
||||
status if status.is_success() => {
|
||||
diesel::update(tenants::table.filter(tenants::id.eq(tenant.id)))
|
||||
.set(tenants::quickwit_index.eq(Some(index_id.clone())))
|
||||
.execute(&mut conn)
|
||||
.context("failed to update tenant quickwit_index")?;
|
||||
diesel::update(tenants::table.filter(tenants::id.eq(tenant.id)))
|
||||
.set(tenants::quickwit_index.eq(Some(index_id.clone())))
|
||||
.execute(&mut conn)
|
||||
.context("failed to update tenant quickwit_index")?;
|
||||
|
||||
println!(
|
||||
"Tenant '{}' quickwit index set to '{}'.",
|
||||
tenant.slug, index_id
|
||||
);
|
||||
}
|
||||
StatusCode::CONFLICT => {
|
||||
let lookup = client
|
||||
.get(format!("{}/api/v1/indexes/{}", base_endpoint, index_id))
|
||||
.send()
|
||||
.await
|
||||
.context("failed to verify existing quickwit index")?;
|
||||
|
||||
let lookup_status = lookup.status();
|
||||
if !lookup_status.is_success() {
|
||||
let body = lookup.text().await.unwrap_or_default();
|
||||
bail!(
|
||||
"quickwit reported conflict but index lookup failed with status {}: {}",
|
||||
lookup_status,
|
||||
body
|
||||
);
|
||||
}
|
||||
|
||||
diesel::update(tenants::table.filter(tenants::id.eq(tenant.id)))
|
||||
.set(tenants::quickwit_index.eq(Some(index_id.clone())))
|
||||
.execute(&mut conn)
|
||||
.context("failed to update tenant quickwit_index")?;
|
||||
|
||||
println!(
|
||||
"Tenant '{}' quickwit index set to '{}'.",
|
||||
tenant.slug, index_id
|
||||
);
|
||||
}
|
||||
status => {
|
||||
let body = response.text().await.unwrap_or_default();
|
||||
bail!(
|
||||
"quickwit create index failed with status {}: {}",
|
||||
status,
|
||||
body
|
||||
);
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"Tenant '{}' quickwit index set to '{}'.",
|
||||
tenant.slug, index_id
|
||||
);
|
||||
}
|
||||
Method::DELETE => {
|
||||
let response = client
|
||||
@@ -694,14 +593,3 @@ async fn quickwit_index(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_index_template(index_id: &str) -> String {
|
||||
let mut template = QUICKWIT_INDEX_TEMPLATE.clone();
|
||||
if let Some(obj) = template.as_object_mut() {
|
||||
obj.insert(
|
||||
"index_id".to_string(),
|
||||
serde_json::Value::String(index_id.to_string()),
|
||||
);
|
||||
}
|
||||
template.to_string()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user