Skip to content

Commit

Permalink
Code review improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
legendofa committed May 7, 2024
1 parent 7d724b3 commit 1aa5547
Show file tree
Hide file tree
Showing 19 changed files with 238 additions and 176 deletions.
1 change: 1 addition & 0 deletions docker-compose-base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ services:
interval: 10s
timeout: 5s
retries: 3
command: --quiet
review-dapr:
image: "daprio/daprd:edge"
command:
Expand Down
79 changes: 0 additions & 79 deletions src/authentication.rs

This file was deleted.

97 changes: 97 additions & 0 deletions src/authorization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use async_graphql::{Context, Error, Result};
use axum::http::HeaderMap;
use bson::Uuid;
use serde::Deserialize;

/// `Authorized-User` HTTP header.
#[derive(Deserialize, Debug)]
pub struct AuthorizedUserHeader {
id: Uuid,
roles: Vec<Role>,
}

/// Extraction of `Authorized-User` header from header map.
impl TryFrom<&HeaderMap> for AuthorizedUserHeader {
type Error = Error;

/// Tries to extract the `Authorized-User` header from a header map.
///
/// Returns a GraphQL error if the extraction fails.
fn try_from(header_map: &HeaderMap) -> Result<Self, Self::Error> {
if let Some(authorized_user_header_value) = header_map.get("Authorized-User") {
if let Ok(authorized_user_header_str) = authorized_user_header_value.to_str() {
let authorized_user_header: AuthorizedUserHeader =
serde_json::from_str(authorized_user_header_str)?;
return Ok(authorized_user_header);
}
}
Err(Error::new(
"Authorization failed. Authorized-User header is not set or could not be parsed.",
))
}
}

/// Role of user.
#[derive(Deserialize, Debug, PartialEq, Clone, Copy)]
#[serde(rename_all = "snake_case")]
enum Role {
Buyer,
Admin,
Employee,
}

impl Role {
/// Defines if user has a permissive role.
///
// * `self` - This role instance.
fn is_permissive(self) -> bool {
match self {
Self::Buyer => false,
Self::Admin => true,
Self::Employee => true,
}
}
}

/// Authorize user of UUID for a context.
///
/// * `context` - GraphQL context containing the `Authorized-User` header.
/// * `id` - Option of UUID of the user to authorize.
pub fn authorize_user(ctx: &Context, id: Option<Uuid>) -> Result<()> {
match ctx.data::<AuthorizedUserHeader>() {
Ok(authorized_user_header) => check_permissions(&authorized_user_header, id),
Err(_) => Err(Error::new(
"Authentication failed. Authorized-User header is not set or could not be parsed.",
)),
}
}

/// Check if user of UUID has a valid permission according to the `Authorized-User` header.
///
/// Permission is valid if the user has `Role::Buyer` and the same UUID as provided in the function parameter.
/// Permission is valid if the user has a permissive role: `user.is_permissive() == true`, regardless of the users UUID.
///
/// * `authorized_user_header` - `Authorized-User` header containing the users UUID and role.
/// * `id` - Option of UUID of the user to authorize.
pub fn check_permissions(
authorized_user_header: &AuthorizedUserHeader,
id: Option<Uuid>,
) -> Result<()> {
let id_contained_in_header = id
.and_then(|id| Some(authorized_user_header.id == id))
.unwrap_or(false);
if authorized_user_header
.roles
.iter()
.any(|role| role.is_permissive())
|| id_contained_in_header
{
return Ok(());
} else {
let message = format!(
"Authentication failed for user of UUID: `{}`. Operation not permitted.",
authorized_user_header.id
);
return Err(Error::new(message));
}
}
21 changes: 17 additions & 4 deletions src/http_event_service.rs → src/event/http_event_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use log::info;
use mongodb::Collection;
use serde::{Deserialize, Serialize};

use crate::{product::Product, product_variant::ProductVariant, user::User};
use crate::graphql::model::{product::Product, product_variant::ProductVariant, user::User};

/// Data to send to Dapr in order to describe a subscription.
#[derive(Serialize)]
Expand All @@ -28,21 +28,22 @@ impl Default for TopicEventResponse {
}
}

/// Relevant part of Dapr event wrapped in a CloudEnvelope.
/// Relevant part of Dapr event wrapped in a cloud envelope.
#[derive(Deserialize, Debug)]
pub struct Event<T> {
pub topic: String,
pub data: T,
}

