Compare commits
10 Commits
2f8ddf0754
...
e0ce62ddb7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0ce62ddb7 | ||
|
|
234ae29e96 | ||
|
|
f8fc5d3dce | ||
|
|
c5bbad6460 | ||
|
|
8d08147329 | ||
|
|
db6a5414d9 | ||
|
|
334ef065af | ||
|
|
2f334d233b | ||
|
|
85ee4589d4 | ||
|
|
ed5f50183a |
@ -8,6 +8,8 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.27", features = ["full"] }
|
tokio = { version = "1.27", features = ["full"] }
|
||||||
axum = "0.6"
|
axum = "0.6"
|
||||||
|
tower = "0.4"
|
||||||
|
tower-http = { version = "0.4", features = ["cors"]}
|
||||||
axum-auth = { version = "0.4", features = ["auth-bearer"]}
|
axum-auth = { version = "0.4", features = ["auth-bearer"]}
|
||||||
sqlx = { version = "0.6", features = ["postgres", "runtime-tokio-rustls", "all-types"] }
|
sqlx = { version = "0.6", features = ["postgres", "runtime-tokio-rustls", "all-types"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|||||||
@ -148,6 +148,7 @@ jobs:
|
|||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
plan:
|
plan:
|
||||||
|
- get: docker-image
|
||||||
- get: repo
|
- get: repo
|
||||||
trigger: true
|
trigger: true
|
||||||
passed: [test]
|
passed: [test]
|
||||||
@ -190,6 +191,7 @@ jobs:
|
|||||||
repository: concourse/oci-build-task
|
repository: concourse/oci-build-task
|
||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
|
- name: docker-image
|
||||||
- name: repo
|
- name: repo
|
||||||
path: .
|
path: .
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,16 @@
|
|||||||
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
|
use axum::{
|
||||||
|
extract::{Query, State},
|
||||||
|
http::StatusCode,
|
||||||
|
response::IntoResponse,
|
||||||
|
Json,
|
||||||
|
};
|
||||||
use axum_auth::AuthBearer;
|
use axum_auth::AuthBearer;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use sqlx::query;
|
use sqlx::{query, query_as};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jwt::handle_token,
|
jwt::handle_token,
|
||||||
models::{ConfirmAttending, MarkAttending, Role},
|
models::{AttendingEntry, ConfirmAttending, GetAttendingQuery, MarkAttending, Role},
|
||||||
AppState,
|
AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -57,6 +62,44 @@ pub async fn confirm_attending(
|
|||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_attending(
|
||||||
|
AuthBearer(token): AuthBearer,
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
Query(attending_query): Query<GetAttendingQuery>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
match handle_token(token, &app_state, Role::Teacher) {
|
||||||
|
Ok(token_data) => token_data,
|
||||||
|
Err(err) => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = query_as!(
|
||||||
|
AttendingEntry,
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
user_id,
|
||||||
|
u.username
|
||||||
|
FROM event_attendees ea
|
||||||
|
INNER JOIN users u
|
||||||
|
ON u.id = ea.user_id
|
||||||
|
WHERE event_id = $1
|
||||||
|
"#,
|
||||||
|
attending_query.event_id,
|
||||||
|
)
|
||||||
|
.fetch_all(&app_state.db_pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(attending) => (StatusCode::OK, Json(json!(attending))),
|
||||||
|
Err(err) => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(json!({
|
||||||
|
"error": format!("Unknown error confirming attendence: {:?}", err)
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn mark_attending(
|
pub async fn mark_attending(
|
||||||
AuthBearer(token): AuthBearer,
|
AuthBearer(token): AuthBearer,
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
|
|||||||
117
src/events.rs
117
src/events.rs
@ -18,9 +18,8 @@ pub async fn get_events_preview(
|
|||||||
AuthBearer(token): AuthBearer,
|
AuthBearer(token): AuthBearer,
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match handle_token(token, &app_state, Role::Student) {
|
if let Err(err) = handle_token(token, &app_state, Role::Student) {
|
||||||
Ok(_) => {}
|
return err;
|
||||||
Err(err) => return err,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = query_as!(
|
let result = query_as!(
|
||||||
@ -66,9 +65,8 @@ pub async fn get_all_events(
|
|||||||
AuthBearer(token): AuthBearer,
|
AuthBearer(token): AuthBearer,
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match handle_token(token, &app_state, Role::Student) {
|
if let Err(err) = handle_token(token, &app_state, Role::Student) {
|
||||||
Ok(_) => {}
|
return err;
|
||||||
Err(err) => return err,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = query_as!(
|
let result = query_as!(
|
||||||
@ -110,6 +108,55 @@ pub async fn get_all_events(
|
|||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn my_events(
|
||||||
|
AuthBearer(token): AuthBearer,
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let token_data = match handle_token(token, &app_state, Role::Teacher) {
|
||||||
|
Err(err) => return err,
|
||||||
|
Ok(token_data) => token_data,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = query_as!(
|
||||||
|
Event,
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
time_start,
|
||||||
|
time_end,
|
||||||
|
event_type AS "event_type!: EventType",
|
||||||
|
points,
|
||||||
|
place,
|
||||||
|
price,
|
||||||
|
created_by
|
||||||
|
FROM
|
||||||
|
events
|
||||||
|
WHERE
|
||||||
|
created_by = $1
|
||||||
|
ORDER BY
|
||||||
|
time_start
|
||||||
|
LIMIT
|
||||||
|
20;
|
||||||
|
"#,
|
||||||
|
token_data.id,
|
||||||
|
)
|
||||||
|
.fetch_all(&app_state.db_pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(events) => (StatusCode::OK, Json(json!(events))),
|
||||||
|
Err(err) => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(json!({
|
||||||
|
"error": format!("Unknown error getting events: {:?}", err)
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_recent_events(
|
pub async fn get_recent_events(
|
||||||
AuthBearer(token): AuthBearer,
|
AuthBearer(token): AuthBearer,
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
@ -215,6 +262,62 @@ pub async fn get_event(
|
|||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deletes an event from the database.
|
||||||
|
///
|
||||||
|
/// If you are a teacher, this only works on your own events.
|
||||||
|
/// If you are an admin, this works on any event.
|
||||||
|
pub async fn delete_event(
|
||||||
|
AuthBearer(token): AuthBearer,
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
Query(get_event_query): Query<GetEventQuery>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
// validate the JWT
|
||||||
|
let token_data = match handle_token(token, &app_state, Role::Teacher) {
|
||||||
|
Err(err) => return err,
|
||||||
|
Ok(token_data) => token_data,
|
||||||
|
};
|
||||||
|
|
||||||
|
// query the database to delete the record
|
||||||
|
let result = query!(
|
||||||
|
r#"
|
||||||
|
DELETE FROM events
|
||||||
|
WHERE
|
||||||
|
id = $1 AND
|
||||||
|
(created_by = $2 OR $3)
|
||||||
|
"#,
|
||||||
|
get_event_query.id,
|
||||||
|
token_data.id,
|
||||||
|
token_data.role == Role::Admin,
|
||||||
|
)
|
||||||
|
.execute(&app_state.db_pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// handle any possible error from the database
|
||||||
|
match result {
|
||||||
|
Ok(result) => {
|
||||||
|
if result.rows_affected() == 1 {
|
||||||
|
(StatusCode::OK, Json(json!({})))
|
||||||
|
} else {
|
||||||
|
// no reccord could be deleted
|
||||||
|
(
|
||||||
|
StatusCode::NOT_FOUND,
|
||||||
|
Json(json!({
|
||||||
|
"error": format!("Event {} not found.", get_event_query.id)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// unknown database error
|
||||||
|
Err(err) => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(json!({
|
||||||
|
"error": format!("Unknown error getting event: {:?}", err)
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
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>,
|
||||||
@ -245,7 +348,7 @@ pub async fn create_event(
|
|||||||
match result {
|
match result {
|
||||||
Ok(record) => (StatusCode::OK, Json(json!({ "data": record.id }))).into_response(),
|
Ok(record) => (StatusCode::OK, Json(json!({ "data": record.id }))).into_response(),
|
||||||
Err(err) => (
|
Err(err) => (
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Json(json!({
|
Json(json!({
|
||||||
"error": format!("Unknown error creating event: {:?}", err)
|
"error": format!("Unknown error creating event: {:?}", err)
|
||||||
})),
|
})),
|
||||||
|
|||||||
@ -51,8 +51,9 @@ pub async fn list_points(
|
|||||||
AuthBearer(token): AuthBearer,
|
AuthBearer(token): AuthBearer,
|
||||||
State(app_state): State<AppState>,
|
State(app_state): State<AppState>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if let Err(err) = handle_token(token, &app_state, Role::Student) {
|
let token_data = match handle_token(token, &app_state, Role::Student) {
|
||||||
return err;
|
Err(err) => return err,
|
||||||
|
Ok(token_data) => token_data,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = query_as!(
|
let result = query_as!(
|
||||||
@ -67,10 +68,14 @@ pub async fn list_points(
|
|||||||
ON u.id = ea.user_id AND ea.confirmed = true
|
ON u.id = ea.user_id AND ea.confirmed = true
|
||||||
LEFT JOIN events e
|
LEFT JOIN events e
|
||||||
ON ea.event_id = e.id
|
ON ea.event_id = e.id
|
||||||
|
WHERE
|
||||||
|
u.grade = $1 AND
|
||||||
|
u.role = 'student'
|
||||||
GROUP BY u.id
|
GROUP BY u.id
|
||||||
ORDER BY points DESC
|
ORDER BY points DESC
|
||||||
;
|
;
|
||||||
"#,
|
"#,
|
||||||
|
token_data.grade,
|
||||||
)
|
)
|
||||||
.fetch_all(&app_state.db_pool)
|
.fetch_all(&app_state.db_pool)
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
21
src/main.rs
21
src/main.rs
@ -7,6 +7,8 @@ mod report;
|
|||||||
mod user;
|
mod user;
|
||||||
mod winner;
|
mod winner;
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
routing::{delete, get, post, put},
|
routing::{delete, get, post, put},
|
||||||
Router,
|
Router,
|
||||||
@ -14,7 +16,7 @@ use axum::{
|
|||||||
use jsonwebtoken::{DecodingKey, EncodingKey};
|
use jsonwebtoken::{DecodingKey, EncodingKey};
|
||||||
use sqlx::postgres::PgPoolOptions;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use std::net::SocketAddr;
|
use tower_http::cors::{self, CorsLayer};
|
||||||
use user::{signin, signup};
|
use user::{signin, signup};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -45,12 +47,19 @@ async fn main() {
|
|||||||
.route("/", get(root))
|
.route("/", get(root))
|
||||||
.route("/user/signup", post(signup))
|
.route("/user/signup", post(signup))
|
||||||
.route("/user/signin", post(signin))
|
.route("/user/signin", post(signin))
|
||||||
.route("/event", get(events::get_event).post(events::create_event))
|
.route(
|
||||||
|
"/event",
|
||||||
|
get(events::get_event)
|
||||||
|
.post(events::create_event)
|
||||||
|
.delete(events::delete_event),
|
||||||
|
)
|
||||||
.route("/event/preview", get(events::get_events_preview))
|
.route("/event/preview", get(events::get_events_preview))
|
||||||
.route("/event/future", get(events::get_all_events))
|
.route("/event/future", get(events::get_all_events))
|
||||||
.route("/event/recent", get(events::get_recent_events))
|
.route("/event/recent", get(events::get_recent_events))
|
||||||
|
.route("/event/my", get(events::my_events))
|
||||||
.route("/report", get(report::get_report))
|
.route("/report", get(report::get_report))
|
||||||
.route("/attending/confirm", put(attending::confirm_attending))
|
.route("/attending/confirm", put(attending::confirm_attending))
|
||||||
|
.route("/attending", get(attending::get_attending))
|
||||||
.route("/attending/mark", post(attending::mark_attending))
|
.route("/attending/mark", post(attending::mark_attending))
|
||||||
.route("/attending/unmark", delete(attending::unmark_attending))
|
.route("/attending/unmark", delete(attending::unmark_attending))
|
||||||
.route("/leaderboard/my_points", get(leaderboard::get_points))
|
.route("/leaderboard/my_points", get(leaderboard::get_points))
|
||||||
@ -59,12 +68,20 @@ async fn main() {
|
|||||||
.route("/winners/recent", get(winner::get_recent_winners))
|
.route("/winners/recent", get(winner::get_recent_winners))
|
||||||
.route("/winners/unclaimed", get(winner::get_unclaimed_winners))
|
.route("/winners/unclaimed", get(winner::get_unclaimed_winners))
|
||||||
.route("/winners/mark", put(winner::mark_claimed))
|
.route("/winners/mark", put(winner::mark_claimed))
|
||||||
|
.route("/prizes", get(winner::get_prizes).post(winner::new_prize))
|
||||||
.with_state(AppState {
|
.with_state(AppState {
|
||||||
db_pool,
|
db_pool,
|
||||||
jwt_encode,
|
jwt_encode,
|
||||||
jwt_decode,
|
jwt_decode,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create CORS midleware to allow the web build of the UI to work.
|
||||||
|
let cors = CorsLayer::new()
|
||||||
|
.allow_methods(cors::Any)
|
||||||
|
.allow_headers(cors::Any)
|
||||||
|
.allow_origin(cors::Any);
|
||||||
|
|
||||||
|
let app = app.layer(cors);
|
||||||
// run our app with hyper
|
// run our app with hyper
|
||||||
// `axum::Server` is a re-export of `hyper::Server`
|
// `axum::Server` is a re-export of `hyper::Server`
|
||||||
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
|
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
|
||||||
|
|||||||
@ -19,6 +19,35 @@ pub struct WinnerEntry {
|
|||||||
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
pub struct PrizeQuery {
|
pub struct PrizeQuery {
|
||||||
pub prize_id: Option<i32>,
|
pub prize_id: Option<i32>,
|
||||||
|
pub grade: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
pub struct PrizeEntry {
|
||||||
|
pub id: i32,
|
||||||
|
pub name: String,
|
||||||
|
pub points_min: i32,
|
||||||
|
pub description: String,
|
||||||
|
pub directions_to_claim: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
pub struct NewPrize {
|
||||||
|
pub name: String,
|
||||||
|
pub points_min: i32,
|
||||||
|
pub description: String,
|
||||||
|
pub directions_to_claim: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
pub struct AttendingEntry {
|
||||||
|
pub user_id: i32,
|
||||||
|
pub username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
pub struct GetAttendingQuery {
|
||||||
|
pub event_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
@ -154,6 +183,7 @@ pub struct Claims {
|
|||||||
pub exp: OffsetDateTime,
|
pub exp: OffsetDateTime,
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
pub grade: i32,
|
||||||
pub role: Role,
|
pub role: Role,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,15 +46,16 @@ pub async fn get_report(
|
|||||||
u.username,
|
u.username,
|
||||||
u.grade,
|
u.grade,
|
||||||
COALESCE(SUM(e.points), 0) AS points,
|
COALESCE(SUM(e.points), 0) AS points,
|
||||||
array_agg(e.title) AS "event_titles!",
|
COALESCE(array_agg(e.title), ARRAY[]::varchar(255)[]) AS "event_titles!",
|
||||||
array_agg(e.description) AS "event_discriptions!",
|
COALESCE(array_agg(e.description), ARRAY[]::text[]) AS "event_discriptions!",
|
||||||
array_agg(e.points) AS "event_points!",
|
COALESCE(array_agg(e.points), ARRAY[]::INTEGER[]) AS "event_points!",
|
||||||
array_agg(e.event_type) AS "event_types!: Vec<String>"
|
COALESCE(array_agg(e.event_type), ARRAY[]::event_type[]) AS "event_types!: Vec<String>"
|
||||||
FROM users u
|
FROM users u
|
||||||
LEFT JOIN event_attendees ea
|
LEFT JOIN event_attendees ea
|
||||||
ON u.id = ea.user_id AND ea.confirmed = true
|
ON u.id = ea.user_id AND ea.confirmed = true
|
||||||
LEFT JOIN events e
|
LEFT JOIN events e
|
||||||
ON ea.event_id = e.id
|
ON ea.event_id = e.id
|
||||||
|
WHERE u.role = 'student'
|
||||||
GROUP BY u.id
|
GROUP BY u.id
|
||||||
ORDER BY u.grade, u.username, points
|
ORDER BY u.grade, u.username, points
|
||||||
"#,
|
"#,
|
||||||
@ -66,9 +67,9 @@ pub async fn get_report(
|
|||||||
Ok(records) => records,
|
Ok(records) => records,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return (
|
return (
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Json(json!({
|
Json(json!({
|
||||||
"error": format!("Unknown error creating event: {:?}", err)
|
"error": format!("Unknown error getting user data: {:?}", err)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.into_response()
|
.into_response()
|
||||||
@ -84,9 +85,9 @@ pub async fn get_report(
|
|||||||
Ok(report) => report,
|
Ok(report) => report,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return (
|
return (
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Json(json!({
|
Json(json!({
|
||||||
"error": format!("Unknown error creating event: {:?}", err)
|
"error": format!("Unknown error rendering report: {:?}", err)
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.into_response()
|
.into_response()
|
||||||
|
|||||||
42
src/user.rs
42
src/user.rs
@ -64,12 +64,13 @@ pub async fn signin(
|
|||||||
|
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
username,
|
username,
|
||||||
role AS "role!: Role"
|
role AS "role!: Role",
|
||||||
FROM users
|
grade
|
||||||
WHERE username = $1 AND password = $2
|
FROM users
|
||||||
|
WHERE username = $1 AND password = $2
|
||||||
"#,
|
"#,
|
||||||
signin.username,
|
signin.username,
|
||||||
pass_hash.as_bytes(),
|
pass_hash.as_bytes(),
|
||||||
@ -82,6 +83,7 @@ pub async fn signin(
|
|||||||
let claims = Claims {
|
let claims = Claims {
|
||||||
exp: OffsetDateTime::now_utc() + JWT_LIFETIME,
|
exp: OffsetDateTime::now_utc() + JWT_LIFETIME,
|
||||||
id: user.id,
|
id: user.id,
|
||||||
|
grade: user.grade,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
role: user.role,
|
role: user.role,
|
||||||
};
|
};
|
||||||
@ -101,21 +103,17 @@ pub async fn signin(
|
|||||||
|
|
||||||
(StatusCode::OK, Json(json!({ "data": token })))
|
(StatusCode::OK, Json(json!({ "data": token })))
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => (
|
||||||
(
|
StatusCode::UNAUTHORIZED,
|
||||||
StatusCode::UNAUTHORIZED,
|
Json(json!({
|
||||||
Json(json!({
|
"error": format!("Incorrect username or password")
|
||||||
"error": format!("Incorrect username or password")
|
})),
|
||||||
})),
|
),
|
||||||
)
|
Err(err) => (
|
||||||
}
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Err(err) => {
|
Json(json!({
|
||||||
(
|
"error": format!("Unknown error signing in: {:?}", err)
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
})),
|
||||||
Json(json!({
|
),
|
||||||
"error": format!("Unknown error signing in: {:?}", err)
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ use axum_auth::AuthBearer;
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use sqlx::{query, query_as};
|
use sqlx::{query, query_as};
|
||||||
|
|
||||||
use crate::models::{WinnerEntry, WinnerQuery};
|
use crate::models::{NewPrize, PrizeEntry, WinnerEntry, WinnerQuery};
|
||||||
use crate::{
|
use crate::{
|
||||||
jwt::handle_token,
|
jwt::handle_token,
|
||||||
models::{PrizeQuery, Role},
|
models::{PrizeQuery, Role},
|
||||||
@ -31,6 +31,7 @@ pub async fn select_winners(
|
|||||||
SELECT
|
SELECT
|
||||||
u.id,
|
u.id,
|
||||||
u.username,
|
u.username,
|
||||||
|
u.grade,
|
||||||
COALESCE(SUM(e.points), 0) AS points,
|
COALESCE(SUM(e.points), 0) AS points,
|
||||||
RANDOM() * COALESCE(SUM(e.points), 0) AS random_value
|
RANDOM() * COALESCE(SUM(e.points), 0) AS random_value
|
||||||
FROM
|
FROM
|
||||||
@ -50,6 +51,7 @@ pub async fn select_winners(
|
|||||||
id, points
|
id, points
|
||||||
FROM weighted_users
|
FROM weighted_users
|
||||||
WHERE points > p.points_min
|
WHERE points > p.points_min
|
||||||
|
AND grade = $3
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
) u
|
) u
|
||||||
WHERE p.id = $1 OR $2
|
WHERE p.id = $1 OR $2
|
||||||
@ -61,6 +63,7 @@ pub async fn select_winners(
|
|||||||
"#,
|
"#,
|
||||||
prize_query.prize_id.unwrap_or(0),
|
prize_query.prize_id.unwrap_or(0),
|
||||||
prize_query.prize_id.is_none(),
|
prize_query.prize_id.is_none(),
|
||||||
|
prize_query.grade,
|
||||||
)
|
)
|
||||||
.execute(&app_state.db_pool)
|
.execute(&app_state.db_pool)
|
||||||
.await;
|
.await;
|
||||||
@ -220,3 +223,78 @@ pub async fn mark_claimed(
|
|||||||
}
|
}
|
||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_prizes(
|
||||||
|
AuthBearer(token): AuthBearer,
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
) -> Response {
|
||||||
|
if let Err(err) = handle_token(token, &app_state, Role::Student) {
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = query_as!(
|
||||||
|
PrizeEntry,
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM prizes
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(&app_state.db_pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(prizes) => (StatusCode::OK, Json(json!(prizes))),
|
||||||
|
Err(err) => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(json!({
|
||||||
|
"error": format!("Unknown error getting winners: {:?}", err)
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new_prize(
|
||||||
|
AuthBearer(token): AuthBearer,
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
Json(new_prize): Json<NewPrize>,
|
||||||
|
) -> Response {
|
||||||
|
if let Err(err) = handle_token(token, &app_state, Role::Student) {
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = query_as!(
|
||||||
|
PrizeEntry,
|
||||||
|
r#"
|
||||||
|
INSERT INTO prizes (name, points_min, description, directions_to_claim)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
"#,
|
||||||
|
new_prize.name,
|
||||||
|
new_prize.points_min,
|
||||||
|
new_prize.description,
|
||||||
|
new_prize.directions_to_claim,
|
||||||
|
)
|
||||||
|
.execute(&app_state.db_pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(result) => {
|
||||||
|
if result.rows_affected() == 1 {
|
||||||
|
(StatusCode::OK, Json(json!({})))
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(json!({"error": "Failed to insert prize."})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(json!({
|
||||||
|
"error": format!("Unknown error getting winners: {:?}", err)
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user