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)]
62#[non_exhaustive]
63pub enum AccessEvent {
64 LoginSuccess,
65 LoginFailure,
66 LockedOut,
67 Logout,
68 /// The operator changed their own password, from the address recorded.
69 PasswordChanged,
70}
71
72impl AccessEvent {
73 /// The stored string form; stable, since it is persisted.
74 pub fn as_str(self) -> &'static str {
75 match self {
76 AccessEvent::LoginSuccess => "login_success",
77 AccessEvent::LoginFailure => "login_failure",
78 AccessEvent::LockedOut => "locked_out",
79 AccessEvent::Logout => "logout",
80 AccessEvent::PasswordChanged => "password_changed",
81 }
82 }
83}