diff --git a/packages/backend/native-utils/crates/database/Cargo.toml b/packages/backend/native-utils/crates/database/Cargo.toml new file mode 100644 index 000000000..69e0c73f7 --- /dev/null +++ b/packages/backend/native-utils/crates/database/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "database" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["napi"] +napi = [] + +[dependencies] +once_cell = "1.17.1" +sea-orm = { version = "0.11.3", features = ["sqlx-postgres", "runtime-tokio-rustls"] } +thiserror = "1.0.40" diff --git a/packages/backend/native-utils/crates/database/src/error.rs b/packages/backend/native-utils/crates/database/src/error.rs new file mode 100644 index 000000000..b70964f2e --- /dev/null +++ b/packages/backend/native-utils/crates/database/src/error.rs @@ -0,0 +1,9 @@ +use sea_orm::error::DbErr; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("The database connections have not been initialized yet")] + Uninitialized, + #[error("ORM error: {0}")] + OrmError(#[from] DbErr), +} diff --git a/packages/backend/native-utils/crates/database/src/lib.rs b/packages/backend/native-utils/crates/database/src/lib.rs new file mode 100644 index 000000000..3c9db912b --- /dev/null +++ b/packages/backend/native-utils/crates/database/src/lib.rs @@ -0,0 +1,18 @@ +pub mod error; + +use once_cell::sync::OnceCell; +use sea_orm::{Database, DatabaseConnection}; + +use crate::error::Error; + +static DB_CONN: OnceCell = OnceCell::new(); + +pub async fn init_database(connection_uri: impl Into) -> Result<(), Error> { + let conn = Database::connect(connection_uri.into()).await?; + DB_CONN.get_or_init(move || conn); + Ok(()) +} + +pub fn get_database() -> Result<&'static DatabaseConnection, Error> { + DB_CONN.get().ok_or(Error::Uninitialized) +} diff --git a/packages/backend/native-utils/crates/model/Cargo.toml b/packages/backend/native-utils/crates/model/Cargo.toml index 7b492a89d..832375ec7 100644 --- a/packages/backend/native-utils/crates/model/Cargo.toml +++ b/packages/backend/native-utils/crates/model/Cargo.toml @@ -6,12 +6,16 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-trait = "0.1.68" chrono = "0.4.24" +database = { path = "../database" } jsonschema = "0.17.0" once_cell = "1.17.1" +parse-display = "0.8.0" schemars = { version = "0.8.12", features = ["chrono"] } sea-orm = { version = "0.11.3", features = ["postgres-array", "sqlx-postgres", "runtime-tokio-rustls", "mock"] } serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" +thiserror = "1.0.40" tokio = { version = "1.28.1", features = ["sync"] } utoipa = "3.3.0" diff --git a/packages/backend/native-utils/crates/model/src/error.rs b/packages/backend/native-utils/crates/model/src/error.rs new file mode 100644 index 000000000..f75c0119a --- /dev/null +++ b/packages/backend/native-utils/crates/model/src/error.rs @@ -0,0 +1,9 @@ +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Failed to parse string")] + ParseError(#[from] parse_display::ParseError), + #[error("Failed to get database connection")] + DatabaseConnectionError(#[from] database::error::Error), + #[error("Database operation error: {0}")] + DatabaseOperationError(#[from] sea_orm::DbErr), +} diff --git a/packages/backend/native-utils/crates/model/src/lib.rs b/packages/backend/native-utils/crates/model/src/lib.rs index b14d29c34..61ba77a59 100644 --- a/packages/backend/native-utils/crates/model/src/lib.rs +++ b/packages/backend/native-utils/crates/model/src/lib.rs @@ -1,3 +1,4 @@ pub mod entity; -pub mod repository; +pub mod error; pub mod schema; +pub mod repository; diff --git a/packages/backend/native-utils/crates/model/src/repository/abuse_user_report.rs b/packages/backend/native-utils/crates/model/src/repository/abuse_user_report.rs deleted file mode 100644 index 8b1378917..000000000 --- a/packages/backend/native-utils/crates/model/src/repository/abuse_user_report.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/backend/native-utils/crates/model/src/repository/antenna.rs b/packages/backend/native-utils/crates/model/src/repository/antenna.rs new file mode 100644 index 000000000..32e17e244 --- /dev/null +++ b/packages/backend/native-utils/crates/model/src/repository/antenna.rs @@ -0,0 +1,47 @@ +use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; +use async_trait::async_trait; + +use crate::entity::{antenna, antenna_note, user_group_joining}; +use crate::error::Error; +use crate::schema::{antenna::Antenna, json_to_keyword, json_to_string_list}; + +use super::Repository; + +#[async_trait] +impl Repository for antenna::Model { + async fn pack(self) -> Result { + let db = database::get_database()?; + let has_unread_note = antenna_note::Entity::find() + .filter(antenna_note::Column::AntennaId.eq(self.id.to_owned())) + .filter(antenna_note::Column::Read.eq(false)) + .one(db) + .await? + .is_some(); + let user_group_joining = match self.user_group_joining_id { + None => None, + Some(id) => user_group_joining::Entity::find_by_id(id).one(db).await?, + }; + let user_group_id = match user_group_joining { + None => None, + Some(m) => Some(m.user_group_id), + }; + + Ok(Antenna { + id: self.id, + created_at: self.created_at.into(), + name: self.name, + keywords: json_to_keyword(&self.keywords), + exclude_keywords: json_to_keyword(&self.exclude_keywords), + src: self.src.try_into()?, + user_list_id: self.user_list_id, + user_group_id, + users: self.users, + instances: json_to_string_list(&self.instances), + case_sensitive: self.case_sensitive, + notify: self.notify, + with_replies: self.with_replies, + with_file: self.with_file, + has_unread_note, + }) + } +} diff --git a/packages/backend/native-utils/crates/model/src/repository/mod.rs b/packages/backend/native-utils/crates/model/src/repository/mod.rs index f7a590081..b720d1d48 100644 --- a/packages/backend/native-utils/crates/model/src/repository/mod.rs +++ b/packages/backend/native-utils/crates/model/src/repository/mod.rs @@ -1 +1,11 @@ -pub mod abuse_user_report; +pub mod antenna; + +use async_trait::async_trait; +use schemars::JsonSchema; + +use crate::error::Error; + +#[async_trait] +trait Repository { + async fn pack(self) -> Result; +} diff --git a/packages/backend/native-utils/crates/model/src/schema/antenna.rs b/packages/backend/native-utils/crates/model/src/schema/antenna.rs index 3bffe4f5f..a8065d09e 100644 --- a/packages/backend/native-utils/crates/model/src/schema/antenna.rs +++ b/packages/backend/native-utils/crates/model/src/schema/antenna.rs @@ -1,25 +1,26 @@ use jsonschema::JSONSchema; use once_cell::sync::Lazy; +use parse_display::FromStr; use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use super::Schema; +use super::{Keyword, Schema, StringList}; +use crate::entity::sea_orm_active_enums::AntennaSrcEnum; -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[derive(Debug, JsonSchema, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Antenna { pub id: String, pub created_at: chrono::DateTime, pub name: String, - pub keywords: Vec>, - pub exclude_keywords: Vec>, + pub keywords: Keyword, + pub exclude_keywords: Keyword, #[schema(inline)] - pub src: AntennaSrcEnum, + pub src: AntennaSrc, pub user_list_id: Option, pub user_group_id: Option, - pub users: Vec, - pub instances: Vec, + pub users: StringList, + pub instances: StringList, #[serde(default)] pub case_sensitive: bool, #[serde(default)] @@ -32,9 +33,10 @@ pub struct Antenna { pub has_unread_note: bool, } -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[derive(Debug, FromStr, JsonSchema, ToSchema)] #[serde(rename_all = "lowercase")] -pub enum AntennaSrcEnum { +#[display(style = "lowercase")] +pub enum AntennaSrc { Home, All, Users, @@ -43,9 +45,18 @@ pub enum AntennaSrcEnum { Instances, } -impl Schema for Antenna {} +impl TryFrom for AntennaSrc { + type Error = parse_display::ParseError; + fn try_from(value: AntennaSrcEnum) -> Result { + value.to_string().parse() + } +} + +// ---- TODO: could be macro +impl Schema for Antenna {} pub static VALIDATOR: Lazy = Lazy::new(|| Antenna::validator()); +// ---- #[cfg(test)] mod tests { diff --git a/packages/backend/native-utils/crates/model/src/schema/app.rs b/packages/backend/native-utils/crates/model/src/schema/app.rs index 861bdc48a..adc404eb9 100644 --- a/packages/backend/native-utils/crates/model/src/schema/app.rs +++ b/packages/backend/native-utils/crates/model/src/schema/app.rs @@ -1,12 +1,11 @@ use jsonschema::JSONSchema; use once_cell::sync::Lazy; use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use utoipa::ToSchema; use super::Schema; -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[derive(Debug, JsonSchema, ToSchema)] #[serde(rename_all = "camelCase")] pub struct App { pub id: String, @@ -20,7 +19,7 @@ pub struct App { } /// This represents `permissions` in `packages/calckey-js/src/consts.ts`. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[derive(Debug, JsonSchema, ToSchema)] pub enum Permission { #[serde(rename = "read:account")] ReadAccount, diff --git a/packages/backend/native-utils/crates/model/src/schema/mod.rs b/packages/backend/native-utils/crates/model/src/schema/mod.rs index cc495aac1..2a0853814 100644 --- a/packages/backend/native-utils/crates/model/src/schema/mod.rs +++ b/packages/backend/native-utils/crates/model/src/schema/mod.rs @@ -3,6 +3,10 @@ pub mod app; use jsonschema::JSONSchema; use schemars::{schema_for, JsonSchema}; +use serde_json::Value; + +type Keyword = Vec>; +type StringList = Vec; /// Structs of schema defitions implement this trait in order to /// provide the JSON Schema validator [`jsonschema::JSONSchema`]. @@ -19,3 +23,29 @@ trait Schema { .expect("Unable to compile schema") } } + +pub(crate) fn json_to_keyword(value: &Value) -> Keyword { + match value.as_array() { + None => vec![vec![]], + Some(or_vec) => or_vec + .iter() + .map(|and_val| match and_val.as_array() { + None => vec![], + Some(and_vec) => and_vec + .iter() + .map(|word| word.as_str().unwrap_or_default().to_string()) + .collect(), + }) + .collect(), + } +} + +pub(crate) fn json_to_string_list(value: &Value) -> StringList { + match value.as_array() { + None => vec![], + Some(v) => v + .iter() + .map(|s| s.as_str().unwrap_or_default().to_string()) + .collect(), + } +}