solidb 1.2.2

A lightweight, high-performance structured database server written in Rust.
use super::system::AppState;
use crate::error::DbError;
use crate::server::authorization::{AuthorizationService, PermissionAction};
use crate::sync::{LogEntry, Operation};
use axum::{
    extract::{Extension, Path, State},
    http::StatusCode,
    response::Json,
};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize)]
pub struct CreateDatabaseRequest {
    pub name: String,
}

#[derive(Debug, Serialize)]
pub struct CreateDatabaseResponse {
    pub name: String,
    pub status: String,
}

#[derive(Debug, Serialize)]
pub struct ListDatabasesResponse {
    pub databases: Vec<String>,
}

pub async fn create_database(
    State(state): State<AppState>,
    Extension(claims): Extension<crate::server::auth::Claims>,
    Json(req): Json<CreateDatabaseRequest>,
) -> Result<Json<CreateDatabaseResponse>, DbError> {
    if req.name.is_empty() {
        return Err(DbError::BadRequest(
            "Database name cannot be empty".to_string(),
        ));
    }
    AuthorizationService::check_permission(&claims, &state, PermissionAction::Admin, None).await?;
    state.storage.create_database(req.name.clone())?;

    // Record to replication log
    // Record to replication log
    if let Some(ref log) = state.replication_log {
        let entry = LogEntry {
            sequence: 0,
            node_id: "".to_string(),
            database: req.name.clone(),
            collection: "".to_string(),
            operation: Operation::CreateDatabase,
            key: "".to_string(),
            data: None,
            timestamp: chrono::Utc::now().timestamp_millis() as u64,
            origin_sequence: None,
        };
        let _ = log.append(entry);
    }

    // `_scripts` and `_slow_queries` used to be created here, on the theory
    // that pre-creating `_slow_queries` avoided a race between concurrent
    // slow-query log tasks. That race is already handled where it happens:
    // `handlers::query` creates the collection and then retries the lookup ten
    // times, and `script_handlers` creates `_scripts` on first use.
    //
    // Creating them eagerly cost two `create_cf` calls — two full OPTIONS
    // rewrites, each proportional to the instance's *total* column-family
    // count — for every database, whether or not it ever ran a script or a
    // slow query. Measured on a dev instance: 43 `_scripts` and 42
    // `_slow_queries` column families across 46 databases, almost all empty.
    //
    // A peer that receives a write for one of these collections auto-creates
    // it (`sync::worker`), so nothing needs announcing up front.

    Ok(Json(CreateDatabaseResponse {
        name: req.name,
        status: "created".to_string(),
    }))
}

pub async fn list_databases(
    State(state): State<AppState>,
    Extension(claims): Extension<crate::server::auth::Claims>,
) -> Json<ListDatabasesResponse> {
    // Only list databases the caller can at least read, so a low-privilege
    // or db-scoped principal can't enumerate every database on the server.
    let permissions = AuthorizationService::get_effective_permissions(&claims, &state)
        .await
        .unwrap_or_default();
    let scoped = claims.scoped_databases.as_deref();
    let databases = state
        .storage
        .list_databases()
        .into_iter()
        .filter(|db| {
            AuthorizationService::check_permission_raw(
                &permissions,
                PermissionAction::Read,
                Some(db),
                scoped,
            )
            .is_ok()
        })
        .collect();
    Json(ListDatabasesResponse { databases })
}

pub async fn delete_database(
    State(state): State<AppState>,
    Extension(claims): Extension<crate::server::auth::Claims>,
    Path(name): Path<String>,
) -> Result<StatusCode, DbError> {
    // Pass the target database so db-scoped admin keys can only delete
    // databases inside their scope.
    AuthorizationService::check_permission(&claims, &state, PermissionAction::Admin, Some(&name))
        .await?;
    state.storage.delete_database(&name)?;

    // Record to replication log
    // Record to replication log
    if let Some(ref log) = state.replication_log {
        let entry = LogEntry {
            sequence: 0,
            node_id: "".to_string(),
            database: name.clone(),
            collection: "".to_string(),
            operation: Operation::DeleteDatabase,
            key: "".to_string(),
            data: None,
            timestamp: chrono::Utc::now().timestamp_millis() as u64,
            origin_sequence: None,
        };
        let _ = log.append(entry);
    }

    Ok(StatusCode::NO_CONTENT)
}