Initial commit

This commit is contained in:
Aleksandr 2025-09-19 18:31:17 +03:00
commit bd9b07052b
81 changed files with 5516 additions and 0 deletions

View file

@ -0,0 +1,53 @@
use eva::data;
use viendesu_core::{
errors,
requests::authors as reqs,
types::{Patch, author, file, user},
};
use crate::requests::status_code;
#[serde_with::apply(Patch => #[serde(default)])]
#[data]
pub struct Update {
pub title: Patch<author::Title>,
pub description: Patch<Option<author::Description>>,
pub pfp: Patch<Option<file::Id>>,
pub slug: Patch<author::Slug>,
pub verified: Patch<bool>,
}
impl_req!(Update => [reqs::update::Ok; reqs::update::Err]);
status_code::direct!(reqs::update::Ok => OK);
status_code::map!(reqs::update::Err => [NotFound]);
#[data]
pub struct Create {
pub title: author::Title,
pub slug: author::Slug,
pub description: Option<author::Description>,
pub owner: Option<user::Id>,
}
impl_req!(Create => [reqs::create::Ok; reqs::create::Err]);
status_code::direct!(reqs::create::Ok => OK);
status_code::map!(reqs::create::Err => [NotFound, AlreadyExists, NoSuchUser]);
#[data]
pub struct Get {}
impl_req!(Get => [reqs::get::Ok; reqs::get::Err]);
status_code::direct!(reqs::get::Ok => OK);
status_code::map!(reqs::get::Err => [NotFound]);
const _: () = {
use errors::authors::*;
use status_code::direct;
direct!(NotFound => NOT_FOUND);
direct!(AlreadyExists => BAD_REQUEST);
};

View file

@ -0,0 +1,56 @@
use eva::data;
use crate::requests::status_code;
use viendesu_core::{
errors,
requests::boards as reqs,
types::{Patch, board, message},
};
#[data]
pub struct Get {}
impl_req!(Get => [reqs::get::Ok; reqs::get::Err]);
status_code::direct!(reqs::get::Ok => OK);
status_code::map!(reqs::get::Err => [NotFound]);
#[data]
pub struct Edit {
pub text: Patch<message::Text>,
pub slug: Patch<Option<board::Slug>>,
}
impl_req!(Edit => [reqs::edit::Ok; reqs::edit::Err]);
status_code::direct!(reqs::edit::Ok => OK);
status_code::map!(reqs::edit::Err => [NotFound]);
#[data]
pub struct Delete {}
impl_req!(Delete => [reqs::delete::Ok; reqs::delete::Err]);
status_code::direct!(reqs::delete::Ok => OK);
status_code::map!(reqs::delete::Err => [NotFound]);
#[data]
pub struct Create {
pub slug: board::Slug,
pub initial_message: message::Text,
pub by: Option<message::ById>,
}
impl_req!(Create => [reqs::create::Ok; reqs::create::Err]);
status_code::direct!(reqs::create::Ok => OK);
status_code::map!(reqs::create::Err => [AlreadyExists]);
const _: () = {
use errors::boards::*;
use status_code::direct;
direct!(AlreadyExists => BAD_REQUEST);
direct!(NotFound => NOT_FOUND);
};

View file

@ -0,0 +1,81 @@
use eva::data;
use crate::requests::status_code;
use viendesu_core::{
errors,
requests::games as reqs,
types::{Patch, author, file, game},
};
#[data]
#[serde_with::apply(Patch => #[serde(default)])]
pub struct Update {
pub title: Patch<game::Title>,
pub description: Patch<Option<game::Description>>,
pub slug: Patch<game::Slug>,
pub thumbnail: Patch<Option<file::Id>>,
pub genres: Patch<game::Genres>,
pub badges: Patch<game::Badges>,
pub tags: Patch<game::Tags>,
pub screenshots: Patch<game::Screenshots>,
pub published: Patch<bool>,
}
impl_req!(Update => [reqs::update::Ok; reqs::update::Err]);
status_code::direct!(reqs::update::Ok => OK);
status_code::map!(reqs::update::Err => [NotFound]);
#[data]
pub struct Search {
pub query: Option<game::SearchQuery>,
pub author: Option<author::Selector>,
#[serde(default)]
pub include: reqs::search::Condition,
#[serde(default)]
pub exclude: reqs::search::Condition,
#[serde(default)]
pub order: reqs::search::Order,
#[serde(default)]
pub sort_by: reqs::search::SortBy,
pub limit: Option<reqs::search::Limit>,
}
impl_req!(Search => [reqs::search::Ok; reqs::search::Err]);
status_code::direct!(reqs::search::Ok => OK);
status_code::map!(reqs::search::Err => [NoSuchAuthor]);
#[data]
pub struct Get {}
impl_req!(Get => [reqs::get::Ok; reqs::get::Err]);
status_code::direct!(reqs::get::Ok => OK);
status_code::map!(reqs::get::Err => [NotFound, NoSuchAuthor]);
#[data]
pub struct Create {
pub title: game::Title,
pub description: Option<game::Description>,
pub thumbnail: Option<file::Id>,
pub author: author::Id,
pub slug: Option<game::Slug>,
pub vndb: Option<game::VndbId>,
pub release_date: Option<game::ReleaseDate>,
}
impl_req!(Create => [reqs::create::Ok; reqs::create::Err]);
status_code::direct!(reqs::create::Ok => CREATED);
status_code::map!(reqs::create::Err => [AlreadyTaken, NoSuchAuthor]);
const _: () = {
use errors::games::*;
use status_code::direct;
direct!(NotFound => NOT_FOUND);
direct!(NotAnOwner => FORBIDDEN);
direct!(AlreadyTaken => BAD_REQUEST);
};

