Initial public release v0.1.0
This commit is contained in:
commit
6c39e0bfbf
87 files changed
+10867
No files matched your search
@@ -0,0 +1,20 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
/// A personal access token row from the `api_tokens` table.
|
||||
///
|
||||
/// `token_hash` is the BLAKE3 hex-encoded hash of the raw `nx9_pat_...` token.
|
||||
/// The raw token is displayed exactly once at creation time and never stored.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct ApiToken {
|
||||
pub id: String,
|
||||
pub user_id: String,
|
||||
pub name: String,
|
||||
/// BLAKE3 hex hash — never expose in API responses.
|
||||
#[serde(skip_serializing)]
|
||||
pub token_hash: String,
|
||||
pub last_used_at: Option<String>,
|
||||
pub expires_at: Option<String>,
|
||||
pub created_at: String,
|
||||
pub revoked: bool,
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct Application {
|
||||
pub id: String,
|
||||
pub tenant_id: String,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
pub enabled: bool,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
/// Audit event severity level.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AuditSeverity {
|
||||
Info,
|
||||
Warning,
|
||||
Critical,
|
||||
}
|
||||
|
||||
impl AuditSeverity {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Info => "info",
|
||||
Self::Warning => "warning",
|
||||
Self::Critical => "critical",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for AuditSeverity {
|
||||
type Err = std::convert::Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"warning" => Ok(Self::Warning),
|
||||
"critical" => Ok(Self::Critical),
|
||||
_ => Ok(Self::Info),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AuditSeverity {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// A row from the `audit_logs` table.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct AuditLog {
|
||||
pub id: String,
|
||||
pub actor_user_id: Option<String>,
|
||||
pub target_user_id: Option<String>,
|
||||
pub action: String,
|
||||
pub resource_type: String,
|
||||
pub resource_id: Option<String>,
|
||||
pub severity: String,
|
||||
pub ip_address: Option<String>,
|
||||
pub user_agent: Option<String>,
|
||||
pub metadata_json: Option<String>,
|
||||
pub created_at: String,
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
pub mod api_token;
|
||||
pub mod application;
|
||||
pub mod audit_log;
|
||||
pub mod permission;
|
||||
pub mod role;
|
||||
pub mod service_account;
|
||||
pub mod session;
|
||||
pub mod tenant;
|
||||
pub mod user;
|
||||
|
||||
pub use api_token::ApiToken;
|
||||
pub use application::Application;
|
||||
pub use audit_log::{AuditLog, AuditSeverity};
|
||||
#[allow(unused_imports)]
|
||||
pub use permission::Permission;
|
||||
pub use role::Role;
|
||||
pub use service_account::ServiceAccount;
|
||||
pub use session::Session;
|
||||
pub use tenant::Tenant;
|
||||
pub use user::{User, UserStatus};
|
||||
@@ -0,0 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct Permission {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct Role {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct ServiceAccount {
|
||||
pub id: String,
|
||||
pub tenant_id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub enabled: bool,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
/// A session row from the `sessions` table.
|
||||
///
|
||||
/// `token_hash` is the BLAKE3 hex-encoded hash of the raw session token.
|
||||
/// The raw token is stored in a cookie and never persisted.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct Session {
|
||||
pub id: String,
|
||||
pub user_id: String,
|
||||
/// BLAKE3 hex hash of the raw cookie value.
|
||||
#[serde(skip_serializing)]
|
||||
pub token_hash: String,
|
||||
pub ip_address: Option<String>,
|
||||
pub user_agent: Option<String>,
|
||||
pub created_at: String,
|
||||
/// Absolute expiry — the session is dead after this regardless of activity.
|
||||
pub expires_at: String,
|
||||
/// Idle timeout — updated on each authenticated request.
|
||||
pub last_seen_at: String,
|
||||
pub revoked: bool,
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct Tenant {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
pub enabled: bool,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
impl Tenant {
|
||||
pub const DEFAULT_ID: &'static str = "00000000-0000-0000-0000-000000000001";
|
||||
pub const DEFAULT_SLUG: &'static str = "default";
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
/// User account status.
|
||||
///
|
||||
/// Stored as INTEGER in SQLite: 1 = Active, 2 = Disabled, 3 = Locked.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum UserStatus {
|
||||
Active = 1,
|
||||
Disabled = 2,
|
||||
Locked = 3,
|
||||
}
|
||||
|
||||
impl UserStatus {
|
||||
pub fn from_i32(v: i32) -> Self {
|
||||
match v {
|
||||
2 => Self::Disabled,
|
||||
3 => Self::Locked,
|
||||
_ => Self::Active,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_i32(self) -> i32 {
|
||||
self as i32
|
||||
}
|
||||
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Active => "active",
|
||||
Self::Disabled => "disabled",
|
||||
Self::Locked => "locked",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UserStatus {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// A user account row from the `users` table.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub tenant_id: String,
|
||||
pub username: String,
|
||||
/// Argon2id PHC string — never expose in API responses.
|
||||
#[serde(skip_serializing)]
|
||||
pub password_hash: String,
|
||||
/// Raw integer status — use `status()` for the typed enum.
|
||||
pub status: i32,
|
||||
pub last_login_at: Option<String>,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
/// Typed status accessor.
|
||||
pub fn status(&self) -> UserStatus {
|
||||
UserStatus::from_i32(self.status)
|
||||
}
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.status() == UserStatus::Active
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user