Move tag adding and retrieval by name to dao
Signed-off-by: trivernis <trivernis@protonmail.com>pull/5/head
parent
744475dd1e
commit
31addcda87
@ -0,0 +1,120 @@
|
|||||||
|
use crate::dao::tag::{map_tag_dto, TagDao};
|
||||||
|
use crate::dto::{AddTagDto, NamespaceDto, TagDto};
|
||||||
|
use mediarepo_core::error::RepoResult;
|
||||||
|
use mediarepo_database::entities::{namespace, tag};
|
||||||
|
use sea_orm::prelude::*;
|
||||||
|
use sea_orm::ActiveValue::Set;
|
||||||
|
use sea_orm::{Condition, ConnectionTrait, DatabaseTransaction};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
|
impl TagDao {
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn add_all(&self, mut tags: Vec<AddTagDto>) -> RepoResult<Vec<TagDto>> {
|
||||||
|
let namespaces = tags.iter().filter_map(|t| t.namespace.clone()).collect();
|
||||||
|
let trx = self.ctx.db.begin().await?;
|
||||||
|
let existing_tags = tags_by_name(&trx, tags.clone()).await?;
|
||||||
|
|
||||||
|
if existing_tags.len() == tags.len() {
|
||||||
|
return Ok(existing_tags);
|
||||||
|
}
|
||||||
|
let existing_tag_map: HashMap<String, TagDto> =
|
||||||
|
HashMap::from_iter(existing_tags.into_iter().map(|t| (t.normalized_name(), t)));
|
||||||
|
|
||||||
|
let namespace_map = add_or_get_all_namespaces(&trx, namespaces).await?;
|
||||||
|
tags.retain(|dto| !existing_tag_map.contains_key(&dto.normalized_name()));
|
||||||
|
let tag_models: Vec<tag::ActiveModel> = tags
|
||||||
|
.iter()
|
||||||
|
.map(|t| tag::ActiveModel {
|
||||||
|
name: Set(t.name.to_owned()),
|
||||||
|
namespace_id: Set(t
|
||||||
|
.namespace
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|n| namespace_map.get(n))
|
||||||
|
.map(|n| n.id())),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
tag::Entity::insert_many(tag_models).exec(&trx).await?;
|
||||||
|
let mut tag_dtos = tags_by_name(&trx, tags).await?;
|
||||||
|
trx.commit().await?;
|
||||||
|
tag_dtos.append(&mut existing_tag_map.into_iter().map(|(_, dto)| dto).collect());
|
||||||
|
|
||||||
|
Ok(tag_dtos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_or_get_all_namespaces(
|
||||||
|
trx: &DatabaseTransaction,
|
||||||
|
mut namespaces: Vec<String>,
|
||||||
|
) -> RepoResult<HashMap<String, NamespaceDto>> {
|
||||||
|
let existing_namespaces = namespaces_by_name(trx, namespaces.clone()).await?;
|
||||||
|
let mut namespace_map = HashMap::from_iter(
|
||||||
|
existing_namespaces
|
||||||
|
.into_iter()
|
||||||
|
.map(|nsp| (nsp.name().to_owned(), nsp)),
|
||||||
|
);
|
||||||
|
if namespaces.len() == namespace_map.len() {
|
||||||
|
return Ok(namespace_map);
|
||||||
|
}
|
||||||
|
namespaces.retain(|nsp| !namespace_map.contains_key(nsp));
|
||||||
|
let namespace_models: Vec<namespace::ActiveModel> = namespaces
|
||||||
|
.iter()
|
||||||
|
.map(|nsp| namespace::ActiveModel {
|
||||||
|
name: Set(nsp.to_owned()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
namespace::Entity::insert_many(namespace_models)
|
||||||
|
.exec(trx)
|
||||||
|
.await?;
|
||||||
|
let additional_namespaces = namespaces_by_name(trx, namespaces.clone()).await?;
|
||||||
|
|
||||||
|
for nsp in additional_namespaces {
|
||||||
|
namespace_map.insert(nsp.name().to_owned(), nsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(namespace_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn namespaces_by_name(
|
||||||
|
trx: &DatabaseTransaction,
|
||||||
|
names: Vec<String>,
|
||||||
|
) -> RepoResult<Vec<NamespaceDto>> {
|
||||||
|
let namespaces: Vec<NamespaceDto> = namespace::Entity::find()
|
||||||
|
.filter(namespace::Column::Name.is_in(names))
|
||||||
|
.all(trx)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(NamespaceDto::new)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(namespaces)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn tags_by_name(trx: &DatabaseTransaction, tags: Vec<AddTagDto>) -> RepoResult<Vec<TagDto>> {
|
||||||
|
let condition = tags
|
||||||
|
.into_iter()
|
||||||
|
.map(build_tag_condition)
|
||||||
|
.fold(Condition::any(), Condition::add);
|
||||||
|
let tags = tag::Entity::find()
|
||||||
|
.find_also_related(namespace::Entity)
|
||||||
|
.filter(condition)
|
||||||
|
.all(trx)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(map_tag_dto)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_tag_condition(tag: AddTagDto) -> Condition {
|
||||||
|
if let Some(namespace) = tag.namespace {
|
||||||
|
Condition::all()
|
||||||
|
.add(tag::Column::Name.eq(tag.name))
|
||||||
|
.add(namespace::Column::Name.eq(namespace))
|
||||||
|
} else {
|
||||||
|
Condition::all().add(tag::Column::Name.eq(tag.name))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
use crate::dao::tag::{map_tag_dto, TagDao};
|
||||||
|
use crate::dto::TagDto;
|
||||||
|
use mediarepo_core::error::RepoResult;
|
||||||
|
use mediarepo_database::entities::{namespace, tag};
|
||||||
|
use sea_orm::prelude::*;
|
||||||
|
use sea_orm::sea_query::Expr;
|
||||||
|
use sea_orm::Condition;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TagByNameQuery {
|
||||||
|
pub namespace: Option<String>,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TagDao {
|
||||||
|
/// Filters all tags by names
|
||||||
|
/// wildcards are supported
|
||||||
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
|
pub async fn all_by_name(&self, names: Vec<TagByNameQuery>) -> RepoResult<Vec<TagDto>> {
|
||||||
|
let mut condition_count = 0;
|
||||||
|
let condition = names
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(name_query_to_condition)
|
||||||
|
.inspect(|_| condition_count += 1)
|
||||||
|
.fold(Condition::any(), Condition::add);
|
||||||
|
if condition_count == 0 {
|
||||||
|
return Ok(vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tags = tag::Entity::find()
|
||||||
|
.find_also_related(namespace::Entity)
|
||||||
|
.filter(condition)
|
||||||
|
.all(&self.ctx.db)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(map_tag_dto)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(tags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_query_to_condition(query: TagByNameQuery) -> Option<Condition> {
|
||||||
|
let TagByNameQuery { namespace, name } = query;
|
||||||
|
let mut condition = Condition::all();
|
||||||
|
|
||||||
|
if !name.ends_with('*') {
|
||||||
|
condition = condition.add(tag::Column::Name.eq(name))
|
||||||
|
} else if name.len() > 1 {
|
||||||
|
condition =
|
||||||
|
condition.add(tag::Column::Name.like(&*format!("{}%", name.trim_end_matches("*"))))
|
||||||
|
} else if namespace.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
condition = if let Some(namespace) = namespace {
|
||||||
|
condition.add(namespace::Column::Name.eq(namespace))
|
||||||
|
} else {
|
||||||
|
condition.add(Expr::tbl(tag::Entity, tag::Column::NamespaceId).is_null())
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(condition)
|
||||||
|
}
|
Loading…
Reference in New Issue