diff options
Diffstat (limited to 'src/models/mod.rs')
| -rw-r--r-- | src/models/mod.rs | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..4d9f734 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,294 @@ +// Data models for SeckelAPI +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +// Authentication models +#[derive(Debug, Deserialize, Serialize)] +pub struct LoginRequest { + pub method: AuthMethod, + pub username: Option<String>, + pub password: Option<String>, + pub pin: Option<String>, + pub login_string: Option<String>, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum AuthMethod { + Password, + Pin, + Token, +} + +#[derive(Debug, Serialize)] +pub struct LoginResponse { + pub success: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub token: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub user: Option<UserInfo>, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option<String>, +} + +#[derive(Debug, Serialize)] +pub struct UserInfo { + pub id: i32, + pub username: String, + pub name: String, + pub role: String, + pub power: i32, +} + +// Database query models +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct QueryRequest { + // Single query mode (when queries is None) + #[serde(skip_serializing_if = "Option::is_none")] + pub action: Option<QueryAction>, + #[serde(skip_serializing_if = "Option::is_none")] + pub table: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub columns: Option<Vec<String>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option<serde_json::Value>, + // Enhanced WHERE clause - supports both simple and complex conditions + #[serde(rename = "where", skip_serializing_if = "Option::is_none")] + pub where_clause: Option<serde_json::Value>, + // New structured filter for complex queries + #[serde(skip_serializing_if = "Option::is_none")] + pub filter: Option<FilterCondition>, + // JOIN support - allows multi-table queries with permission validation + #[serde(skip_serializing_if = "Option::is_none")] + pub joins: Option<Vec<Join>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option<u32>, + #[serde(skip_serializing_if = "Option::is_none")] + pub offset: Option<u32>, + #[serde(skip_serializing_if = "Option::is_none")] + pub order_by: Option<Vec<OrderBy>>, + + // Batch mode (when queries is Some) - action/table apply to ALL queries in batch + #[serde(skip_serializing_if = "Option::is_none")] + pub queries: Option<Vec<BatchQuery>>, + /// Whether to rollback on error in batch mode (defaults to config setting) + #[serde(skip_serializing_if = "Option::is_none")] + pub rollback_on_error: Option<bool>, +} + +/// Individual query in a batch - inherits action/table from parent QueryRequest +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct BatchQuery { + // Only the variable parts per query - no action/table duplication + #[serde(skip_serializing_if = "Option::is_none")] + pub columns: Option<Vec<String>>, + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option<serde_json::Value>, + #[serde(rename = "where", skip_serializing_if = "Option::is_none")] + pub where_clause: Option<serde_json::Value>, + #[serde(skip_serializing_if = "Option::is_none")] + pub filter: Option<FilterCondition>, + #[serde(skip_serializing_if = "Option::is_none")] + pub limit: Option<u32>, + #[serde(skip_serializing_if = "Option::is_none")] + pub offset: Option<u32>, + #[serde(skip_serializing_if = "Option::is_none")] + pub order_by: Option<Vec<OrderBy>>, +} + +/// JOIN specification for multi-table queries +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Join { + /// Type of join (INNER, LEFT, RIGHT) + #[serde(rename = "type")] + pub join_type: JoinType, + /// Table to join with + pub table: String, + /// Join condition (e.g., "assets.category_id = categories.id") + pub on: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(rename_all = "UPPERCASE")] +pub enum JoinType { + Inner, + Left, + Right, +} + +/// Enhanced filter condition supporting operators and nested logic +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(untagged)] +pub enum FilterCondition { + /// Simple condition: {"column": "name", "op": "=", "value": "John"} + Simple { + column: String, + #[serde(rename = "op")] + operator: FilterOperator, + value: serde_json::Value, + }, + /// Logical AND/OR: {"and": [condition1, condition2]} + Logical { + #[serde(rename = "and")] + and_conditions: Option<Vec<FilterCondition>>, + #[serde(rename = "or")] + or_conditions: Option<Vec<FilterCondition>>, + }, + /// NOT condition: {"not": condition} + Not { not: Box<FilterCondition> }, +} + +/// Supported WHERE clause operators +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum FilterOperator { + #[serde(rename = "=")] + Eq, // Equal + #[serde(rename = "!=")] + Ne, // Not equal + #[serde(rename = ">")] + Gt, // Greater than + #[serde(rename = ">=")] + Gte, // Greater than or equal + #[serde(rename = "<")] + Lt, // Less than + #[serde(rename = "<=")] + Lte, // Less than or equal + Like, // LIKE pattern matching + #[serde(rename = "not_like")] + NotLike, // NOT LIKE + In, // IN (value1, value2, ...) + #[serde(rename = "not_in")] + NotIn, // NOT IN (...) + #[serde(rename = "is_null")] + IsNull, // IS NULL + #[serde(rename = "is_not_null")] + IsNotNull, // IS NOT NULL + Between, // BETWEEN value1 AND value2 +} + +/// ORDER BY clause +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct OrderBy { + pub column: String, + #[serde(default = "default_order_direction")] + pub direction: OrderDirection, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +#[serde(rename_all = "UPPERCASE")] +pub enum OrderDirection { + ASC, + DESC, +} + +fn default_order_direction() -> OrderDirection { + OrderDirection::ASC +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum QueryAction { + Select, + Insert, + Update, + Delete, + Count, +} + +#[derive(Debug, Serialize, Clone)] +pub struct QueryResponse { + pub success: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option<serde_json::Value>, + #[serde(skip_serializing_if = "Option::is_none")] + pub rows_affected: Option<u64>, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub warning: Option<String>, + // Batch results (when queries field was used) + #[serde(skip_serializing_if = "Option::is_none")] + pub results: Option<Vec<QueryResponse>>, +} + +// Database entities +#[derive(Debug, sqlx::FromRow)] +#[allow(dead_code)] // Fields are used for database serialization +pub struct User { + pub id: i32, + pub name: String, + pub username: String, + pub password: String, + pub pin_code: Option<String>, + pub login_string: Option<String>, + pub role_id: i32, + pub email: Option<String>, + pub phone: Option<String>, + pub notes: Option<String>, + pub active: bool, + pub last_login_date: Option<DateTime<Utc>>, + pub created_date: DateTime<Utc>, + pub password_reset_token: Option<String>, + pub password_reset_expiry: Option<DateTime<Utc>>, +} + +#[derive(Debug, sqlx::FromRow)] +#[allow(dead_code)] // Fields are used for database serialization +pub struct Role { + pub id: i32, + pub name: String, + pub power: i32, + pub created_at: DateTime<Utc>, +} + +// Session management +#[derive(Debug, Clone)] +pub struct Session { + pub user_id: i32, + pub username: String, + pub role_id: i32, + pub role_name: String, + pub power: i32, + pub created_at: DateTime<Utc>, + pub last_accessed: DateTime<Utc>, +} + +// Permission types +#[derive(Debug, Clone, PartialEq)] +pub enum Permission { + Read, + Write, + ReadWrite, + None, +} + +impl Permission { + pub fn from_str(s: &str) -> Self { + match s { + "r" => Permission::Read, + "w" => Permission::Write, + "rw" => Permission::ReadWrite, + _ => Permission::None, + } + } + + pub fn can_read(&self) -> bool { + matches!(self, Permission::Read | Permission::ReadWrite) + } + + pub fn can_write(&self) -> bool { + matches!(self, Permission::Write | Permission::ReadWrite) + } +} + +// Permissions response +#[derive(Debug, Serialize)] +pub struct PermissionsResponse { + pub success: bool, + pub user: UserInfo, + pub permissions: HashMap<String, String>, + pub security_clearance: Option<String>, + pub user_settings_access: String, +} |
