added query for reports
This commit is contained in:
parent
a48cace929
commit
7842a6014c
@ -9,7 +9,7 @@ services:
|
|||||||
POSTGRES_USER: school_app_api_user
|
POSTGRES_USER: school_app_api_user
|
||||||
PGDATA: /db
|
PGDATA: /db
|
||||||
volumes:
|
volumes:
|
||||||
- ./pgdata:/db:rw
|
- /home/mitchell/prod/school_app_api/pgdata:/db:rw
|
||||||
|
|
||||||
school-app-api:
|
school-app-api:
|
||||||
image: registry.mdev.local/school_app_api
|
image: registry.mdev.local/school_app_api
|
||||||
|
|||||||
5
migrations/20230414021153_event_attending.sql
Normal file
5
migrations/20230414021153_event_attending.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
-- fill existing users with grade 9
|
||||||
|
ALTER TABLE users ADD COLUMN grade INTEGER NOT NULL DEFAULT 9;
|
||||||
|
ALTER TABLE users ALTER COLUMN grade DROP DEFAULT;
|
||||||
|
|
||||||
|
ALTER TABLE event_attendees ADD COLUMN confirmed BOOLEAN NOT NULL DEFAULT false;
|
||||||
157
src/events.rs
157
src/events.rs
@ -1,107 +1,14 @@
|
|||||||
use axum::{
|
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
|
||||||
extract::State,
|
|
||||||
http::StatusCode,
|
|
||||||
response::{IntoResponse, Response},
|
|
||||||
Json,
|
|
||||||
};
|
|
||||||
use axum_auth::AuthBearer;
|
use axum_auth::AuthBearer;
|
||||||
use jsonwebtoken::{Algorithm, Validation};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use sqlx::{
|
use sqlx::{query, query_as};
|
||||||
query, query_as,
|
|
||||||
types::{chrono::NaiveDateTime, BigDecimal},
|
use crate::{
|
||||||
|
jwt::handle_token,
|
||||||
|
models::{Event, EventType, NewEventRequestEntry, Role},
|
||||||
|
AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{jwt::Claims, user::Role, AppState};
|
|
||||||
|
|
||||||
#[derive(
|
|
||||||
sqlx::Type, Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
|
|
||||||
)]
|
|
||||||
#[sqlx(type_name = "event_type", rename_all = "snake_case")]
|
|
||||||
enum EventType {
|
|
||||||
Sports,
|
|
||||||
Meetings,
|
|
||||||
Drama,
|
|
||||||
Music,
|
|
||||||
Other,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
||||||
pub struct EventsPreviewQuery {
|
|
||||||
count: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Debug)]
|
|
||||||
pub struct NewEventRequestEntry {
|
|
||||||
title: String,
|
|
||||||
description: String,
|
|
||||||
#[serde(with = "serialize_datetime")]
|
|
||||||
time_start: NaiveDateTime,
|
|
||||||
#[serde(with = "serialize_datetime")]
|
|
||||||
time_end: NaiveDateTime,
|
|
||||||
event_type: EventType,
|
|
||||||
points: i32,
|
|
||||||
place: Option<String>,
|
|
||||||
#[serde(with = "serialize_big_decimal")]
|
|
||||||
price: BigDecimal,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Debug)]
|
|
||||||
pub struct Event {
|
|
||||||
id: i32,
|
|
||||||
title: String,
|
|
||||||
description: String,
|
|
||||||
#[serde(with = "serialize_datetime")]
|
|
||||||
time_start: NaiveDateTime,
|
|
||||||
#[serde(with = "serialize_datetime")]
|
|
||||||
time_end: NaiveDateTime,
|
|
||||||
event_type: EventType,
|
|
||||||
points: i32,
|
|
||||||
place: Option<String>,
|
|
||||||
#[serde(with = "serialize_big_decimal")]
|
|
||||||
price: BigDecimal,
|
|
||||||
created_by: Option<i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
mod serialize_datetime {
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use serde::{self, Deserialize, Deserializer, Serializer};
|
|
||||||
use sqlx::types::chrono::NaiveDateTime;
|
|
||||||
|
|
||||||
pub fn serialize<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_str(&dt.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize<'de, D: Deserializer<'de>>(
|
|
||||||
deserializer: D,
|
|
||||||
) -> Result<NaiveDateTime, D::Error> {
|
|
||||||
let str = <String as Deserialize>::deserialize(deserializer)?;
|
|
||||||
NaiveDateTime::from_str(&str).map_err(|err| serde::de::Error::custom(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod serialize_big_decimal {
|
|
||||||
use serde::{self, Deserialize, Deserializer, Serializer};
|
|
||||||
use sqlx::types::BigDecimal;
|
|
||||||
|
|
||||||
pub fn serialize<S>(bd: &BigDecimal, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_str(&bd.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<BigDecimal, D::Error> {
|
|
||||||
let float = f32::deserialize(deserializer)?;
|
|
||||||
BigDecimal::try_from(float).map_err(|err| serde::de::Error::custom(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_events_preview(State(app_state): State<AppState>) -> impl IntoResponse {
|
pub async fn get_events_preview(State(app_state): State<AppState>) -> impl IntoResponse {
|
||||||
let result = query_as!(
|
let result = query_as!(
|
||||||
Event,
|
Event,
|
||||||
@ -144,7 +51,7 @@ LIMIT
|
|||||||
pub async fn create_event(
|
pub async fn create_event(
|
||||||
AuthBearer(token): AuthBearer,
|
AuthBearer(token): AuthBearer,
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
Json(event_post_query): Json<NewEventRequestEntry>,
|
Json(event_req): Json<NewEventRequestEntry>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let token_data = match handle_token(token, &app_state, Role::Teacher) {
|
let token_data = match handle_token(token, &app_state, Role::Teacher) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
@ -157,14 +64,14 @@ pub async fn create_event(
|
|||||||
VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9 )
|
VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9 )
|
||||||
RETURNING id
|
RETURNING id
|
||||||
"#,
|
"#,
|
||||||
event_post_query.title,
|
event_req.title,
|
||||||
event_post_query.description,
|
event_req.description,
|
||||||
event_post_query.time_start,
|
event_req.time_start,
|
||||||
event_post_query.time_end,
|
event_req.time_end,
|
||||||
event_post_query.event_type as EventType,
|
event_req.event_type as EventType,
|
||||||
event_post_query.points,
|
event_req.points,
|
||||||
event_post_query.place,
|
event_req.place,
|
||||||
event_post_query.price,
|
event_req.price,
|
||||||
token_data.id,
|
token_data.id,
|
||||||
).fetch_one(&app_state.db_pool).await;
|
).fetch_one(&app_state.db_pool).await;
|
||||||
|
|
||||||
@ -179,35 +86,3 @@ pub async fn create_event(
|
|||||||
.into_response(),
|
.into_response(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_token(
|
|
||||||
token: String,
|
|
||||||
app_state: &AppState,
|
|
||||||
minimum_role: Role,
|
|
||||||
) -> Result<Claims, Response> {
|
|
||||||
let token_result = jsonwebtoken::decode::<Claims>(
|
|
||||||
&token,
|
|
||||||
&app_state.jwt_decode,
|
|
||||||
&Validation::new(Algorithm::HS256),
|
|
||||||
);
|
|
||||||
let token_data = match token_result {
|
|
||||||
Ok(token_data) => token_data.claims,
|
|
||||||
Err(err) => {
|
|
||||||
return Err((
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
Json(json!({ "error": format!("Failed to parse JWT: {}", err) })),
|
|
||||||
)
|
|
||||||
.into_response())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if token_data.role < minimum_role {
|
|
||||||
return Err((
|
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
Json(json!({
|
|
||||||
"error": format!("You must be at least a {:?} to do this", minimum_role)
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.into_response());
|
|
||||||
}
|
|
||||||
Ok(token_data)
|
|
||||||
}
|
|
||||||
|
|||||||
74
src/jwt.rs
74
src/jwt.rs
@ -1,22 +1,18 @@
|
|||||||
|
use crate::{
|
||||||
|
models::{Claims, Role},
|
||||||
|
AppState,
|
||||||
|
};
|
||||||
|
use axum::{
|
||||||
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
Json,
|
||||||
|
};
|
||||||
|
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Validation};
|
||||||
|
use serde_json::json;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use jsonwebtoken::{DecodingKey, EncodingKey};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use sqlx::types::time::OffsetDateTime;
|
|
||||||
|
|
||||||
use crate::user::Role;
|
|
||||||
|
|
||||||
pub const JWT_LIFETIME: Duration = Duration::from_secs(60 * 60 * 24 * 7);
|
pub const JWT_LIFETIME: Duration = Duration::from_secs(60 * 60 * 24 * 7);
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, Hash)]
|
|
||||||
pub struct Claims {
|
|
||||||
#[serde(with = "jwt_numeric_date")]
|
|
||||||
pub exp: OffsetDateTime,
|
|
||||||
pub id: i32,
|
|
||||||
pub username: String,
|
|
||||||
pub role: Role,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_jwt_keys() -> (EncodingKey, DecodingKey) {
|
pub fn get_jwt_keys() -> (EncodingKey, DecodingKey) {
|
||||||
let secret = std::env::var("SAAPI_JWT_SECRET").unwrap_or("Sdb\\y5PP`,hmG+98".into());
|
let secret = std::env::var("SAAPI_JWT_SECRET").unwrap_or("Sdb\\y5PP`,hmG+98".into());
|
||||||
(
|
(
|
||||||
@ -25,26 +21,34 @@ pub fn get_jwt_keys() -> (EncodingKey, DecodingKey) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
mod jwt_numeric_date {
|
pub fn handle_token(
|
||||||
//! Custom serialization of OffsetDateTime to conform with the JWT spec (RFC 7519 section 2, "Numeric Date")
|
token: String,
|
||||||
use serde::{self, Deserialize, Deserializer, Serializer};
|
app_state: &AppState,
|
||||||
use sqlx::types::time::OffsetDateTime;
|
minimum_role: Role,
|
||||||
|
) -> Result<Claims, Response> {
|
||||||
/// Serializes an OffsetDateTime to a Unix timestamp (milliseconds since 1970/1/1T00:00:00T)
|
let token_result = jsonwebtoken::decode::<Claims>(
|
||||||
pub fn serialize<S>(date: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
|
&token,
|
||||||
where
|
&app_state.jwt_decode,
|
||||||
S: Serializer,
|
&Validation::new(Algorithm::HS256),
|
||||||
{
|
);
|
||||||
let timestamp = date.unix_timestamp();
|
let token_data = match token_result {
|
||||||
serializer.serialize_i64(timestamp)
|
Ok(token_data) => token_data.claims,
|
||||||
|
Err(err) => {
|
||||||
|
return Err((
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(json!({ "error": format!("Failed to parse JWT: {}", err) })),
|
||||||
|
)
|
||||||
|
.into_response())
|
||||||
}
|
}
|
||||||
|
};
|
||||||
/// Attempts to deserialize an i64 and use as a Unix timestamp
|
if token_data.role < minimum_role {
|
||||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
|
return Err((
|
||||||
where
|
StatusCode::UNAUTHORIZED,
|
||||||
D: Deserializer<'de>,
|
Json(json!({
|
||||||
{
|
"error": format!("You must be at least a {:?} to do this", minimum_role)
|
||||||
OffsetDateTime::from_unix_timestamp(i64::deserialize(deserializer)?)
|
})),
|
||||||
.map_err(|_| serde::de::Error::custom("invalid Unix timestamp value"))
|
)
|
||||||
|
.into_response());
|
||||||
}
|
}
|
||||||
|
Ok(token_data)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
mod events;
|
mod events;
|
||||||
mod jwt;
|
mod jwt;
|
||||||
|
mod models;
|
||||||
|
mod report;
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
|
|||||||
162
src/models.rs
Normal file
162
src/models.rs
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::types::{chrono::NaiveDateTime, time::OffsetDateTime, BigDecimal};
|
||||||
|
|
||||||
|
/// The possible event types.
|
||||||
|
#[derive(
|
||||||
|
sqlx::Type, Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
#[sqlx(type_name = "event_type", rename_all = "snake_case")]
|
||||||
|
pub enum EventType {
|
||||||
|
Sports,
|
||||||
|
Meetings,
|
||||||
|
Drama,
|
||||||
|
Music,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The model for the body of the create event request.
|
||||||
|
#[derive(Clone, Deserialize, Debug)]
|
||||||
|
pub struct NewEventRequestEntry {
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
#[serde(with = "serde_datetime")]
|
||||||
|
pub time_start: NaiveDateTime,
|
||||||
|
#[serde(with = "serde_datetime")]
|
||||||
|
pub time_end: NaiveDateTime,
|
||||||
|
pub event_type: EventType,
|
||||||
|
pub points: i32,
|
||||||
|
pub place: Option<String>,
|
||||||
|
#[serde(with = "serde_big_decimal")]
|
||||||
|
pub price: BigDecimal,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The model for an Event in the db.
|
||||||
|
#[derive(Clone, Serialize, Debug)]
|
||||||
|
pub struct Event {
|
||||||
|
pub id: i32,
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
#[serde(with = "serde_datetime")]
|
||||||
|
pub time_start: NaiveDateTime,
|
||||||
|
#[serde(with = "serde_datetime")]
|
||||||
|
pub time_end: NaiveDateTime,
|
||||||
|
pub event_type: EventType,
|
||||||
|
pub points: i32,
|
||||||
|
pub place: Option<String>,
|
||||||
|
#[serde(with = "serde_big_decimal")]
|
||||||
|
pub price: BigDecimal,
|
||||||
|
pub created_by: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The model for the body of the signup request.
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct Signup {
|
||||||
|
pub username: String,
|
||||||
|
pub role: Role,
|
||||||
|
pub grade: i32,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The possible roles for a user.
|
||||||
|
#[derive(
|
||||||
|
sqlx::Type, Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
// #[sqlx(type_name = "role", rename_all = "snake_case")]
|
||||||
|
pub enum Role {
|
||||||
|
Student,
|
||||||
|
Teacher,
|
||||||
|
Admin,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The model for the signin request.
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct Signin {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The model for the query parameters of the report request.
|
||||||
|
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
pub struct ReportQuery {
|
||||||
|
pub grade: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The claims for the JWT.
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug, Hash)]
|
||||||
|
pub struct Claims {
|
||||||
|
#[serde(with = "serde_numeric_date")]
|
||||||
|
pub exp: OffsetDateTime,
|
||||||
|
pub id: i32,
|
||||||
|
pub username: String,
|
||||||
|
pub role: Role,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
sqlx::Type, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
pub struct UserReportEntry {
|
||||||
|
pub username: String,
|
||||||
|
pub points: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Module for (de)serializing [OffsetDateTime] to conform with the JWT spec (RFC 7519 section 2, "Numeric Date")
|
||||||
|
mod serde_numeric_date {
|
||||||
|
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||||
|
use sqlx::types::time::OffsetDateTime;
|
||||||
|
|
||||||
|
pub fn serialize<S>(date: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let timestamp = date.unix_timestamp();
|
||||||
|
serializer.serialize_i64(timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
OffsetDateTime::from_unix_timestamp(i64::deserialize(deserializer)?)
|
||||||
|
.map_err(|_| serde::de::Error::custom("invalid Unix timestamp value"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Module for (de)serializing [NaiveDateTime]
|
||||||
|
mod serde_datetime {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||||
|
use sqlx::types::chrono::NaiveDateTime;
|
||||||
|
|
||||||
|
pub fn serialize<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&dt.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D: Deserializer<'de>>(
|
||||||
|
deserializer: D,
|
||||||
|
) -> Result<NaiveDateTime, D::Error> {
|
||||||
|
let str = <String as Deserialize>::deserialize(deserializer)?;
|
||||||
|
NaiveDateTime::from_str(&str).map_err(|err| serde::de::Error::custom(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Module for (de)serializing [BigDecimal]
|
||||||
|
mod serde_big_decimal {
|
||||||
|
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||||
|
use sqlx::types::BigDecimal;
|
||||||
|
|
||||||
|
pub fn serialize<S>(bd: &BigDecimal, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&bd.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<BigDecimal, D::Error> {
|
||||||
|
let float = f32::deserialize(deserializer)?;
|
||||||
|
BigDecimal::try_from(float).map_err(|err| serde::de::Error::custom(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/report.rs
Normal file
59
src/report.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::{Query, State},
|
||||||
|
http::StatusCode,
|
||||||
|
response::IntoResponse,
|
||||||
|
Json,
|
||||||
|
};
|
||||||
|
use axum_auth::AuthBearer;
|
||||||
|
use serde_json::json;
|
||||||
|
use sqlx::{query, query_as};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
jwt::handle_token,
|
||||||
|
models::{ReportQuery, Role, UserReportEntry},
|
||||||
|
AppState,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn report(
|
||||||
|
AuthBearer(token): AuthBearer,
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
Query(report_query): Query<ReportQuery>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let token_data = match handle_token(token, &app_state, Role::Teacher) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(value) => return value,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = query_as!(
|
||||||
|
UserReportEntry,
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
u.username,
|
||||||
|
COALESCE(SUM(e.points), 0) AS "points!: i32"
|
||||||
|
FROM
|
||||||
|
users u
|
||||||
|
LEFT JOIN event_attendees ea
|
||||||
|
ON u.id = ea.user_id AND ea.confirmed = true
|
||||||
|
LEFT JOIN events e
|
||||||
|
ON ea.event_id = e.id
|
||||||
|
WHERE
|
||||||
|
u.grade = $1 OR $2
|
||||||
|
GROUP BY u.id;
|
||||||
|
"#,
|
||||||
|
report_query.grade.unwrap_or(0),
|
||||||
|
report_query.grade.is_none(),
|
||||||
|
)
|
||||||
|
.fetch_all(&app_state.db_pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(record) => (StatusCode::OK, Json(json!({ "data": record }))).into_response(),
|
||||||
|
Err(err) => (
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(json!({
|
||||||
|
"error": format!("Unknown error creating event: {}", err)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/user.rs
38
src/user.rs
@ -1,43 +1,14 @@
|
|||||||
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
|
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
|
||||||
use jsonwebtoken::Header;
|
use jsonwebtoken::Header;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use sqlx::types::time::OffsetDateTime;
|
use sqlx::types::time::OffsetDateTime;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jwt::{Claims, JWT_LIFETIME},
|
jwt::JWT_LIFETIME,
|
||||||
|
models::{Claims, Role, Signin, Signup},
|
||||||
AppState,
|
AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
pub struct Signup {
|
|
||||||
username: String,
|
|
||||||
role: Role,
|
|
||||||
password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(
|
|
||||||
sqlx::Type, Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
|
|
||||||
)]
|
|
||||||
#[sqlx(type_name = "role", rename_all = "snake_case")]
|
|
||||||
pub enum Role {
|
|
||||||
Student,
|
|
||||||
Teacher,
|
|
||||||
Admin,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
pub struct Signin {
|
|
||||||
username: String,
|
|
||||||
password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, sqlx::FromRow)]
|
|
||||||
struct User {
|
|
||||||
id: u64,
|
|
||||||
username: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn signup(
|
pub async fn signup(
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
Json(signup): Json<Signup>,
|
Json(signup): Json<Signup>,
|
||||||
@ -47,12 +18,13 @@ pub async fn signup(
|
|||||||
|
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO users (username, role, password)
|
INSERT INTO users (username, role, grade, password)
|
||||||
VALUES ( $1, $2, $3 )
|
VALUES ( $1, $2, $3, $4 )
|
||||||
RETURNING id;
|
RETURNING id;
|
||||||
"#,
|
"#,
|
||||||
signup.username,
|
signup.username,
|
||||||
signup.role as Role,
|
signup.role as Role,
|
||||||
|
signup.grade,
|
||||||
pass_hash.as_bytes(),
|
pass_hash.as_bytes(),
|
||||||
)
|
)
|
||||||
.fetch_one(&app_state.db_pool)
|
.fetch_one(&app_state.db_pool)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user