Skip to content

Commit

Permalink
implemented most feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
fritzrehde committed Oct 14, 2024
1 parent 09e41d1 commit 10f526f
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 26 deletions.
22 changes: 21 additions & 1 deletion backend/server/src/handler/ratings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,35 @@ 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>,
Path(application_id): Path<i64>,
_admin: ApplicationReviewerAdmin,
mut transaction: DBTransaction<'_>,
Json(new_rating): Json<NewRating>,
) -> Result<impl IntoResponse, ChaosError> {
Rating::create(new_rating, state.snowflake_generator, &mut transaction.tx).await?;
Rating::create(
new_rating,
application_id,
state.snowflake_generator,
&mut transaction.tx,
)
.await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, "Successfully created rating"))
}

pub async fn update(
State(_state): State<AppState>,
Path(rating_id): Path<i64>,
// TODO: authorization: needs to be user that created the rating and is a current member of organisation.
_admin: ApplicationReviewerAdmin,
mut transaction: DBTransaction<'_>,
Json(updated_rating): Json<NewRating>,
) -> Result<impl IntoResponse, ChaosError> {
Rating::update(rating_id, updated_rating, &mut transaction.tx).await?;
transaction.tx.commit().await?;
Ok((StatusCode::OK, "Successfully updated rating"))
}

pub async fn get_ratings_for_application(
State(_state): State<AppState>,
Path(application_id): Path<i64>,
Expand Down
6 changes: 4 additions & 2 deletions backend/server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,13 @@ async fn main() -> Result<()> {
)
.route(
"/api/v1/ratings/:rating_id",
get(RatingsHandler::get).delete(RatingsHandler::delete),
get(RatingsHandler::get)
.delete(RatingsHandler::delete)
.put(RatingsHandler::update),
)
.route(
"/api/v1/:application_id/rating",
put(RatingsHandler::create_rating),
post(RatingsHandler::create_rating),
)
.route(
"/api/v1/:application_id/ratings",
Expand Down
47 changes: 46 additions & 1 deletion backend/server/src/models/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use crate::models::error::ChaosError;
use crate::service::auth::is_super_user;
use crate::service::jwt::decode_auth_token;
use crate::service::organisation::assert_user_is_admin;
use crate::service::ratings::assert_user_is_application_reviewer_admin;
use crate::service::ratings::{
assert_user_is_application_reviewer_admin, assert_user_is_rating_creator,
};
use axum::extract::{FromRef, FromRequestParts, Path};
use axum::http::request::Parts;
use axum::response::{IntoResponse, Redirect, Response};
Expand Down Expand Up @@ -176,6 +178,7 @@ where
let pool = &app_state.db;
let user_id = claims.sub;

// TODO: check if application_id or rating_id was passed.
// TODO: How am I guaranteeing this rating id is in the endpoint? Would it be a bad request if the rating id was left out?
let Path(rating_id) = parts
.extract::<Path<i64>>()
Expand All @@ -187,3 +190,45 @@ where
Ok(ApplicationReviewerAdmin { user_id })
}
}

pub struct RatingCreatorAdmin {
pub user_id: i64,
}

#[async_trait]
impl<S> FromRequestParts<S> for RatingCreatorAdmin
where
AppState: FromRef<S>,
S: Send + Sync,
{
type Rejection = ChaosError;

async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
// TODO: put into separate function, since this is just getting the id through jwt, and duplicated here.
let app_state = AppState::from_ref(state);
let decoding_key = &app_state.decoding_key;
let jwt_validator = &app_state.jwt_validator;
let TypedHeader(cookies) = parts
.extract::<TypedHeader<Cookie>>()
.await
.map_err(|_| ChaosError::NotLoggedIn)?;

let token = cookies.get("auth_token").ok_or(ChaosError::NotLoggedIn)?;

let claims =
decode_auth_token(token, decoding_key, jwt_validator).ok_or(ChaosError::NotLoggedIn)?;

let pool = &app_state.db;
let user_id = claims.sub;

// TODO
// let Path(rating_id) = parts
// .extract::<Path<i64>>()
// .await
// .map_err(|_| ChaosError::BadRequest)?;

// assert_user_is_rating_creator(user_id, rating_id, pool).await?;

Ok(RatingCreatorAdmin { user_id })
}
}
63 changes: 45 additions & 18 deletions backend/server/src/models/ratings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,39 @@ pub struct Rating {
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?
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,
pub rater_user_id: i64,
pub rating: i32,
}

