add repository trait

This commit is contained in:
Namekuji 2023-05-25 08:55:20 -04:00
parent 4e82dab1eb
commit bc0e0b14d0
No known key found for this signature in database
GPG key ID: B541BD6E646CABC7
12 changed files with 169 additions and 17 deletions

View file

@ -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"

View file

@ -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),
}

View file

@ -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<DatabaseConnection> = OnceCell::new();
pub async fn init_database(connection_uri: impl Into<String>) -> 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)
}

View file

@ -6,12 +6,16 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
async-trait = "0.1.68"
chrono = "0.4.24" chrono = "0.4.24"
database = { path = "../database" }
jsonschema = "0.17.0" jsonschema = "0.17.0"
once_cell = "1.17.1" once_cell = "1.17.1"
parse-display = "0.8.0"
schemars = { version = "0.8.12", features = ["chrono"] } schemars = { version = "0.8.12", features = ["chrono"] }
sea-orm = { version = "0.11.3", features = ["postgres-array", "sqlx-postgres", "runtime-tokio-rustls", "mock"] } sea-orm = { version = "0.11.3", features = ["postgres-array", "sqlx-postgres", "runtime-tokio-rustls", "mock"] }
serde = { version = "1.0.163", features = ["derive"] } serde = { version = "1.0.163", features = ["derive"] }
serde_json = "1.0.96" serde_json = "1.0.96"
thiserror = "1.0.40"
tokio = { version = "1.28.1", features = ["sync"] } tokio = { version = "1.28.1", features = ["sync"] }
utoipa = "3.3.0" utoipa = "3.3.0"

View file

@ -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),
}

View file

@ -1,3 +1,4 @@
pub mod entity; pub mod entity;
pub mod repository; pub mod error;
pub mod schema; pub mod schema;
pub mod repository;

View file

@ -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<Antenna> for antenna::Model {
async fn pack(self) -> Result<Antenna, Error> {
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,
})
}
}

View file

@ -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<T: JsonSchema> {
async fn pack(self) -> Result<T, Error>;
}

View file

@ -1,25 +1,26 @@
use jsonschema::JSONSchema; use jsonschema::JSONSchema;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use parse_display::FromStr;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema; 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")] #[serde(rename_all = "camelCase")]
pub struct Antenna { pub struct Antenna {
pub id: String, pub id: String,
pub created_at: chrono::DateTime<chrono::Utc>, pub created_at: chrono::DateTime<chrono::Utc>,
pub name: String, pub name: String,
pub keywords: Vec<Vec<String>>, pub keywords: Keyword,
pub exclude_keywords: Vec<Vec<String>>, pub exclude_keywords: Keyword,
#[schema(inline)] #[schema(inline)]
pub src: AntennaSrcEnum, pub src: AntennaSrc,
pub user_list_id: Option<String>, pub user_list_id: Option<String>,
pub user_group_id: Option<String>, pub user_group_id: Option<String>,
pub users: Vec<String>, pub users: StringList,
pub instances: Vec<String>, pub instances: StringList,
#[serde(default)] #[serde(default)]
pub case_sensitive: bool, pub case_sensitive: bool,
#[serde(default)] #[serde(default)]
@ -32,9 +33,10 @@ pub struct Antenna {
pub has_unread_note: bool, pub has_unread_note: bool,
} }
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] #[derive(Debug, FromStr, JsonSchema, ToSchema)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum AntennaSrcEnum { #[display(style = "lowercase")]
pub enum AntennaSrc {
Home, Home,
All, All,
Users, Users,
@ -43,9 +45,18 @@ pub enum AntennaSrcEnum {
Instances, Instances,
} }
impl Schema<Self> for Antenna {} impl TryFrom<AntennaSrcEnum> for AntennaSrc {
type Error = parse_display::ParseError;
fn try_from(value: AntennaSrcEnum) -> Result<Self, Self::Error> {
value.to_string().parse()
}
}
// ---- TODO: could be macro
impl Schema<Self> for Antenna {}
pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| Antenna::validator()); pub static VALIDATOR: Lazy<JSONSchema> = Lazy::new(|| Antenna::validator());
// ----
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View file

@ -1,12 +1,11 @@
use jsonschema::JSONSchema; use jsonschema::JSONSchema;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema; use utoipa::ToSchema;
use super::Schema; use super::Schema;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] #[derive(Debug, JsonSchema, ToSchema)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct App { pub struct App {
pub id: String, pub id: String,
@ -20,7 +19,7 @@ pub struct App {
} }
/// This represents `permissions` in `packages/calckey-js/src/consts.ts`. /// This represents `permissions` in `packages/calckey-js/src/consts.ts`.
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] #[derive(Debug, JsonSchema, ToSchema)]
pub enum Permission { pub enum Permission {
#[serde(rename = "read:account")] #[serde(rename = "read:account")]
ReadAccount, ReadAccount,

View file

@ -3,6 +3,10 @@ pub mod app;
use jsonschema::JSONSchema; use jsonschema::JSONSchema;
use schemars::{schema_for, JsonSchema}; use schemars::{schema_for, JsonSchema};
use serde_json::Value;
type Keyword = Vec<Vec<String>>;
type StringList = Vec<String>;
/// Structs of schema defitions implement this trait in order to /// Structs of schema defitions implement this trait in order to
/// provide the JSON Schema validator [`jsonschema::JSONSchema`]. /// provide the JSON Schema validator [`jsonschema::JSONSchema`].
@ -19,3 +23,29 @@ trait Schema<T: JsonSchema> {
.expect("Unable to compile 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(),
}
}