Skip to main content

laterite_auth/
models.rs

1//! Persistent row types for the auth schema.
2
3use chrono::{DateTime, Utc};
4use serde::Serialize;
5
6/// A backend user: an operator of the admin surface.
7///
8/// Names follow the structured `first_name` plus optional `last_name` pair, the
9/// same shape the wider ecosystem standardizes on so integrations and any
10/// future extensions can rely on the same fields. `password_hash` is skipped in
11/// serialization so an identity can be handed to a template or API response
12/// without leaking the stored credential.
13#[derive(Debug, Clone, Serialize)]
14pub struct BackendUser {
15    pub id: i64,
16    pub username: String,
17    pub email: String,
18    pub first_name: String,
19    pub last_name: Option<String>,
20    #[serde(skip)]
21    pub password_hash: String,
22    pub is_superuser: bool,
23    pub is_active: bool,
24    /// The operator's own display timezone as an IANA name (e.g. `Asia/Kolkata`).
25    /// `None` means the operator inherits the deployment default. Storage is
26    /// always UTC; this only affects how timestamps render for this operator.
27    pub timezone: Option<String>,
28    /// The operator's own UI language as a base tag (e.g. `kn`). `None` means the
29    /// operator inherits the deployment default. This only affects the admin's own
30    /// strings, not stored content.
31    pub locale: Option<String>,
32    pub created_at: DateTime<Utc>,
33    pub updated_at: DateTime<Utc>,
34}
35
36impl BackendUser {
37    /// The display name, derived rather than stored: `first_name` plus
38    /// `last_name` when present.
39    pub fn full_name(&self) -> String {
40        match &self.last_name {
41            Some(last) if !last.is_empty() => format!("{} {}", self.first_name, last),
42            _ => self.first_name.clone(),
43        }
44    }
45}
46
47/// A lightweight backend-user projection for listings (no credential fields).
48#[derive(Debug, Clone, Serialize)]
49pub struct BackendUserSummary {
50    pub id: i64,
51    pub username: String,
52    pub email: String,
53    pub first_name: String,
54    pub last_name: Option<String>,
55    pub is_superuser: bool,
56    pub is_active: bool,
57    pub created_at: DateTime<Utc>,
58}
59
60/// The kind of event recorded in the access log.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum AccessEvent {
63    LoginSuccess,
64    LoginFailure,
65    LockedOut,
66    Logout,
67}
68
69impl AccessEvent {
70    /// The stored string form; stable, since it is persisted.
71    pub fn as_str(self) -> &'static str {
72        match self {
73            AccessEvent::LoginSuccess => "login_success",
74            AccessEvent::LoginFailure => "login_failure",
75            AccessEvent::LockedOut => "locked_out",
76            AccessEvent::Logout => "logout",
77        }
78    }
79}