View file

@ -0,0 +1,53 @@
use eva::data;
use crate::requests::status_code;
use viendesu_core::{
errors,
requests::messages as reqs,
types::{message, thread},
};
#[data]
pub struct Edit {
pub text: message::Text,
}
impl_req!(Edit => [reqs::edit::Ok; reqs::edit::Err]);
status_code::direct!(reqs::edit::Ok => OK);
status_code::map!(reqs::edit::Err => [NotFound]);
#[data]
pub struct Delete {}
impl_req!(Delete => [reqs::delete::Ok; reqs::delete::Err]);
status_code::direct!(reqs::delete::Ok => OK);
status_code::map!(reqs::delete::Err => [NotFound]);
#[data]
pub struct Post {
pub thread: thread::Selector,
pub text: message::Text,
}
impl_req!(Post => [reqs::post::Ok; reqs::post::Err]);
status_code::direct!(reqs::post::Ok => OK);
status_code::map!(reqs::post::Err => [NoSuchThread]);
#[data]
pub struct Get {}
impl_req!(Get => [reqs::get::Ok; reqs::get::Err]);
status_code::direct!(reqs::get::Ok => OK);
status_code::map!(reqs::get::Err => [NotFound]);
const _: () = {
use errors::messages::*;
use status_code::direct;
direct!(NotFound => NOT_FOUND);
};

32
http/src/requests/mod.rs Normal file
View file

@ -0,0 +1,32 @@
pub mod status_code;
pub trait Request: Send + Sync + 'static + for<'de> serde::Deserialize<'de> {
type Response: IsResponse;
type Error: IsResponse;
}
eva::trait_set! {
pub trait IsResponse = status_code::HasStatusCode + serde::Serialize + Send + Sync + 'static;
}
macro_rules! impl_req {
($Input:ty => [$Ok:ty; $Err:ty]) => {
const _: () = {
use $crate::requests::Request;
impl Request for $Input {
type Response = $Ok;
type Error = $Err;
}
};
};
}
pub mod users;
pub mod authors;
pub mod games;
pub mod boards;
pub mod messages;
pub mod threads;

View file

@ -0,0 +1,81 @@
use http::status::StatusCode;
use viendesu_core::errors;
pub trait HasStatusCode {
fn status_code(&self) -> StatusCode;
}
impl<O: HasStatusCode, E: HasStatusCode> HasStatusCode for Result<O, E> {
fn status_code(&self) -> StatusCode {
match self {
Ok(o) => o.status_code(),
Err(e) => e.status_code(),
}
}
}
impl<S: HasStatusCode> HasStatusCode for errors::Generic<S> {
fn status_code(&self) -> StatusCode {
match self {
Self::Aux(aux) => aux.status_code(),
Self::Spec(spec) => spec.status_code(),
}
}
}
impl HasStatusCode for errors::Aux {
fn status_code(&self) -> StatusCode {
use StatusCode as C;
use errors::Aux::*;
match self {
Unauthenticated => C::UNAUTHORIZED,
InvalidRole(e) => e.status_code(),
InvalidSession(e) => e.status_code(),
Captcha(..) | Deserialization(..) => C::BAD_REQUEST,
Db(..) | InternalError(..) | ObjectStore(..) | Mail(..) => C::INTERNAL_SERVER_ERROR,
}
}
}
macro_rules! map {
($Ty:ty => [$($Item:ident),* $(,)?]) => {
const _: () = {
use $crate::requests::status_code::HasStatusCode;
use ::http::status::StatusCode;
impl HasStatusCode for $Ty {
fn status_code(&self) -> StatusCode {
match *self {$(
Self::$Item(ref e) => e.status_code(),
)*}
}
}
};
};
}
pub(crate) use map;
macro_rules! direct {
($($ty:ty => $code:ident),* $(,)?) => {$(
const _: () = {
use $crate::requests::status_code::HasStatusCode;
use ::http::status::StatusCode;
impl HasStatusCode for $ty {
fn status_code(&self) -> StatusCode {
StatusCode::$code
}
}
};
)*};
}
pub(crate) use direct;
direct! {
errors::auth::InvalidRole => FORBIDDEN,
errors::auth::InvalidSession => BAD_REQUEST,
}

View file

