From 3b8c52918351b1a66083016147650b17ddf55a10 Mon Sep 17 00:00:00 2001 From: Patrick Michl Date: Sat, 30 Apr 2022 23:59:21 +0200 Subject: [PATCH] rework DB Models --- Cargo.lock | 10 + Cargo.toml | 3 +- migrations/20220423204756_add_user.sql | 2 +- migrations/20220424172900_add_devices.sql | 6 +- migrations/20220424175554_add_sessions.sql | 10 +- sqlx-data.json | 180 +++++++++--------- src/api/client_server/auth.rs | 37 ++-- src/api/client_server/errors/api_error.rs | 5 +- .../errors/authentication_error.rs | 18 +- src/api/client_server/errors/mod.rs | 2 +- src/models/devices.rs | 48 ++--- src/models/sessions.rs | 40 ++-- src/models/users.rs | 75 ++++++-- src/requests/mod.rs | 2 +- src/responses/authentication.rs | 4 +- src/responses/mod.rs | 2 +- src/types/error_code.rs | 2 +- src/types/mod.rs | 1 + src/types/user_id.rs | 34 +++- src/types/uuid.rs | 45 +++++ 20 files changed, 342 insertions(+), 184 deletions(-) create mode 100644 src/types/uuid.rs diff --git a/Cargo.lock b/Cargo.lock index 839e597..25f9b25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -612,6 +612,7 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", + "uuid", ] [[package]] @@ -1445,6 +1446,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "uuid" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0" +dependencies = [ + "getrandom", +] + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 8893ad6..e82c226 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,5 @@ sqlx = { version = "0.5", features = ["sqlite", "macros", "runtime-tokio-rustls" anyhow = "1.0" thiserror = "1.0" argon2 = { version = "0.4", features = ["std"] } -rand_core = { version = "0.6", features = ["std"] } \ No newline at end of file +rand_core = { version = "0.6", features = ["std"] } +uuid = { version = "1.0", features = ["v4"] } \ No newline at end of file diff --git a/migrations/20220423204756_add_user.sql b/migrations/20220423204756_add_user.sql index 25a5aba..031e081 100644 --- a/migrations/20220423204756_add_user.sql +++ b/migrations/20220423204756_add_user.sql @@ -1,6 +1,6 @@ -- Add migration script here CREATE TABLE users( - id INTEGER PRIMARY KEY NOT NULL, + uuid TEXT PRIMARY KEY NOT NULL, user_id CHAR(255) NOT NULL, display_name TEXT NOT NULL, password TEXT NOT NULL diff --git a/migrations/20220424172900_add_devices.sql b/migrations/20220424172900_add_devices.sql index 99a6cf8..70fae10 100644 --- a/migrations/20220424172900_add_devices.sql +++ b/migrations/20220424172900_add_devices.sql @@ -1,11 +1,11 @@ -- Add migration script here CREATE TABLE devices( - id INTEGER PRIMARY KEY NOT NULL, - user_id INT NOT NULL, + uuid TEXT PRIMARY KEY NOT NULL, + user_uuid INT NOT NULL, device_id TEXT NOT NULL, display_name TEXT NOT NULL, - FOREIGN KEY(user_id) REFERENCES users(id) + FOREIGN KEY(user_uuid) REFERENCES users(uuid) ); CREATE INDEX device_id_index ON devices (device_id); \ No newline at end of file diff --git a/migrations/20220424175554_add_sessions.sql b/migrations/20220424175554_add_sessions.sql index d51295a..f4b16eb 100644 --- a/migrations/20220424175554_add_sessions.sql +++ b/migrations/20220424175554_add_sessions.sql @@ -1,10 +1,10 @@ -- Add migration script here CREATE TABLE sessions( - id INTEGER PRIMARY KEY NOT NULL, - device_id INT NOT NULL, - value TEXT NOT NULL, - FOREIGN KEY(device_id) REFERENCES devices(id) + uuid TEXT PRIMARY KEY NOT NULL, + device_uuid INT NOT NULL, + key TEXT NOT NULL, + FOREIGN KEY(device_uuid) REFERENCES devices(uuid) ); -CREATE INDEX value_index ON sessions (value); \ No newline at end of file +CREATE INDEX key_index ON sessions (key); \ No newline at end of file diff --git a/sqlx-data.json b/sqlx-data.json index 11f0b85..cf0d6af 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -1,15 +1,81 @@ { "db": "SQLite", - "22e18063d81e86afceca0e0c74c9070a8b21a406cba7cf7c01a966a869a9dad8": { + "112b5723b62084ee14191a2f8773d2493814d70bfc4d4ce046655958d2ae472b": { "describe": { "columns": [ { - "name": "id", + "name": "uuid: Uuid", "ordinal": 0, + "type_info": "Text" + }, + { + "name": "user_uuid: Uuid", + "ordinal": 1, "type_info": "Int64" }, { - "name": "user_id", + "name": "device_id", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "display_name", + "ordinal": 3, + "type_info": "Text" + } + ], + "nullable": [ + false, + false, + false, + false + ], + "parameters": { + "Right": 4 + } + }, + "query": "insert into devices(uuid, user_uuid, device_id, display_name)\n values(?, ?, ?, ?)\n returning uuid as 'uuid: Uuid', user_uuid as 'user_uuid: Uuid', device_id, display_name" + }, + "221d0935dff8911fe58ac047d39e11b0472d2180d7c297291a5dc440e00efb80": { + "describe": { + "columns": [ + { + "name": "uuid: Uuid", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "device_uuid: Uuid", + "ordinal": 1, + "type_info": "Int64" + }, + { + "name": "key", + "ordinal": 2, + "type_info": "Text" + } + ], + "nullable": [ + false, + false, + false + ], + "parameters": { + "Right": 3 + } + }, + "query": "insert into sessions(uuid, device_uuid, key)\n values(?, ?, ?)\n returning uuid as 'uuid: Uuid', device_uuid as 'device_uuid: Uuid', key" + }, + "3fead3dac0e110757bc30be40bb0c6c2bc02127b6d9b6145bfc40fa5fe22ad06": { + "describe": { + "columns": [ + { + "name": "uuid: Uuid", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "user_id: UserId", "ordinal": 1, "type_info": "Text" }, @@ -31,10 +97,10 @@ false ], "parameters": { - "Right": 1 + "Right": 4 } }, - "query": "select id, user_id, display_name, password from users where user_id = ?" + "query": "insert into users(uuid, user_id, display_name, password)\n values (?, ?, ?, ?)\n returning uuid as 'uuid: Uuid', user_id as 'user_id: UserId', display_name, password" }, "58d27b1d424297504f1da2e3b9b4020121251c1155fbf5dc870dafbef97659f3": { "describe": { @@ -54,88 +120,16 @@ }, "query": "select user_id from users where user_id = ?" }, - "87395ffa7fe0382080056bdf67805044fbe89770ef366b4553d9797059034e44": { + "778b7f0a1c66f00812f0232a5904b7b6b295720ebb75d1c2720afeeda4f66936": { "describe": { "columns": [ { - "name": "id", + "name": "uuid: Uuid", "ordinal": 0, - "type_info": "Int64" - }, - { - "name": "user_id", - "ordinal": 1, - "type_info": "Int64" - }, - { - "name": "device_id", - "ordinal": 2, "type_info": "Text" }, { - "name": "display_name", - "ordinal": 3, - "type_info": "Text" - } - ], - "nullable": [ - false, - false, - false, - false - ], - "parameters": { - "Right": 3 - } - }, - "query": "insert into devices(user_id, device_id, display_name) values(?, ?, ?) returning id, user_id, device_id, display_name" - }, - "9a092e024bfe2854631e0572880f761125a2261a973b46d721db2e1401ce9aec": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int64" - }, - { - "name": "user_id", - "ordinal": 1, - "type_info": "Int64" - }, - { - "name": "device_id", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "display_name", - "ordinal": 3, - "type_info": "Text" - } - ], - "nullable": [ - false, - false, - false, - false - ], - "parameters": { - "Right": 1 - } - }, - "query": "select id, user_id, device_id, display_name from devices where user_id = ?" - }, - "bb4e2386cf2987aa86f2c4dcac1159687b7220c770542bf52485e7e16f9ce987": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int64" - }, - { - "name": "user_id", + "name": "user_id: UserId", "ordinal": 1, "type_info": "Text" }, @@ -157,39 +151,45 @@ false ], "parameters": { - "Right": 3 + "Right": 5 } }, - "query": "insert into users(user_id, display_name, password) values (?, ?, ?) returning id, user_id, display_name, password" + "query": "update users set uuid = ?, user_id = ?, display_name = ?, password = ?\n where uuid = ?\n returning uuid as 'uuid: Uuid', user_id as 'user_id: UserId', display_name, password" }, - "cd7c9d3ae02f9553d4b7c4e8d98f63c6f556ac230a339683fe25025592812033": { + "f57c49e5390c81f971851ff9ab35242a472b9efbb1ffa658de9b102188769750": { "describe": { "columns": [ { - "name": "id", + "name": "uuid: Uuid", "ordinal": 0, - "type_info": "Int64" + "type_info": "Text" }, { - "name": "device_id", + "name": "user_id: UserId", "ordinal": 1, - "type_info": "Int64" + "type_info": "Text" }, { - "name": "value", + "name": "display_name", "ordinal": 2, "type_info": "Text" + }, + { + "name": "password", + "ordinal": 3, + "type_info": "Text" } ], "nullable": [ + false, false, false, false ], "parameters": { - "Right": 2 + "Right": 1 } }, - "query": "insert into sessions(device_id, value) values(?, ?) returning id, device_id, value" + "query": "select uuid as 'uuid: Uuid', user_id as 'user_id: UserId', display_name, password\n from users where user_id = ?" } } \ No newline at end of file diff --git a/src/api/client_server/auth.rs b/src/api/client_server/auth.rs index 3ff81b0..a47269e 100644 --- a/src/api/client_server/auth.rs +++ b/src/api/client_server/auth.rs @@ -10,7 +10,14 @@ use axum::{ use rand_core::OsRng; use sqlx::SqlitePool; -use crate::{responses::{registration::RegistrationResponse, authentication::{AuthenticationResponse, AuthenticationSuccess}}, api::client_server::errors::authentication_error::AuthenticationError}; +use crate::{ + api::client_server::errors::authentication_error::AuthenticationError, + models::sessions::Session, + responses::{ + authentication::{AuthenticationResponse, AuthenticationSuccess}, + registration::RegistrationResponse, + }, +}; use crate::{ models::devices::Device, responses::{flow::Flows, registration::RegistrationSuccess}, @@ -41,10 +48,11 @@ async fn get_login() -> Result, ApiError> { async fn post_login( Extension(config): Extension>, Extension(db): Extension, - Json(body): Json + Json(body): Json, ) -> Result, ApiError> { - let user = UserId::new("name", "server_name").ok().ok_or(AuthenticationError::InvalidUserId)?; - todo!("Flesh this out more"); + let user = UserId::new("name", "server_name") + .ok() + .ok_or(AuthenticationError::InvalidUserId)?; let resp = AuthenticationSuccess::new("", "", &user); Ok(Json(AuthenticationResponse::Success(resp))) @@ -92,22 +100,29 @@ async fn post_register( None => "Random displayname", }; - let user = - User::create(&db, &user_id, &user_id.to_string(), auth_data.password()).await?; - let device = Device::create(&db, &user, "test", display_name).await?; + let user = User::new(&user_id, &user_id.to_string(), auth_data.password())? + .create(&db) + .await?; + + let device = Device::new(&user, "test", display_name)? + .create(&db) + .await?; (user, device) } }; if body.inhibit_login().unwrap_or(false) { - let resp = RegistrationSuccess::new(None, device.device_id(), user.user_id()); + let resp = RegistrationSuccess::new(None, device.device_id(), &user.user_id().to_string()); Ok(Json(RegistrationResponse::Success(resp))) } else { - let session = device.create_session(&db).await?; - let resp = - RegistrationSuccess::new(Some(session.value()), device.device_id(), user.user_id()); + let session = Session::new(&device)?.create(&db).await?; + let resp = RegistrationSuccess::new( + Some(session.key()), + device.device_id(), + &user.user_id().to_string(), + ); Ok(Json(RegistrationResponse::Success(resp))) } diff --git a/src/api/client_server/errors/api_error.rs b/src/api/client_server/errors/api_error.rs index 09a63ca..4c10970 100644 --- a/src/api/client_server/errors/api_error.rs +++ b/src/api/client_server/errors/api_error.rs @@ -38,8 +38,9 @@ pub enum ApiError { impl From for ApiError { fn from(err: anyhow::Error) -> Self { map_err!(err, - sqlx::Error => ApiError::DBError, - RegistrationError => ApiError::RegistrationError + sqlx::Error => ApiError::DBError, + RegistrationError => ApiError::RegistrationError, + AuthenticationError => ApiError::AuthenticationError ); ApiError::Generic(err) diff --git a/src/api/client_server/errors/authentication_error.rs b/src/api/client_server/errors/authentication_error.rs index 470469d..3b7927f 100644 --- a/src/api/client_server/errors/authentication_error.rs +++ b/src/api/client_server/errors/authentication_error.rs @@ -30,12 +30,22 @@ impl IntoResponse for AuthenticationError { .into_response(), Self::Forbidden => ( StatusCode::FORBIDDEN, - Json(ErrorResponse::new(ErrorCode::Forbidden, &self.to_string(), None)), - ).into_response(), + Json(ErrorResponse::new( + ErrorCode::Forbidden, + &self.to_string(), + None, + )), + ) + .into_response(), Self::UserDeactivated => ( StatusCode::FORBIDDEN, - Json(ErrorResponse::new(ErrorCode::UserDeactivated, &self.to_string(), None)), - ).into_response(), + Json(ErrorResponse::new( + ErrorCode::UserDeactivated, + &self.to_string(), + None, + )), + ) + .into_response(), } } } diff --git a/src/api/client_server/errors/mod.rs b/src/api/client_server/errors/mod.rs index 3c74ea9..46bac93 100644 --- a/src/api/client_server/errors/mod.rs +++ b/src/api/client_server/errors/mod.rs @@ -20,4 +20,4 @@ impl ErrorResponse { retry_after_ms, } } -} \ No newline at end of file +} diff --git a/src/models/devices.rs b/src/models/devices.rs index facfdfc..079dcaa 100644 --- a/src/models/devices.rs +++ b/src/models/devices.rs @@ -1,50 +1,50 @@ use sqlx::SqlitePool; +use crate::types::uuid::Uuid; + use super::{sessions::Session, users::User}; pub struct Device { - id: i64, - user_id: i64, + uuid: Uuid, + user_uuid: Uuid, device_id: String, display_name: String, } impl Device { - pub async fn create( - conn: &SqlitePool, - user: &User, - device_id: &str, - display_name: &str, - ) -> anyhow::Result { - let user_id = user.id(); - Ok(sqlx::query_as!(Self, "insert into devices(user_id, device_id, display_name) values(?, ?, ?) returning id, user_id, device_id, display_name", user_id, device_id, display_name).fetch_one(conn).await?) + pub fn new(user: &User, device_id: &str, display_name: &str) -> anyhow::Result { + Ok(Self { + uuid: uuid::Uuid::new_v4().into(), + user_uuid: user.uuid().clone(), + device_id: device_id.to_owned(), + display_name: display_name.to_owned(), + }) } - pub async fn by_user(conn: &SqlitePool, user: &User) -> anyhow::Result { - let user_id = user.id(); + pub async fn create(&self, conn: &SqlitePool) -> anyhow::Result { Ok(sqlx::query_as!( Self, - "select id, user_id, device_id, display_name from devices where user_id = ?", - user_id + "insert into devices(uuid, user_uuid, device_id, display_name) + values(?, ?, ?, ?) + returning uuid as 'uuid: Uuid', user_uuid as 'user_uuid: Uuid', device_id, display_name", + self.uuid, + self.user_uuid, + self.device_id, + self.display_name) + .fetch_one(conn).await? ) - .fetch_one(conn) - .await?) - } - - pub async fn create_session(&self, conn: &SqlitePool) -> anyhow::Result { - Ok(Session::create(conn, self, "random_session_id").await?) } /// Get the device's id. #[must_use] - pub fn id(&self) -> i64 { - self.id + pub fn uuid(&self) -> &Uuid { + &self.uuid } /// Get the device's user id. #[must_use] - pub fn user_id(&self) -> i64 { - self.user_id + pub fn user_uuid(&self) -> &Uuid { + &self.user_uuid } /// Get a reference to the device's device id. diff --git a/src/models/sessions.rs b/src/models/sessions.rs index 1e4c733..c1d0907 100644 --- a/src/models/sessions.rs +++ b/src/models/sessions.rs @@ -1,21 +1,33 @@ use sqlx::SqlitePool; +use crate::types::uuid::Uuid; + use super::devices::Device; pub struct Session { - id: i64, - device_id: i64, - value: String, + uuid: Uuid, + device_uuid: Uuid, + key: String, } impl Session { - pub async fn create(conn: &SqlitePool, device: &Device, value: &str) -> anyhow::Result { - let device_id = device.id(); + pub fn new(device: &Device) -> anyhow::Result { + Ok(Self { + uuid: uuid::Uuid::new_v4().into(), + device_uuid: device.uuid().clone(), + key: String::new(), + }) + } + + pub async fn create(&self, conn: &SqlitePool) -> anyhow::Result { Ok(sqlx::query_as!( Self, - "insert into sessions(device_id, value) values(?, ?) returning id, device_id, value", - device_id, - value + "insert into sessions(uuid, device_uuid, key) + values(?, ?, ?) + returning uuid as 'uuid: Uuid', device_uuid as 'device_uuid: Uuid', key", + self.uuid, + self.device_uuid, + self.key ) .fetch_one(conn) .await?) @@ -23,19 +35,19 @@ impl Session { /// Get the session's id. #[must_use] - pub fn id(&self) -> i64 { - self.id + pub fn uuid(&self) -> &Uuid { + &self.uuid } /// Get the session's device id. #[must_use] - pub fn device_id(&self) -> i64 { - self.device_id + pub fn device_uuid(&self) -> &Uuid { + &self.device_uuid } /// Get a reference to the session's value. #[must_use] - pub fn value(&self) -> &str { - self.value.as_ref() + pub fn key(&self) -> &str { + self.key.as_ref() } } diff --git a/src/models/users.rs b/src/models/users.rs index d1680e0..8e2020c 100644 --- a/src/models/users.rs +++ b/src/models/users.rs @@ -1,17 +1,33 @@ +use crate::types::uuid::Uuid; use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; use rand_core::OsRng; -use sqlx::SqlitePool; +use sqlx::{encode::IsNull, sqlite::SqliteTypeInfo, FromRow, Sqlite, SqlitePool}; use crate::types::user_id::UserId; pub struct User { - id: i64, - user_id: String, + uuid: Uuid, + user_id: UserId, display_name: String, password: String, } impl User { + pub fn new(user_id: &UserId, display_name: &str, password: &str) -> anyhow::Result { + let argon2 = Argon2::default(); + let salt = SaltString::generate(OsRng); + let password = argon2 + .hash_password(password.as_bytes(), &salt)? + .to_string(); + + Ok(Self { + uuid: uuid::Uuid::new_v4().into(), + user_id: user_id.clone(), + display_name: display_name.to_owned(), + password, + }) + } + pub async fn exists(conn: &SqlitePool, user_id: &UserId) -> anyhow::Result { Ok( sqlx::query!("select user_id from users where user_id = ?", user_id) @@ -21,25 +37,42 @@ impl User { ) } - pub async fn create( - conn: &SqlitePool, - user_id: &UserId, - display_name: &str, - password: &str, - ) -> anyhow::Result { - let salt = SaltString::generate(OsRng); - let argon2 = Argon2::default(); - let pw_hash = argon2 - .hash_password(password.as_bytes(), &salt)? - .to_string(); - - Ok(sqlx::query_as!(Self, "insert into users(user_id, display_name, password) values (?, ?, ?) returning id, user_id, display_name, password", user_id, display_name, pw_hash).fetch_one(conn).await?) + pub async fn create(&self, conn: &SqlitePool) -> anyhow::Result { + Ok(sqlx::query_as!( + Self, + "insert into users(uuid, user_id, display_name, password) + values (?, ?, ?, ?) + returning uuid as 'uuid: Uuid', user_id as 'user_id: UserId', display_name, password", + self.uuid, + self.user_id, + self.display_name, + self.password + ) + .fetch_one(conn) + .await?) + } + + pub async fn update(&self, conn: &SqlitePool) -> anyhow::Result { + Ok(sqlx::query_as!( + Self, + "update users set uuid = ?, user_id = ?, display_name = ?, password = ? + where uuid = ? + returning uuid as 'uuid: Uuid', user_id as 'user_id: UserId', display_name, password", + self.uuid, + self.user_id, + self.display_name, + self.password, + self.uuid + ) + .fetch_one(conn) + .await?) } pub async fn by_user_id(conn: &SqlitePool, user_id: &UserId) -> anyhow::Result { Ok(sqlx::query_as!( Self, - "select id, user_id, display_name, password from users where user_id = ?", + "select uuid as 'uuid: Uuid', user_id as 'user_id: UserId', display_name, password + from users where user_id = ?", user_id ) .fetch_one(conn) @@ -48,14 +81,14 @@ impl User { /// Get the user's id. #[must_use] - pub fn id(&self) -> i64 { - self.id + pub fn uuid(&self) -> &Uuid { + &self.uuid } /// Get a reference to the user's user id. #[must_use] - pub fn user_id(&self) -> &str { - self.user_id.as_ref() + pub fn user_id(&self) -> &UserId { + &self.user_id } /// Get a reference to the user's password. diff --git a/src/requests/mod.rs b/src/requests/mod.rs index 9b90583..038a1c7 100644 --- a/src/requests/mod.rs +++ b/src/requests/mod.rs @@ -1 +1 @@ -pub mod registration; \ No newline at end of file +pub mod registration; diff --git a/src/responses/authentication.rs b/src/responses/authentication.rs index 63fbf68..68493bf 100644 --- a/src/responses/authentication.rs +++ b/src/responses/authentication.rs @@ -5,7 +5,7 @@ use crate::types::user_id::UserId; #[derive(Debug, serde::Serialize)] #[serde(untagged)] pub enum AuthenticationResponse { - Success(AuthenticationSuccess) + Success(AuthenticationSuccess), } #[derive(Debug, serde::Serialize)] @@ -23,4 +23,4 @@ impl AuthenticationSuccess { user_id: user_id.to_string(), } } -} \ No newline at end of file +} diff --git a/src/responses/mod.rs b/src/responses/mod.rs index 7c34e93..dbee683 100644 --- a/src/responses/mod.rs +++ b/src/responses/mod.rs @@ -1,5 +1,5 @@ +pub mod authentication; pub mod flow; pub mod registration; pub mod username_available; pub mod versions; -pub mod authentication; \ No newline at end of file diff --git a/src/types/error_code.rs b/src/types/error_code.rs index 650f72a..5b48e5d 100644 --- a/src/types/error_code.rs +++ b/src/types/error_code.rs @@ -12,7 +12,7 @@ pub enum ErrorCode { UserInUse, InvalidUsername, Exclusive, - UserDeactivated + UserDeactivated, } impl serde::Serialize for ErrorCode { diff --git a/src/types/mod.rs b/src/types/mod.rs index 4859701..c9b765b 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -5,3 +5,4 @@ pub mod identifier; pub mod identifier_type; pub mod user_id; pub mod user_interactive_authorization; +pub mod uuid; diff --git a/src/types/user_id.rs b/src/types/user_id.rs index 6f724f1..8bda56a 100644 --- a/src/types/user_id.rs +++ b/src/types/user_id.rs @@ -1,10 +1,40 @@ use std::fmt::Display; -#[derive(sqlx::Type)] -#[sqlx(transparent)] +use sqlx::{encode::IsNull, Sqlite}; + +#[derive(Clone)] #[repr(transparent)] pub struct UserId(String); +impl sqlx::Type for UserId { + fn type_info() -> ::TypeInfo { + <&str as sqlx::Type>::type_info() + } +} + +impl<'e> sqlx::Encode<'e, Sqlite> for UserId { + fn encode_by_ref( + &self, + buf: &mut >::ArgumentBuffer, + ) -> sqlx::encode::IsNull { + buf.push(sqlx::sqlite::SqliteArgumentValue::Text( + self.0.to_string().into(), + )); + + IsNull::No + } +} + +impl<'d> sqlx::Decode<'d, Sqlite> for UserId { + fn decode( + value: >::ValueRef, + ) -> Result { + let value = >::decode(value)?; + + Ok(UserId(value)) + } +} + impl UserId { pub fn new(name: &str, server_name: &str) -> anyhow::Result { let user_id = Self(format!("@{name}:{server_name}")); diff --git a/src/types/uuid.rs b/src/types/uuid.rs new file mode 100644 index 0000000..36586ff --- /dev/null +++ b/src/types/uuid.rs @@ -0,0 +1,45 @@ +use sqlx::{encode::IsNull, Sqlite, Type}; + +#[derive(Debug, Clone)] +pub struct Uuid(pub uuid::Uuid); + +impl Uuid { + pub fn new_v4() -> Self { + Uuid(uuid::Uuid::new_v4()) + } +} + +impl Type for Uuid { + fn type_info() -> ::TypeInfo { + <&str as Type>::type_info() + } +} + +impl<'e> sqlx::Encode<'e, Sqlite> for Uuid { + fn encode_by_ref( + &self, + buf: &mut >::ArgumentBuffer, + ) -> sqlx::encode::IsNull { + buf.push(sqlx::sqlite::SqliteArgumentValue::Text( + self.0.to_string().into(), + )); + + IsNull::No + } +} + +impl<'d> sqlx::Decode<'d, Sqlite> for Uuid { + fn decode( + value: >::ValueRef, + ) -> Result { + let value = <&str as sqlx::Decode>::decode(value)?; + + Ok(Uuid(uuid::Uuid::parse_str(value)?)) + } +} + +impl From for Uuid { + fn from(uuid: uuid::Uuid) -> Self { + Uuid(uuid) + } +}