summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--migrations/20220920135729_add-warnings-table.sql10
-rw-r--r--src/main.rs3
-rw-r--r--src/models/mod.rs1
-rw-r--r--src/models/model.rs2
-rw-r--r--src/models/warning.rs122
-rw-r--r--src/routes/mod.rs1
-rw-r--r--src/routes/warning.rs64
7 files changed, 201 insertions, 2 deletions
diff --git a/migrations/20220920135729_add-warnings-table.sql b/migrations/20220920135729_add-warnings-table.sql
new file mode 100644
index 0000000..887e4f5
--- /dev/null
+++ b/migrations/20220920135729_add-warnings-table.sql
@@ -0,0 +1,10 @@
+CREATE TABLE warnings (
+ id SERIAL PRIMARY KEY,
+ user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
+ model_id INTEGER REFERENCES models(id) ON DELETE SET NULL,
+ resolved_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
+ note TEXT,
+ admin_note TEXT,
+ created TIMESTAMP NOT NULL,
+ updated TIMESTAMP NOT NULL
+);
diff --git a/src/main.rs b/src/main.rs
index 13f8054..66caecd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -56,7 +56,8 @@ async fn create_app() -> Router {
let api_routes = Router::new()
.nest("/users", routes::user::create_route())
.nest("/auth", routes::auth::create_route())
- .nest("/models", routes::model::create_route());
+ .nest("/models", routes::model::create_route())
+ .nest("/warnings", routes::warning::create_route());
Router::new()
.route(
diff --git a/src/models/mod.rs b/src/models/mod.rs
index 7e61662..45a11f3 100644
--- a/src/models/mod.rs
+++ b/src/models/mod.rs
@@ -1,3 +1,4 @@
pub mod auth;
pub mod model;
pub mod user;
+pub mod warning;
diff --git a/src/models/model.rs b/src/models/model.rs
index 5aa3ddf..071f515 100644
--- a/src/models/model.rs
+++ b/src/models/model.rs
@@ -38,7 +38,7 @@ pub struct ModelCreate {
#[derive(Serialize, sqlx::FromRow)]
pub struct ModelUser {
- id: i32,
+ pub id: i32,
name: String,
description: Option<String>,
duration: i32,
diff --git a/src/models/warning.rs b/src/models/warning.rs
new file mode 100644
index 0000000..0725d53
--- /dev/null
+++ b/src/models/warning.rs
@@ -0,0 +1,122 @@
+use crate::{config::CONFIG, db::get_client, errors::AppError};
+use chrono::{Local, NaiveDateTime};
+use serde::{Deserialize, Serialize};
+use sqlx::Row;
+
+/// Model for models.
+#[derive(Deserialize, Serialize, sqlx::FromRow)]
+pub struct Warning {
+ id: i32,
+ user_id: Option<i32>,
+ model_id: Option<i32>,
+ resolved_by: Option<i32>,
+ note: String,
+ admin_note: String,
+ created: NaiveDateTime,
+ updated: NaiveDateTime,
+}
+
+/// Payload used to create a new warning
+#[derive(Deserialize)]
+pub struct WarningCreate {
+ pub model_id: i32,
+ pub note: String,
+}
+
+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,
+ }
+ }
+
+ /// 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<Warning>, AppError> {
+ let pool = unsafe { get_client() };
+ let rows: Vec<Warning> = match user_id {
+ Some(id) => {
+ sqlx::query_as(
+ r#"
+ SELECT * FROM warnings WHERE user_id = $1
+ LIMIT $2 OFFSET $3
+ "#,
+ )
+ .bind(id)
+ .bind(CONFIG.page_limit)
+ .bind(CONFIG.page_limit * page)
+ .fetch_all(pool)
+ .await?
+ }
+ None => {
+ sqlx::query_as(
+ r#"
+ SELECT * FROM warnings
+ LIMIT $1 OFFSET $2
+ "#,
+ )
+ .bind(CONFIG.page_limit)
+ .bind(CONFIG.page_limit * page)
+ .fetch_all(pool)
+ .await?
+ }
+ };
+
+ Ok(rows)
+ }
+
+ /// 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)
+ }
+}
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
index c69f18e..a0e8031 100644
--- a/src/routes/mod.rs
+++ b/src/routes/mod.rs
@@ -1,6 +1,7 @@
pub mod auth;
pub mod model;
pub mod user;
+pub mod warning;
use crate::errors::AppError;
use axum::{
diff --git a/src/routes/warning.rs b/src/routes/warning.rs
new file mode 100644
index 0000000..87ca514
--- /dev/null
+++ b/src/routes/warning.rs
@@ -0,0 +1,64 @@
+use crate::{
+ errors::AppError,
+ models::{
+ auth::Claims,
+ model::Model,
+ user::User,
+ warning::{Warning, WarningCreate},
+ },
+ pagination::Pagination,
+ routes::JsonCreate,
+};
+use axum::{extract::Query, routing::get, Json, Router};
+use serde::Serialize;
+
+/// Create routes for `/v1/warnings/` namespace
+pub fn create_route() -> Router {
+ Router::new().route("/", get(list_warnings).post(create_warning))
+}
+
+#[derive(Serialize)]
+struct WarningPagination {
+ count: i64,
+ results: Vec<Warning>,
+}
+
+/// List warnings. A staffer can see everything.
+async fn list_warnings(
+ pagination: Query<Pagination>,
+ claims: Claims,
+) -> Result<Json<WarningPagination>, AppError> {
+ let page = pagination.0.page.unwrap_or_default();
+
+ let user = User::find_by_id(claims.user_id).await?;
+
+ let (results, count) = match user.is_staff.unwrap() {
+ true => (
+ Warning::list(page, None).await?,
+ Warning::count(None).await?,
+ ),
+ false => (
+ Warning::list(page, Some(user.id)).await?,
+ Warning::count(Some(user.id)).await?,
+ ),
+ };
+
+ Ok(Json(WarningPagination { count, results }))
+}
+
+/// Create a warning. Checks Authorization token
+async fn create_warning(
+ Json(payload): Json<WarningCreate>,
+ claims: Claims,
+) -> Result<JsonCreate<Warning>, AppError> {
+ let model = match Model::find_by_id(payload.model_id).await {
+ Ok(model) => model,
+ Err(_) => return Err(AppError::NotFound("Model not found".to_string())),
+ };
+
+ let warning = Warning::new(claims.user_id, model.id, payload.note);
+
+ let warning_new = Warning::create(warning).await?;
+
+ Ok(JsonCreate(warning_new))
+}