summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--migrations/20221003064044_add-likes.sql6
-rw-r--r--src/models/likes.rs91
-rw-r--r--src/models/mod.rs1
-rw-r--r--src/routes/model.rs44
4 files changed, 142 insertions, 0 deletions
diff --git a/migrations/20221003064044_add-likes.sql b/migrations/20221003064044_add-likes.sql
new file mode 100644
index 0000000..c94d488
--- /dev/null
+++ b/migrations/20221003064044_add-likes.sql
@@ -0,0 +1,6 @@
+CREATE TABLE likes (
+ id SERIAL PRIMARY KEY,
+ user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
+ model_id INTEGER REFERENCES models(id) ON DELETE SET NULL,
+ created TIMESTAMP NOT NULL
+);
diff --git a/src/models/likes.rs b/src/models/likes.rs
new file mode 100644
index 0000000..56001d9
--- /dev/null
+++ b/src/models/likes.rs
@@ -0,0 +1,91 @@
+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
index 45a11f3..8062fe3 100644
--- a/src/models/mod.rs
+++ b/src/models/mod.rs
@@ -1,4 +1,5 @@
pub mod auth;
+pub mod likes;
pub mod model;
pub mod user;
pub mod warning;
diff --git a/src/routes/model.rs b/src/routes/model.rs
index 9242c31..d72d7af 100644
--- a/src/routes/model.rs
+++ b/src/routes/model.rs
@@ -3,6 +3,7 @@ use crate::{
files::{delete_upload, upload},
models::{
auth::Claims,
+ likes::Like,
model::{Model, ModelCreate, ModelUpload, ModelUser},
user::User,
},
@@ -21,6 +22,7 @@ pub fn create_route() -> Router {
Router::new()
.route("/", get(list_models).post(create_model))
.route("/:id", get(get_model).delete(delete_model).put(edit_model))
+ .route("/:id/like", post(add_like).delete(delete_like))
.route("/:id/upload", post(upload_model_file))
.route("/:id/upload/:uid", delete(delete_model_file))
}
@@ -207,3 +209,45 @@ async fn delete_model_file(
Err(e) => Err(e),
}
}
+
+/// Assign a like to a model from the Authorization user
+async fn add_like(claims: Claims, Path(model_id): Path<i32>) -> Result<StatusCode, AppError> {
+ let model = match Model::find_by_id(model_id).await {
+ Ok(model) => model,
+ Err(_) => {
+ return Err(AppError::NotFound("Model not found".to_string()));
+ }
+ };
+
+ let user = User::find_by_id(claims.user_id).await?;
+
+ let like = Like::new(user.id, model.id);
+
+ match like.save().await {
+ Ok(_) => Ok(StatusCode::CREATED),
+ Err(e) => {
+ return Err(e);
+ }
+ }
+}
+
+/// Remove a like from a model and an Authorization user
+async fn delete_like(claims: Claims, Path(model_id): Path<i32>) -> Result<StatusCode, AppError> {
+ let model = match Model::find_by_id(model_id).await {
+ Ok(model) => model,
+ Err(_) => {
+ return Err(AppError::NotFound("Model not found".to_string()));
+ }
+ };
+
+ let user = User::find_by_id(claims.user_id).await?;
+
+ let like = Like::new(user.id, model.id);
+
+ match like.remove().await {
+ Ok(_) => Ok(StatusCode::NO_CONTENT),
+ Err(e) => {
+ return Err(e);
+ }
+ }
+}