#[derive(Deserialize, Serialize)]
pub struct RatingDetails {
pub id: i64,
pub rater_id: i64,
pub rater_name: String,
pub rating: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}

#[derive(Deserialize, Serialize)]
pub struct RatingsDetails {
// TODO: should this be: Vec<Rating> instead?
pub struct ApplicationRatings {
pub ratings: Vec<RatingDetails>,
}

impl Rating {
/// Create a new rating.
pub async fn create(
new_rating: NewRating,
application_id: i64,
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;

Expand All @@ -65,16 +63,43 @@ impl Rating {
Ok(())
}

/// Create a new rating.
pub async fn update(
rating_id: i64,
updated_rating: NewRating,
transaction: &mut Transaction<'_, Postgres>,
) -> Result<(), ChaosError> {
let rating = updated_rating.rating;
let current_time = Utc::now();

let _ = sqlx::query!(
"
UPDATE application_ratings
SET rating = $2, updated_at = $3
WHERE id = $1
RETURNING id;
",
rating_id,
rating,
current_time
)
.fetch_one(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
SELECT r.id, rater_id, u.name as rater_name, r.rating, r.updated_at
FROM application_ratings r
JOIN users u ON u.id = r.id
WHERE r.id = $1
",
rating_id
)
Expand All @@ -88,34 +113,36 @@ impl Rating {
pub async fn get_all_ratings_from_application_id(
application_id: i64,
transaction: &mut Transaction<'_, Postgres>,
) -> Result<RatingsDetails, ChaosError> {
) -> Result<ApplicationRatings, ChaosError> {
let ratings = sqlx::query_as!(
RatingDetails,
"
SELECT id, rating, created_at
FROM application_ratings
WHERE application_id = $1
SELECT r.id, rater_id, u.name as rater_name, r.rating, r.updated_at
FROM application_ratings r
JOIN users u ON u.id = r.id
WHERE r.application_id = $1
",
application_id
)
.fetch_all(transaction.deref_mut())
.await?;

Ok(RatingsDetails { ratings })
Ok(ApplicationRatings { ratings })
}

pub async fn delete(
rating_id: i64,
transaction: &mut Transaction<'_, Postgres>,
) -> Result<(), ChaosError> {
// TODO: fix sth kavika wanted fixed.
sqlx::query!(
// Throws error if rating id doesn't exist.
let _ = sqlx::query!(
"
DELETE FROM application_ratings WHERE id = $1
RETURNING id;
",
rating_id
)
.execute(transaction.deref_mut())
.fetch_one(transaction.deref_mut())
.await?;

Ok(())
Expand Down
16 changes: 12 additions & 4 deletions backend/server/src/service/ratings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ use sqlx::{Pool, Postgres, Transaction};
use std::ops::DerefMut;
use uuid::Uuid;

/// Any member of the organisation that owns the campaign is an application
/// viewer, because all members are either directors or execs (TODO: might be
/// changed in the future).
pub async fn assert_user_is_application_reviewer_admin(
user_id: i64,
rating_id: i64,
pool: &Pool<Postgres>,
) -> Result<(), ChaosError> {
// Any member of the organisation that owns the campaign is an application
// viewer, because all members are either directors or execs (TODO: might be
// changed in the future).
let is_admin = sqlx::query!(
r#"
SELECT EXISTS (
Expand All @@ -29,7 +29,7 @@ pub async fn assert_user_is_application_reviewer_admin(
-- Assert user is member of the organisation that owns the campaign
-- this application belongs to.
AND om.user_id = $2
);
)
"#,
rating_id,
user_id
Expand All @@ -45,3 +45,11 @@ pub async fn assert_user_is_application_reviewer_admin(

Ok(())
}

pub async fn assert_user_is_rating_creator(
user_id: i64,
rating_id: i64,
pool: &Pool<Postgres>,
) -> Result<(), ChaosError> {
todo!()
}

0 comments on commit 10f526f

Please sign in to comment.