init
This commit is contained in:
commit
618d1c40d6
3
.env.example
Normal file
3
.env.example
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
DATABASE_URL=mysql://root:rooter@localhost:16306/catter
|
||||||
|
HOST=127.0.0.1
|
||||||
|
PORT=8000
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
.env
|
||||||
3284
Cargo.lock
generated
Normal file
3284
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "catter"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-web = "4"
|
||||||
|
sea-orm = { version = "1.1.5", features = [
|
||||||
|
"sqlx-mysql",
|
||||||
|
"runtime-tokio-native-tls",
|
||||||
|
"macros",
|
||||||
|
] }
|
||||||
|
dotenvy = "0.15"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
bcrypt = "0.17"
|
||||||
|
jwt = "0.16"
|
||||||
20
README.md
Normal file
20
README.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Rusty Catter
|
||||||
|
|
||||||
|
Re-implementation of [Catter](https://github.com/lilianalillyy/lilianaa.dev/tree/main/catter), a small project originally written in PHP for serving pictures of my cats.
|
||||||
|
Code is very WIP. The entities are generated by SeaORM CLI based on the current database schema. May (probably will) change.
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
| ------------ | -------------------------------- |
|
||||||
|
| HOST | Hostname of the server |
|
||||||
|
| PORT | Port of the server |
|
||||||
|
| DATABASE_URL | Database URL to the MySQL server |
|
||||||
|
|
||||||
|
### Docker env vars
|
||||||
|
|
||||||
|
Docker setup is essentially the same as in the original PHP project, just without php-fpm & nginx. Environment variables have changed:
|
||||||
|
|
||||||
|
- `MYSQL_*` -> `DOCKER_MYSQL_*`
|
||||||
|
- `MYSQL_PORT` -> `DOCKER_EXPOSED_MYSQL_PORT`
|
||||||
|
- `ADMINER_PORT` -> `DOCKER_ADMINER_PORT`
|
||||||
8
compose.dev.yml
Normal file
8
compose.dev.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
services:
|
||||||
|
adminer:
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
image: dockette/adminer:latest
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "${DOCKER_ADMINER_PORT:-16003}:80"
|
||||||
18
compose.yml
Normal file
18
compose.yml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: mysql:8.0
|
||||||
|
volumes:
|
||||||
|
- "db-data:/var/lib/mysql"
|
||||||
|
restart: always
|
||||||
|
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
|
||||||
|
hostname: "${MYSQL_HOST:-db}"
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: "${DOCKER_MYSQL_PASSWORD:-rooter}"
|
||||||
|
MYSQL_DATABASE: "${DOCKER_MYSQL_NAME:-catter}"
|
||||||
|
ports:
|
||||||
|
- "${DOCKER_EXPOSED_MYSQL_PORT:-16306}:3306"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db-data:
|
||||||
|
external: true
|
||||||
|
name: catter-db-data
|
||||||
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
tab_spaces = 2
|
||||||
36
src/endpoints/auth/login.rs
Normal file
36
src/endpoints/auth/login.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
use crate::{
|
||||||
|
endpoints::prelude::*,
|
||||||
|
entities::{prelude::User, user},
|
||||||
|
models::{error::ErrorResponse, user::UserModel},
|
||||||
|
state::AppState,
|
||||||
|
utils::PasswordHasher,
|
||||||
|
};
|
||||||
|
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct LoginRequest {
|
||||||
|
email: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/login")]
|
||||||
|
pub async fn handler(body: Json<LoginRequest>, state: Data<AppState>) -> impl Responder {
|
||||||
|
let user = User::find()
|
||||||
|
.filter(user::Column::Email.eq(body.email.clone()))
|
||||||
|
.one(&state.db)
|
||||||
|
.await
|
||||||
|
.expect("database error");
|
||||||
|
|
||||||
|
if user.is_none() {
|
||||||
|
return HttpResponse::Unauthorized().json(ErrorResponse::new(401, "invalid email"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = user.unwrap();
|
||||||
|
|
||||||
|
if !PasswordHasher::verify(&body.password, &user.password) {
|
||||||
|
return HttpResponse::Unauthorized().json(ErrorResponse::new(401, "invalid password"));
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse::Ok().json(UserModel::from(user))
|
||||||
|
}
|
||||||
7
src/endpoints/auth/mod.rs
Normal file
7
src/endpoints/auth/mod.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use actix_web::dev::HttpServiceFactory;
|
||||||
|
|
||||||
|
mod login;
|
||||||
|
|
||||||
|
pub fn scope() -> impl HttpServiceFactory + 'static {
|
||||||
|
actix_web::web::scope("/auth").service(login::handler)
|
||||||
|
}
|
||||||
7
src/endpoints/misc/mod.rs
Normal file
7
src/endpoints/misc/mod.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use actix_web::dev::HttpServiceFactory;
|
||||||
|
|
||||||
|
mod ping;
|
||||||
|
|
||||||
|
pub fn scope() -> impl HttpServiceFactory + 'static {
|
||||||
|
actix_web::web::scope("/misc").service(ping::handler)
|
||||||
|
}
|
||||||
6
src/endpoints/misc/ping.rs
Normal file
6
src/endpoints/misc/ping.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use actix_web::{HttpResponse, Responder, get};
|
||||||
|
|
||||||
|
#[get("/ping")]
|
||||||
|
pub async fn handler() -> impl Responder {
|
||||||
|
HttpResponse::Ok().body("pong")
|
||||||
|
}
|
||||||
4
src/endpoints/mod.rs
Normal file
4
src/endpoints/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub mod auth;
|
||||||
|
pub mod misc;
|
||||||
|
mod prelude;
|
||||||
|
pub mod users;
|
||||||
4
src/endpoints/prelude.rs
Normal file
4
src/endpoints/prelude.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub use actix_web::{
|
||||||
|
HttpResponse, Responder, get, post,
|
||||||
|
web::{Data, Json},
|
||||||
|
};
|
||||||
12
src/endpoints/users/list.rs
Normal file
12
src/endpoints/users/list.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use sea_orm::EntityTrait;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
endpoints::prelude::*, entities::prelude::User, models::user::UserModel, state::AppState,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
pub async fn handler(state: Data<AppState>) -> impl Responder {
|
||||||
|
let users = User::find().all(&state.db).await.expect("database error");
|
||||||
|
|
||||||
|
HttpResponse::Ok().json(UserModel::from_many(users))
|
||||||
|
}
|
||||||
7
src/endpoints/users/mod.rs
Normal file
7
src/endpoints/users/mod.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use actix_web::dev::HttpServiceFactory;
|
||||||
|
|
||||||
|
mod list;
|
||||||
|
|
||||||
|
pub fn scope() -> impl HttpServiceFactory + 'static {
|
||||||
|
actix_web::web::scope("/users").service(list::handler)
|
||||||
|
}
|
||||||
52
src/entities/cat.rs
Normal file
52
src/entities/cat.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.6
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "cat")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
pub cat_camera_tag: Option<i32>,
|
||||||
|
pub image: String,
|
||||||
|
pub content: Option<String>,
|
||||||
|
pub date: DateTime,
|
||||||
|
pub thumbnail: Option<String>,
|
||||||
|
pub hidden: i8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(has_many = "super::cat_content_tags::Entity")]
|
||||||
|
CatContentTags,
|
||||||
|
#[sea_orm(has_many = "super::cat_tag::Entity")]
|
||||||
|
CatTag,
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::tag::Entity",
|
||||||
|
from = "Column::CatCameraTag",
|
||||||
|
to = "super::tag::Column::Id",
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "NoAction"
|
||||||
|
)]
|
||||||
|
Tag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::cat_content_tags::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::CatContentTags.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::cat_tag::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::CatTag.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::tag::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Tag.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
46
src/entities/cat_content_tags.rs
Normal file
46
src/entities/cat_content_tags.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.6
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "cat_content_tags")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub cat_id: i32,
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub tag_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::cat::Entity",
|
||||||
|
from = "Column::CatId",
|
||||||
|
to = "super::cat::Column::Id",
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
Cat,
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::tag::Entity",
|
||||||
|
from = "Column::TagId",
|
||||||
|
to = "super::tag::Column::Id",
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
Tag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::cat::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Cat.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::tag::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Tag.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
46
src/entities/cat_tag.rs
Normal file
46
src/entities/cat_tag.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.6
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "cat_tag")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub cat_id: i32,
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub tag_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::cat::Entity",
|
||||||
|
from = "Column::CatId",
|
||||||
|
to = "super::cat::Column::Id",
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
Cat,
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::tag::Entity",
|
||||||
|
from = "Column::TagId",
|
||||||
|
to = "super::tag::Column::Id",
|
||||||
|
on_update = "NoAction",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
Tag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::cat::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Cat.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::tag::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Tag.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
17
src/entities/doctrine_migration_versions.rs
Normal file
17
src/entities/doctrine_migration_versions.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.6
|
||||||
|
|
||||||
|
// use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
// #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
// #[sea_orm(table_name = "doctrine_migration_versions")]
|
||||||
|
// pub struct Model {
|
||||||
|
// #[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
// pub version: String,
|
||||||
|
// pub executed_at: Option<DateTime>,
|
||||||
|
// pub execution_time: Option<i32>,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
// pub enum Relation {}
|
||||||
|
|
||||||
|
// impl ActiveModelBehavior for ActiveModel {}
|
||||||
10
src/entities/mod.rs
Normal file
10
src/entities/mod.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.6
|
||||||
|
|
||||||
|
pub mod prelude;
|
||||||
|
|
||||||
|
pub mod cat;
|
||||||
|
pub mod cat_content_tags;
|
||||||
|
pub mod cat_tag;
|
||||||
|
// pub mod doctrine_migration_versions;
|
||||||
|
pub mod tag;
|
||||||
|
pub mod user;
|
||||||
8
src/entities/prelude.rs
Normal file
8
src/entities/prelude.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.6
|
||||||
|
|
||||||
|
pub use super::cat::Entity as Cat;
|
||||||
|
pub use super::cat_content_tags::Entity as CatContentTags;
|
||||||
|
pub use super::cat_tag::Entity as CatTag;
|
||||||
|
// pub use super::doctrine_migration_versions::Entity as DoctrineMigrationVersions;
|
||||||
|
pub use super::tag::Entity as Tag;
|
||||||
|
pub use super::user::Entity as User;
|
||||||
42
src/entities/tag.rs
Normal file
42
src/entities/tag.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.6
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "tag")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
pub r#type: String,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(has_many = "super::cat::Entity")]
|
||||||
|
Cat,
|
||||||
|
#[sea_orm(has_many = "super::cat_content_tags::Entity")]
|
||||||
|
CatContentTags,
|
||||||
|
#[sea_orm(has_many = "super::cat_tag::Entity")]
|
||||||
|
CatTag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::cat::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Cat.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::cat_content_tags::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::CatContentTags.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::cat_tag::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::CatTag.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
19
src/entities/user.rs
Normal file
19
src/entities/user.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.6
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "user")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
#[sea_orm(unique)]
|
||||||
|
pub email: String,
|
||||||
|
pub roles: Json,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
50
src/main.rs
Normal file
50
src/main.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
pub mod endpoints;
|
||||||
|
pub mod entities;
|
||||||
|
pub mod models;
|
||||||
|
pub mod state;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use actix_web::{App, HttpServer, web};
|
||||||
|
use sea_orm::Database;
|
||||||
|
use state::AppState;
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> Result<(), std::io::Error> {
|
||||||
|
dotenvy::dotenv().expect("Failed to load env vars");
|
||||||
|
|
||||||
|
let host = env::var("HOST").expect("HOST is required");
|
||||||
|
let port = env::var("PORT")
|
||||||
|
.expect("PORT is required")
|
||||||
|
.parse::<u16>()
|
||||||
|
.expect("PORT is invalid");
|
||||||
|
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL is required");
|
||||||
|
|
||||||
|
println!("connecting to db ...");
|
||||||
|
|
||||||
|
let db = Database::connect(database_url)
|
||||||
|
.await
|
||||||
|
.expect("Failed to establish database connection");
|
||||||
|
|
||||||
|
// TODO: migrations
|
||||||
|
|
||||||
|
let state = AppState { db };
|
||||||
|
|
||||||
|
println!("starting a server at {}:{}", host, port);
|
||||||
|
|
||||||
|
let app_factory = move || {
|
||||||
|
App::new()
|
||||||
|
.app_data(web::Data::new(state.clone()))
|
||||||
|
.configure(|cfg| {
|
||||||
|
cfg.service(endpoints::auth::scope());
|
||||||
|
cfg.service(endpoints::misc::scope());
|
||||||
|
cfg.service(endpoints::users::scope());
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpServer::new(app_factory)
|
||||||
|
.bind((host.as_str(), port))?
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
||||||
20
src/models/error.rs
Normal file
20
src/models/error.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ErrorResponse {
|
||||||
|
pub status_code: i16,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorResponse {
|
||||||
|
pub fn server_error(message: &str) -> Self {
|
||||||
|
Self::new(500, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(status_code: i16, message: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
status_code,
|
||||||
|
message: String::from(message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/models/mod.rs
Normal file
2
src/models/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod error;
|
||||||
|
pub mod user;
|
||||||
30
src/models/user.rs
Normal file
30
src/models/user.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct UserModel {
|
||||||
|
pub id: i32,
|
||||||
|
pub email: String,
|
||||||
|
pub roles: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::entities::user::Model> for UserModel {
|
||||||
|
fn from(value: crate::entities::user::Model) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id,
|
||||||
|
email: value.email,
|
||||||
|
roles: match value.roles {
|
||||||
|
sea_orm::JsonValue::Array(roles) => roles
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|v| v.as_str().map(|s| s.to_owned()))
|
||||||
|
.collect(),
|
||||||
|
_ => vec![],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserModel {
|
||||||
|
pub fn from_many(values: Vec<crate::entities::user::Model>) -> Vec<Self> {
|
||||||
|
values.into_iter().map(Into::into).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/state.rs
Normal file
6
src/state.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AppState {
|
||||||
|
pub db: DatabaseConnection,
|
||||||
|
}
|
||||||
14
src/utils/hasher.rs
Normal file
14
src/utils/hasher.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
pub struct PasswordHasher {}
|
||||||
|
|
||||||
|
impl PasswordHasher {
|
||||||
|
// DO NOT CHANGE THIS VALUE OR HELL WILL RISE
|
||||||
|
pub const BCRYPT_PASSWORD_COST: u32 = 13;
|
||||||
|
|
||||||
|
pub fn hash(password: &str) -> Option<String> {
|
||||||
|
bcrypt::hash(password, Self::BCRYPT_PASSWORD_COST).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(password: &str, hash: &str) -> bool {
|
||||||
|
bcrypt::verify(password, hash).is_ok_and(|v| v)
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/utils/mod.rs
Normal file
3
src/utils/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mod hasher;
|
||||||
|
|
||||||
|
pub use hasher::PasswordHasher;
|
||||||
Loading…
x
Reference in New Issue
Block a user