summaryrefslogtreecommitdiffstats
path: root/src/models
diff options
context:
space:
mode:
Diffstat (limited to 'src/models')
-rw-r--r--src/models/auth.rs122
-rw-r--r--src/models/likes.rs91
-rw-r--r--src/models/mod.rs5
-rw-r--r--src/models/model.rs479
-rw-r--r--src/models/user.rs250
-rw-r--r--src/models/warning.rs382
6 files changed, 0 insertions, 1329 deletions
diff --git a/src/models/auth.rs b/src/models/auth.rs
deleted file mode 100644
index 8a673dd..0000000
--- a/src/models/auth.rs
+++ /dev/null
@@ -1,122 +0,0 @@
-use crate::errors::AppError;
-use axum::{
- async_trait,
- extract::{FromRequest, RequestParts, TypedHeader},
- headers::{authorization::Bearer, Authorization},
-};
-use chrono::{Duration, Local};
-use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
-use once_cell::sync::Lazy;
-use serde::{Deserialize, Serialize};
-
-struct Keys {
- encoding: EncodingKey,
- decoding: DecodingKey,
-}
-
-/// Claims struct
-#[derive(Serialize, Deserialize)]
-pub struct Claims {
- /// ID from the user model
- pub user_id: i32,
- /// Expiration timestamp
- exp: usize,
-}
-
-/// Body used as response to login
-#[derive(Serialize)]
-pub struct AuthBody {
- /// Access token string
- access_token: String,
- /// "Bearer" string
- token_type: String,
-}
-
-/// Payload used for login
-#[derive(Deserialize)]
-pub struct LoginCredentials {
- pub username: String,
- pub password: String,
-}
-
-/// Paylod used for user creation
-#[derive(Deserialize)]
-pub struct SignUpForm {
- pub name: String,
- pub email: String,
- pub username: String,
- pub password1: String,
- pub password2: String,
-}
-
-static KEYS: Lazy<Keys> = Lazy::new(|| {
- let secret = &crate::config::CONFIG.jwt_secret;
- Keys::new(secret.as_bytes())
-});
-
-impl Keys {
- fn new(secret: &[u8]) -> Self {
- Self {
- encoding: EncodingKey::from_secret(secret),
- decoding: DecodingKey::from_secret(secret),
- }
- }
-}
-
-impl Claims {
- /// Create a new Claim using the `user_id` and the current timestamp + 2 days
- pub fn new(user_id: i32) -> Self {
- let expiration = Local::now() + Duration::days(1);
-
- Self {
- user_id,
- exp: expiration.timestamp() as usize,
- }
- }
-
- /// Returns the token as a string. If a token is not encoded, raises an
- /// `AppError::TokenCreation`
- pub fn get_token(&self) -> Result<String, AppError> {
- let token = encode(&Header::default(), &self, &KEYS.encoding)
- .map_err(|_| AppError::TokenCreation)?;
-
- Ok(token)
- }
-}
-
-impl AuthBody {
- pub fn new(access_token: String) -> Self {
- Self {
- access_token,
- token_type: "Bearer".to_string(),
- }
- }
-}
-
-/// Parse a request to get the Authorization header and then decode it checking its validation
-#[async_trait]
-impl<B> FromRequest<B> for Claims
-where
- B: Send,
-{
- type Rejection = AppError;
-
- async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
- // Extract the token from the authorization header
- let TypedHeader(Authorization(bearer)) =
- TypedHeader::<Authorization<Bearer>>::from_request(req)
- .await
- .map_err(|_| AppError::InvalidToken)?;
- // Decode the user data
- let token_data = decode::<Claims>(bearer.token(), &KEYS.decoding, &Validation::default())
- .map_err(|_| AppError::InvalidToken)?;
-
- let now = Local::now().timestamp() as usize;
-
- if token_data.claims.exp < now {
- return Err(AppError::InvalidToken);
- }
-
- Ok(token_data.claims)
- }
-}
diff --git a/src/models/likes.rs b/src/models/likes.rs
deleted file mode 100644
index 56001d9..0000000
--- a/src/models/likes.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-use crate::{db::get_client, errors::AppError};
-use chrono::{Local, NaiveDateTime};
-use serde::{Deserialize, Serialize};
-use sqlx::Row;
-
-/// Likes model
-#[derive(Serialize, Deserialize, sqlx::FromRow)]
-pub struct Like {
- id: i32,
- user_id: i32,
- model_id: i32,
- created: NaiveDateTime,
-}
-
-impl Like {
- /// Create a new like
- pub fn new(user_id: i32, model_id: i32) -> Self {
- let now = Local::now().naive_utc();
- Self {
- id: 0,
- user_id,
- model_id,
- created: now,
- }
- }
-
- /// Returns `true` if an user has already assigned a like to a model
- pub async fn exists(&self) -> Result<bool, AppError> {
- let pool = unsafe { get_client() };
- let cursor = sqlx::query(
- r#"
- SELECT COUNT(id) as count FROM likes WHERE user_id = $1 AND model_id = $2
- "#,
- )
- .bind(self.user_id)
- .bind(self.model_id)
- .fetch_one(pool)
- .await?;
-
- let count: i64 = cursor.try_get(0).unwrap();
-
- Ok(count > 0)
- }
-
- /// Save new like into db
- pub async fn save(&self) -> Result<Like, AppError> {
- let pool = unsafe { get_client() };
-
- if self.exists().await? {
- return Err(AppError::BadRequest(
- "This user already likes this model".to_string(),
- ));
- }
-
- let rec: Like = sqlx::query_as(
- r#"
- INSERT INTO likes (user_id, model_id, created)
- VALUES ($1, $2, $3)
- RETURNING *
- "#,
- )
- .bind(self.user_id)
- .bind(self.model_id)
- .bind(self.created)
- .fetch_one(pool)
- .await?;
-
- Ok(rec)
- }
-
- /// Remove a like
- pub async fn remove(&self) -> Result<(), AppError> {
- let pool = unsafe { get_client() };
-
- if !self.exists().await? {
- return Err(AppError::NotFound("Like not found".to_string()));
- }
-
- sqlx::query(
- r#"
- DELETE FROM likes WHERE user_id = $1 AND model_id = $2
- "#,
- )
- .bind(self.user_id)
- .bind(self.model_id)
- .execute(pool)
- .await?;
-
- Ok(())
- }
-}
diff --git a/src/models/mod.rs b/src/models/mod.rs
deleted file mode 100644
index 8062fe3..0000000
--- a/src/models/mod.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-pub mod auth;
-pub mod likes;
-pub mod model;
-pub mod user;
-pub mod warning;
diff --git a/src/models/model.rs b/src/models/model.rs
deleted file mode 100644
index 87e1997..0000000
--- a/src/models/model.rs
+++ /dev/null
@@ -1,479 +0,0 @@
-use crate::{config::CONFIG, db::get_client, errors::AppError, json::number_from_string};
-use serde_json::json;
-use sqlx::types::JsonValue;
-use sqlx::Row;
-
-use chrono::{Local, NaiveDateTime};
-use serde::{Deserialize, Serialize};
-use validator::Validate;
-
-/// Model for models.
-#[derive(Deserialize, Serialize, Validate, sqlx::FromRow)]
-pub struct Model {
- id: i32,
- #[validate(length(min = 2, message = "Can not be empty"))]
- name: String,
- description: Option<String>,
- duration: f64,
- height: f64,
- weight: f64,
- printer: Option<String>,
- material: Option<String>,
- author_id: i32,
- created: NaiveDateTime,
- updated: NaiveDateTime,
-}
-
-/// Payload used for model creation
-#[derive(Deserialize)]
-pub struct ModelCreate {
- pub name: String,
- pub description: Option<String>,
- #[serde(deserialize_with = "number_from_string")]
- pub duration: f64,
- #[serde(deserialize_with = "number_from_string")]
- pub height: f64,
- #[serde(deserialize_with = "number_from_string")]
- pub weight: f64,
- pub printer: Option<String>,
- pub material: Option<String>,
-}
-
-/// Payload used for model searching
-#[derive(Deserialize)]
-pub struct ModelFilter {
- /// Stands for "query"
- pub q: String,
-}
-
-#[derive(Serialize, sqlx::FromRow)]
-pub struct ModelUser {
- pub id: i32,
- name: String,
- description: Option<String>,
- duration: f64,
- height: f64,
- weight: f64,
- printer: Option<String>,
- material: Option<String>,
- author_id: i32,
- created: NaiveDateTime,
- updated: NaiveDateTime,
- author: Option<JsonValue>,
- uploads: Option<JsonValue>,
- likes: Option<JsonValue>,
-}
-
-#[derive(Deserialize, Serialize, sqlx::FromRow)]
-pub struct ModelUpload {
- id: i32,
- pub model_id: i32,
- pub filepath: String,
- created: NaiveDateTime,
-}
-
-impl Model {
- #[allow(clippy::too_many_arguments)]
- pub fn new(
- name: String,
- description: Option<String>,
- duration: f64,
- height: f64,
- weight: f64,
- printer: Option<String>,
- material: Option<String>,
- author_id: i32,
- ) -> Self {
- let now = Local::now().naive_utc();
- Self {
- id: 0,
- name,
- description,
- duration,
- height,
- weight,
- printer,
- material,
- author_id,
- created: now,
- updated: now,
- }
- }
-
- /// Create a new model
- pub async fn create(model: Model) -> Result<Model, AppError> {
- let pool = unsafe { get_client() };
-
- model
- .validate()
- .map_err(|error| AppError::BadRequest(error.to_string()))?;
-
- let rec: Model = sqlx::query_as(
- r#"
- INSERT INTO models (name, description, duration, height, weight, printer, material, author_id, created, updated)
- VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
- RETURNING *
- "#)
- .bind(model.name)
- .bind(model.description)
- .bind(model.duration)
- .bind(model.height)
- .bind(model.weight)
- .bind(model.printer)
- .bind(model.material)
- .bind(model.author_id)
- .bind(model.created)
- .bind(model.updated)
- .fetch_one(pool)
- .await?;
-
- Ok(rec)
- }
-
- /// Edit a model
- pub async fn edit(id: i32, model: Model) -> Result<Model, AppError> {
- let pool = unsafe { get_client() };
-
- model
- .validate()
- .map_err(|error| AppError::BadRequest(error.to_string()))?;
-
- let rec: Model = sqlx::query_as(
- r#"
- UPDATE models SET name = $1, description = $2, duration = $3, height = $4, weight = $5, printer = $6, material = $7, updated = $8
- WHERE id = $9
- RETURNING *
- "#)
- .bind(model.name)
- .bind(model.description)
- .bind(model.duration)
- .bind(model.height)
- .bind(model.weight)
- .bind(model.printer)
- .bind(model.material)
- .bind(model.updated)
- .bind(id)
- .fetch_one(pool)
- .await?;
-
- Ok(rec)
- }
-
- /// Returns the model with id = `model_id`
- pub async fn find_by_id(model_id: i32) -> Result<ModelUser, AppError> {
- let pool = unsafe { get_client() };
-
- let rec: ModelUser = sqlx::query_as(
- r#"
- WITH model_uploads AS (
- SELECT models.id, json_agg(uploads.*) filter(WHERE uploads.* IS NOT NULL) AS uploads
- FROM models
- LEFT JOIN uploads ON uploads.model_id = models.id
- GROUP BY models.id
- ),
- model_likes AS (
- SELECT models.id, json_agg(likes.*) filter(WHERE likes.* IS NOT NULL) AS likes
- FROM models
- LEFT JOIN likes ON likes.model_id = models.id
- GROUP BY models.id
- ),
- model_author AS (
- SELECT models.id, json_build_object('id', users.id, 'name', users.name, 'email', users.email, 'username', users.username, 'is_staff', users.is_staff, 'avatar', users.avatar) as author
- FROM models
- JOIN users ON users.id = models.author_id
- )
- SELECT models.*, author, uploads, likes
- FROM models
- INNER JOIN model_author using (id)
- INNER JOIN model_uploads using (id)
- INNER JOIN model_likes using (id)
- WHERE models.id = $1
- "#)
- .bind(model_id)
- .fetch_one(pool)
- .await?;
-
- Ok(rec)
- }
-
- /// List all models
- pub async fn list(page: i64) -> Result<Vec<ModelUser>, AppError> {
- let pool = unsafe { get_client() };
- let rows: Vec<ModelUser> = sqlx::query_as(
- r#"
- WITH model_uploads AS (
- SELECT models.id, json_agg(uploads.*) filter(WHERE uploads.* IS NOT NULL) AS uploads
- FROM models
- LEFT JOIN uploads ON uploads.model_id = models.id
- GROUP BY models.id
- ),
- model_likes AS (
- SELECT models.id, json_agg(likes.*) filter(WHERE likes.* IS NOT NULL) AS likes
- FROM models
- LEFT JOIN likes ON likes.model_id = models.id
- GROUP BY models.id
- ),
- model_author AS (
- SELECT models.id, json_build_object('id', users.id, 'name', users.name, 'email', users.email, 'username', users.username, 'is_staff', users.is_staff, 'avatar', users.avatar) as author
- FROM models
- JOIN users ON users.id = models.author_id
- )
- SELECT models.*, author, uploads, likes
- FROM models
- INNER JOIN model_author using (id)
- INNER JOIN model_uploads using (id)
- INNER JOIN model_likes using (id)
- ORDER BY id DESC
- LIMIT $1 OFFSET $2
- "#)
- .bind(CONFIG.page_limit)
- .bind(CONFIG.page_limit * page)
- .fetch_all(pool)
- .await?;
-
- Ok(rows)
- }
-
- /// Filter models by some cols
- pub async fn filter(page: i64, query: String) -> Result<Vec<ModelUser>, AppError> {
- let pool = unsafe { get_client() };
- let rows: Vec<ModelUser> = sqlx::query_as(
- r#"
- WITH model_uploads AS (
- SELECT models.id, json_agg(uploads.*) filter(WHERE uploads.* IS NOT NULL) AS uploads
- FROM models
- LEFT JOIN uploads ON uploads.model_id = models.id
- GROUP BY models.id
- ),
- model_likes AS (
- SELECT models.id, json_agg(likes.*) filter(WHERE likes.* IS NOT NULL) AS likes
- FROM models
- LEFT JOIN likes ON likes.model_id = models.id
- GROUP BY models.id
- ),
- model_author AS (
- SELECT models.id, json_build_object('id', users.id, 'name', users.name, 'email', users.email, 'username', users.username, 'is_staff', users.is_staff, 'avatar', users.avatar) as author
- FROM models
- JOIN users ON users.id = models.author_id
- )
- SELECT models.*, author, uploads, likes
- FROM models
- INNER JOIN model_author using (id)
- INNER JOIN model_uploads using (id)
- INNER JOIN model_likes using (id)
- WHERE models.name ILIKE $1 OR description ILIKE $1 OR printer ILIKE $1 OR material ILIKE $1
- ORDER BY id DESC
- LIMIT $2 OFFSET $3
- "#)
- .bind(format!("%{}%", query))
- .bind(CONFIG.page_limit)
- .bind(CONFIG.page_limit * page)
- .fetch_all(pool)
- .await?;
-
- Ok(rows)
- }
-
- /// List author's models
- pub async fn list_from_author(page: i64, author: i32) -> Result<Vec<ModelUser>, AppError> {
- let pool = unsafe { get_client() };
- let rows: Vec<ModelUser> = sqlx::query_as(
- r#"
- WITH model_uploads AS (
- SELECT models.id, json_agg(uploads.*) filter(WHERE uploads.* IS NOT NULL) AS uploads
- FROM models
- LEFT JOIN uploads ON uploads.model_id = models.id
- GROUP BY models.id
- ),
- model_likes AS (
- SELECT models.id, json_agg(likes.*) filter(WHERE likes.* IS NOT NULL) AS likes
- FROM models
- LEFT JOIN likes ON likes.model_id = models.id
- GROUP BY models.id
- ),
- model_author AS (
- SELECT models.id, json_build_object('id', users.id, 'name', users.name, 'email', users.email, 'username', users.username, 'is_staff', users.is_staff, 'avatar', users.avatar) as author
- FROM models
- JOIN users ON users.id = models.author_id
- )
- SELECT models.*, author, uploads, likes
- FROM models
- INNER JOIN model_author using (id)
- INNER JOIN model_uploads using (id)
- INNER JOIN model_likes using (id)
- WHERE models.author_id = $1
- ORDER BY id DESC
- LIMIT $2 OFFSET $3
- "#)
- .bind(author)
- .bind(CONFIG.page_limit)
- .bind(CONFIG.page_limit * page)
- .fetch_all(pool)
- .await?;
-
- Ok(rows)
- }
-
- /// Delete a model
- pub async fn delete(model_id: i32) -> Result<(), AppError> {
- let pool = unsafe { get_client() };
-
- sqlx::query(
- r#"
- DELETE FROM models WHERE id = $1
- "#,
- )
- .bind(model_id)
- .execute(pool)
- .await?;
-
- Ok(())
- }
-
- /// Return the number of models.
- pub async fn count() -> Result<i64, AppError> {
- let pool = unsafe { get_client() };
- let cursor = sqlx::query(r#"SELECT COUNT(id) as count FROM models"#)
- .fetch_one(pool)
- .await?;
-
- let count: i64 = cursor.try_get(0).unwrap();
- Ok(count)
- }
-
- /// Return the number of author models
- pub async fn count_filter_by_author(author: i32) -> Result<i64, AppError> {
- let pool = unsafe { get_client() };
- let cursor = sqlx::query(r#"SELECT COUNT(id) as count FROM models WHERE author_id = $1"#)
- .bind(author)
- .fetch_one(pool)
- .await?;
-
- let count: i64 = cursor.try_get(0).unwrap();
- Ok(count)
- }
-
- /// Return the number of models filtered by query
- pub async fn count_filter(query: String) -> Result<i64, AppError> {
- let pool = unsafe { get_client() };
- let cursor = sqlx::query(
- r#"
- SELECT COUNT(id) as count FROM models
- WHERE models.name ILIKE $1 OR description ILIKE $1 OR printer ILIKE $1 OR material ILIKE $1
- "#
- )
- .bind(format!("%{}%", query))
- .fetch_one(pool)
- .await?;
-
- let count: i64 = cursor.try_get(0).unwrap();
- Ok(count)
- }
-}
-
-impl ModelUser {
- /// Returns the author id from the `JsonValue`
- pub fn author_id(&self) -> JsonValue {
- match &self.author {
- Some(json) => json.get("id").unwrap().clone(),
- None => json!(0),
- }
- }
-
- /// Returns a vec of string made by all the filepaths from the model
- pub async fn list_upload_filepaths(&self) -> Option<Vec<String>> {
- // Raise a `None` if `self.uploads` is `None`
- self.uploads.as_ref()?;
-
- let uploads = ModelUpload::find_by_model(self.id)
- .await
- .unwrap_or_default();
-
- let paths = uploads
- .iter()
- .map(|x| x.filepath.clone())
- .collect::<Vec<String>>();
-
- Some(paths)
- }
-}
-
-impl ModelUpload {
- pub fn new(filepath: String, model_id: i32) -> Self {
- let now = Local::now().naive_utc();
- Self {
- id: 0,
- filepath,
- model_id,
- created: now,
- }
- }
-
- /// Create a new upload for model
- pub async fn create(file: ModelUpload) -> Result<ModelUpload, AppError> {
- let pool = unsafe { get_client() };
-
- let rec: ModelUpload = sqlx::query_as(
- r#"
- INSERT INTO uploads (filepath, model_id, created)
- VALUES ( $1, $2, $3)
- RETURNING *
- "#,
- )
- .bind(file.filepath)
- .bind(file.model_id)
- .bind(file.created)
- .fetch_one(pool)
- .await?;
-
- Ok(rec)
- }
-
- /// Find all paths of a model
- pub async fn find_by_model(model_id: i32) -> Result<Vec<ModelUpload>, AppError> {
- let pool = unsafe { get_client() };
-
- let rec: Vec<ModelUpload> = sqlx::query_as(
- r#"
- SELECT * FROM uploads WHERE model_id = $1
- "#,
- )
- .bind(model_id)
- .fetch_all(pool)
- .await?;
-
- Ok(rec)
- }
-
- /// Returns the model upload with id = `upload_id`
- pub async fn find_by_id(id: i32) -> Result<ModelUpload, AppError> {
- let pool = unsafe { get_client() };
-
- let rec: ModelUpload = sqlx::query_as(
- r#"
- SELECT * FROM uploads WHERE id = $1
- "#,
- )
- .bind(id)
- .fetch_one(pool)
- .await?;
-
- Ok(rec)
- }
-
- /// Delete a model upload
- pub async fn delete(upload_id: i32) -> Result<(), AppError> {
- let pool = unsafe { get_client() };
-
- sqlx::query(
- r#"
- DELETE FROM uploads WHERE id = $1
- "#,
- )
- .bind(upload_id)
- .execute(pool)
- .await?;
-
- Ok(())
- }
-}
diff --git a/src/models/user.rs b/src/models/user.rs
deleted file mode 100644
index d09394b..0000000
--- a/src/models/user.rs
+++ /dev/null
@@ -1,250 +0,0 @@
-use crate::{
- config::CONFIG,
- db::get_client,
- errors::AppError,
- models::model::{Model, ModelUser},
-};
-
-use serde::{Deserialize, Serialize};
-use serde_with::{serde_as, NoneAsEmptyString};
-use sqlx::Row;
-use validator::Validate;
-
-/// User model
-#[derive(Deserialize, Serialize, Validate)]
-pub struct User {
- id: i32,
- name: String,
- #[validate(length(min = 4, message = "Can not be empty"))]
- email: String,
- #[validate(length(min = 2, message = "Can not be empty"))]
- username: String,
- #[validate(length(min = 8, message = "Must be min 8 chars length"))]
- password: String,
- is_staff: Option<bool>,
- avatar: Option<String>,
-}
-
-/// Paylod used for user editing
-#[derive(Deserialize)]
-pub struct UserEdit {
- pub name: String,
- pub email: String,
- pub username: String,
- pub is_staff: Option<bool>,
-}
-
-/// Response used to print a user (or a users list)
-#[serde_as]
-#[derive(Deserialize, Serialize, sqlx::FromRow, Validate)]
-pub struct UserList {
- pub id: i32,
- pub name: String,
- #[validate(length(min = 4, message = "Can not be empty"))]
- pub email: String,
- #[validate(length(min = 2, message = "Can not be empty"))]
- pub username: String,
- pub is_staff: Option<bool>,
- #[serde_as(as = "NoneAsEmptyString")]
- pub avatar: Option<String>,
-}
-
-impl User {
- /// By default an user has id = 0. It is not created yet
- pub fn new(name: String, email: String, username: String, password: String) -> Self {
- Self {
- id: 0,
- name,
- email,
- username,
- password,
- is_staff: Some(false),
- avatar: None,
- }
- }
-
- /// Create a new user from the model using a SHA256 crypted password
- pub async fn create(user: User) -> Result<UserList, AppError> {
- let pool = unsafe { get_client() };
-
- user.validate()
- .map_err(|error| AppError::BadRequest(error.to_string()))?;
-
- let crypted_password = sha256::digest(user.password);
-
- let rec: UserList = sqlx::query_as(
- r#"
- INSERT INTO users (name, email, username, password)
- VALUES ( $1, $2, $3, $4)
- RETURNING id, name, email, username, is_staff, avatar
- "#,
- )
- .bind(user.name)
- .bind(user.email)
- .bind(user.username)
- .bind(crypted_password)
- .fetch_one(pool)
- .await?;
-
- Ok(rec)
- }
-
- /// Find a user using the model. It used for login
- pub async fn find(user: User) -> Result<UserList, AppError> {
- let pool = unsafe { get_client() };
-
- let crypted_password = sha256::digest(user.password);
-
- let rec: UserList = sqlx::query_as(
- r#"
- SELECT id, name, email, username, is_staff, avatar FROM "users"
- WHERE username = $1 AND password = $2
- "#,
- )
- .bind(user.username)
- .bind(crypted_password)
- .fetch_one(pool)
- .await?;
-
- Ok(rec)
- }
-
- /// Returns the user with id = `user_id`
- pub async fn find_by_id(user_id: i32) -> Result<UserList, AppError> {
- let pool = unsafe { get_client() };
-
- let rec: UserList = sqlx::query_as(
- r#"
- SELECT id, name, email, username, is_staff, avatar FROM "users"
- WHERE id = $1
- "#,
- )
- .bind(user_id)
- .fetch_one(pool)
- .await?;
-
- Ok(rec)
- }
-
- /// List all users
- pub async fn list(page: i64) -> Result<Vec<UserList>, AppError> {
- let pool = unsafe { get_client() };
- let rows: Vec<UserList> = sqlx::query_as(
- r#"SELECT id, name, email, username, is_staff, avatar FROM users
- ORDER BY id DESC
- LIMIT $1 OFFSET $2
- "#,
- )
- .bind(CONFIG.page_limit)
- .bind(CONFIG.page_limit * page)
- .fetch_all(pool)
- .await?;
-
- Ok(rows)
- }
-
- /// Return the number of users.
- pub async fn count() -> Result<i64, AppError> {
- let pool = unsafe { get_client() };
- let cursor = sqlx::query(r#"SELECT COUNT(id) as count FROM users"#)
- .fetch_one(pool)
- .await?;
-
- let count: i64 = cursor.try_get(0).unwrap();
- Ok(count)
- }
-
- /// Prevent the "uniquess" Postgres fields check. Check if username has been taken
- pub async fn username_has_taken(username: &String) -> Result<bool, AppError> {
- let pool = unsafe { get_client() };
- let cursor = sqlx::query(
- r#"
- SELECT COUNT(id) as count FROM users WHERE username = $1
- "#,
- )
- .bind(username)
- .fetch_one(pool)
- .await?;
-
- let count: i64 = cursor.try_get(0).unwrap();
-
- Ok(count > 0)
- }
-
- /// Prevent the "uniquess" Postgres fields check. Check if email has been taken
- pub async fn email_has_taken(email: &String) -> Result<bool, AppError> {
- let pool = unsafe { get_client() };
- let cursor = sqlx::query(
- r#"
- SELECT COUNT(id) as count FROM users WHERE email = $1
- "#,
- )
- .bind(email)
- .fetch_one(pool)
- .await?;
-
- let count: i64 = cursor.try_get(0).unwrap();
-
- Ok(count > 0)
- }
-}
-
-impl UserList {
- /// Edit an user avatar
- pub async fn edit_avatar(&mut self, avatar: Option<String>) -> Result<(), AppError> {
- let pool = unsafe { get_client() };
- sqlx::query(
- r#"
- UPDATE users SET avatar = $1 WHERE id = $2
- "#,
- )
- .bind(&avatar)
- .bind(self.id)
- .execute(pool)
- .await?;
-
- self.avatar = avatar;
-
- Ok(())
- }
-
- /// Edit an user
- pub async fn edit(&mut self, payload: UserEdit) -> Result<(), AppError> {
- let pool = unsafe { get_client() };
-
- // Make assignments before the `sqlx::query()` so to perform validation.
- // If the `AppError::BadRequest` is raised, the query (and then the update) will be skipped
- self.name = payload.name.clone();
- self.username = payload.username.clone();
- self.email = payload.email.clone();
- self.is_staff = payload.is_staff;
-
- self.validate()
- .map_err(|error| AppError::BadRequest(error.to_string()))?;
-
- sqlx::query(
- r#"
- UPDATE users SET name = $1, username = $2, email = $3, is_staff = $4 WHERE id = $5
- "#,
- )
- .bind(&payload.name)
- .bind(&payload.username)
- .bind(&payload.email)
- .bind(payload.is_staff.unwrap_or_default())
- .bind(self.id)
- .execute(pool)
- .await?;
-
- Ok(())
- }
-
- /// Get all models created by an user
- pub async fn get_models(&self, page: i64) -> Result<Vec<ModelUser>, AppError> {
- Model::list_from_author(page, self.id).await
- }
-
- /// Returns the number of models for an user
- pub async fn count_models(&self) -> Result<i64, AppError> {
- Model::count_filter_by_author(self.id).await
- }
-}
diff --git a/src/models/warning.rs b/src/models/warning.rs
deleted file mode 100644
index c420dd0..0000000
--- a/src/models/warning.rs
+++ /dev/null
@@ -1,382 +0,0 @@
-use crate::{config::CONFIG, db::get_client, errors::AppError};
-use chrono::{Local, NaiveDateTime};
-use serde::{Deserialize, Serialize};
-use sqlx::types::JsonValue;
-use sqlx::Row;
-use std::convert::From;
-
-/// Model for warnings.
-#[derive(Deserialize, Serialize, sqlx::FromRow)]
-pub struct Warning {
- pub id: i32,
- pub user_id: Option<i32>,
- pub model_id: Option<i32>,
- pub resolved_by: Option<i32>,
- pub note: String,
- pub admin_note: String,
- pub created: NaiveDateTime,
- pub updated: NaiveDateTime,
-}
-
-#[derive(Serialize, sqlx::FromRow)]
-pub struct WarningUser {
- pub id: i32,
- pub user_id: Option<i32>,
- pub model_id: Option<i32>,
- pub resolved_by: Option<i32>,
- pub note: String,
- pub admin_note: String,
- pub created: NaiveDateTime,
- pub updated: NaiveDateTime,
- user: Option<JsonValue>,
- resolved: Option<JsonValue>,
-}
-
-/// Impl conversion from `WarningUser` to `Warning`
-impl From<WarningUser> for Warning {
- fn from(item: WarningUser) -> Self {
- Self {
- id: item.id,
- user_id: item.user_id,
- model_id: item.model_id,
- resolved_by: item.resolved_by,
- note: item.note,
- admin_note: item.admin_note,
- created: item.created,
- updated: item.created,
- }
- }
-}
-
-/// Payload used to create a new warning
-#[derive(Deserialize)]
-pub struct WarningCreate {
- pub model_id: i32,
- pub note: String,
-}
-
-/// Payload used to edit a warning
-#[derive(Deserialize)]
-pub struct WarningEdit {
- pub admin_note: String,
-}
-
-/// Payload used for warning filtering
-#[derive(Deserialize)]
-pub struct WarningFilterPayload {
- pub model_id: Option<i32>,
- pub resolved_by: Option<i32>,
-}
-
-/// Struct used as argument for filtering by the backend
-#[derive(Debug)]
-pub struct WarningFilter {
- pub model_id: Option<i32>,
- pub resolved_by: Option<i32>,
- pub user_id: Option<i32>,
-}
-
-impl Warning {
- /// Create a warning means create an object which has an `user_id` (creator of the warning), a
- /// `model_id` (suspect model) and a `note`
- pub fn new(user_id: i32, model_id: i32, note: String) -> Self {
- let now = Local::now().naive_utc();
- Self {
- id: 0,
- user_id: Some(user_id),
- model_id: Some(model_id),
- resolved_by: None,
- note,
- admin_note: String::new(),
- created: now,
- updated: now,
- }
- }
-
- /// Delete a report
- pub async fn delete(warning_id: i32) -> Result<(), AppError> {
- let pool = unsafe { get_client() };
-
- sqlx::query(
- r#"
- DELETE FROM warnings WHERE id = $1
- "#,
- )
- .bind(warning_id)
- .execute(pool)
- .await?;
-
- Ok(())
- }
-
- /// List all warnings. A staffer can see all the warnings, a user cannot
- pub async fn list(page: i64, user_id: Option<i32>) -> Result<Vec<WarningUser>, AppError> {
- let pool = unsafe { get_client() };
- let query = r#"
- SELECT
- warnings.*,
- json_build_object('id', users.id, 'name', users.name, 'email', users.email, 'username', users.username, 'is_staff', users.is_staff, 'avatar', users.avatar) as user,
- coalesce(r.data, '{}'::json) as resolved
- FROM warnings
- JOIN users ON users.id = warnings.user_id
- LEFT JOIN (
- SELECT id, json_build_object('id', r.id, 'name', r.name, 'email', r.email, 'username', r.username, 'is_staff', r.is_staff, 'avatar', r.avatar) as data
- FROM users r
- ) r ON r.id = warnings.resolved_by
- "#;
-
- let rows: Vec<WarningUser> = match user_id {
- Some(id) => {
- sqlx::query_as(&format!(
- r#"{} WHERE user_id = $1 ORDER BY id DESC LIMIT $2 OFFSET $3"#,
- query
- ))
- .bind(id)
- .bind(CONFIG.page_limit)
- .bind(CONFIG.page_limit * page)
- .fetch_all(pool)
- .await?
- }
- None => {
- sqlx::query_as(&format!(r#"{} ORDER BY id DESC LIMIT $1 OFFSET $2"#, query))
- .bind(CONFIG.page_limit)
- .bind(CONFIG.page_limit * page)
- .fetch_all(pool)
- .await?
- }
- };
-
- Ok(rows)
- }
-
- /// Returns the warning with id = `warning_id`
- pub async fn find_by_id(warning_id: i32) -> Result<WarningUser, AppError> {
- let pool = unsafe { get_client() };
-
- let rec: WarningUser = sqlx::query_as(
- r#"
- SELECT
- warnings.*,
- json_build_object('id', users.id, 'name', users.name, 'email', users.email, 'username', users.username, 'is_staff', users.is_staff, 'avatar', users.avatar) as user,
- coalesce(r.data, '{}'::json) as resolved
- FROM warnings
- JOIN users ON users.id = warnings.user_id
- LEFT JOIN (
- SELECT id, json_build_object('id', r.id, 'name', r.name, 'email', r.email, 'username', r.username, 'is_staff', r.is_staff, 'avatar', r.avatar) as data
- FROM users r
- ) r ON r.id = warnings.resolved_by
- WHERE warnings.id = $1
- "#)
- .bind(warning_id)
- .fetch_one(pool)
- .await?;
-
- Ok(rec)
- }
-
- /// Return the number of warnings.
- pub async fn count(user_id: Option<i32>) -> Result<i64, AppError> {
- let pool = unsafe { get_client() };
-
- let cursor = match user_id {
- Some(id) => {
- sqlx::query(r#"SELECT COUNT(id) as count FROM warnings WHERE user_id = $1"#)
- .bind(id)
- .fetch_one(pool)
- .await?
- }
- None => {
- sqlx::query(r#"SELECT COUNT(id) as count FROM warnings"#)
- .fetch_one(pool)
- .await?
- }
- };
-
- let count: i64 = cursor.try_get(0).unwrap();
- Ok(count)
- }
-
- /// Create a new upload for model
- pub async fn create(warning: Warning) -> Result<Warning, AppError> {
- let pool = unsafe { get_client() };
-
- let rec: Warning = sqlx::query_as(
- r#"
- INSERT INTO warnings (user_id, model_id, resolved_by, note, admin_note, created, updated)
- VALUES ( $1, $2, $3, $4, $5, $6, $7)
- RETURNING *
- "#,
- )
- .bind(warning.user_id)
- .bind(warning.model_id)
- .bind(warning.resolved_by)
- .bind(warning.note)
- .bind(warning.admin_note)
- .bind(warning.created)
- .bind(warning.updated)
- .fetch_one(pool)
- .await?;
-
- Ok(rec)
- }
-
- /// Filter warnings. Pass a `WarningFilter` argument. You can filter only by model_id or (not
- /// both) resolved by
- pub async fn filter(page: i64, args: WarningFilter) -> Result<Vec<WarningUser>, AppError> {
- let pool = unsafe { get_client() };
-
- let mut query = r#"
- SELECT
- warnings.*,
- json_build_object('id', users.id, 'name', users.name, 'email', users.email, 'username', users.username, 'is_staff', users.is_staff, 'avatar', users.avatar) as user,
- coalesce(r.data, '{}'::json) as resolved
- FROM warnings
- JOIN users ON users.id = warnings.user_id
- LEFT JOIN (
- SELECT id, json_build_object('id', r.id, 'name', r.name, 'email', r.email, 'username', r.username, 'is_staff', r.is_staff, 'avatar', r.avatar) as data
- FROM users r
- ) r ON r.id = warnings.resolved_by
- "#.to_string();
-
- if args.model_id.is_some() {
- query += r#"WHERE model_id = $1"#;
- } else {
- match args.resolved_by {
- Some(_) => {
- query += r#" WHERE warnings.resolved_by = $1"#;
- }
- None => {
- query += r#" WHERE warnings.resolved_by IS NULL"#;
- }
- };
- }
-
- let rows: Vec<WarningUser> = match args.user_id {
- Some(id) => {
- let q = if args.model_id.is_some() {
- query = format!(
- r#"{} AND user_id = $2 ORDER BY id DESC LIMIT $3 OFFSET $4"#,
- query
- );
- sqlx::query_as(&query).bind(args.model_id.unwrap())
- } else if args.resolved_by.is_some() {
- query = format!(
- r#"{} AND user_id = $2 ORDER BY id DESC LIMIT $3 OFFSET $4"#,
- query
- );
- sqlx::query_as(&query).bind(args.resolved_by.unwrap())
- } else {
- query = format!(
- r#"{} AND user_id = $1 ORDER BY id DESC LIMIT $2 OFFSET $3"#,
- query
- );
- sqlx::query_as(&query)
- };
-
- q.bind(id)
- .bind(CONFIG.page_limit)
- .bind(CONFIG.page_limit * page)
- .fetch_all(pool)
- .await?
- }
- None => {
- let q = if args.model_id.is_some() {
- query = format!(r#"{} ORDER BY id DESC LIMIT $2 OFFSET $3"#, query);
- sqlx::query_as(&query).bind(args.model_id.unwrap())
- } else if args.resolved_by.is_some() {
- query = format!(r#"{} ORDER BY id DESC LIMIT $2 OFFSET $3"#, query);
- sqlx::query_as(&query).bind(args.resolved_by.unwrap())
- } else {
- query = format!(r#"{} ORDER BY id DESC LIMIT $1 OFFSET $2"#, query);
- sqlx::query_as(&query)
- };
-
- q.bind(CONFIG.page_limit)
- .bind(CONFIG.page_limit * page)
- .fetch_all(pool)
- .await?
- }
- };
-
- Ok(rows)
- }
-
- /// Return the number of filtered warnings.
- pub async fn count_by_model_id(args: WarningFilter) -> Result<i64, AppError> {
- let pool = unsafe { get_client() };
-
- let mut query = r#"
- SELECT COUNT(id) as count FROM warnings
- "#
- .to_string();
-
- if args.model_id.is_some() {
- query += r#" WHERE model_id = $1"#;
- } else {
- match args.resolved_by {
- Some(_) => {
- query += r#" WHERE warnings.resolved_by = $1"#;
- }
- None => {
- query += r#" WHERE warnings.resolved_by IS NULL"#;
- }
- };
- }
-
- let cursor = match args.user_id {
- Some(id) => {
- let q = if args.model_id.is_some() {
- query = format!(r#"{} AND user_id = $2"#, query);
- sqlx::query(&query).bind(args.model_id.unwrap())
- } else if args.resolved_by.is_some() {
- query = format!(r#"{} AND user_id = $2"#, query);
- sqlx::query(&query).bind(args.resolved_by.unwrap())
- } else {
- query = format!(r#"{} AND user_id = $1"#, query);
- sqlx::query(&query)
- };
-
- q.bind(id).fetch_one(pool).await?
- }
- None => {
- let q = if args.model_id.is_some() {
- sqlx::query(&query).bind(args.model_id.unwrap())
- } else if args.resolved_by.is_some() {
- sqlx::query(&query).bind(args.resolved_by.unwrap())
- } else {
- sqlx::query(&query)
- };
-
- q.fetch_one(pool).await?
- }
- };
-
- let count: i64 = cursor.try_get(0).unwrap();
- Ok(count)
- }
-
- /// Edit a warning
- pub async fn edit(&mut self, resolver: i32, payload: WarningEdit) -> Result<(), AppError> {
- let pool = unsafe { get_client() };
-
- let now = Local::now().naive_utc();
-
- sqlx::query(
- r#"
- UPDATE warnings SET admin_note = $1, resolved_by = $2, updated = $3 WHERE id = $4
- "#,
- )
- .bind(&payload.admin_note)
- .bind(resolver)
- .bind(now)
- .bind(self.id)
- .execute(pool)
- .await?;
-
- self.admin_note = payload.admin_note;
- self.resolved_by = Some(resolver);
- self.updated = now;
-
- Ok(())
- }
-}