Compare commits
No commits in common. "e0ce62ddb7aca1d07718e96bd5dce5e91e73deeb" and "2f8ddf075487bde42f8b2eaff9d836c535931f87" have entirely different histories.
e0ce62ddb7
...
2f8ddf0754
@ -8,8 +8,6 @@ edition = "2021"
|
||||
[dependencies]
|
||||
tokio = { version = "1.27", features = ["full"] }
|
||||
axum = "0.6"
|
||||
tower = "0.4"
|
||||
tower-http = { version = "0.4", features = ["cors"]}
|
||||
axum-auth = { version = "0.4", features = ["auth-bearer"]}
|
||||
sqlx = { version = "0.6", features = ["postgres", "runtime-tokio-rustls", "all-types"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
@ -148,7 +148,6 @@ jobs:
|
||||
|
||||
- name: build
|
||||
plan:
|
||||
- get: docker-image
|
||||
- get: repo
|
||||
trigger: true
|
||||
passed: [test]
|
||||
@ -191,7 +190,6 @@ jobs:
|
||||
repository: concourse/oci-build-task
|
||||
|
||||
inputs:
|
||||
- name: docker-image
|
||||
- name: repo
|
||||
path: .
|
||||
|
||||
|
||||
@ -1,16 +1,11 @@
|
||||
use axum::{
|
||||
extract::{Query, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
Json,
|
||||
};
|
||||
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
|
||||
use axum_auth::AuthBearer;
|
||||
use serde_json::json;
|
||||
use sqlx::{query, query_as};
|
||||
use sqlx::query;
|
||||
|
||||
use crate::{
|
||||
jwt::handle_token,
|
||||
models::{AttendingEntry, ConfirmAttending, GetAttendingQuery, MarkAttending, Role},
|
||||
models::{ConfirmAttending, MarkAttending, Role},
|
||||
AppState,
|
||||
};
|
||||
|
||||
@ -62,44 +57,6 @@ pub async fn confirm_attending(
|
||||
.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(
|
||||
AuthBearer(token): AuthBearer,
|
||||
State(app_state): State<AppState>,
|
||||
|
||||
117
src/events.rs
117
src/events.rs
@ -18,8 +18,9 @@ pub async fn get_events_preview(
|
||||
AuthBearer(token): AuthBearer,
|
||||
State(app_state): State<AppState>,
|
||||
) -> impl IntoResponse {
|
||||
if let Err(err) = handle_token(token, &app_state, Role::Student) {
|
||||
return err;
|
||||
match handle_token(token, &app_state, Role::Student) {
|
||||
Ok(_) => {}
|
||||
Err(err) => return err,
|
||||
};
|
||||
|
||||
let result = query_as!(
|
||||
@ -65,8 +66,9 @@ pub async fn get_all_events(
|
||||
AuthBearer(token): AuthBearer,
|
||||
State(app_state): State<AppState>,
|
||||
) -> impl IntoResponse {
|
||||
if let Err(err) = handle_token(token, &app_state, Role::Student) {
|
||||
return err;
|
||||
match handle_token(token, &app_state, Role::Student) {
|
||||
Ok(_) => {}
|
||||
Err(err) => return err,
|
||||
};
|
||||
|
||||
let result = query_as!(
|
||||
@ -108,55 +110,6 @@ pub async fn get_all_events(
|
||||
.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(
|
||||
AuthBearer(token): AuthBearer,
|
||||
State(app_state): State<AppState>,
|
||||
@ -262,62 +215,6 @@ pub async fn get_event(
|
||||
.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(
|
||||
AuthBearer(token): AuthBearer,
|
||||
State(app_state): State<AppState>,
|
||||
@ -348,7 +245,7 @@ pub async fn create_event(
|
||||
match result {
|
||||
Ok(record) => (StatusCode::OK, Json(json!({ "data": record.id }))).into_response(),
|
||||
Err(err) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({
|
||||
"error": format!("Unknown error creating event: {:?}", err)
|
||||
})),
|
||||
|
||||
@ -51,9 +51,8 @@ pub async fn list_points(
|
||||
AuthBearer(token): AuthBearer,
|
||||
State(app_state): State<AppState>,
|
||||
) -> impl IntoResponse {
|
||||
let token_data = match handle_token(token, &app_state, Role::Student) {
|
||||
Err(err) => return err,
|
||||
Ok(token_data) => token_data,
|
||||
if let Err(err) = handle_token(token, &app_state, Role::Student) {
|
||||
return err;
|
||||
};
|
||||
|
||||
let result = query_as!(
|
||||
@ -68,14 +67,10 @@ pub async fn list_points(
|
||||
ON u.id = ea.user_id AND ea.confirmed = true
|
||||
LEFT JOIN events e
|
||||
ON ea.event_id = e.id
|
||||
WHERE
|
||||
u.grade = $1 AND
|
||||
u.role = 'student'
|
||||
GROUP BY u.id
|
||||
ORDER BY points DESC
|
||||
;
|
||||
"#,
|
||||
token_data.grade,
|
||||
)
|
||||
.fetch_all(&app_state.db_pool)
|
||||
.await;
|
||||
|
||||
21
src/main.rs
21
src/main.rs
@ -7,8 +7,6 @@ mod report;
|
||||
mod user;
|
||||
mod winner;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use axum::{
|
||||
routing::{delete, get, post, put},
|
||||
Router,
|
||||
@ -16,7 +14,7 @@ use axum::{
|
||||
use jsonwebtoken::{DecodingKey, EncodingKey};
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use sqlx::PgPool;
|
||||
use tower_http::cors::{self, CorsLayer};
|
||||
use std::net::SocketAddr;
|
||||
use user::{signin, signup};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -47,19 +45,12 @@ async fn main() {
|
||||
.route("/", get(root))
|
||||
.route("/user/signup", post(signup))
|
||||
.route("/user/signin", post(signin))
|
||||
.route(
|
||||
"/event",
|
||||
get(events::get_event)
|
||||
.post(events::create_event)
|
||||
.delete(events::delete_event),
|
||||
)
|
||||
.route("/event", get(events::get_event).post(events::create_event))
|
||||
.route("/event/preview", get(events::get_events_preview))
|
||||
.route("/event/future", get(events::get_all_events))
|
||||
.route("/event/recent", get(events::get_recent_events))
|
||||
.route("/event/my", get(events::my_events))
|
||||
.route("/report", get(report::get_report))
|
||||
.route("/attending/confirm", put(attending::confirm_attending))
|
||||
.route("/attending", get(attending::get_attending))
|
||||
.route("/attending/mark", post(attending::mark_attending))
|
||||
.route("/attending/unmark", delete(attending::unmark_attending))
|
||||
.route("/leaderboard/my_points", get(leaderboard::get_points))
|
||||
@ -68,20 +59,12 @@ async fn main() {
|
||||
.route("/winners/recent", get(winner::get_recent_winners))
|
||||
.route("/winners/unclaimed", get(winner::get_unclaimed_winners))
|
||||
.route("/winners/mark", put(winner::mark_claimed))
|
||||
.route("/prizes", get(winner::get_prizes).post(winner::new_prize))
|
||||
.with_state(AppState {
|
||||
db_pool,
|
||||
jwt_encode,
|
||||
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
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
|
||||
|
||||
@ -19,35 +19,6 @@ pub struct WinnerEntry {
|
||||
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct PrizeQuery {
|
||||
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)]
|
||||
@ -183,7 +154,6 @@ pub struct Claims {
|
||||
pub exp: OffsetDateTime,
|
||||
pub id: i32,
|
||||
pub username: String,
|
||||
pub grade: i32,
|
||||
pub role: Role,
|
||||
}
|
||||
|
||||
|
||||
@ -46,16 +46,15 @@ pub async fn get_report(
|
||||
u.username,
|
||||
u.grade,
|
||||
COALESCE(SUM(e.points), 0) AS points,
|
||||
COALESCE(array_agg(e.title), ARRAY[]::varchar(255)[]) AS "event_titles!",
|
||||
COALESCE(array_agg(e.description), ARRAY[]::text[]) AS "event_discriptions!",
|
||||
COALESCE(array_agg(e.points), ARRAY[]::INTEGER[]) AS "event_points!",
|
||||
COALESCE(array_agg(e.event_type), ARRAY[]::event_type[]) AS "event_types!: Vec<String>"
|
||||
array_agg(e.title) AS "event_titles!",
|
||||
array_agg(e.description) AS "event_discriptions!",
|
||||
array_agg(e.points) AS "event_points!",
|
||||
array_agg(e.event_type) AS "event_types!: Vec<String>"
|
||||
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.role = 'student'
|
||||
GROUP BY u.id
|
||||
ORDER BY u.grade, u.username, points
|
||||
"#,
|
||||
@ -67,9 +66,9 @@ pub async fn get_report(
|
||||
Ok(records) => records,
|
||||
Err(err) => {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({
|
||||
"error": format!("Unknown error getting user data: {:?}", err)
|
||||
"error": format!("Unknown error creating event: {:?}", err)
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
@ -85,9 +84,9 @@ pub async fn get_report(
|
||||
Ok(report) => report,
|
||||
Err(err) => {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({
|
||||
"error": format!("Unknown error rendering report: {:?}", err)
|
||||
"error": format!("Unknown error creating event: {:?}", err)
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
|
||||
42
src/user.rs
42
src/user.rs
@ -64,13 +64,12 @@ pub async fn signin(
|
||||
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
role AS "role!: Role",
|
||||
grade
|
||||
FROM users
|
||||
WHERE username = $1 AND password = $2
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
role AS "role!: Role"
|
||||
FROM users
|
||||
WHERE username = $1 AND password = $2
|
||||
"#,
|
||||
signin.username,
|
||||
pass_hash.as_bytes(),
|
||||
@ -83,7 +82,6 @@ pub async fn signin(
|
||||
let claims = Claims {
|
||||
exp: OffsetDateTime::now_utc() + JWT_LIFETIME,
|
||||
id: user.id,
|
||||
grade: user.grade,
|
||||
username: user.username,
|
||||
role: user.role,
|
||||
};
|
||||
@ -103,17 +101,21 @@ pub async fn signin(
|
||||
|
||||
(StatusCode::OK, Json(json!({ "data": token })))
|
||||
}
|
||||
Ok(None) => (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(json!({
|
||||
"error": format!("Incorrect username or password")
|
||||
})),
|
||||
),
|
||||
Err(err) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({
|
||||
"error": format!("Unknown error signing in: {:?}", err)
|
||||
})),
|
||||
),
|
||||
Ok(None) => {
|
||||
(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(json!({
|
||||
"error": format!("Incorrect username or password")
|
||||
})),
|
||||
)
|
||||
}
|
||||
Err(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 sqlx::{query, query_as};
|
||||
|
||||
use crate::models::{NewPrize, PrizeEntry, WinnerEntry, WinnerQuery};
|
||||
use crate::models::{WinnerEntry, WinnerQuery};
|
||||
use crate::{
|
||||
jwt::handle_token,
|
||||
models::{PrizeQuery, Role},
|
||||
@ -31,7 +31,6 @@ pub async fn select_winners(
|
||||
SELECT
|
||||
u.id,
|
||||
u.username,
|
||||
u.grade,
|
||||
COALESCE(SUM(e.points), 0) AS points,
|
||||
RANDOM() * COALESCE(SUM(e.points), 0) AS random_value
|
||||
FROM
|
||||
@ -51,7 +50,6 @@ pub async fn select_winners(
|
||||
id, points
|
||||
FROM weighted_users
|
||||
WHERE points > p.points_min
|
||||
AND grade = $3
|
||||
LIMIT 1
|
||||
) u
|
||||
WHERE p.id = $1 OR $2
|
||||
@ -63,7 +61,6 @@ pub async fn select_winners(
|
||||
"#,
|
||||
prize_query.prize_id.unwrap_or(0),
|
||||
prize_query.prize_id.is_none(),
|
||||
prize_query.grade,
|
||||
)
|
||||
.execute(&app_state.db_pool)
|
||||
.await;
|
||||
@ -223,78 +220,3 @@ pub async fn mark_claimed(
|
||||
}
|
||||
.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