/// Relevant part of Dapr event.data.
/// Relevant part of Dapr event data.
#[derive(Deserialize, Debug)]
pub struct EventData {
pub id: Uuid,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
/// Relevant part of product variant creation event data.
pub struct ProductVariantEventData {
/// Product variant UUID.
pub id: Uuid,
Expand Down Expand Up @@ -83,6 +84,9 @@ pub async fn list_topic_subscriptions() -> Result<Json<Vec<Pubsub>>, StatusCode>
}

/// HTTP endpoint to receive events.
///
/// * `state` - Service state containing database connections.
/// * `event` - Event handled by endpoint.
#[debug_handler(state = HttpEventServiceState)]
pub async fn on_topic_event(
State(state): State<HttpEventServiceState>,
Expand All @@ -101,6 +105,9 @@ pub async fn on_topic_event(
}

/// HTTP endpoint to product variant creation events.
///
/// * `state` - Service state containing database connections.
/// * `event` - Event handled by endpoint.
#[debug_handler(state = HttpEventServiceState)]
pub async fn on_product_variant_creation_event(
State(state): State<HttpEventServiceState>,
Expand All @@ -120,6 +127,9 @@ pub async fn on_product_variant_creation_event(
}

/// Add a newly created product variant to MongoDB.
///
/// * `collection` - MongoDB collection to add newly created product variant to.
/// * `product_variant` - Newly created product variant.
pub async fn add_product_variant_to_mongodb(
collection: &Collection<ProductVariant>,
product_variant: ProductVariant,
Expand All @@ -130,7 +140,10 @@ pub async fn add_product_variant_to_mongodb(
}
}

/// Create a new object: T in MongoDB.
/// Create a new object: `T` in MongoDB.
///
/// * `collection` - MongoDB collection to add newly created object to.
/// * `id` - UUID of newly created object.
pub async fn create_in_mongodb<T: Serialize + From<Uuid>>(
collection: &Collection<T>,
id: Uuid,
Expand Down
1 change: 1 addition & 0 deletions src/event/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod http_event_service;
4 changes: 4 additions & 0 deletions src/graphql/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod model;
pub mod mutation;
pub mod mutation_input_structs;
pub mod query;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use async_graphql::{OutputType, SimpleObject};
use mongodb_cursor_pagination::FindResult;

/// A base connection for an OutputType.
/// A base connection for an output type.
#[derive(SimpleObject)]
#[graphql(shareable)]
pub struct BaseConnection<T: OutputType> {
Expand All @@ -12,8 +13,6 @@ pub struct BaseConnection<T: OutputType> {
pub total_count: u64,
}

use mongodb_cursor_pagination::FindResult;

pub struct FindResultWrapper<Node>(pub FindResult<Node>);

/// Object that writes total count of items in a query, regardless of pagination.
Expand All @@ -22,7 +21,7 @@ pub struct AdditionalFields {
total_count: u64,
}

/// Implementation of conversion from MongoDB pagination to GraphQL Connection.
/// Implementation of conversion from MongoDB pagination to GraphQL connection.
impl<Node> From<FindResultWrapper<Node>> for BaseConnection<Node>
where
Node: OutputType,
Expand Down
2 changes: 2 additions & 0 deletions src/graphql/model/connection/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod base_connection;
pub mod review_connection;
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use async_graphql::SimpleObject;

use crate::{base_connection::BaseConnection, review::Review};
use super::{super::review::Review, base_connection::BaseConnection};

/// A connection of Reviews.
/// A connection of reviews.
#[derive(Debug, SimpleObject, Clone)]
#[graphql(shareable)]
pub struct ReviewConnection {
Expand All @@ -14,7 +14,7 @@ pub struct ReviewConnection {
pub total_count: u64,
}

/// Implementation of conversion from BaseConnection<Review> to ReviewConnection.
/// Implementation of conversion from `BaseConnection<Review>` to `ReviewConnection`.
///
/// Prevents GraphQL naming conflicts.
impl From<BaseConnection<Review>> for ReviewConnection {
Expand Down
6 changes: 6 additions & 0 deletions src/graphql/model/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pub mod connection;
pub mod order_datatypes;
pub mod product;
pub mod product_variant;
pub mod review;
pub mod user;
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ impl Default for OrderDirection {
}
}

/// Implements conversion to i32 for MongoDB document sorting.
/// Implements conversion to `i32`` for MongoDB document sorting.
impl From<OrderDirection> for i32 {
fn from(value: OrderDirection) -> Self {
match value {
Expand Down Expand Up @@ -78,7 +78,7 @@ impl Default for ReviewOrderInput {

/// Describes the fields that a foreign types can be ordered by.
///
/// Only the Id valid at the moment.
/// Only the id valid at the moment.
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
pub enum CommonOrderField {
/// Orders by "id".
Expand Down
8 changes: 5 additions & 3 deletions src/product.rs → src/graphql/model/product.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ use mongodb::{options::FindOptions, Collection, Database};
use mongodb_cursor_pagination::{error::CursorError, FindResult, PaginatedCursor};
use serde::{Deserialize, Serialize};

use crate::{
base_connection::{BaseConnection, FindResultWrapper},
use super::{
connection::{
base_connection::{BaseConnection, FindResultWrapper},
review_connection::ReviewConnection,
},
order_datatypes::ReviewOrderInput,
product_variant::calculate_average_rating,
review::Review,
review_connection::ReviewConnection,
};

#[derive(Debug, Serialize, Deserialize, PartialEq, Copy, Clone, SimpleObject)]
Expand Down
13 changes: 9 additions & 4 deletions src/product_variant.rs → src/graphql/model/product_variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ use mongodb::{options::FindOptions, Collection, Database};
use mongodb_cursor_pagination::{error::CursorError, FindResult, PaginatedCursor};
use serde::{Deserialize, Serialize};

use crate::{
base_connection::{BaseConnection, FindResultWrapper},
http_event_service::ProductVariantEventData,
use crate::event::http_event_service::ProductVariantEventData;

use super::{
connection::{
base_connection::{BaseConnection, FindResultWrapper},
review_connection::ReviewConnection,
},
order_datatypes::ReviewOrderInput,
review::Review,
review_connection::ReviewConnection,
};

#[derive(Debug, Serialize, Deserialize, PartialEq, Copy, Clone, SimpleObject)]
Expand Down Expand Up @@ -86,6 +89,8 @@ impl From<ProductVariantEventData> for ProductVariant {
/// Shared function to calculate average rating of a review connection.
///
/// Filters reviews with `is_visible == false` to exclude them from the average rating.
///
/// `review_connection` - Connection of reviews to calculate average rating for.
pub async fn calculate_average_rating<'a>(review_connection: ReviewConnection) -> Result<f32> {
let reviews = review_connection.nodes.clone();
let (accumulated_reviews, total_count) = reviews.iter().filter(|r| r.is_visible).fold(
Expand Down
Loading

0 comments on commit 1aa5547

Please sign in to comment.