Remove remaining models in favour of dao-dto architecture

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/5/head
trivernis 3 years ago
parent e9641e604e
commit 0261d2fe3c
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -1,7 +1,5 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::path::PathBuf; use std::path::PathBuf;
use sea_orm::DatabaseConnection; use sea_orm::DatabaseConnection;
@ -10,17 +8,10 @@ use mediarepo_core::error::RepoResult;
use mediarepo_core::fs::file_hash_store::FileHashStore; use mediarepo_core::fs::file_hash_store::FileHashStore;
use mediarepo_core::fs::thumbnail_store::ThumbnailStore; use mediarepo_core::fs::thumbnail_store::ThumbnailStore;
use crate::dao::{DaoContext, DaoProvider};
use mediarepo_core::utils::parse_namespace_and_tag;
use mediarepo_database::get_database; use mediarepo_database::get_database;
use mediarepo_database::queries::analysis::{get_all_counts, Counts}; use mediarepo_database::queries::analysis::{get_all_counts, Counts};
use crate::dao::{DaoContext, DaoProvider};
use crate::namespace::Namespace;
use crate::tag::Tag;
#[derive(Clone)] #[derive(Clone)]
pub struct Repo { pub struct Repo {
db: DatabaseConnection, db: DatabaseConnection,
@ -67,65 +58,6 @@ impl Repo {
&self.db &self.db
} }
/// Finds all tags that are assigned to the given list of hashes
#[tracing::instrument(level = "debug", skip_all)]
pub async fn find_tags_for_file_identifiers(&self, cds: Vec<Vec<u8>>) -> RepoResult<Vec<Tag>> {
Tag::for_cd_list(self.db.clone(), cds).await
}
/// Adds or finds a tag
#[tracing::instrument(level = "debug", skip(self))]
pub async fn add_or_find_tag<S: ToString + Debug>(&self, tag: S) -> RepoResult<Tag> {
let (namespace, name) = parse_namespace_and_tag(tag.to_string());
if let Some(namespace) = namespace {
self.add_or_find_namespaced_tag(name, namespace).await
} else {
self.add_or_find_unnamespaced_tag(name).await
}
}
/// Adds or finds an unnamespaced tag
#[tracing::instrument(level = "debug", skip(self))]
pub async fn add_or_find_unnamespaced_tag(&self, name: String) -> RepoResult<Tag> {
if let Some(tag) = Tag::by_name(self.db.clone(), &name, None).await? {
Ok(tag)
} else {
self.add_unnamespaced_tag(name).await
}
}
/// Adds an unnamespaced tag
#[tracing::instrument(level = "debug", skip(self))]
pub async fn add_unnamespaced_tag(&self, name: String) -> RepoResult<Tag> {
Tag::add(self.db.clone(), name, None).await
}
/// Adds or finds a namespaced tag
#[tracing::instrument(level = "debug", skip(self))]
pub async fn add_or_find_namespaced_tag(
&self,
name: String,
namespace: String,
) -> RepoResult<Tag> {
if let Some(tag) = Tag::by_name(self.db.clone(), &name, Some(namespace.clone())).await? {
Ok(tag)
} else {
self.add_namespaced_tag(name, namespace).await
}
}
/// Adds a namespaced tag
#[tracing::instrument(level = "debug", skip(self))]
pub async fn add_namespaced_tag(&self, name: String, namespace: String) -> RepoResult<Tag> {
let namespace =
if let Some(namespace) = Namespace::by_name(self.db.clone(), &namespace).await? {
namespace
} else {
Namespace::add(self.db.clone(), namespace).await?
};
Tag::add(self.db.clone(), name, Some(namespace.id())).await
}
/// Returns the size of the main storage /// Returns the size of the main storage
#[inline] #[inline]
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]

