Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ratings CRUD #516

Draft
wants to merge 4 commits into
base: CHAOS-224-KHAOS-rewrite
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/migrations/20240406031915_create_applications.sql
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ CREATE INDEX IDX_multi_option_answer_options_question_options on multi_option_an
CREATE INDEX IDX_multi_option_answer_options_answers on multi_option_answer_options (answer_id);

CREATE TABLE application_ratings (
id SERIAL PRIMARY KEY,
id BIGINT PRIMARY KEY,
application_id BIGINT NOT NULL,
rater_id BIGINT NOT NULL,
rating INTEGER NOT NULL,
Expand Down
1 change: 1 addition & 0 deletions backend/server/src/handler/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod auth;
pub mod organisation;
pub mod ratings;
61 changes: 61 additions & 0 deletions backend/server/src/handler/ratings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::models::app::AppState;
use crate::models::auth::SuperUser;
use crate::models::error::ChaosError;
use crate::models::ratings::{NewRating, Rating};
use crate::models::transaction::DBTransaction;
use axum::extract::{Json, Path, State};
use axum::http::StatusCode;
use axum::response::IntoResponse;

pub struct RatingsHandler;

impl RatingsHandler {
// TODO: are all the user permissions as required? Who should be able to do what with ratings?
pub async fn create_rating(
State(state): State<AppState>,
_user: SuperUser,
mut transaction: DBTransaction<'_>,
Json(new_rating): Json<NewRating>,
) -> Result<impl IntoResponse, ChaosError> {
Rating::create(new_rating, state.snowflake_generator, &mut transaction.tx).await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, "Successfully created rating"))
}

pub async fn get_ratings_for_application(
State(_state): State<AppState>,
Path(application_id): Path<i64>,
_user: SuperUser,
mut transaction: DBTransaction<'_>,
) -> Result<impl IntoResponse, ChaosError> {
let ratings =
Rating::get_all_ratings_from_application_id(application_id, &mut transaction.tx)
.await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, Json(ratings)))
}

// TODO: should I use transaction here? Organisation still uses state.db for these simpler getters.
pub async fn get(
State(_state): State<AppState>,
Path(rating_id): Path<i64>,
_user: SuperUser,
mut transaction: DBTransaction<'_>,
) -> Result<impl IntoResponse, ChaosError> {
let org = Rating::get_rating(rating_id, &mut transaction.tx).await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, Json(org)))
}

// TODO: should I use transaction here?
pub async fn delete(
State(_state): State<AppState>,
Path(id): Path<i64>,
_user: SuperUser,
mut transaction: DBTransaction<'_>,
) -> Result<impl IntoResponse, ChaosError> {
Rating::delete(id, &mut transaction.tx).await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, "Successfully deleted rating"))
}
}
11 changes: 11 additions & 0 deletions backend/server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::models::storage::Storage;
use anyhow::Result;
use axum::routing::{get, patch, post, put};
use axum::Router;
use handler::ratings::RatingsHandler;
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation};
use models::app::AppState;
use snowflake::SnowflakeIdGenerator;
Expand Down Expand Up @@ -89,6 +90,16 @@ async fn main() -> Result<()> {
.put(OrganisationHandler::update_admins)
.delete(OrganisationHandler::remove_admin),
)
.route("/api/v1/ratings/:rating_id", get(RatingsHandler::get))
.route(
"/api/v1/:application_id/rating",
put(RatingsHandler::create_rating),
fritzrehde marked this conversation as resolved.
Show resolved Hide resolved
)
.route(
"/api/v1/:application_id/ratings",
get(RatingsHandler::get_ratings_for_application),
)
// TODO: should ratings also have a delete endpoint? I think old backend didn't
.with_state(state);

let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
Expand Down
1 change: 1 addition & 0 deletions backend/server/src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod auth;
pub mod campaign;
pub mod error;
pub mod organisation;
pub mod ratings;
pub mod storage;
pub mod transaction;
pub mod user;
122 changes: 122 additions & 0 deletions backend/server/src/models/ratings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use crate::models::error::ChaosError;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use snowflake::SnowflakeIdGenerator;
use sqlx::{FromRow, Postgres, Transaction};
use std::ops::DerefMut;

#[derive(Deserialize, Serialize, Clone, FromRow, Debug)]
pub struct Rating {
pub id: i64,
pub application_id: i64,
pub rater_user_id: i64,
pub rating: i32,
// TODO: what's the point of storing created_at and updated_at if they are not accessible to users through endpoints?
fritzrehde marked this conversation as resolved.
Show resolved Hide resolved
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}

// TODO: Does the user have to provide rater user id in their JSON? How do they get that? can't we get that in the RatingsHandler::create_rating method?
#[derive(Deserialize, Serialize)]
pub struct NewRating {
pub application_id: i64,
fritzrehde marked this conversation as resolved.
Show resolved Hide resolved
pub rater_user_id: i64,
pub rating: i32,
}

#[derive(Deserialize, Serialize)]
pub struct RatingDetails {
pub id: i64,
pub rating: i32,
pub created_at: DateTime<Utc>,
fritzrehde marked this conversation as resolved.
Show resolved Hide resolved
}
fritzrehde marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Deserialize, Serialize)]
pub struct RatingsDetails {
fritzrehde marked this conversation as resolved.
Show resolved Hide resolved
// TODO: should this be: Vec<Rating> instead?
pub ratings: Vec<RatingDetails>,
}

impl Rating {
/// Create a new rating.
pub async fn create(
new_rating: NewRating,
mut snowflake_generator: SnowflakeIdGenerator,
transaction: &mut Transaction<'_, Postgres>,
) -> Result<(), ChaosError> {
let rating_id = snowflake_generator.generate();
let application_id = new_rating.application_id;
let rater_user_id = new_rating.rater_user_id;
let rating = new_rating.rating;

sqlx::query!(
"
INSERT INTO application_ratings (id, application_id, rater_id, rating)
VALUES ($1, $2, $3, $4)
",
rating_id,
application_id,
rater_user_id,
rating
)
.execute(transaction.deref_mut())
.await?;

Ok(())
}

pub async fn get_rating(
rating_id: i64,
transaction: &mut Transaction<'_, Postgres>,
) -> Result<RatingDetails, ChaosError> {
let rating = sqlx::query_as!(
RatingDetails,
"
SELECT id, rating, created_at
FROM application_ratings
WHERE id = $1
",
rating_id
)
.fetch_one(transaction.deref_mut())
.await?;

Ok(rating)
}

/// Get all ratings for a certain application.
pub async fn get_all_ratings_from_application_id(
application_id: i64,
transaction: &mut Transaction<'_, Postgres>,
) -> Result<RatingsDetails, ChaosError> {
let ratings = sqlx::query_as!(
RatingDetails,
"
SELECT id, rating, created_at
FROM application_ratings
WHERE application_id = $1
",
application_id
)
.fetch_all(transaction.deref_mut())
.await?;

Ok(RatingsDetails { ratings })
}

pub async fn delete(
rating_id: i64,
transaction: &mut Transaction<'_, Postgres>,
) -> Result<(), ChaosError> {
sqlx::query!(
"
DELETE FROM application_ratings WHERE id = $1
",
rating_id
)
.execute(transaction.deref_mut())
.await?;

Ok(())
}
}
Loading