Working Login & Register functionality
This commit is contained in:
12
src/data/.sqlx/query-3ffe9168c1eb30bb54c65055314655c21f03d04973e4291e6d4b6c7847364659.json
generated
Normal file
12
src/data/.sqlx/query-3ffe9168c1eb30bb54c65055314655c21f03d04973e4291e6d4b6c7847364659.json
generated
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "INSERT INTO Packages (base, name, version, description, url, flagged_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 8
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3ffe9168c1eb30bb54c65055314655c21f03d04973e4291e6d4b6c7847364659"
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "package_base",
|
||||
"name": "base",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | UNSIGNED | NO_DEFAULT_VALUE",
|
||||
|
12
src/data/.sqlx/query-6b6f842d169e2a9628474edcd6dad10878bb9aaa612f62080d40ba24682b0c3b.json
generated
Normal file
12
src/data/.sqlx/query-6b6f842d169e2a9628474edcd6dad10878bb9aaa612f62080d40ba24682b0c3b.json
generated
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "UPDATE Packages SET base = ? WHERE id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "6b6f842d169e2a9628474edcd6dad10878bb9aaa612f62080d40ba24682b0c3b"
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "package_base",
|
||||
"name": "base",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | UNSIGNED | NO_DEFAULT_VALUE",
|
||||
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "UPDATE Packages SET package_base = ? WHERE id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c8de918b432ce82bc7bf1d09a378f9b092d74c2298ac126f7edbb7d59c536910"
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "INSERT INTO Packages (package_base, name, version, description, url, flagged_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 8
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "dfc2574c39f3f5a9afc1bdb642d8bf8a5ce85e7f84fa0dadb53c88cce63e5634"
|
||||
}
|
@ -4,18 +4,14 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
derive_more = { version = "1.0.0", features = ["deref", "into"] }
|
||||
derive_more = { version = "2.0.1", features = ["deref", "into"] }
|
||||
|
||||
futures = "0.3.31"
|
||||
chrono = { version = "0.4.39", default-features = false, features = [
|
||||
"std",
|
||||
"now",
|
||||
] }
|
||||
sqlx = { version = "0.8.3", default-features = false, features = [
|
||||
"mysql",
|
||||
"macros",
|
||||
"chrono",
|
||||
"runtime-tokio",
|
||||
] }
|
||||
sqlx = { version = "0.8.3", default-features = false, features = ["mysql", "macros", "chrono", "runtime-tokio"] }
|
||||
|
||||
# thiserror = "2.0.11"
|
||||
# garde = { version = "0.22.0", features = ["email", "url", "derive"] }
|
||||
|
@ -1,4 +1,5 @@
|
||||
//! `MySQL` adapters.
|
||||
pub mod base;
|
||||
pub mod package;
|
||||
pub mod search;
|
||||
pub mod user;
|
||||
|
@ -12,7 +12,7 @@ where
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
{
|
||||
}
|
||||
impl<E> crate::port::CRUD<E> for BaseAdapter
|
||||
impl<E> crate::port::Crud<E> for BaseAdapter
|
||||
where
|
||||
E: Send,
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
|
@ -12,7 +12,7 @@ where
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
{
|
||||
}
|
||||
impl<E> crate::port::CRUD<E> for PackageAdapter
|
||||
impl<E> crate::port::Crud<E> for PackageAdapter
|
||||
where
|
||||
E: Send,
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
@ -26,7 +26,7 @@ where
|
||||
let created_at = Utc::now();
|
||||
let id = sqlx::query!(
|
||||
"INSERT INTO Packages \
|
||||
(package_base, name, version, description, url, flagged_at, created_at, updated_at) \
|
||||
(base, name, version, description, url, flagged_at, created_at, updated_at) \
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
data.package_base.id,
|
||||
data.name.as_str(),
|
||||
@ -43,7 +43,7 @@ where
|
||||
|
||||
Ok(Self::Existing {
|
||||
id,
|
||||
package_base: data.package_base.id,
|
||||
base: data.package_base.id,
|
||||
name: data.name.into(),
|
||||
version: data.version.into(),
|
||||
description: data.description.into(),
|
||||
@ -88,7 +88,7 @@ where
|
||||
}
|
||||
Field::PackageBase(package_base) => {
|
||||
sqlx::query!(
|
||||
"UPDATE Packages SET package_base = ? WHERE id = ?",
|
||||
"UPDATE Packages SET base = ? WHERE id = ?",
|
||||
package_base.id,
|
||||
existing.id
|
||||
)
|
||||
@ -107,7 +107,7 @@ where
|
||||
existing.id
|
||||
)
|
||||
}
|
||||
Field::URL(url) => {
|
||||
Field::Url(url) => {
|
||||
sqlx::query!(
|
||||
"UPDATE Packages SET url = ? WHERE id = ?",
|
||||
url.as_ref(),
|
||||
@ -135,10 +135,10 @@ where
|
||||
|
||||
match data {
|
||||
Field::Name(s) => existing.name = s.into(),
|
||||
Field::PackageBase(s) => existing.package_base = s.id,
|
||||
Field::PackageBase(s) => existing.base = s.id,
|
||||
Field::Version(s) => existing.version = s.into(),
|
||||
Field::Description(o) => existing.description = o.into(),
|
||||
Field::URL(o) => existing.url = o.into(),
|
||||
Field::Url(o) => existing.url = o.into(),
|
||||
Field::FlaggedAt(date_time) => existing.flagged_at = date_time,
|
||||
Field::CreatedAt(date_time) => existing.created_at = date_time,
|
||||
Field::UpdatedAt(date_time) => existing.updated_at = date_time,
|
||||
|
152
src/data/src/adapter/mysql/search.rs
Normal file
152
src/data/src/adapter/mysql/search.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use crate::port::search::{Data, Entry, Mode, Order, SearchRepository};
|
||||
use crate::{Result, adapter::mysql::search};
|
||||
|
||||
// use chrono::Utc;
|
||||
use futures::TryStreamExt;
|
||||
use sqlx::{Executor, MySql, QueryBuilder, Row};
|
||||
|
||||
pub struct UserAdapter;
|
||||
|
||||
impl<E> SearchRepository<E> for UserAdapter
|
||||
where
|
||||
E: Send,
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
{
|
||||
async fn search(connection: &E, data: Data) -> Result<Vec<Entry>> {
|
||||
let mut builder = QueryBuilder::new(
|
||||
"SELECT \
|
||||
p.id, p.name, p.version, p.url, p.description, \
|
||||
p.updated_at, p.created_at, \
|
||||
pb.id AS base_id, pb.name AS base_name, \
|
||||
( \
|
||||
SELECT COUNT(DISTINCT pbur.user) \
|
||||
FROM PackageBaseUserRoles pbur \
|
||||
WHERE pbur.base = pb.id AND pbur.role = 3 \
|
||||
) AS maintainers_num \
|
||||
FROM \
|
||||
Packages p \
|
||||
JOIN \
|
||||
PackageBases pb ON p.base = pb.id ",
|
||||
);
|
||||
|
||||
let mut push_search = |cond, param| {
|
||||
builder.push(format_args!(
|
||||
" {cond} {param} {} ",
|
||||
if data.exact { "=" } else { "LIKE" }
|
||||
));
|
||||
builder.push_bind(if data.exact {
|
||||
data.search.to_string()
|
||||
} else {
|
||||
format!("%{}%", data.search.as_str())
|
||||
});
|
||||
};
|
||||
|
||||
let join_user = " JOIN PackageBaseUserRoles pbur ON pb.id = pbur.base \
|
||||
JOIN Users u ON pbur.user = u.id WHERE ";
|
||||
|
||||
match data.mode {
|
||||
Mode::Url => push_search("WHERE", "p.url"),
|
||||
Mode::Name => push_search("WHERE", "p.name"),
|
||||
Mode::PackageBase => push_search("WHERE", "pb.name"),
|
||||
Mode::Description => push_search("WHERE", "p.description"),
|
||||
Mode::BaseDescription => push_search("WHERE", "pb.description"),
|
||||
Mode::NameAndDescription => {
|
||||
// WHERE (p.name LIKE '%search_term%' OR p.description LIKE '%search_term%')
|
||||
builder.push(" WHERE p.name LIKE ");
|
||||
builder.push_bind(format!("%{}%", data.search.as_str()));
|
||||
builder.push(" OR p.description LIKE ");
|
||||
builder.push_bind(format!("%{}%", data.search.as_str()));
|
||||
}
|
||||
Mode::User => {
|
||||
push_search(
|
||||
"WHERE EXISTS ( \
|
||||
SELECT 1 \
|
||||
FROM PackageBaseUserRoles pbur \
|
||||
JOIN Users u ON pbur.user = u.id \
|
||||
WHERE pbur.base = pb.id AND",
|
||||
"u.name",
|
||||
);
|
||||
builder.push(" ) ");
|
||||
}
|
||||
Mode::Flagger => {
|
||||
push_search(join_user, "u.name");
|
||||
builder.push(" AND pbur.role = 4 ");
|
||||
} // 4
|
||||
Mode::Packager => {
|
||||
push_search(join_user, "u.name");
|
||||
builder.push(" AND pbur.role = 2 ");
|
||||
} // 2
|
||||
Mode::Submitter => {
|
||||
push_search(join_user, "u.name");
|
||||
builder.push(" AND pbur.role = 1 ");
|
||||
} // 1
|
||||
Mode::Maintainer => {
|
||||
push_search(join_user, "u.name");
|
||||
builder.push(" AND pbur.role = 3 ");
|
||||
} // 3
|
||||
}
|
||||
|
||||
builder.push(format_args!(
|
||||
" ORDER BY {} {} LIMIT {};",
|
||||
match data.order {
|
||||
Order::Name => "p.name",
|
||||
Order::Version => "p.version",
|
||||
Order::BaseName => "pb.name",
|
||||
Order::UpdatedAt => "p.updated_at",
|
||||
Order::CreatedAt => "p.created_at",
|
||||
},
|
||||
if data.ascending { "ASC" } else { "DESC" },
|
||||
data.limit
|
||||
));
|
||||
|
||||
let mut entries = Vec::new();
|
||||
|
||||
let mut rows = builder.build().fetch(connection);
|
||||
while let Some(row) = rows.try_next().await? {
|
||||
entries.push(Entry {
|
||||
id: row.try_get("id")?,
|
||||
name: row.try_get("name")?,
|
||||
version: row.try_get("version")?,
|
||||
base_id: row.try_get("base_id")?,
|
||||
base_name: row.try_get("base_name")?,
|
||||
url: row.try_get("url")?,
|
||||
description: row.try_get("description")?,
|
||||
submitter_id: row.try_get("submitter_id")?,
|
||||
submitter_name: row.try_get("submitter_name")?,
|
||||
updated_at: row.try_get("updated_at")?,
|
||||
created_at: row.try_get("created_at")?,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Validation;
|
||||
use crate::port::search::Search;
|
||||
use sqlx::MySqlPool;
|
||||
|
||||
#[sqlx::test]
|
||||
async fn search() -> crate::Result {
|
||||
let pool = MySqlPool::connect_lazy(
|
||||
&std::env::var("DATABASE_URL")
|
||||
.expect("environment variable `DATABASE_URL` should be set"),
|
||||
)?;
|
||||
|
||||
let data = Data {
|
||||
mode: Mode::NameAndDescription,
|
||||
order: Order::UpdatedAt,
|
||||
search: Search::new("f")?,
|
||||
limit: 50,
|
||||
exact: true,
|
||||
ascending: false,
|
||||
};
|
||||
|
||||
UserAdapter::search(&pool, data).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ where
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
{
|
||||
}
|
||||
impl<E> crate::port::CRUD<E> for UserAdapter
|
||||
impl<E> crate::port::Crud<E> for UserAdapter
|
||||
where
|
||||
E: Send,
|
||||
for<'a> &'a E: Executor<'a, Database = MySql>,
|
||||
|
@ -10,12 +10,13 @@ pub type Result<T = (), E = BoxDynError> = std::result::Result<T, E>;
|
||||
|
||||
pub use chrono::Utc;
|
||||
|
||||
pub use atomic::Atomic;
|
||||
pub use connect::*;
|
||||
|
||||
pub use adapter::mysql::base::BaseAdapter as MySqlBaseAdapter;
|
||||
pub use adapter::mysql::package::PackageAdapter as MySqlPackageAdapter;
|
||||
pub use adapter::mysql::user::UserAdapter as MySqlUserAdapter;
|
||||
pub use port::base::{self, Base, BaseRepository};
|
||||
pub use port::package::{self, Package, PackageRepository};
|
||||
pub use port::user::{self, User, UserRepository};
|
||||
pub use atomic::Atomic;
|
||||
pub use connect::*;
|
||||
pub use port::base::{Base, BaseRepository};
|
||||
pub use port::package::{Package, PackageRepository};
|
||||
pub use port::user::{User, UserRepository};
|
||||
pub use port::*;
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
//! Low-level repository traits for unified data access.
|
||||
//!
|
||||
//! No data validation besides very basic one like length violation.
|
||||
use crate::Result;
|
||||
//! Very mild argument validation.
|
||||
use crate::{BoxDynError, Result};
|
||||
|
||||
pub mod base;
|
||||
pub mod package;
|
||||
pub mod search;
|
||||
pub mod user;
|
||||
|
||||
pub trait CRUD<C> {
|
||||
pub trait Crud<C> {
|
||||
type New;
|
||||
type Unique;
|
||||
type Update;
|
||||
@ -16,23 +17,27 @@ pub trait CRUD<C> {
|
||||
fn create(
|
||||
connection: &mut C,
|
||||
data: Self::New,
|
||||
) -> impl Future<Output = crate::Result<Self::Existing>> + Send;
|
||||
) -> impl Future<Output = Result<Self::Existing>> + Send;
|
||||
fn read(
|
||||
connection: &C,
|
||||
data: Self::Unique,
|
||||
) -> impl Future<Output = crate::Result<Option<Self::Existing>>> + Send;
|
||||
) -> impl Future<Output = Result<Option<Self::Existing>>> + Send;
|
||||
fn update(
|
||||
connection: &mut C,
|
||||
existing: &mut Self::Existing,
|
||||
data: Self::Update,
|
||||
) -> impl Future<Output = crate::Result> + Send;
|
||||
fn delete(connection: &mut C, data: Self::Unique)
|
||||
-> impl Future<Output = crate::Result> + Send;
|
||||
) -> impl Future<Output = Result> + Send;
|
||||
fn delete(connection: &mut C, data: Self::Unique) -> impl Future<Output = Result> + Send;
|
||||
}
|
||||
|
||||
trait CharLength {
|
||||
pub trait CharLength {
|
||||
fn length(&self) -> usize;
|
||||
}
|
||||
impl CharLength for &str {
|
||||
fn length(&self) -> usize {
|
||||
self.chars().count()
|
||||
}
|
||||
}
|
||||
impl CharLength for String {
|
||||
fn length(&self) -> usize {
|
||||
self.chars().count()
|
||||
@ -44,15 +49,43 @@ impl CharLength for Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
trait MaxLength {
|
||||
trait Validatable {
|
||||
type Inner: CharLength;
|
||||
const MAX_LENGTH: usize;
|
||||
fn encapsulate(value: Self::Inner) -> Self;
|
||||
}
|
||||
|
||||
fn validate(value: &Self::Inner) -> Result<(), &'static str> {
|
||||
#[allow(private_bounds)] // don't expose the impl details
|
||||
pub trait Validation<T>: Validatable
|
||||
where
|
||||
T: CharLength + Into<Self::Inner>,
|
||||
{
|
||||
fn valid(value: &T) -> Result<(), String> {
|
||||
if value.length() > Self::MAX_LENGTH {
|
||||
Err("too long")
|
||||
Err(format!(
|
||||
"too long (length: {}, max length: {})",
|
||||
value.length(),
|
||||
Self::MAX_LENGTH
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn new(value: T) -> Result<Self, (T, BoxDynError)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match Self::valid(&value) {
|
||||
Ok(()) => Ok(Self::encapsulate(value.into())),
|
||||
Err(e) => Err((value, e.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Validation<U> for T
|
||||
where
|
||||
T: Validatable,
|
||||
U: CharLength + Into<T::Inner>,
|
||||
{
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
use super::MaxLength;
|
||||
use super::Validatable;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_more::{Deref, Into};
|
||||
|
||||
pub trait BaseRepository<C>:
|
||||
super::CRUD<C, New = New, Unique = u64, Update = Field, Existing = Base>
|
||||
super::Crud<C, New = New, Unique = u64, Update = Field, Existing = Base>
|
||||
{
|
||||
}
|
||||
|
||||
@ -13,33 +13,21 @@ pub trait BaseRepository<C>:
|
||||
|
||||
#[derive(Clone, Deref, Into)]
|
||||
pub struct Name(String);
|
||||
impl MaxLength for Name {
|
||||
impl Validatable for Name {
|
||||
type Inner = String;
|
||||
const MAX_LENGTH: usize = 127;
|
||||
}
|
||||
impl TryFrom<String> for Name {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
Self::validate(&value)?;
|
||||
Ok(Self(value))
|
||||
fn encapsulate(value: Self::Inner) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deref, Into)]
|
||||
pub struct Description(Option<String>);
|
||||
impl MaxLength for Description {
|
||||
impl Validatable for Description {
|
||||
type Inner = Option<String>;
|
||||
const MAX_LENGTH: usize = 510;
|
||||
}
|
||||
impl TryFrom<Option<String>> for Description {
|
||||
type Error = (Option<String>, &'static str);
|
||||
|
||||
fn try_from(value: Option<String>) -> Result<Self, Self::Error> {
|
||||
match Self::validate(&value) {
|
||||
Ok(()) => Ok(Self(value)),
|
||||
Err(e) => Err((value, e)),
|
||||
}
|
||||
fn encapsulate(value: Self::Inner) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,79 +1,51 @@
|
||||
use super::MaxLength;
|
||||
use super::Validatable;
|
||||
use crate::Base;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_more::{Deref, Into};
|
||||
|
||||
pub trait PackageRepository<C>:
|
||||
super::CRUD<C, New = New, Update = Field, Unique = Unique, Existing = Package>
|
||||
super::Crud<C, New = New, Update = Field, Unique = Unique, Existing = Package>
|
||||
{
|
||||
}
|
||||
|
||||
#[derive(Clone, Deref, Into)]
|
||||
pub struct Name(String);
|
||||
impl MaxLength for Name {
|
||||
impl Validatable for Name {
|
||||
type Inner = String;
|
||||
const MAX_LENGTH: usize = 127;
|
||||
}
|
||||
impl TryFrom<String> for Name {
|
||||
type Error = (String, &'static str);
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
match Self::validate(&value) {
|
||||
Ok(()) => Ok(Self(value)),
|
||||
Err(e) => Err((value, e)),
|
||||
}
|
||||
fn encapsulate(value: Self::Inner) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deref, Into)]
|
||||
pub struct Version(String);
|
||||
impl MaxLength for Version {
|
||||
impl Validatable for Version {
|
||||
type Inner = String;
|
||||
const MAX_LENGTH: usize = 127;
|
||||
}
|
||||
impl TryFrom<String> for Version {
|
||||
type Error = (String, &'static str);
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
match Self::validate(&value) {
|
||||
Ok(()) => Ok(Self(value)),
|
||||
Err(e) => Err((value, e)),
|
||||
}
|
||||
fn encapsulate(value: Self::Inner) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deref, Into)]
|
||||
pub struct Description(Option<String>);
|
||||
impl MaxLength for Description {
|
||||
impl Validatable for Description {
|
||||
type Inner = Option<String>;
|
||||
const MAX_LENGTH: usize = 255;
|
||||
}
|
||||
impl TryFrom<Option<String>> for Description {
|
||||
type Error = (Option<String>, &'static str);
|
||||
|
||||
fn try_from(value: Option<String>) -> Result<Self, Self::Error> {
|
||||
match Self::validate(&value) {
|
||||
Ok(()) => Ok(Self(value)),
|
||||
Err(e) => Err((value, e)),
|
||||
}
|
||||
fn encapsulate(value: Self::Inner) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deref, Into)]
|
||||
pub struct URL(Option<String>);
|
||||
impl MaxLength for URL {
|
||||
pub struct Url(Option<String>);
|
||||
impl Validatable for Url {
|
||||
type Inner = Option<String>;
|
||||
const MAX_LENGTH: usize = 510;
|
||||
}
|
||||
impl TryFrom<Option<String>> for URL {
|
||||
type Error = (Option<String>, &'static str);
|
||||
|
||||
fn try_from(value: Option<String>) -> Result<Self, Self::Error> {
|
||||
match Self::validate(&value) {
|
||||
Ok(()) => Ok(Self(value)),
|
||||
Err(e) => Err((value, e)),
|
||||
}
|
||||
fn encapsulate(value: Self::Inner) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +59,7 @@ pub enum Field {
|
||||
Name(Name),
|
||||
Version(Version),
|
||||
Description(Description),
|
||||
URL(URL),
|
||||
Url(Url),
|
||||
FlaggedAt(Option<DateTime<Utc>>),
|
||||
CreatedAt(DateTime<Utc>),
|
||||
UpdatedAt(DateTime<Utc>),
|
||||
@ -98,13 +70,13 @@ pub struct New {
|
||||
pub name: Name,
|
||||
pub version: Version,
|
||||
pub description: Description,
|
||||
pub url: URL,
|
||||
pub url: Url,
|
||||
pub flagged_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
pub struct Package {
|
||||
pub(crate) id: u64,
|
||||
pub(crate) package_base: u64,
|
||||
pub(crate) base: u64,
|
||||
pub(crate) name: String,
|
||||
pub(crate) version: String,
|
||||
pub(crate) description: Option<String>,
|
||||
@ -119,7 +91,7 @@ impl Package {
|
||||
self.id
|
||||
}
|
||||
pub const fn package_base(&self) -> u64 {
|
||||
self.package_base
|
||||
self.base
|
||||
}
|
||||
pub const fn name(&self) -> &String {
|
||||
&self.name
|
||||
|
69
src/data/src/port/search.rs
Normal file
69
src/data/src/port/search.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use super::Validatable;
|
||||
use crate::Result;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_more::{Deref, Into};
|
||||
|
||||
pub trait SearchRepository<C> {
|
||||
fn search(connection: &C, data: Data) -> impl Future<Output = Result<Vec<Entry>>> + Send;
|
||||
}
|
||||
|
||||
#[derive(Clone, Deref, Into)]
|
||||
pub struct Search(String);
|
||||
impl Validatable for Search {
|
||||
type Inner = String;
|
||||
const MAX_LENGTH: usize = 255;
|
||||
fn encapsulate(value: Self::Inner) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Data {
|
||||
pub mode: Mode,
|
||||
pub order: Order,
|
||||
pub search: Search,
|
||||
|
||||
pub limit: u8,
|
||||
pub exact: bool,
|
||||
pub ascending: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct Entry {
|
||||
pub id: u64,
|
||||
pub name: Box<str>,
|
||||
pub version: Box<str>,
|
||||
pub base_id: u64,
|
||||
pub base_name: Box<str>,
|
||||
pub url: Option<Box<str>>,
|
||||
pub description: Box<str>,
|
||||
pub submitter_id: u64,
|
||||
pub submitter_name: Box<str>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Mode {
|
||||
Url,
|
||||
Name,
|
||||
PackageBase,
|
||||
Description,
|
||||
BaseDescription,
|
||||
NameAndDescription,
|
||||
User,
|
||||
Flagger,
|
||||
Packager,
|
||||
Submitter,
|
||||
Maintainer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Order {
|
||||
Name,
|
||||
Version,
|
||||
BaseName,
|
||||
// Submitter,
|
||||
UpdatedAt,
|
||||
CreatedAt,
|
||||
}
|
@ -1,61 +1,40 @@
|
||||
use super::MaxLength;
|
||||
use super::Validatable;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_more::{Deref, Into};
|
||||
|
||||
pub trait UserRepository<C>:
|
||||
super::CRUD<C, New = New, Update = Field, Unique = Unique, Existing = User>
|
||||
super::Crud<C, New = New, Update = Field, Unique = Unique, Existing = User>
|
||||
{
|
||||
}
|
||||
|
||||
#[derive(Clone, Deref, Into)]
|
||||
pub struct Name(String);
|
||||
impl MaxLength for Name {
|
||||
impl Validatable for Name {
|
||||
type Inner = String;
|
||||
const MAX_LENGTH: usize = 31;
|
||||
}
|
||||
impl TryFrom<String> for Name {
|
||||
type Error = (String, &'static str);
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
match Self::validate(&value) {
|
||||
Ok(()) => Ok(Self(value)),
|
||||
Err(e) => Err((value, e)),
|
||||
}
|
||||
fn encapsulate(value: Self::Inner) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deref, Into)]
|
||||
pub struct Email(String);
|
||||
impl MaxLength for Email {
|
||||
impl Validatable for Email {
|
||||
type Inner = String;
|
||||
const MAX_LENGTH: usize = 255;
|
||||
}
|
||||
impl TryFrom<String> for Email {
|
||||
type Error = (String, &'static str);
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
match Self::validate(&value) {
|
||||
Ok(()) => Ok(Self(value)),
|
||||
Err(e) => Err((value, e)),
|
||||
}
|
||||
fn encapsulate(value: Self::Inner) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deref, Into)]
|
||||
pub struct Password(String);
|
||||
impl MaxLength for Password {
|
||||
impl Validatable for Password {
|
||||
type Inner = String;
|
||||
const MAX_LENGTH: usize = 255;
|
||||
}
|
||||
impl TryFrom<String> for Password {
|
||||
type Error = (String, &'static str);
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
match Self::validate(&value) {
|
||||
Ok(()) => Ok(Self(value)),
|
||||
Err(e) => Err((value, e)),
|
||||
}
|
||||
fn encapsulate(value: Self::Inner) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,7 +60,7 @@ pub struct New {
|
||||
pub last_used: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct User {
|
||||
pub(crate) id: u64,
|
||||
pub(crate) name: String,
|
||||
|
Reference in New Issue
Block a user