diff --git a/engine/migrations/0001_init.up.sql b/engine/migrations/0001_init.up.sql index ab295f1..3ac22ff 100644 --- a/engine/migrations/0001_init.up.sql +++ b/engine/migrations/0001_init.up.sql @@ -11,7 +11,7 @@ CREATE TABLE users ( ); -- Insert a system user -INSERT INTO users (user_id, oauth_sub, oauth_data, nickname) VALUES (0, '$$SYSTEM$$', '{}', 'System'); +INSERT INTO users (user_id, oauth_sub, oauth_data, nickname) VALUES (1, '$$SYSTEM$$', '{}', 'System'); -- Create Sessions table CREATE TABLE sessions ( diff --git a/engine/migrations/0002_logentry.down.sql b/engine/migrations/0002_logentry.down.sql new file mode 100644 index 0000000..5df9e7f --- /dev/null +++ b/engine/migrations/0002_logentry.down.sql @@ -0,0 +1 @@ +DROP TABLE logentries; diff --git a/engine/migrations/0002_logentry.up.sql b/engine/migrations/0002_logentry.up.sql new file mode 100644 index 0000000..ff064f8 --- /dev/null +++ b/engine/migrations/0002_logentry.up.sql @@ -0,0 +1,25 @@ +-- Create the logs table +CREATE TABLE logs ( + log_id SERIAL PRIMARY KEY, + resource_type TEXT NOT NULL, + resource_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + action TEXT NOT NULL, + data TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- Insert a test log entry +INSERT INTO logs (resource_type, resource_id, user_id, action, data) VALUES ('test', 1, 1, 'test', 'test'); + +-- Create indexes for common queries +CREATE INDEX logs_resource_type_idx ON logs(resource_type); +CREATE INDEX logs_resource_id_idx ON logs(resource_id); +CREATE INDEX logs_user_id_idx ON logs(user_id); +CREATE INDEX logs_action_idx ON logs(action); +CREATE INDEX logs_created_at_idx ON logs(created_at); + +-- Add foreign key constraint to users table +ALTER TABLE logs +ADD CONSTRAINT logs_user_id_fkey +FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE; diff --git a/engine/src/models/field/definition.rs b/engine/src/models/field/definition.rs index 2afcbc3..5d45bdd 100644 --- a/engine/src/models/field/definition.rs +++ b/engine/src/models/field/definition.rs @@ -2,7 +2,7 @@ use crate::database::Database; use crate::models::field::kind::FieldKind; use serde::{Deserialize, Serialize}; use poem_openapi::Object; -use sqlx::FromRow; +use sqlx::{query_as, FromRow}; #[derive(FromRow, Object, Debug, Clone, Serialize, Deserialize)] pub struct FieldDefinition { @@ -19,7 +19,7 @@ impl FieldDefinition { name: String, database: &Database, ) -> Result { - sqlx::query_as!( + query_as!( FieldDefinition, "INSERT INTO field_definitions (kind, name) VALUES ($1, $2) RETURNING *", kind.to_string(), diff --git a/engine/src/models/field/mod.rs b/engine/src/models/field/mod.rs index 974c1c7..a2e7dd0 100644 --- a/engine/src/models/field/mod.rs +++ b/engine/src/models/field/mod.rs @@ -1,7 +1,2 @@ -use serde::{Deserialize, Serialize}; -use sqlx::prelude::*; - -use crate::database::Database; - pub mod kind; pub mod definition; diff --git a/engine/src/models/item/field.rs b/engine/src/models/item/field.rs index 76857eb..c308255 100644 --- a/engine/src/models/item/field.rs +++ b/engine/src/models/item/field.rs @@ -1,6 +1,7 @@ use crate::database::Database; use serde::{Deserialize, Serialize}; +use sqlx::query_as; #[derive(sqlx::FromRow, poem_openapi::Object, Debug, Clone, Serialize, Deserialize)] pub struct ItemField { @@ -18,7 +19,7 @@ impl ItemField { value: serde_json::Value, database: &Database, ) -> Result { - sqlx::query_as!( + query_as!( ItemField, "INSERT INTO item_fields (item_id, definition_id, value) VALUES ($1, $2, $3) RETURNING *", item_id, diff --git a/engine/src/models/item/media.rs b/engine/src/models/item/media.rs index 433e31d..4fdba2f 100644 --- a/engine/src/models/item/media.rs +++ b/engine/src/models/item/media.rs @@ -1,5 +1,6 @@ use crate::database::Database; use serde::{Deserialize, Serialize}; +use sqlx::query_as; #[derive(sqlx::FromRow, poem_openapi::Object, Debug, Clone, Serialize, Deserialize)] pub struct ItemMedia { @@ -13,7 +14,7 @@ impl ItemMedia { media_id: i32, database: &Database, ) -> Result { - sqlx::query_as!( + query_as!( ItemMedia, "INSERT INTO item_media (item_id, media_id) VALUES ($1, $2) RETURNING *", item_id, diff --git a/engine/src/models/item/mod.rs b/engine/src/models/item/mod.rs index eef3c3e..6a7c6c4 100644 --- a/engine/src/models/item/mod.rs +++ b/engine/src/models/item/mod.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; -use sqlx::prelude::*; +use sqlx::{query, query_as}; use tracing::info; +use chrono::{DateTime, Utc}; use crate::database::Database; @@ -14,8 +15,8 @@ pub struct Item { pub product_id: Option, pub owner_id: Option, pub location_id: Option, - pub created_at: Option>, - pub updated_at: Option>, + pub created_at: Option>, + pub updated_at: Option>, } impl Default for Item { @@ -41,7 +42,7 @@ impl Item { product_id: Option, database: &Database, ) -> Result { - sqlx::query_as!( + query_as!( Item, "INSERT INTO items (item_id, name, owner_id, location_id, product_id) VALUES ($1, $2, $3, $4, $5) RETURNING *", item_id, @@ -58,13 +59,13 @@ impl Item { owner_id: i32, database: &Database, ) -> Result, sqlx::Error> { - sqlx::query_as!(Item, "SELECT * FROM items WHERE owner_id = $1", owner_id) + query_as!(Item, "SELECT * FROM items WHERE owner_id = $1", owner_id) .fetch_all(&database.pool) .await } pub async fn get_by_id(item_id: String, database: &Database) -> Result, sqlx::Error> { - sqlx::query_as!(Item, "SELECT * FROM items WHERE item_id = $1", item_id) + query_as!(Item, "SELECT * FROM items WHERE item_id = $1", item_id) .fetch_optional(&database.pool) .await } @@ -78,7 +79,7 @@ impl Item { loop { let id_str = id.to_string(); info!("Checking if id {} is taken", id_str); - if sqlx::query("SELECT 1 FROM items WHERE item_id = $1") + if query("SELECT 1 FROM items WHERE item_id = $1") .bind(id_str.clone()) .fetch_optional(&database.pool) .await diff --git a/engine/src/models/location.rs b/engine/src/models/location.rs index a12cc7d..eab1661 100644 --- a/engine/src/models/location.rs +++ b/engine/src/models/location.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use chrono::{DateTime, Utc}; use poem_openapi::Object; -use sqlx::FromRow; +use sqlx::{query_as, FromRow}; use crate::database::Database; @@ -15,7 +15,7 @@ pub struct Location { impl Location { pub async fn create(name: String, database: &Database) -> Result { - sqlx::query_as!( + query_as!( Location, "INSERT INTO locations (name) VALUES ($1) RETURNING *", name diff --git a/engine/src/models/log/mod.rs b/engine/src/models/log/mod.rs new file mode 100644 index 0000000..b6c0c26 --- /dev/null +++ b/engine/src/models/log/mod.rs @@ -0,0 +1,45 @@ +use sqlx::{prelude::FromRow, query_as}; +use serde::{Deserialize, Serialize}; +use chrono::{DateTime, Utc}; +use poem_openapi::Object; + +use crate::database::Database; + +/// Represents a log entry +/// When an action is performed on a resource, a log entry is created +/// This resource can then be queried by user, resource_type, (resource_type + resource_id), or action +#[derive(FromRow, Object, Debug, Clone, Serialize, Deserialize)] +pub struct LogEntry { + pub log_id: i32, + pub resource_type: String, + pub resource_id: i32, + pub user_id: i32, + pub action: String, + pub data: String, + pub created_at: DateTime, +} + +impl LogEntry { + /// Create a new log entry + /// Let postgres generate the id and created_at + pub async fn new(db: &Database, resource_type: String, resource_id: i32, user_id: i32, action: String, data: String) -> Result { + let log_entry = query_as!( + LogEntry, + "INSERT INTO logs (resource_type, resource_id, user_id, action, data) VALUES ($1, $2, $3, $4, $5) RETURNING *", + resource_type, resource_id, user_id, action, data + ).fetch_one(&db.pool).await?; + + Ok(log_entry) + } + + /// Find by log_id + pub async fn find_by_log_id(db: &Database, log_id: i32) -> Result { + let log_entry = query_as!( + LogEntry, + "SELECT * FROM logs WHERE log_id = $1", + log_id + ).fetch_one(&db.pool).await?; + + Ok(log_entry) + } +} diff --git a/engine/src/models/media.rs b/engine/src/models/media.rs index 0d41660..e639f47 100644 --- a/engine/src/models/media.rs +++ b/engine/src/models/media.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use sqlx::FromRow; +use sqlx::{query_as, FromRow}; use poem_openapi::Object; use chrono::{DateTime, Utc}; use crate::database::Database; @@ -21,7 +21,7 @@ impl Media { kind: String, database: &Database, ) -> Result { - sqlx::query_as!( + query_as!( Media, "INSERT INTO media (description, url, kind) VALUES ($1, $2, $3) RETURNING *", Some(description), @@ -36,7 +36,7 @@ impl Media { media_id: i32, database: &Database, ) -> Result { - sqlx::query_as!(Media, "SELECT * FROM media WHERE media_id = $1", media_id) + query_as!(Media, "SELECT * FROM media WHERE media_id = $1", media_id) .fetch_one(&database.pool) .await } diff --git a/engine/src/models/mod.rs b/engine/src/models/mod.rs index 1357746..7152097 100644 --- a/engine/src/models/mod.rs +++ b/engine/src/models/mod.rs @@ -6,3 +6,4 @@ pub mod products; pub mod sessions; pub mod tags; pub mod users; +pub mod log; diff --git a/engine/src/models/products.rs b/engine/src/models/products.rs index 25e333d..685f922 100644 --- a/engine/src/models/products.rs +++ b/engine/src/models/products.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use sqlx::FromRow; +use sqlx::{query_as, FromRow}; use poem_openapi::Object; use chrono::{DateTime, Utc}; use crate::database::Database; @@ -14,7 +14,7 @@ pub struct Product { impl Product { pub async fn create(name: String, database: &Database) -> Result { - sqlx::query_as!( + query_as!( Product, "INSERT INTO products (name) VALUES ($1) RETURNING *", name @@ -24,7 +24,7 @@ impl Product { } pub async fn get_by_id(product_id: i32, database: &Database) -> Result, sqlx::Error> { - sqlx::query_as!(Product, "SELECT * FROM products WHERE product_id = $1", product_id) + query_as!(Product, "SELECT * FROM products WHERE product_id = $1", product_id) .fetch_optional(&database.pool) .await } diff --git a/engine/src/models/sessions.rs b/engine/src/models/sessions.rs index 7b2ad37..c19a756 100644 --- a/engine/src/models/sessions.rs +++ b/engine/src/models/sessions.rs @@ -1,6 +1,6 @@ use poem_openapi::Object; use serde::{Deserialize, Serialize}; -use sqlx::types::ipnetwork::IpNetwork; +use sqlx::{query_as, types::ipnetwork::IpNetwork}; use chrono::{DateTime, Utc}; use crate::database::Database; @@ -25,7 +25,7 @@ impl Session { user_ip: &IpNetwork, database: &Database, ) -> Result { - let session = sqlx::query_as!(Session, + let session = query_as!(Session, "INSERT INTO sessions (session_id, user_id, user_agent, user_ip) VALUES ($1, $2, $3, $4) RETURNING *", session_id, user_id, user_agent, user_ip.to_string() ) @@ -35,7 +35,7 @@ impl Session { } pub async fn _get_by_id(id: &str, database: &Database) -> Result, sqlx::Error> { - let session = sqlx::query_as!( + let session = query_as!( Session, "SELECT * FROM sessions WHERE session_id = $1 AND valid = TRUE", id @@ -47,7 +47,7 @@ impl Session { } pub async fn try_access(id: &str, database: &Database) -> Result, sqlx::Error> { - let session = sqlx::query_as!( + let session = query_as!( Session, "UPDATE sessions SET last_access = NOW() WHERE session_id = $1 AND valid = TRUE RETURNING *", id @@ -63,7 +63,7 @@ impl Session { user_id: i32, database: &Database, ) -> Result, sqlx::Error> { - let sessions = sqlx::query_as!( + let sessions = query_as!( Session, "SELECT * FROM sessions WHERE user_id = $1 AND valid = TRUE", user_id @@ -79,7 +79,7 @@ impl Session { user_id: i32, database: &Database, ) -> Result, sqlx::Error> { - let sessions = sqlx::query_as!( + let sessions = query_as!( Session, "UPDATE sessions SET valid = FALSE WHERE user_id = $1 RETURNING *", user_id @@ -96,7 +96,7 @@ impl Session { user_id: i32, database: &Database, ) -> Result, sqlx::Error> { - let sessions = sqlx::query_as!( + let sessions = query_as!( Session, "UPDATE sessions SET valid = FALSE WHERE user_id = $1 AND session_id = $2 RETURNING *", user_id, @@ -114,7 +114,7 @@ impl Session { database: &Database, _invalidate_before: chrono::DateTime, ) -> Result, sqlx::Error> { - let sessions = sqlx::query_as!( + let sessions = query_as!( Session, "UPDATE sessions SET valid = FALSE WHERE user_id = $1 AND last_access < $2 RETURNING *", user_id, diff --git a/engine/src/models/tags.rs b/engine/src/models/tags.rs index 18551d4..03d234d 100644 --- a/engine/src/models/tags.rs +++ b/engine/src/models/tags.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use sqlx::FromRow; +use sqlx::{query_as, FromRow}; use chrono::{DateTime, Utc}; use poem_openapi::Object; use crate::database::Database; @@ -14,7 +14,7 @@ pub struct Tag { impl Tag { pub async fn create(name: String, database: &Database) -> Result { - sqlx::query_as!(Tag, "INSERT INTO tags (name) VALUES ($1) RETURNING *", name) + query_as!(Tag, "INSERT INTO tags (name) VALUES ($1) RETURNING *", name) .fetch_one(&database.pool) .await } diff --git a/engine/src/models/users.rs b/engine/src/models/users.rs index 76bf593..cff1157 100644 --- a/engine/src/models/users.rs +++ b/engine/src/models/users.rs @@ -1,6 +1,6 @@ use openid::Userinfo; use serde::{Deserialize, Serialize}; -use sqlx::{types::Json, FromRow}; +use sqlx::{query, query_as, types::Json, FromRow}; use url::Url; use chrono::{DateTime, Utc}; use poem_openapi::Object; @@ -31,7 +31,7 @@ impl UserEntry { let oauth_data_json: Json = Json(oauth_userinfo.clone()); // upsert into users table and select the result - sqlx::query("INSERT INTO users (oauth_sub, oauth_data, nickname) VALUES ($1, $2, $3) ON CONFLICT (oauth_sub) DO UPDATE SET oauth_data = $2, nickname = $3 RETURNING user_id, oauth_sub, oauth_data, nickname, created_at, updated_at") + query("INSERT INTO users (oauth_sub, oauth_data, nickname) VALUES ($1, $2, $3) ON CONFLICT (oauth_sub) DO UPDATE SET oauth_data = $2, nickname = $3 RETURNING user_id, oauth_sub, oauth_data, nickname, created_at, updated_at") .bind(oauth_sub) .bind(oauth_data_json) .bind(nickname) @@ -44,7 +44,7 @@ impl UserEntry { oauth_sub: String, database: &Database, ) -> Result, sqlx::Error> { - sqlx::query_as!( + query_as!( UserEntry, r#"SELECT user_id, oauth_sub, oauth_data::text::json as "oauth_data!: Json", nickname, created_at, updated_at FROM users WHERE oauth_sub = $1"#, @@ -58,7 +58,7 @@ impl UserEntry { user_id: i32, database: &Database, ) -> Result, sqlx::Error> { - sqlx::query_as!( + query_as!( UserEntry, r#"SELECT user_id, oauth_sub, oauth_data::text::json as "oauth_data!: Json", nickname, created_at, updated_at FROM users WHERE user_id = $1"#, diff --git a/engine/src/routes/instance.rs b/engine/src/routes/instance.rs index addaf9c..0d29066 100644 --- a/engine/src/routes/instance.rs +++ b/engine/src/routes/instance.rs @@ -4,7 +4,7 @@ use poem::web::{Data}; use poem_openapi::{payload::Json, Enum, Object, OpenApi}; use serde::{Deserialize, Serialize}; -use crate::{auth::middleware::AuthToken, models::users::User, state::AppState}; +use crate::{auth::middleware::AuthToken, state::AppState}; pub struct ApiInstance; diff --git a/engine/src/routes/sessions/mod.rs b/engine/src/routes/sessions/mod.rs index b369999..1d0b7b0 100644 --- a/engine/src/routes/sessions/mod.rs +++ b/engine/src/routes/sessions/mod.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use poem::web::{Data, Path}; use poem_openapi::{payload::Json, OpenApi}; -use reqwest::{Error, StatusCode}; use tracing::info; use crate::{