Skip to content

Commit

Permalink
feat: chart endpoint init endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
matthew-hagemann committed Nov 27, 2023
1 parent 6440891 commit 0bcab50
Show file tree
Hide file tree
Showing 14 changed files with 264 additions and 50 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*.rock
*.nix

proto/*.rs
/proto/*.rs
venv/
build/
*.charm
Expand Down
8 changes: 6 additions & 2 deletions src/app/interfaces/routes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use tonic::transport::server::Router;
use tonic_reflection::server::ServerReflection;

use crate::features::{app, user};
use crate::features::{app, chart, user};

pub fn build_reflection_service(
) -> tonic_reflection::server::ServerReflectionServer<impl ServerReflection> {
Expand All @@ -16,6 +16,10 @@ pub fn build_reflection_service(
pub fn build_servers<R>(router: Router<R>) -> Router<R> {
let user_service = user::service::build_service();
let app_service = app::service::build_service();
let chart_service = chart::service::build_service();

router.add_service(user_service).add_service(app_service)
router
.add_service(user_service)
.add_service(app_service)
.add_service(chart_service)
}
39 changes: 17 additions & 22 deletions src/features/app/infrastructure.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use crate::{app::AppContext, features::common::entities::Vote};
use sqlx::Row;
use crate::{app::AppContext, features::common::entities::VoteSummary};
use tracing::error;

use super::errors::AppError;

pub(crate) async fn get_votes_by_snap_id(
app_ctx: &AppContext,
snap_id: &str,
) -> Result<Vec<Vote>, AppError> {
) -> Result<VoteSummary, AppError> {
let mut pool = app_ctx
.infrastructure()
.repository()
Expand All @@ -16,32 +15,28 @@ pub(crate) async fn get_votes_by_snap_id(
error!("{error:?}");
AppError::FailedToGetRating
})?;
let result = sqlx::query(

let result = sqlx::query_as::<_, VoteSummary>(
// Changed to query_as with VoteSummary
r#"
SELECT
votes.id,
votes.snap_id,
votes.vote_up
FROM
votes
WHERE
votes.snap_id = $1
"#,
SELECT
$1 as snap_id, // Explicitly select snap_id since it's not part of the GROUP BY
COUNT(*) AS total_votes,
COUNT(*) FILTER (WHERE votes.vote_up) AS positive_votes
FROM
votes
WHERE
votes.snap_id = $1
GROUP BY votes.snap_id
"#,
)
.bind(snap_id)
.fetch_all(&mut *pool)
.fetch_one(&mut *pool) // Changed to fetch_one since we're expecting a single summarized row
.await
.map_err(|error| {
error!("{error:?}");
AppError::Unknown
})?;

let votes: Vec<Vote> = result
.into_iter()
.map(|row| Vote {
vote_up: row.get("vote_up"),
})
.collect();

Ok(votes)
Ok(result)
}
2 changes: 1 addition & 1 deletion src/features/app/use_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub async fn get_rating(app_ctx: &AppContext, snap_id: String) -> Result<Rating,
AppError::Unknown
})?;

let rating = Rating::new(snap_id, votes);
let rating = Rating::new(votes);

Ok(rating)
}
53 changes: 53 additions & 0 deletions src/features/chart/entities.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use sqlx::FromRow;

use crate::features::common::entities::{calculate_band, Rating, VoteSummary};
use crate::features::pb::chart as pb;

pub struct Chart {
pub timeframe: pb::Timeframe,
pub chart_data: Vec<ChartData>,
}

impl Chart {
pub fn new(timeframe: pb::Timeframe, data: Vec<VoteSummary>) -> Self {
let mut chart_data: Vec<ChartData> =
data.into_iter().map(ChartData::from_vote_summary).collect();

chart_data.sort_by(|a, b| {
b.raw_rating
.partial_cmp(&a.raw_rating)
.unwrap_or(std::cmp::Ordering::Equal)
});

Chart {
timeframe,
chart_data,
}
}
}

#[derive(Debug, Clone, FromRow)]
pub struct ChartData {
pub raw_rating: f32,
pub rating: Rating,
}

impl ChartData {
pub fn from_vote_summary(vote_summary: VoteSummary) -> Self {
let (raw_rating, ratings_band) = calculate_band(&vote_summary);
let rating = Rating {
snap_id: vote_summary.snap_id,
total_votes: vote_summary.total_votes as u64,
ratings_band,
};
let raw_rating = raw_rating.unwrap_or(0.0) as f32;
Self { raw_rating, rating }
}

pub fn into_dto(self) -> pb::ChartData {
pb::ChartData {
raw_rating: self.raw_rating,
rating: Some(self.rating.into_dto()),
}
}
}
9 changes: 9 additions & 0 deletions src/features/chart/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ChartError {
#[error("failed to get chart for timeframe")]
FailedToGetChart,
#[error("unknown chart error")]
Unknown,
}
51 changes: 51 additions & 0 deletions src/features/chart/infrastructure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use crate::app::AppContext;
use crate::features::common::entities::VoteSummary;
use crate::features::pb::chart::Timeframe;
use tracing::error;

use super::errors::ChartError;

pub(crate) async fn get_votes_summary_by_timeframe(
app_ctx: &AppContext,
timeframe: Timeframe,
) -> Result<Vec<VoteSummary>, ChartError> {
let mut pool = app_ctx
.infrastructure()
.repository()
.await
.map_err(|error| {
error!("{error:?}");
ChartError::FailedToGetChart
})?;

// Generate WHERE clause based on timeframe
let where_clause = match timeframe {
Timeframe::Week => "WHERE votes.created >= NOW() - INTERVAL '1 week'",
Timeframe::Month => "WHERE votes.created >= NOW() - INTERVAL '1 month'",
Timeframe::Unspecified => "", // Adjust as needed for Unspecified case
};

let query = format!(
r#"
SELECT
votes.snap_id,
COUNT(*) AS total_votes,
COUNT(*) FILTER (WHERE votes.vote_up) AS positive_votes
FROM
votes
{}
GROUP BY votes.snap_id
"#,
where_clause
);

let result = sqlx::query_as::<_, VoteSummary>(&query)
.fetch_all(&mut *pool)
.await
.map_err(|error| {
error!("{error:?}");
ChartError::Unknown
})?;

Ok(result)
}
44 changes: 44 additions & 0 deletions src/features/chart/interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use crate::app::AppContext;

use crate::features::pb::chart::{GetChartRequest, GetChartResponse, Timeframe};
use tonic::{Request, Response, Status};

use crate::features::pb::chart::chart_server::Chart;

use super::{service::ChartService, use_cases};

#[tonic::async_trait]
impl Chart for ChartService {
#[tracing::instrument]
async fn get_chart(
&self,
request: Request<GetChartRequest>,
) -> Result<Response<GetChartResponse>, Status> {
let app_ctx = request.extensions().get::<AppContext>().unwrap().clone();

let GetChartRequest { timeframe } = request.into_inner();

let timeframe = match timeframe {
0 => Timeframe::Unspecified,
1 => Timeframe::Week,
2 => Timeframe::Month,
_ => Timeframe::Unspecified,
};

let result = use_cases::get_chart(&app_ctx, timeframe).await;

match result {
Ok(result) => {

let ordered_chart_data = result.chart_data.into_iter().map(|chart_data| chart_data.into_dto()).collect();

let payload = GetChartResponse {
timeframe: timeframe.into(),
ordered_chart_data,
};
Ok(Response::new(payload))
},
Err(_error) => Err(Status::unknown("Internal server error")),
}
}
}
6 changes: 6 additions & 0 deletions src/features/chart/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pub mod entities;
mod errors;
mod infrastructure;
pub mod interface;
pub mod service;
mod use_cases;
9 changes: 9 additions & 0 deletions src/features/chart/service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use crate::features::pb::chart::chart_server::ChartServer;

#[derive(Debug, Default)]
pub struct ChartService;

pub fn build_service() -> ChartServer<ChartService> {
let service = ChartService;
ChartServer::new(service)
}
19 changes: 19 additions & 0 deletions src/features/chart/use_cases.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use super::{errors::ChartError, infrastructure::get_votes_summary_by_timeframe};
use crate::app::AppContext;
use crate::features::chart::entities::Chart;
use crate::features::pb::chart::Timeframe;
use tracing::error;
// use super::{entities::Rating, errors::AppError, infrastructure::get_votes_by_snap_id};

pub async fn get_chart(app_ctx: &AppContext, timeframe: Timeframe) -> Result<Chart, ChartError> {
let votes = get_votes_summary_by_timeframe(app_ctx, timeframe)
.await
.map_err(|error| {
error!("{error:?}");
ChartError::Unknown
})?;

let chart = Chart::new(timeframe, votes);

Ok(chart)
}
Loading

0 comments on commit 0bcab50

Please sign in to comment.