diff --git a/.sqlx/query-3f9c634ce803bbdf38b2079ede3ba7b006b839d916154a924ab23ee6afad4a1c.json b/.sqlx/query-3f9c634ce803bbdf38b2079ede3ba7b006b839d916154a924ab23ee6afad4a1c.json deleted file mode 100644 index e2efbca..0000000 --- a/.sqlx/query-3f9c634ce803bbdf38b2079ede3ba7b006b839d916154a924ab23ee6afad4a1c.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM repositories WHERE owner_user = $1 AND name = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "3f9c634ce803bbdf38b2079ede3ba7b006b839d916154a924ab23ee6afad4a1c" -} diff --git a/.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json b/.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json deleted file mode 100644 index 5bcf2df..0000000 --- a/.sqlx/query-606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM users WHERE username = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "display_name", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "image_url", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "bio", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "password", - "type_info": "Text" - }, - { - "ordinal": 6, - "name": "public_key", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "enc_private_key", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - true, - false, - true, - true, - false, - false, - false - ] - }, - "hash": "606364c79e0990deb07dfbe6c32b3d302d083ec5333f3a5ce04113c38a041100" -} diff --git a/.sqlx/query-80ab1ccbe26784f7c847729edfb6470f223e608c2bee2681230a3011603e403f.json b/.sqlx/query-80ab1ccbe26784f7c847729edfb6470f223e608c2bee2681230a3011603e403f.json deleted file mode 100644 index 7e4538d..0000000 --- a/.sqlx/query-80ab1ccbe26784f7c847729edfb6470f223e608c2bee2681230a3011603e403f.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO users VALUES ($1, null, $2, null, null, $3, $4, $5) returning *", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "display_name", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "image_url", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "bio", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "password", - "type_info": "Text" - }, - { - "ordinal": 6, - "name": "public_key", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "enc_private_key", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Text", - "Text", - "Text" - ] - }, - "nullable": [ - false, - true, - false, - true, - true, - false, - false, - false - ] - }, - "hash": "80ab1ccbe26784f7c847729edfb6470f223e608c2bee2681230a3011603e403f" -} diff --git a/.sqlx/query-b56ee50882e7eae84981cbfc91b5a53b1150d57a2da8fad5eebb9b1a88d7ccab.json b/.sqlx/query-b56ee50882e7eae84981cbfc91b5a53b1150d57a2da8fad5eebb9b1a88d7ccab.json deleted file mode 100644 index 9832255..0000000 --- a/.sqlx/query-b56ee50882e7eae84981cbfc91b5a53b1150d57a2da8fad5eebb9b1a88d7ccab.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "b56ee50882e7eae84981cbfc91b5a53b1150d57a2da8fad5eebb9b1a88d7ccab" -} diff --git a/.sqlx/query-bbd782d2ea06a991db7e2cddbab3796cf695dbe9e645d969598d9de8153f80b6.json b/.sqlx/query-bbd782d2ea06a991db7e2cddbab3796cf695dbe9e645d969598d9de8153f80b6.json deleted file mode 100644 index 2c92c0e..0000000 --- a/.sqlx/query-bbd782d2ea06a991db7e2cddbab3796cf695dbe9e645d969598d9de8153f80b6.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT owner_user, name, description, visibility as \"visibility: _\", default_branch FROM repositories WHERE owner_user = $1 AND name = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "owner_user", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "description", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "visibility: _", - "type_info": { - "Custom": { - "name": "visibility", - "kind": { - "Enum": [ - "public", - "unlisted", - "private" - ] - } - } - } - }, - { - "ordinal": 4, - "name": "default_branch", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - false, - false, - true, - false, - false - ] - }, - "hash": "bbd782d2ea06a991db7e2cddbab3796cf695dbe9e645d969598d9de8153f80b6" -} diff --git a/.sqlx/query-ca33818a5e95965aa6330d004be4de2d1d5786541a64fbe2afb7b8254ffbfbf0.json b/.sqlx/query-ca33818a5e95965aa6330d004be4de2d1d5786541a64fbe2afb7b8254ffbfbf0.json deleted file mode 100644 index 9cc89e3..0000000 --- a/.sqlx/query-ca33818a5e95965aa6330d004be4de2d1d5786541a64fbe2afb7b8254ffbfbf0.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT visibility as \"visibility: _\", owner_user, name, description, default_branch FROM repositories WHERE owner_user = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "visibility: _", - "type_info": { - "Custom": { - "name": "visibility", - "kind": { - "Enum": [ - "public", - "unlisted", - "private" - ] - } - } - } - }, - { - "ordinal": 1, - "name": "owner_user", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "description", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "default_branch", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false, - true, - false - ] - }, - "hash": "ca33818a5e95965aa6330d004be4de2d1d5786541a64fbe2afb7b8254ffbfbf0" -} diff --git a/.sqlx/query-df2100447de87e6200a2d9af6b1adacf9892d51abe3ea588c975cc37c3a6825f.json b/.sqlx/query-df2100447de87e6200a2d9af6b1adacf9892d51abe3ea588c975cc37c3a6825f.json deleted file mode 100644 index 747040f..0000000 --- a/.sqlx/query-df2100447de87e6200a2d9af6b1adacf9892d51abe3ea588c975cc37c3a6825f.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO repositories VALUES ($1, $2, $3, $4, $5) RETURNING owner_user, name, description, visibility as \"visibility: _\", default_branch", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "owner_user", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "description", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "visibility: _", - "type_info": { - "Custom": { - "name": "visibility", - "kind": { - "Enum": [ - "public", - "unlisted", - "private" - ] - } - } - } - }, - { - "ordinal": 4, - "name": "default_branch", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Text", - { - "Custom": { - "name": "visibility", - "kind": { - "Enum": [ - "public", - "unlisted", - "private" - ] - } - } - }, - "Text" - ] - }, - "nullable": [ - false, - false, - true, - false, - false - ] - }, - "hash": "df2100447de87e6200a2d9af6b1adacf9892d51abe3ea588c975cc37c3a6825f" -} diff --git a/.sqlx/query-df25d6977ebaeea72c5f1648100fbbd30365d0e20194a80fd291f979e73d2e7c.json b/.sqlx/query-df25d6977ebaeea72c5f1648100fbbd30365d0e20194a80fd291f979e73d2e7c.json deleted file mode 100644 index 6fe8469..0000000 --- a/.sqlx/query-df25d6977ebaeea72c5f1648100fbbd30365d0e20194a80fd291f979e73d2e7c.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM user_settings WHERE username = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "value", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "df25d6977ebaeea72c5f1648100fbbd30365d0e20194a80fd291f979e73d2e7c" -} diff --git a/Cargo.lock b/Cargo.lock index 506f567..9dd9d2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -350,6 +350,41 @@ dependencies = [ ] [[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.29", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.29", +] + +[[package]] name = "data-encoding" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -390,6 +425,9 @@ name = "deranged" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +dependencies = [ + "serde", +] [[package]] name = "digest" @@ -729,6 +767,7 @@ dependencies = [ "semver", "serde", "serde_json", + "serde_with", "sqlx", "thiserror", "toml", @@ -922,6 +961,12 @@ dependencies = [ ] [[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] name = "idna" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -939,6 +984,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] @@ -949,6 +995,7 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", "hashbrown 0.14.0", + "serde", ] [[package]] @@ -1799,6 +1846,35 @@ dependencies = [ ] [[package]] +name = "serde_with" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" +dependencies = [ + "base64 0.21.3", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.0.0", + "serde", + "serde_json", + "serde_with_macros", + "time 0.3.28", +] + +[[package]] +name = "serde_with_macros" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] name = "sha1" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2142,6 +2218,12 @@ dependencies = [ ] [[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] name = "subtle" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/giterated-daemon/.sqlx/query-4707bb69d91fa6df545148c281bae53365c53011bea92e75996f030397ae6b07.json b/giterated-daemon/.sqlx/query-4707bb69d91fa6df545148c281bae53365c53011bea92e75996f030397ae6b07.json deleted file mode 100644 index 1067301..0000000 --- a/giterated-daemon/.sqlx/query-4707bb69d91fa6df545148c281bae53365c53011bea92e75996f030397ae6b07.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO repository_settings VALUES ($1, $2, $3) ON CONFLICT (repository, name) DO UPDATE SET value = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "4707bb69d91fa6df545148c281bae53365c53011bea92e75996f030397ae6b07" -} diff --git a/giterated-daemon/.sqlx/query-b56ee50882e7eae84981cbfc91b5a53b1150d57a2da8fad5eebb9b1a88d7ccab.json b/giterated-daemon/.sqlx/query-b56ee50882e7eae84981cbfc91b5a53b1150d57a2da8fad5eebb9b1a88d7ccab.json deleted file mode 100644 index 9832255..0000000 --- a/giterated-daemon/.sqlx/query-b56ee50882e7eae84981cbfc91b5a53b1150d57a2da8fad5eebb9b1a88d7ccab.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "b56ee50882e7eae84981cbfc91b5a53b1150d57a2da8fad5eebb9b1a88d7ccab" -} diff --git a/giterated-daemon/.sqlx/query-d20bc04a9a3883bc2dbd9068d1a77264fff64d9f220f95b78f35e4f371cce160.json b/giterated-daemon/.sqlx/query-d20bc04a9a3883bc2dbd9068d1a77264fff64d9f220f95b78f35e4f371cce160.json deleted file mode 100644 index cd3b660..0000000 --- a/giterated-daemon/.sqlx/query-d20bc04a9a3883bc2dbd9068d1a77264fff64d9f220f95b78f35e4f371cce160.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM repository_settings WHERE repository = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "repository", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "value", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "d20bc04a9a3883bc2dbd9068d1a77264fff64d9f220f95b78f35e4f371cce160" -} diff --git a/giterated-daemon/.sqlx/query-df25d6977ebaeea72c5f1648100fbbd30365d0e20194a80fd291f979e73d2e7c.json b/giterated-daemon/.sqlx/query-df25d6977ebaeea72c5f1648100fbbd30365d0e20194a80fd291f979e73d2e7c.json deleted file mode 100644 index 6fe8469..0000000 --- a/giterated-daemon/.sqlx/query-df25d6977ebaeea72c5f1648100fbbd30365d0e20194a80fd291f979e73d2e7c.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM user_settings WHERE username = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "value", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "df25d6977ebaeea72c5f1648100fbbd30365d0e20194a80fd291f979e73d2e7c" -} diff --git a/giterated-daemon/migrations/20230913232601_repository_metadata.sql b/giterated-daemon/migrations/20230913232601_repository_metadata.sql new file mode 100644 index 0000000..ca9e8dd --- /dev/null +++ b/giterated-daemon/migrations/20230913232601_repository_metadata.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS repository_metadata +( + repository TEXT NOT NULL, + name TEXT NOT NULL, + value TEXT NOT NULL +); + +CREATE UNIQUE INDEX unique_per_repository_metadata ON repository_metadata (repository, name); \ No newline at end of file diff --git a/giterated-daemon/migrations/20230913232625_user_metadata.sql b/giterated-daemon/migrations/20230913232625_user_metadata.sql new file mode 100644 index 0000000..f3d2e83 --- /dev/null +++ b/giterated-daemon/migrations/20230913232625_user_metadata.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS user_metadata +( + username TEXT NOT NULL, + name TEXT NOT NULL, + value TEXT NOT NULL +); + +CREATE UNIQUE INDEX unique_per_user_metadata ON user_metadata (username, name); + +CREATE TABLE IF NOT EXISTS user_settings +( + username TEXT NOT NULL, + name TEXT NOT NULL, + value TEXT NOT NULL +); + +CREATE UNIQUE INDEX unique_per_user_settings ON user_settings (username, name); \ No newline at end of file diff --git a/giterated-daemon/src/backend/git.rs b/giterated-daemon/src/backend/git.rs index 3ae0b3a..2010954 100644 --- a/giterated-daemon/src/backend/git.rs +++ b/giterated-daemon/src/backend/git.rs @@ -9,7 +9,8 @@ use giterated_models::{ Commit, IssueLabel, Repository, RepositoryIssue, RepositorySummary, RepositoryTreeEntry, RepositoryVisibility, }, - user::User, + settings::{AnySetting, RepositoryDescription, RepositoryVisibilitySetting, Setting}, + user::{User, UserParseError}, }, operation::{ instance::RepositoryCreateRequest, @@ -18,12 +19,18 @@ use giterated_models::{ RepositoryIssuesCountRequest, RepositoryIssuesRequest, }, }, + values::AnyValue, }; +use serde_json::Value; use sqlx::{Either, PgPool}; -use std::path::{Path, PathBuf}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; use thiserror::Error; +use tokio::sync::Mutex; -use super::{IssuesBackend, RepositoryBackend}; +use super::{IssuesBackend, MetadataBackend, RepositoryBackend}; // TODO: Handle this //region database structures @@ -98,6 +105,7 @@ pub struct GitBackend { pub pg_pool: PgPool, pub repository_folder: String, pub instance: Instance, + pub settings_provider: Arc>, } impl GitBackend { @@ -105,11 +113,13 @@ impl GitBackend { pg_pool: &PgPool, repository_folder: &str, instance: impl ToOwned, + settings_provider: Arc>, ) -> Self { Self { pg_pool: pg_pool.clone(), repository_folder: repository_folder.to_string(), instance: instance.to_owned(), + settings_provider, } } @@ -286,6 +296,52 @@ impl RepositoryBackend for GitBackend { } } + async fn get_value( + &mut self, + repository: &Repository, + name: &str, + ) -> Result, Error> { + Ok(match name { + "description" => unsafe { + AnyValue::from_raw( + self.get_setting(repository, RepositoryDescription::name()) + .await? + .0, + ) + }, + "visibility" => unsafe { + AnyValue::from_raw( + self.get_setting(repository, RepositoryVisibilitySetting::name()) + .await? + .0, + ) + }, + _ => { + return Err(UserParseError.into()); + } + }) + } + async fn get_setting( + &mut self, + repository: &Repository, + name: &str, + ) -> Result { + let mut provider = self.settings_provider.lock().await; + + Ok(provider.repository_get(repository, name).await?) + } + async fn write_setting( + &mut self, + repository: &Repository, + name: &str, + setting: &Value, + ) -> Result<(), Error> { + let mut provider = self.settings_provider.lock().await; + + provider + .repository_write(repository, name, AnySetting(setting.clone())) + .await + } // async fn repository_info( // &mut self, // requester: Option<&User>, @@ -519,3 +575,10 @@ impl IssuesBackend for GitBackend { todo!() } } + +#[derive(Debug, sqlx::FromRow)] +struct RepositoryMetadata { + pub repository: String, + pub name: String, + pub value: String, +} diff --git a/giterated-daemon/src/backend/mod.rs b/giterated-daemon/src/backend/mod.rs index 5ef2dc1..ef3ffca 100644 --- a/giterated-daemon/src/backend/mod.rs +++ b/giterated-daemon/src/backend/mod.rs @@ -46,6 +46,18 @@ pub trait RepositoryBackend { requester: Option<&User>, user: &User, ) -> Result, Error>; + async fn get_value( + &mut self, + user: &Repository, + name: &str, + ) -> Result, Error>; + async fn get_setting(&mut self, user: &Repository, name: &str) -> Result; + async fn write_setting( + &mut self, + user: &Repository, + name: &str, + setting: &Value, + ) -> Result<(), Error>; async fn exists(&mut self, repository: &Repository) -> Result; } @@ -95,18 +107,23 @@ pub trait UserBackend: AuthBackend { } #[async_trait::async_trait] -pub trait SettingsBackend: Send + Sync { - async fn user_get(&mut self, user: &User) -> Result, Error>; - async fn user_write(&mut self, user: &User, settings: &[(String, String)]) - -> Result<(), Error>; - +pub trait MetadataBackend { + async fn user_get(&mut self, user: &User, name: &str) -> Result; + async fn user_write( + &mut self, + user: &User, + name: &str, + setting: AnySetting, + ) -> Result<(), Error>; async fn repository_get( &mut self, repository: &Repository, - ) -> Result, Error>; + name: &str, + ) -> Result; async fn repository_write( &mut self, repository: &Repository, - settings: &[(String, String)], + name: &str, + setting: AnySetting, ) -> Result<(), Error>; } diff --git a/giterated-daemon/src/backend/settings.rs b/giterated-daemon/src/backend/settings.rs index 05b4b5f..1e03bdb 100644 --- a/giterated-daemon/src/backend/settings.rs +++ b/giterated-daemon/src/backend/settings.rs @@ -1,98 +1,84 @@ use anyhow::Error; -use futures_util::StreamExt; -use giterated_models::model::{repository::Repository, user::User}; -use serde_json::Value; -use sqlx::{Either, PgPool}; -use super::SettingsBackend; +use giterated_models::model::{repository::Repository, settings::AnySetting, user::User}; + +use sqlx::PgPool; + +use super::MetadataBackend; pub struct DatabaseSettings { pub pg_pool: PgPool, } #[async_trait::async_trait] -impl SettingsBackend for DatabaseSettings { - async fn user_get(&mut self, user: &User) -> Result, Error> { - let settings = sqlx::query_as!( - UserSettingRow, - r#"SELECT * FROM user_settings WHERE username = $1"#, - user.username - ) - .fetch_many(&self.pg_pool) - .filter_map(|result| async move { - if let Ok(Either::Right(row)) = result { - Some(row) - } else { - None - } - }) - .filter_map(|row| async move { - if let Ok(value) = serde_json::from_str(&row.value) { - Some((row.name, value)) - } else { - None - } - }) - .collect::>() - .await; - - Ok(settings) +impl MetadataBackend for DatabaseSettings { + async fn user_get(&mut self, _user: &User, _name: &str) -> Result { + todo!() } async fn user_write( &mut self, - user: &User, - settings: &[(String, String)], + _user: &User, + _name: &str, + _value: AnySetting, ) -> Result<(), Error> { - for (name, value) in settings { - sqlx::query!("INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3", - user.username, name, value) - .execute(&self.pg_pool).await?; - } + // for (name, value) in settings { + // sqlx::query!("INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3", + // user.username, name, value) + // .execute(&self.pg_pool).await?; + // } - Ok(()) + // Ok(()) + + todo!() } async fn repository_get( &mut self, - repository: &Repository, - ) -> Result, Error> { - let settings = sqlx::query_as!( - RepositorySettingRow, - r#"SELECT * FROM repository_settings WHERE repository = $1"#, - repository.to_string() - ) - .fetch_many(&self.pg_pool) - .filter_map(|result| async move { - if let Ok(Either::Right(row)) = result { - Some(row) - } else { - None - } - }) - .filter_map(|row| async move { - if let Ok(value) = serde_json::from_str(&row.value) { - Some((row.name, value)) - } else { - None - } - }) - .collect::>() - .await; + _repository: &Repository, + _name: &str, + ) -> Result { + // let settings = sqlx::query_as!( + // RepositorySettingRow, + // r#"SELECT * FROM repository_settings WHERE repository = $1"#, + // repository.to_string() + // ) + // .fetch_many(&self.pg_pool) + // .filter_map(|result| async move { + // if let Ok(Either::Right(row)) = result { + // Some(row) + // } else { + // None + // } + // }) + // .filter_map(|row| async move { + // if let Ok(value) = serde_json::from_str(&row.value) { + // Some((row.name, value)) + // } else { + // None + // } + // }) + // .collect::>() + // .await; + + // Ok(settings) - Ok(settings) + todo!() } async fn repository_write( &mut self, - repository: &Repository, - settings: &[(String, String)], + _repository: &Repository, + _name: &str, + _value: AnySetting, ) -> Result<(), Error> { - for (name, value) in settings { - sqlx::query!("INSERT INTO repository_settings VALUES ($1, $2, $3) ON CONFLICT (repository, name) DO UPDATE SET value = $3", - repository.to_string(), name, value) - .execute(&self.pg_pool).await?; - } + // for (name, value) in settings { + // sqlx::query!("INSERT INTO repository_settings VALUES ($1, $2, $3) ON CONFLICT (repository, name) DO UPDATE SET value = $3", + // repository.to_string(), name, value) + // .execute(&self.pg_pool).await?; + // } + + // Ok(()) - Ok(()) + todo!() } } diff --git a/giterated-daemon/src/backend/user.rs b/giterated-daemon/src/backend/user.rs index 87afe2c..81de57f 100644 --- a/giterated-daemon/src/backend/user.rs +++ b/giterated-daemon/src/backend/user.rs @@ -7,8 +7,10 @@ use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, Pa use base64::{engine::general_purpose::STANDARD, Engine as _}; use giterated_models::{ model::{ - authenticated::UserAuthenticationToken, instance::Instance, settings::AnySetting, - user::User, + authenticated::UserAuthenticationToken, + instance::Instance, + settings::{AnySetting, Setting, UserBio, UserDisplayName}, + user::{User, UserParseError}, }, operation::instance::{AuthenticationTokenRequest, RegisterAccountRequest}, values::AnyValue, @@ -26,13 +28,13 @@ use tokio::sync::Mutex; use crate::authentication::AuthenticationTokenGranter; -use super::{AuthBackend, SettingsBackend, UserBackend}; +use super::{AuthBackend, MetadataBackend, UserBackend}; pub struct UserAuth { pub pg_pool: PgPool, pub this_instance: Instance, pub auth_granter: Arc>, - pub settings_provider: Arc>, + pub settings_provider: Arc>, } impl UserAuth { @@ -40,7 +42,7 @@ impl UserAuth { pool: PgPool, this_instance: &Instance, granter: Arc>, - settings_provider: Arc>, + settings_provider: Arc>, ) -> Self { Self { pg_pool: pool, @@ -53,20 +55,38 @@ impl UserAuth { #[async_trait::async_trait] impl UserBackend for UserAuth { - async fn get_value(&mut self, _user: &User, _name: &str) -> Result, Error> { - todo!() + async fn get_value(&mut self, user: &User, name: &str) -> Result, Error> { + Ok(match name { + "display_name" => unsafe { + AnyValue::from_raw(self.get_setting(user, UserDisplayName::name()).await?.0) + }, + "bio" => unsafe { + AnyValue::from_raw(self.get_setting(user, UserBio::name()).await?.0) + }, + _ => { + return Err(UserParseError.into()); + } + }) } - async fn get_setting(&mut self, _user: &User, _name: &str) -> Result { - todo!() + async fn get_setting(&mut self, user: &User, name: &str) -> Result { + let mut provider = self.settings_provider.lock().await; + + Ok(provider.user_get(user, name).await?) } + async fn write_setting( &mut self, - _user: &User, - _name: &str, - _setting: &Value, + user: &User, + name: &str, + setting: &Value, ) -> Result<(), Error> { - todo!() + let mut provider = self.settings_provider.lock().await; + + provider + .user_write(user, name, AnySetting(setting.clone())) + .await } + async fn exists(&mut self, user: &User) -> Result { Ok(sqlx::query_as!( UserRow, @@ -210,6 +230,13 @@ struct UserRow { pub enc_private_key: Vec, } +#[derive(Debug, sqlx::FromRow)] +struct UserValue { + pub username: String, + pub name: String, + pub value: String, +} + #[derive(Debug, thiserror::Error)] pub enum AuthenticationError { #[error("invalid password")] diff --git a/giterated-daemon/src/connection/wrapper.rs b/giterated-daemon/src/connection/wrapper.rs index 93d0c36..99f87fd 100644 --- a/giterated-daemon/src/connection/wrapper.rs +++ b/giterated-daemon/src/connection/wrapper.rs @@ -18,7 +18,7 @@ use toml::Table; use crate::{ authentication::AuthenticationTokenGranter, - backend::{RepositoryBackend, SettingsBackend, UserBackend}, + backend::{MetadataBackend, RepositoryBackend, UserBackend}, database_backend::Foobackend, federation::connections::InstanceConnections, keys::PublicKeyCache, @@ -32,7 +32,7 @@ pub async fn connection_wrapper( repository_backend: Arc>, user_backend: Arc>, auth_granter: Arc>, - settings_backend: Arc>, + settings_backend: Arc>, addr: SocketAddr, instance: impl ToOwned, instance_connections: Arc>, @@ -210,7 +210,7 @@ pub struct ConnectionState { pub repository_backend: Arc>, pub user_backend: Arc>, pub auth_granter: Arc>, - pub settings_backend: Arc>, + pub settings_backend: Arc>, pub addr: SocketAddr, pub instance: Instance, pub handshaked: Arc, diff --git a/giterated-daemon/src/database_backend/handler.rs b/giterated-daemon/src/database_backend/handler.rs index 2d4637b..8730e07 100644 --- a/giterated-daemon/src/database_backend/handler.rs +++ b/giterated-daemon/src/database_backend/handler.rs @@ -117,16 +117,6 @@ impl OperationWrapper { } } -fn test_operation( - _object: &User, - _operation: SetSetting, - _state: (), -) -> Pin>> + Send + 'static>> { - todo!() -} - -fn foo() {} - pub fn user_get_value( object: &User, operation: GetValue>, @@ -147,43 +137,98 @@ pub fn user_get_value( } pub fn user_get_setting( - _object: &User, - _operation: GetSetting, - _state: DatabaseBackend, + object: &User, + operation: GetSetting, + state: DatabaseBackend, ) -> BoxFuture<'static, Result>> { - todo!() + let object = object.clone(); + + async move { + let mut user_backend = state.user_backend.lock().await; + let value = user_backend + .get_setting(&object, &operation.setting_name) + .await + .map_err(|e| OperationError::Internal(e.to_string()))?; + + Ok(value) + } + .boxed() } pub fn user_set_setting( - _object: &User, - _operation: SetSetting, - _state: DatabaseBackend, + object: &User, + operation: SetSetting, + state: DatabaseBackend, ) -> BoxFuture<'static, Result<(), OperationError>> { - todo!() + let object = object.clone(); + + async move { + let mut user_backend = state.user_backend.lock().await; + let value = user_backend + .write_setting(&object, &operation.setting_name, &operation.value.0) + .await + .map_err(|e| OperationError::Internal(e.to_string()))?; + + Ok(value) + } + .boxed() } pub fn repository_get_value( - _object: &Repository, - _operation: GetValue>, - _state: DatabaseBackend, + object: &Repository, + operation: GetValue>, + state: DatabaseBackend, ) -> BoxFuture<'static, Result, OperationError>> { - todo!() + let object = object.clone(); + + async move { + let mut repository_backend = state.repository_backend.lock().await; + let value = repository_backend + .get_value(&object, &operation.value_name) + .await + .map_err(|e| OperationError::Internal(e.to_string()))?; + + Ok(value) + } + .boxed() } pub fn repository_get_setting( - _object: &Repository, - _operation: GetSetting, - _state: DatabaseBackend, + object: &Repository, + operation: GetSetting, + state: DatabaseBackend, ) -> BoxFuture<'static, Result>> { - todo!() + let object = object.clone(); + + async move { + let mut repository_backend = state.repository_backend.lock().await; + let value = repository_backend + .get_setting(&object, &operation.setting_name) + .await + .map_err(|e| OperationError::Internal(e.to_string()))?; + + Ok(value) + } + .boxed() } pub fn repository_set_setting( - _object: &Repository, - _operation: SetSetting, - _state: DatabaseBackend, + object: &Repository, + operation: SetSetting, + state: DatabaseBackend, ) -> BoxFuture<'static, Result<(), OperationError>> { - todo!() + let object = object.clone(); + + async move { + let mut repository_backend = state.repository_backend.lock().await; + let value = repository_backend + .write_setting(&object, &operation.setting_name, &operation.value.0) + .await + .map_err(|e| OperationError::Internal(e.to_string()))?; + + Ok(value) + } + .boxed() } pub struct OperationHandlers { @@ -225,14 +270,13 @@ impl OperationHandlers { ) -> Result, OperationError>> { if let Some(handler) = self.operations.get_mut(operation_name) { handler - .handle( - AnyObject(serde_json::to_string(object).unwrap()), - operation, - state, - ) + .handle(AnyObject(object.to_string()), operation, state) .await } else { - panic!() + Err(OperationError::Internal(format!( + "unknown operation: {}", + operation_name + ))) } } } diff --git a/giterated-daemon/src/database_backend/mod.rs b/giterated-daemon/src/database_backend/mod.rs index de41251..a49b169 100644 --- a/giterated-daemon/src/database_backend/mod.rs +++ b/giterated-daemon/src/database_backend/mod.rs @@ -2,7 +2,6 @@ pub mod handler; use std::{str::FromStr, sync::Arc}; -use futures_util::TryFutureExt; use giterated_models::{ error::OperationError, model::{instance::Instance, repository::Repository, user::User}, @@ -63,9 +62,8 @@ impl ObjectBackend for DatabaseBackend { ) -> Result> { let serialized = serde_json::to_value(operation).map_err(|e| OperationError::Internal(e.to_string()))?; - let object_name = object.to_string(); - - if let Ok(user) = User::from_str(&object_name) { + let object = object.to_string(); + if let Ok(user) = User::from_str(&object) { let mut handler = OperationHandlers::default(); handler @@ -93,7 +91,7 @@ impl ObjectBackend for DatabaseBackend { )), }, } - } else if let Ok(_repository) = Repository::from_str(&object_name) { + } else if let Ok(repository) = Repository::from_str(&object) { let mut handler = OperationHandlers::default(); handler @@ -101,9 +99,27 @@ impl ObjectBackend for DatabaseBackend { .insert(repository_get_setting) .insert(repository_set_setting); - // handler.handle(&repository, D::operation_name(), bincode::deserialize(&serialized).unwrap(), DatabaseBackendState).await; - todo!() - } else if Instance::from_str(&object_name).is_ok() { + match handler + .handle( + &repository, + D::operation_name(), + serde_json::from_value(serialized.clone()).unwrap(), + self.clone(), + ) + .await + { + Ok(result) => Ok(serde_json::from_slice(&result) + .map_err(|e| OperationError::Internal(e.to_string()))?), + Err(err) => match err { + OperationError::Internal(internal) => Err(OperationError::Internal(internal)), + OperationError::Unhandled => Err(OperationError::Unhandled), + OperationError::Operation(err) => Err(OperationError::Operation( + serde_json::from_slice(&err) + .map_err(|e| OperationError::Internal(e.to_string()))?, + )), + }, + } + } else if serde_json::from_str::(&object).is_ok() { Err(OperationError::Unhandled) } else { Err(OperationError::Unhandled) @@ -159,10 +175,16 @@ impl ObjectBackend for DatabaseBackend { } } +// These tests verify that the essential handling of the database backend is +// functional and correct. +#[cfg(test)] mod test { use std::{str::FromStr, sync::Arc}; use anyhow::Error; + use giterated_models::model::settings::UserDisplayName; + use giterated_models::operation::ObjectBackend; + use giterated_models::values::repository::Description; use giterated_models::{ model::{ authenticated::UserAuthenticationToken, @@ -176,10 +198,11 @@ mod test { AuthenticationTokenRequest, RegisterAccountRequest, RepositoryCreateRequest, }, repository::RepositoryFileInspectRequest, - GiteratedObjectValue, ObjectBackend, + GiteratedObjectValue, }, values::{user::DisplayName, AnyValue}, }; + use serde_json::Value; use tokio::sync::Mutex; @@ -199,7 +222,10 @@ mod test { .unwrap()) } async fn get_setting(&mut self, _user: &User, _name: &str) -> Result { - todo!() + Ok(serde_json::from_slice( + &serde_json::to_vec(&DisplayName(String::from("test"))).unwrap(), + ) + .unwrap()) } async fn write_setting( &mut self, @@ -207,7 +233,7 @@ mod test { _name: &str, _setting: &Value, ) -> Result<(), Error> { - todo!() + Ok(()) } async fn exists(&mut self, user: &User) -> Result { Ok(user == &User::from_str("test_user:test.giterated.dev").unwrap()) @@ -257,8 +283,43 @@ mod test { ) -> Result, Error> { todo!() } - async fn exists(&mut self, _repository: &Repository) -> Result { - todo!() + + async fn get_value( + &mut self, + _repository: &Repository, + _name: &str, + ) -> Result, Error> { + Ok(serde_json::from_slice( + &serde_json::to_vec(&Description(String::from("test"))).unwrap(), + ) + .unwrap()) + } + async fn get_setting( + &mut self, + _repository: &Repository, + _name: &str, + ) -> Result { + Ok(serde_json::from_slice( + &serde_json::to_vec(&Description(String::from("test"))).unwrap(), + ) + .unwrap()) + } + async fn write_setting( + &mut self, + _repository: &Repository, + _name: &str, + _setting: &Value, + ) -> Result<(), Error> { + Ok(()) + } + + async fn exists(&mut self, repository: &Repository) -> Result { + // Ok(true) + Ok(repository + == &Repository::from_str( + "test_user:test.giterated.dev/repository@test.giterated.dev", + ) + .unwrap()) } } @@ -283,4 +344,77 @@ mod test { .await .expect("object value should have been returned"); } + + #[tokio::test] + async fn test_user_get_setting() { + let backend = test_backend(); + + let mut user = backend + .get_object::("test_user:test.giterated.dev") + .await + .expect("object should have been returned"); + + user.get_setting::() + .await + .expect("object value should have been returned"); + } + + #[tokio::test] + async fn test_user_set_setting() { + let backend = test_backend(); + + let mut user = backend + .get_object::("test_user:test.giterated.dev") + .await + .expect("object should have been returned"); + + user.set_setting::(UserDisplayName(String::from("test"))) + .await + .expect("object value should have been returned"); + } + + #[tokio::test] + async fn test_respository_get() { + let backend = test_backend(); + + let mut repository = backend + .get_object::("test_user:test.giterated.dev/repository@test.giterated.dev") + .await + .expect("object should have been returned"); + + repository + .get::() + .await + .expect("object value should have been returned"); + } + + #[tokio::test] + async fn test_repository_get_setting() { + let backend = test_backend(); + + let mut repository = backend + .get_object::("test_user:test.giterated.dev/repository@test.giterated.dev") + .await + .expect("object should have been returned"); + + repository + .get_setting::() + .await + .expect("object value should have been returned"); + } + + #[tokio::test] + async fn test_repository_set_setting() { + let backend = test_backend(); + + let mut repository = backend + .get_object::("test_user:test.giterated.dev/repository@test.giterated.dev") + .await + .expect("object should have been returned"); + + repository + .set_setting::(Description(String::from("test"))) + .await + .expect("object value should have been returned"); + } } diff --git a/giterated-daemon/src/main.rs b/giterated-daemon/src/main.rs index 24fbc2a..4aa456b 100644 --- a/giterated-daemon/src/main.rs +++ b/giterated-daemon/src/main.rs @@ -62,6 +62,7 @@ async fn main() -> Result<(), Error> { ), instance: Instance::from_str(config["giterated"]["instance"].as_str().unwrap()) .unwrap(), + settings_provider: settings.clone(), })); let token_granter = Arc::new(Mutex::new(AuthenticationTokenGranter { diff --git a/giterated-models/Cargo.toml b/giterated-models/Cargo.toml index ee2d9c6..1179cea 100644 --- a/giterated-models/Cargo.toml +++ b/giterated-models/Cargo.toml @@ -23,6 +23,7 @@ toml = { version = "0.7" } git2 = "0.17" chrono = { version = "0.4", features = [ "serde" ] } async-trait = "0.1" +serde_with = "3.3.0" # Git backend sqlx = { version = "0.7", default-features = false, features = [ "macros", "chrono" ] } diff --git a/giterated-models/src/model/instance.rs b/giterated-models/src/model/instance.rs index 8eb755c..b6b6f36 100644 --- a/giterated-models/src/model/instance.rs +++ b/giterated-models/src/model/instance.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{fmt::Display, str::FromStr}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -42,9 +42,9 @@ impl GiteratedObject for Instance { } } -impl ToString for Instance { - fn to_string(&self) -> String { - self.url.clone() +impl Display for Instance { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.url) } } diff --git a/giterated-models/src/model/repository.rs b/giterated-models/src/model/repository.rs index 9c73e1e..4eb5c51 100644 --- a/giterated-models/src/model/repository.rs +++ b/giterated-models/src/model/repository.rs @@ -21,6 +21,9 @@ use super::{instance::Instance, user::User}; /// be valid: /// /// ``` +//# use giterated_models::model::repository::Repository; +//# use giterated_models::model::instance::Instance; +//# use giterated_models::model::user::User; /// let repository = Repository { /// owner: User::from_str("barson:giterated.dev").unwrap(), /// name: String::from("foo"), @@ -38,24 +41,24 @@ pub struct Repository { pub instance: Instance, } +impl Display for Repository { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("{}/{}@{}", self.owner, self.name, self.instance)) + } +} + impl GiteratedObject for Repository { fn object_name() -> &'static str { "repository" } fn from_object_str(object_str: &str) -> Result { - Ok(Repository::from_str(object_str).unwrap()) - } -} - -impl ToString for Repository { - fn to_string(&self) -> String { - format!("{}/{}@{}", self.owner, self.name, self.instance.to_string()) + Ok(Repository::from_str(object_str)?) } } impl TryFrom for Repository { - type Error = (); + type Error = RepositoryParseError; fn try_from(value: String) -> Result { Self::from_str(&value) @@ -63,7 +66,7 @@ impl TryFrom for Repository { } impl FromStr for Repository { - type Err = (); + type Err = RepositoryParseError; fn from_str(s: &str) -> Result { let mut by_ampersand = s.split('@'); @@ -81,6 +84,9 @@ impl FromStr for Repository { } } +#[derive(Debug, thiserror::Error)] +pub enum RepositoryParseError {} + /// Visibility of the repository to the general eye #[derive(PartialEq, Eq, Debug, Hash, Serialize, Deserialize, Clone, sqlx::Type)] #[sqlx(type_name = "visibility", rename_all = "lowercase")] diff --git a/giterated-models/src/model/settings.rs b/giterated-models/src/model/settings.rs index 30fd966..c0b5ef4 100644 --- a/giterated-models/src/model/settings.rs +++ b/giterated-models/src/model/settings.rs @@ -6,7 +6,7 @@ pub trait Setting: Serialize + DeserializeOwned { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AnySetting(Value); +pub struct AnySetting(pub Value); impl Setting for AnySetting { fn name() -> &'static str { @@ -23,7 +23,7 @@ impl Setting for UserBio { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct UserDisplayName(pub String); impl Setting for UserDisplayName { @@ -40,3 +40,21 @@ impl Setting for UserDisplayImage { "Profile Image" } } + +#[derive(Debug, Serialize, Deserialize)] +pub struct RepositoryDescription(pub String); + +impl Setting for RepositoryDescription { + fn name() -> &'static str { + "Repository Description" + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct RepositoryVisibilitySetting(pub String); + +impl Setting for RepositoryVisibilitySetting { + fn name() -> &'static str { + "Repository Visibility" + } +} diff --git a/giterated-models/src/model/user.rs b/giterated-models/src/model/user.rs index 4de906a..73ecc96 100644 --- a/giterated-models/src/model/user.rs +++ b/giterated-models/src/model/user.rs @@ -57,9 +57,13 @@ impl From for User { } impl FromStr for User { - type Err = (); + type Err = UserParseError; fn from_str(s: &str) -> Result { + if s.contains('/') { + return Err(UserParseError); + } + let mut colon_split = s.split(':'); let username = colon_split.next().unwrap().to_string(); let instance = Instance::from_str(colon_split.next().unwrap()).unwrap(); @@ -68,6 +72,10 @@ impl FromStr for User { } } +#[derive(thiserror::Error, Debug)] +#[error("failed to parse user")] +pub struct UserParseError; + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Password(pub String); diff --git a/giterated-models/src/operation/mod.rs b/giterated-models/src/operation/mod.rs index 5cdbaea..58765c9 100644 --- a/giterated-models/src/operation/mod.rs +++ b/giterated-models/src/operation/mod.rs @@ -1,4 +1,10 @@ -use std::{any::type_name, fmt::Debug, marker::PhantomData}; +use std::{ + any::type_name, + convert::Infallible, + fmt::{Debug, Display}, + marker::PhantomData, + str::FromStr, +}; use anyhow::Error; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -14,7 +20,7 @@ pub mod instance; pub mod repository; pub mod user; -pub trait GiteratedObject: Send + Serialize + DeserializeOwned + ToString { +pub trait GiteratedObject: Send + Display + FromStr { fn object_name() -> &'static str; fn from_object_str(object_str: &str) -> Result; @@ -121,11 +127,38 @@ impl + Send> Gite #[derive(Serialize)] #[serde(bound(deserialize = "O: GiteratedObject, V: GiteratedOperation"))] pub struct GiteratedMessage> { + #[serde(with = "string")] pub object: O, pub operation: String, pub payload: V, } +mod string { + use std::fmt::Display; + use std::str::FromStr; + + use serde::{de, Deserialize, Deserializer, Serializer}; + + pub fn serialize(value: &T, serializer: S) -> Result + where + T: Display, + S: Serializer, + { + serializer.collect_str(value) + } + + pub fn deserialize<'de, T, D>(deserializer: D) -> Result + where + T: FromStr, + T::Err: Display, + D: Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(de::Error::custom) + } +} + impl GiteratedMessage { pub fn try_into>( &self, @@ -187,9 +220,17 @@ impl GiteratedObject for AnyObject { } } -impl ToString for AnyObject { - fn to_string(&self) -> String { - self.0.to_string() +impl Display for AnyObject { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } +} + +impl FromStr for AnyObject { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(Self(s.to_owned())) } } diff --git a/giterated-models/src/values/mod.rs b/giterated-models/src/values/mod.rs index d05d323..26f6650 100644 --- a/giterated-models/src/values/mod.rs +++ b/giterated-models/src/values/mod.rs @@ -39,6 +39,10 @@ pub struct GetSetting { impl GiteratedOperation for GetSetting { + fn operation_name() -> &'static str { + "get_setting" + } + type Success = S; type Failure = GetSettingError; @@ -54,6 +58,10 @@ pub struct SetSetting { } impl GiteratedOperation for SetSetting { + fn operation_name() -> &'static str { + "set_setting" + } + type Success = (); type Failure = SetSettingError; @@ -70,6 +78,15 @@ pub struct AnyValue { _marker: PhantomData, } +impl AnyValue { + pub unsafe fn from_raw(value: Value) -> Self { + Self { + value, + _marker: Default::default(), + } + } +} + impl GiteratedObjectValue for AnyValue { type Object = O; diff --git a/giterated-models/src/values/repository.rs b/giterated-models/src/values/repository.rs index fedf67a..e538e34 100644 --- a/giterated-models/src/values/repository.rs +++ b/giterated-models/src/values/repository.rs @@ -1,7 +1,10 @@ use serde::{Deserialize, Serialize}; use crate::{ - model::repository::{Repository, RepositoryVisibility}, + model::{ + repository::{Repository, RepositoryVisibility}, + settings::Setting, + }, operation::GiteratedObjectValue, }; @@ -28,6 +31,12 @@ impl GiteratedObjectValue for Description { } } +impl Setting for Description { + fn name() -> &'static str { + "description" + } +} + #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Visibility(pub RepositoryVisibility); diff --git a/migrations/20230913232554_repository_metadata.sql b/migrations/20230913232554_repository_metadata.sql new file mode 100644 index 0000000..8ddc1d3 --- /dev/null +++ b/migrations/20230913232554_repository_metadata.sql @@ -0,0 +1 @@ +-- Add migration script here