@ -57,6 +57,28 @@ impl TagDao {
Ok(namespaces) Ok(namespaces)
} }
#[tracing::instrument(level = "debug", skip(self))]
pub async fn all_for_cds(&self, cds: Vec<Vec<u8>>) -> RepoResult<Vec<TagDto>> {
let tags = tag::Entity::find()
.find_also_related(namespace::Entity)
.join(
JoinType::LeftJoin,
content_descriptor_tag::Relation::Tag.def().rev(),
)
.join(
JoinType::InnerJoin,
content_descriptor_tag::Relation::ContentDescriptorId.def(),
)
.filter(content_descriptor::Column::Descriptor.is_in(cds))
.all(&self.ctx.db)
.await?
.into_iter()
.map(map_tag_dto)
.collect();
Ok(tags)
}
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub async fn tags_for_cd(&self, cd_id: i64) -> RepoResult<Vec<TagDto>> { pub async fn tags_for_cd(&self, cd_id: i64) -> RepoResult<Vec<TagDto>> {
let tags = tag::Entity::find() let tags = tag::Entity::find()

@ -1,124 +0,0 @@
use std::fmt::Debug;
use chrono::{Local, NaiveDateTime};
use sea_orm::prelude::*;
use sea_orm::{DatabaseConnection, Set};
use mediarepo_core::error::RepoResult;
use mediarepo_database::entities::file_metadata;
#[derive(Clone)]
pub struct FileMetadata {
db: DatabaseConnection,
model: file_metadata::Model,
}
impl FileMetadata {
#[tracing::instrument(level = "trace")]
pub(crate) fn new(db: DatabaseConnection, model: file_metadata::Model) -> Self {
Self { db, model }
}
/// Fetches the file by id
#[tracing::instrument(level = "debug", skip(db))]
pub async fn by_id(db: DatabaseConnection, id: i64) -> RepoResult<Option<Self>> {
let file_metadata = file_metadata::Entity::find_by_id(id)
.one(&db)
.await?
.map(|m| FileMetadata::new(db, m));
Ok(file_metadata)
}
/// Fetches metadata for all given file ids
#[tracing::instrument(level = "debug", skip(db))]
pub async fn all_by_ids(db: DatabaseConnection, ids: Vec<i64>) -> RepoResult<Vec<Self>> {
let file_metadata = file_metadata::Entity::find()
.filter(file_metadata::Column::FileId.is_in(ids))
.all(&db)
.await?
.into_iter()
.map(|m| FileMetadata::new(db.clone(), m))
.collect();
Ok(file_metadata)
}
/// Adds a file with its hash to the database
#[tracing::instrument(level = "debug", skip(db))]
pub(crate) async fn add(
db: DatabaseConnection,
file_id: i64,
size: i64,
creation_time: NaiveDateTime,
change_time: NaiveDateTime,
) -> RepoResult<Self> {
let file = file_metadata::ActiveModel {
file_id: Set(file_id),
size: Set(size),
import_time: Set(Local::now().naive_local()),
creation_time: Set(creation_time),
change_time: Set(change_time),
..Default::default()
};
let model = file.insert(&db).await?;
Ok(Self::new(db, model))
}
pub fn file_id(&self) -> i64 {
self.model.file_id
}
pub fn size(&self) -> i64 {
self.model.size
}
pub fn name(&self) -> &Option<String> {
&self.model.name
}
pub fn comment(&self) -> &Option<String> {
&self.model.comment
}
pub fn import_time(&self) -> &NaiveDateTime {
&self.model.import_time
}
pub fn creation_time(&self) -> &NaiveDateTime {
&self.model.creation_time
}
pub fn change_time(&self) -> &NaiveDateTime {
&self.model.change_time
}
/// Changes the name of the file
#[tracing::instrument(level = "debug", skip(self))]
pub async fn set_name<S: ToString + Debug>(&mut self, name: S) -> RepoResult<()> {
let mut active_model = self.get_active_model();
active_model.name = Set(Some(name.to_string()));
self.model = active_model.update(&self.db).await?;
Ok(())
}
/// Changes the comment of the file
#[tracing::instrument(level = "debug", skip(self))]
pub async fn set_comment<S: ToString + Debug>(&mut self, comment: S) -> RepoResult<()> {
let mut active_file = self.get_active_model();
active_file.comment = Set(Some(comment.to_string()));
self.model = active_file.update(&self.db).await?;
Ok(())
}
/// Returns the active model of the file with only the id set
fn get_active_model(&self) -> file_metadata::ActiveModel {
file_metadata::ActiveModel {
file_id: Set(self.file_id()),
..Default::default()
}
}
}

@ -1,6 +1,3 @@
pub mod dao; pub mod dao;
pub mod dto; pub mod dto;
pub mod file_metadata;
pub mod namespace;
pub mod tag;
pub mod type_keys; pub mod type_keys;

@ -1,143 +0,0 @@
use std::fmt::Debug;
use sea_orm::{
Condition, ConnectionTrait, DatabaseBackend, DatabaseConnection, InsertResult, Set, Statement,
};
use sea_orm::prelude::*;
use mediarepo_core::error::RepoResult;
use mediarepo_database::entities::namespace;
#[derive(Clone)]
pub struct Namespace {
#[allow(dead_code)]
db: DatabaseConnection,
model: namespace::Model,
}
impl Namespace {
#[tracing::instrument(level = "trace")]
pub(crate) fn new(db: DatabaseConnection, model: namespace::Model) -> Self {
Self { db, model }
}
/// Retrieves a list of all namespaces
#[tracing::instrument(level = "debug", skip(db))]
pub async fn all(db: DatabaseConnection) -> RepoResult<Vec<Self>> {
let namespaces = namespace::Entity::find()
.all(&db)
.await?
.into_iter()
.map(|model| Self::new(db.clone(), model))
.collect();
Ok(namespaces)
}
/// Retrieves the namespace by id
#[tracing::instrument(level = "debug", skip(db))]
pub async fn by_id(db: DatabaseConnection, id: i64) -> RepoResult<Option<Self>> {
let namespace = namespace::Entity::find_by_id(id)
.one(&db)
.await?
.map(|model| Self::new(db, model));
Ok(namespace)
}
/// Retrieves a namespace by its name
#[tracing::instrument(level = "debug", skip(db))]
pub async fn by_name<S: AsRef<str> + Debug>(
db: DatabaseConnection,
name: S,
) -> RepoResult<Option<Self>> {
let namespace = namespace::Entity::find()
.filter(namespace::Column::Name.eq(name.as_ref()))
.one(&db)
.await?
.map(|model| Self::new(db, model));
Ok(namespace)
}
/// Returns all namespaces by name
#[tracing::instrument(level = "debug", skip(db))]
pub async fn all_by_name(db: DatabaseConnection, names: Vec<String>) -> RepoResult<Vec<Self>> {
if names.is_empty() {
return Ok(Vec::with_capacity(0));
}
let mut condition = Condition::any();
for name in names {
condition = condition.add(namespace::Column::Name.eq(name));
}
let namespaces = namespace::Entity::find()
.filter(condition)
.all(&db)
.await?
.into_iter()
.map(|model| Self::new(db.clone(), model))
.collect();
Ok(namespaces)
}
/// Adds all namespaces to the database
#[tracing::instrument(level = "debug", skip(db))]
pub async fn add_all(db: DatabaseConnection, names: Vec<String>) -> RepoResult<Vec<Self>> {
if names.is_empty() {
return Ok(vec![]);
}
let models: Vec<namespace::ActiveModel> = names
.into_iter()
.map(|name| namespace::ActiveModel {
name: Set(name),
..Default::default()
})
.collect();
let txn = db.begin().await?;
let last_id = txn
.query_one(Statement::from_string(
DatabaseBackend::Sqlite,
r#"SELECT MAX(id) AS "max_id" FROM namespaces;"#.to_owned(),
))
.await?
.and_then(|result| result.try_get("", "max_id").ok())
.unwrap_or(-1);
let result: InsertResult<namespace::ActiveModel> =
namespace::Entity::insert_many(models).exec(&txn).await?;
let namespaces = namespace::Entity::find()
.filter(namespace::Column::Id.between(last_id, result.last_insert_id + 1))
.all(&txn)
.await?
.into_iter()
.map(|model| Self::new(db.clone(), model))
.collect();
txn.commit().await?;
Ok(namespaces)
}
/// Adds a namespace to the database
#[tracing::instrument(level = "debug", skip(db))]
pub async fn add<S: ToString + Debug>(db: DatabaseConnection, name: S) -> RepoResult<Self> {
let active_model = namespace::ActiveModel {
name: Set(name.to_string()),
..Default::default()
};
let model = active_model.insert(&db).await?;
Ok(Self::new(db, model))
}
/// The ID of the namespace
pub fn id(&self) -> i64 {
self.model.id
}
/// The name of the namespace
pub fn name(&self) -> &String {
&self.model.name
}
}

@ -1,227 +0,0 @@
use std::fmt::Debug;
use sea_orm::{Condition, DatabaseBackend, DatabaseConnection, JoinType, Set, Statement};
use sea_orm::{InsertResult, QuerySelect};
use sea_orm::prelude::*;
use sea_orm::query::ConnectionTrait;
use sea_orm::sea_query::Expr;
use mediarepo_core::error::RepoResult;
use mediarepo_database::entities::content_descriptor;
use mediarepo_database::entities::content_descriptor_tag;
use mediarepo_database::entities::namespace;
use mediarepo_database::entities::tag;
use crate::namespace::Namespace;
#[derive(Clone)]
pub struct Tag {
db: DatabaseConnection,
model: tag::Model,
namespace: Option<namespace::Model>,
}
impl Tag {
#[tracing::instrument(level = "trace")]
pub(crate) fn new(
db: DatabaseConnection,
model: tag::Model,
namespace: Option<namespace::Model>,
) -> Self {
Self {
db,
model,
namespace,
}
}
/// Returns all tags stored in the database
#[tracing::instrument(level = "debug", skip(db))]
pub async fn all(db: DatabaseConnection) -> RepoResult<Vec<Self>> {
let tags: Vec<Self> = tag::Entity::find()
.left_join(namespace::Entity)
.select_also(namespace::Entity)
.all(&db)
.await?
.into_iter()
.map(|(tag, namespace)| Self::new(db.clone(), tag, namespace))
.collect();
Ok(tags)
}
/// Returns the tag by id
#[tracing::instrument(level = "debug", skip(db))]
pub async fn by_id(db: DatabaseConnection, id: i64) -> RepoResult<Option<Self>> {
let tag = tag::Entity::find_by_id(id)
.find_also_related(namespace::Entity)
.one(&db)
.await?
.map(|(model, namespace)| Self::new(db, model, namespace));
Ok(tag)
}
/// Returns one tag by name and namespace
#[tracing::instrument(level = "debug", skip(db))]
pub async fn by_name<S1: ToString + Debug>(
db: DatabaseConnection,
name: S1,
namespace: Option<String>,
) -> RepoResult<Option<Self>> {
let mut entries = Self::all_by_name(db, vec![(namespace, name.to_string())]).await?;
Ok(entries.pop())
}
/// Retrieves the namespaced tags by name and namespace
#[tracing::instrument(level = "debug", skip(db))]
pub async fn all_by_name(
db: DatabaseConnection,
namespaces_with_names: Vec<(Option<String>, String)>,
) -> RepoResult<Vec<Self>> {
if namespaces_with_names.is_empty() {
return Ok(vec![]);
}
let mut or_condition = Condition::any();
for (namespace, name) in namespaces_with_names {
let mut all_condition = Condition::all();
if !name.ends_with('*') {
all_condition = all_condition.add(tag::Column::Name.eq(name))
} else if name.len() > 1 {
all_condition = all_condition
.add(tag::Column::Name.like(&*format!("{}%", name.trim_end_matches("*"))))
} else if namespace.is_none() {
continue; // would result in an empty condition otherwise
}
all_condition = if let Some(namespace) = namespace {
all_condition.add(namespace::Column::Name.eq(namespace))
} else {
all_condition.add(Expr::tbl(tag::Entity, tag::Column::NamespaceId).is_null())
};
or_condition = or_condition.add(all_condition);
}
let tags: Vec<Self> = tag::Entity::find()
.find_also_related(namespace::Entity)
.filter(or_condition)
.group_by(tag::Column::Id)
.all(&db)
.await?
.into_iter()
.map(|(t, n)| Self::new(db.clone(), t, n))
.collect();
Ok(tags)
}
/// Returns all tags that are assigned to any of the passed hashes
#[tracing::instrument(level = "debug", skip_all)]
pub async fn for_cd_list(db: DatabaseConnection, cds: Vec<Vec<u8>>) -> RepoResult<Vec<Self>> {
let tags: Vec<Self> = tag::Entity::find()
.find_also_related(namespace::Entity)
.join(
JoinType::LeftJoin,
content_descriptor_tag::Relation::Tag.def().rev(),
)
.join(
JoinType::InnerJoin,
content_descriptor_tag::Relation::ContentDescriptorId.def(),
)
.filter(content_descriptor::Column::Descriptor.is_in(cds))
.group_by(tag::Column::Id)
.all(&db)
.await?
.into_iter()
.map(|(t, n)| Self::new(db.clone(), t, n))
.collect();
Ok(tags)
}
pub async fn add_all(
db: DatabaseConnection,
namespaces_with_names: Vec<(Option<i64>, String)>,
) -> RepoResult<Vec<Self>> {
if namespaces_with_names.is_empty() {
return Ok(vec![]);
}
let models: Vec<tag::ActiveModel> = namespaces_with_names
.into_iter()
.map(|(namespace_id, name)| tag::ActiveModel {
name: Set(name),
namespace_id: Set(namespace_id),
..Default::default()
})
.collect();
let txn = db.begin().await?;
let last_id: i64 = txn
.query_one(Statement::from_string(
DatabaseBackend::Sqlite,
r#"SELECT MAX(id) as "max_id" FROM tags"#.to_owned(),
))
.await?
.and_then(|res| res.try_get("", "max_id").ok())
.unwrap_or(-1);
let result: InsertResult<tag::ActiveModel> =
tag::Entity::insert_many(models).exec(&txn).await?;
let tags: Vec<Self> = tag::Entity::find()
.find_also_related(namespace::Entity)
.filter(tag::Column::Id.between(last_id, result.last_insert_id + 1))
.all(&txn)
.await?
.into_iter()
.map(|(t, n)| Self::new(db.clone(), t, n))
.collect();
txn.commit().await?;
Ok(tags)
}
/// Adds a new tag to the database
#[tracing::instrument(level = "debug", skip(db))]
pub async fn add<S: ToString + Debug>(
db: DatabaseConnection,
name: S,
namespace_id: Option<i64>,
) -> RepoResult<Self> {
let active_model = tag::ActiveModel {
name: Set(name.to_string()),
namespace_id: Set(namespace_id),
..Default::default()
};
let model: tag::Model = active_model.insert(&db).await?;
let namespace = model.find_related(namespace::Entity).one(&db).await?;
Ok(Self::new(db, model, namespace))
}
/// The ID of the tag
pub fn id(&self) -> i64 {
self.model.id
}
/// The name of the tag
pub fn name(&self) -> &String {
&self.model.name
}
/// The namespace of the tag
pub fn namespace(&self) -> Option<Namespace> {
self.namespace
.clone()
.map(|n| Namespace::new(self.db.clone(), n))
}
/// Returns the normalized name of the tag (namespace:tag)
pub fn normalized_name(&self) -> String {
if let Some(namespace) = &self.namespace {
format!("{}:{}", namespace.name, self.model.name)
} else {
self.model.name.to_owned()
}
}
}

@ -5,27 +5,11 @@ use mediarepo_core::mediarepo_api::types::tags::{NamespaceResponse, TagResponse}
use mediarepo_logic::dto::{ use mediarepo_logic::dto::{
FileDto, FileMetadataDto, FileStatus as FileStatusModel, NamespaceDto, TagDto, ThumbnailDto, FileDto, FileMetadataDto, FileStatus as FileStatusModel, NamespaceDto, TagDto, ThumbnailDto,
}; };
use mediarepo_logic::file_metadata::FileMetadata;
use mediarepo_logic::namespace::Namespace;
use mediarepo_logic::tag::Tag;
pub trait FromModel<M> { pub trait FromModel<M> {
fn from_model(model: M) -> Self; fn from_model(model: M) -> Self;
} }
impl FromModel<FileMetadata> for FileMetadataResponse {
fn from_model(metadata: FileMetadata) -> Self {
Self {
file_id: metadata.file_id(),
name: metadata.name().to_owned(),
comment: metadata.comment().to_owned(),
creation_time: metadata.creation_time().to_owned(),
change_time: metadata.change_time().to_owned(),
import_time: metadata.import_time().to_owned(),
}
}
}
impl FromModel<FileMetadataDto> for FileMetadataResponse { impl FromModel<FileMetadataDto> for FileMetadataResponse {
fn from_model(model: FileMetadataDto) -> Self { fn from_model(model: FileMetadataDto) -> Self {
Self { Self {
@ -60,16 +44,6 @@ impl FromModel<FileStatusModel> for FileStatus {
} }
} }
impl FromModel<Tag> for TagResponse {
fn from_model(model: Tag) -> Self {
Self {
id: model.id(),
namespace: model.namespace().map(|n| n.name().to_owned()),
name: model.name().to_owned(),
}
}
}
impl FromModel<TagDto> for TagResponse { impl FromModel<TagDto> for TagResponse {
fn from_model(model: TagDto) -> Self { fn from_model(model: TagDto) -> Self {
Self { Self {
@ -91,15 +65,6 @@ impl FromModel<ThumbnailDto> for ThumbnailMetadataResponse {
} }
} }
impl FromModel<Namespace> for NamespaceResponse {
fn from_model(model: Namespace) -> Self {
Self {
id: model.id(),
name: model.name().to_owned(),
}
}
}
impl FromModel<NamespaceDto> for NamespaceResponse { impl FromModel<NamespaceDto> for NamespaceResponse {
fn from_model(model: NamespaceDto) -> Self { fn from_model(model: NamespaceDto) -> Self {
Self { Self {

@ -6,7 +6,9 @@ use mediarepo_core::mediarepo_api::types::files::{GetFileTagsRequest, GetFilesTa
use mediarepo_core::mediarepo_api::types::tags::{ use mediarepo_core::mediarepo_api::types::tags::{
ChangeFileTagsRequest, NamespaceResponse, TagResponse, ChangeFileTagsRequest, NamespaceResponse, TagResponse,
}; };
use mediarepo_core::utils::parse_namespace_and_tag;
use mediarepo_logic::dao::DaoProvider; use mediarepo_logic::dao::DaoProvider;
use mediarepo_logic::dto::AddTagDto;
use crate::from_model::FromModel; use crate::from_model::FromModel;
use crate::utils::{file_by_identifier, get_repo_from_context}; use crate::utils::{file_by_identifier, get_repo_from_context};
@ -85,7 +87,8 @@ impl TagsNamespace {
let repo = get_repo_from_context(ctx).await; let repo = get_repo_from_context(ctx).await;
let request = event.payload::<GetFilesTagsRequest>()?; let request = event.payload::<GetFilesTagsRequest>()?;
let tag_responses: Vec<TagResponse> = repo let tag_responses: Vec<TagResponse> = repo
.find_tags_for_file_identifiers( .tag()
.all_for_cds(
request request
.cds .cds
.into_par_iter() .into_par_iter()
@ -102,17 +105,21 @@ impl TagsNamespace {
Ok(()) Ok(())
} }
/// Creates all tags given as input or returns the existing tag /// Creates all tags given as input or returns the existing tags
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn create_tags(ctx: &Context, event: Event) -> IPCResult<()> { async fn create_tags(ctx: &Context, event: Event) -> IPCResult<()> {
let repo = get_repo_from_context(ctx).await; let repo = get_repo_from_context(ctx).await;
let tags = event.payload::<Vec<String>>()?; let tags = event.payload::<Vec<String>>()?;
let mut created_tags = Vec::new(); let created_tags = repo
.tag()
.add_all(
tags.into_iter()
.map(parse_namespace_and_tag)
.map(AddTagDto::from_tuple)
.collect(),
)
.await?;
for tag in tags {
let created_tag = repo.add_or_find_tag(tag).await?;
created_tags.push(created_tag);
}
let responses: Vec<TagResponse> = created_tags let responses: Vec<TagResponse> = created_tags
.into_iter() .into_iter()
.map(TagResponse::from_model) .map(TagResponse::from_model)

Loading…
Cancel
Save