@ -0,0 +1,66 @@
use eva::data;
use crate::requests::status_code;
use viendesu_core::{
errors,
requests::threads as reqs,
types::{Patch, board, message, thread},
};
#[data]
pub struct Get {}
impl_req!(Get => [reqs::get::Ok; reqs::get::Err]);
status_code::direct!(reqs::get::Ok => OK);
status_code::map!(reqs::get::Err => [NotFound]);
#[data]
pub struct Delete {}
impl_req!(Delete => [reqs::delete::Ok; reqs::delete::Err]);
status_code::direct!(reqs::delete::Ok => OK);
status_code::map!(reqs::delete::Err => [NotFound]);
#[data]
pub struct Edit {
pub text: Patch<message::Text>,
}
impl_req!(Edit => [reqs::edit::Ok; reqs::edit::Err]);
status_code::direct!(reqs::edit::Ok => OK);
status_code::map!(reqs::edit::Err => [NotFound, NotAnOwner]);
#[data]
pub struct Search {
#[serde(default)]
pub limit: reqs::search::Limit,
pub after: Option<thread::Id>,
}
impl_req!(Search => [reqs::search::Ok; reqs::search::Err]);
status_code::direct!(reqs::search::Ok => OK);
status_code::map!(reqs::search::Err => []);
#[data]
pub struct Create {
pub board: board::Selector,
pub initial_message: message::Text,
}
impl_req!(Create => [reqs::create::Ok; reqs::create::Err]);
status_code::direct!(reqs::create::Ok => CREATED);
status_code::map!(reqs::create::Err => [NoSuchBoard]);
const _: () = {
use errors::threads::*;
use status_code::direct;
direct!(NotAnOwner => FORBIDDEN);
direct!(NotFound => NOT_FOUND);
};

102
http/src/requests/users.rs Normal file
View file

@ -0,0 +1,102 @@
use eva::data;
use viendesu_core::{
errors,
requests::users as reqs,
types::{Patch, file, user as core},
};
use super::status_code;
#[data]
pub struct CheckAuth {}
impl_req!(CheckAuth => [reqs::check_auth::Ok; reqs::check_auth::Err]);
status_code::direct!(reqs::check_auth::Ok => OK);
status_code::map!(reqs::check_auth::Err => []);
#[data]
#[non_exhaustive]
#[derive(Default)]
pub struct Get {}
impl_req!(Get => [reqs::get::Ok; reqs::get::Err]);
status_code::map!(reqs::get::Err => [NotFound]);
status_code::direct!(reqs::get::Ok => OK);
#[data]
pub struct SignIn {
pub nickname: core::Nickname,
pub password: core::Password,
}
impl_req!(SignIn => [reqs::sign_in::Ok; reqs::sign_in::Err]);
status_code::map!(reqs::sign_in::Err => [NotFound, InvalidPassword, MustCompleteSignUp]);
status_code::direct!(reqs::sign_in::Ok => OK);
#[data]
#[non_exhaustive]
#[derive(Default)]
pub struct ConfirmSignUp {}
impl_req!(ConfirmSignUp => [reqs::confirm_sign_up::Ok; reqs::confirm_sign_up::Err]);
status_code::direct!(reqs::confirm_sign_up::Ok => OK);
status_code::map!(reqs::confirm_sign_up::Err => [NotFoundOrCompleted, InvalidSignUpToken]);
#[data]
pub struct SignUp {
pub nickname: core::Nickname,
pub email: core::Email,
pub password: core::Password,
pub display_name: Option<core::DisplayName>,
}
impl_req!(SignUp => [reqs::sign_up::Ok; reqs::sign_up::Err]);
status_code::direct!(reqs::sign_up::Ok => CREATED);
status_code::map!(reqs::sign_up::Err => [AlreadyTaken]);
#[serde_with::apply(
Patch => #[serde(default)]
)]
#[data]
#[non_exhaustive]
#[derive(Default)]
pub struct Update {
pub nickname: Patch<core::Nickname>,
pub display_name: Patch<Option<core::DisplayName>>,
pub bio: Patch<Option<core::Bio>>,
pub password: Patch<core::Password>,
pub role: Patch<core::Role>,
pub pfp: Patch<Option<file::Id>>,
pub email: Patch<core::Email>,
}
impl From<reqs::update::Update> for Update {
fn from(u: reqs::update::Update) -> Self {
Self {
nickname: u.nickname,
display_name: u.display_name,
bio: u.bio,
password: u.password,
role: u.role,
pfp: u.pfp,
email: u.email,
}
}
}
impl_req!(Update => [reqs::update::Ok; reqs::update::Err]);
status_code::direct!(reqs::update::Ok => OK);
status_code::map!(reqs::update::Err => [NotFound]);
const _: () = {
use errors::users::*;
use status_code::direct;
direct!(NotFound => NOT_FOUND);
direct!(InvalidPassword => FORBIDDEN);
direct!(MustCompleteSignUp => FORBIDDEN);
direct!(AlreadyTaken => BAD_REQUEST);
direct!(NotFoundOrCompleted => BAD_REQUEST);
direct!(InvalidSignUpToken => BAD_REQUEST);
};