From b7f981e3a60e243021efebd84db9287ea43c67d4 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Tue, 31 May 2022 21:27:35 +0200 Subject: [PATCH] Adds the ability to join or leave a gamenight. --- backend/src/api.rs | 83 +++++++++++++++++++++++++-- backend/src/main.rs | 2 + backend/src/schema/gamenight.rs | 7 ++- frontend/src/App.js | 12 ++-- frontend/src/api/Api.js | 20 +++++++ frontend/src/api/FetchResource.js | 4 +- frontend/src/components/Gamenight.jsx | 65 ++++++++++++++++++--- 7 files changed, 171 insertions(+), 22 deletions(-) diff --git a/backend/src/api.rs b/backend/src/api.rs index fc30e6d..f0529a1 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -31,6 +31,8 @@ struct ApiResponse { #[serde(skip_serializing_if = "Option::is_none")] gamenights: Option>, #[serde(skip_serializing_if = "Option::is_none")] + gamenight: Option, + #[serde(skip_serializing_if = "Option::is_none")] games: Option>, } @@ -43,6 +45,7 @@ impl ApiResponse { message: None, user: None, gamenights: None, + gamenight: None, games: None, }; @@ -52,6 +55,7 @@ impl ApiResponse { message: Some(Cow::Owned(message)), user: None, gamenights: None, + gamenight: None, games: None, } } @@ -65,16 +69,29 @@ impl ApiResponse { jwt: jwt, }), gamenights: None, + gamenight: None, games: None, } } - fn gamenight_response(gamenights: Vec) -> Self { + fn gamenights_response(gamenights: Vec) -> Self { Self { result: Self::SUCCES_RESULT, message: None, user: None, gamenights: Some(gamenights), + gamenight: None, + games: None, + } + } + + fn gamenight_response(gamenight: GamenightOutput) -> Self { + Self { + result: Self::SUCCES_RESULT, + message: None, + user: None, + gamenights: None, + gamenight: Some(gamenight), games: None, } } @@ -85,6 +102,7 @@ impl ApiResponse { message: None, user: None, gamenights: None, + gamenight: None, games: Some(games), } } @@ -140,6 +158,63 @@ pub struct GamenightOutput { participants: Vec, } +#[derive(Debug, Serialize, Deserialize)] +pub struct GamenightUpdate { + action: String +} + +#[patch("/gamenights/", format = "application/json", data = "")] +pub async fn patch_gamenight(conn: DbConn, user: User, gamenight_id: String, patch_json: Json) -> ApiResponseVariant { + let uuid = Uuid::parse_str(&gamenight_id).unwrap(); + let patch = patch_json.into_inner(); + match patch.action.as_str() { + "RemoveParticipant" => { + let entry = GamenightParticipantsEntry { + gamenight_id: uuid, + user_id: user.id + }; + match remove_participant(&conn, entry).await { + Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), + Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))) + } + } + "AddParticipant" => { + let entry = GamenightParticipantsEntry { + gamenight_id: uuid, + user_id: user.id + }; + match add_participant(&conn, entry).await { + Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), + Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))) + } + } + _ => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)) + } +} + +#[get("/gamenights/")] +pub async fn gamenight(conn: DbConn, _user: User, gamenight_id: String) -> ApiResponseVariant { + let uuid = Uuid::parse_str(&gamenight_id).unwrap(); + let gamenight = match get_gamenight(&conn, uuid).await { + Ok(result) => result, + Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), + }; + let games = match get_games_of_gamenight(&conn, uuid).await { + Ok(result) => result, + Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), + }; + let participants = match load_participants(&conn, uuid).await { + Ok(result) => result, + Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), + }; + let gamenight_output = GamenightOutput { + gamenight: gamenight, + game_list: games, + participants: participants + }; + return ApiResponseVariant::Value(json!(ApiResponse::gamenight_response(gamenight_output))) +} + #[get("/gamenights")] pub async fn gamenights(conn: DbConn, _user: User) -> ApiResponseVariant { let gamenights = match get_all_gamenights(&conn).await { @@ -164,7 +239,7 @@ pub async fn gamenights(conn: DbConn, _user: User) -> ApiResponseVariant { .collect(); match game_results { - Ok(result) => ApiResponseVariant::Value(json!(ApiResponse::gamenight_response(result))), + Ok(result) => ApiResponseVariant::Value(json!(ApiResponse::gamenights_response(result))), Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), } } @@ -220,7 +295,7 @@ pub async fn gamenights_post_json( gamenight_id: gamenight_id, user_id: user.id, }; - match insert_participant(&conn, participant).await { + match add_participant(&conn, participant).await { Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), } @@ -375,7 +450,7 @@ pub async fn post_participants( _user: User, entry_json: Json, ) -> ApiResponseVariant { - match insert_participant(&conn, entry_json.into_inner()).await { + match add_participant(&conn, entry_json.into_inner()).await { Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))), } diff --git a/backend/src/main.rs b/backend/src/main.rs index 2e1c2b0..26c47dc 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -50,6 +50,8 @@ async fn rocket() -> _ { .mount( "/api", routes![ + api::gamenight, + api::patch_gamenight, api::gamenights, api::gamenights_unauthorized, api::gamenights_post_json, diff --git a/backend/src/schema/gamenight.rs b/backend/src/schema/gamenight.rs index 3cc1c0a..fc7a4c6 100644 --- a/backend/src/schema/gamenight.rs +++ b/backend/src/schema/gamenight.rs @@ -110,9 +110,9 @@ pub async fn insert_gamenight( .await?) } -pub async fn get_gamenight(conn: &DbConn, game_id: Uuid) -> Result { +pub async fn get_gamenight(conn: &DbConn, gamenight_id: Uuid) -> Result { Ok(conn - .run(move |c| gamenight::table.find(game_id).first(c)) + .run(move |c| gamenight::table.find(gamenight_id).first(c)) .await?) } @@ -190,7 +190,7 @@ pub async fn load_participants( .await?) } -pub async fn insert_participant( +pub async fn add_participant( conn: &DbConn, participant: GamenightParticipantsEntry, ) -> Result { @@ -198,6 +198,7 @@ pub async fn insert_participant( .run(move |c| { diesel::insert_into(gamenight_participants::table) .values(&participant) + .on_conflict_do_nothing() .execute(c) }) .await?) diff --git a/frontend/src/App.js b/frontend/src/App.js index 2982bda..f48c64e 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -15,7 +15,7 @@ function App() { const [gamenights, setGamenights] = useState([]); const [flashData, setFlashData] = useState({}); const [games, setGames] = useState([]); - const [activeGamenight, setActiveGamenight] = useState(null); + const [activeGamenightId, setActiveGamenightId] = useState(null); const POST_HEADER = {'Content-Type': 'application/json'}; const AUTH_HEADER = {'Authorization': `Bearer ${user?.jwt}`}; @@ -42,7 +42,7 @@ function App() { }; const dismissActiveGamenight = () => { - setActiveGamenight(null); + setActiveGamenightId(null); }; useEffect(() => { @@ -72,7 +72,7 @@ function App() { ); } else { let mainview; - if(activeGamenight === null) { + if(activeGamenightId === null) { mainview = ( setActiveGamenight(g)}/> + onSelectGamenight={(g) => setActiveGamenightId(g.id)}/> ) } else { mainview = ( ) } diff --git a/frontend/src/api/Api.js b/frontend/src/api/Api.js index 182ea18..3630501 100644 --- a/frontend/src/api/Api.js +++ b/frontend/src/api/Api.js @@ -28,6 +28,15 @@ export function get_gamenights(token) { }); } +export function get_gamenight(gamenight_id, token) { + return fetchResource(`api/gamenights/${gamenight_id}`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${token}` + }, + }); +} + export function post_gamenight(input, token) { return fetchResource('api/gamenights', { method: 'POST', @@ -39,6 +48,17 @@ export function post_gamenight(input, token) { }); } +export function patch_gamenight(gamenight_id, input, token) { + return fetchResource(`api/gamenights/${gamenight_id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify(input) + }) +} + export function delete_gamenight(input, token) { return fetchResource('api/gamenights', { method: 'DELETE', diff --git a/frontend/src/api/FetchResource.js b/frontend/src/api/FetchResource.js index 252c79a..0be490e 100644 --- a/frontend/src/api/FetchResource.js +++ b/frontend/src/api/FetchResource.js @@ -21,9 +21,7 @@ function ApiError(message, data, status) { const fetchResource = (path, userOptions = {}) => { const defaultOptions = {}; - const defaultHeaders = { - 'Content-Type': 'application/json' - }; + const defaultHeaders = {}; const options = { ...defaultOptions, diff --git a/frontend/src/components/Gamenight.jsx b/frontend/src/components/Gamenight.jsx index e6446b9..624196e 100644 --- a/frontend/src/components/Gamenight.jsx +++ b/frontend/src/components/Gamenight.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemText from '@mui/material/ListItemText'; @@ -6,15 +6,26 @@ import ListSubheader from '@mui/material/ListSubheader'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import IconButton from '@mui/material/IconButton'; import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; import moment from 'moment'; - +import {unpack_api_result, get_gamenight, patch_gamenight} from '../api/Api'; function Gamenight(props) { const [dense, setDense] = useState(true); + const [gamenight, setGamenight] = useState(null); - let games = props.gamenight.game_list.map(g => + const fetchGamenight = () => { + if (props.user !== null) { + unpack_api_result(get_gamenight(props.gamenightId, props.user.jwt), props.setFlash) + .then(result => setGamenight(result.gamenight)); + } + } + + useEffect(fetchGamenight, []); + + let games = gamenight?.game_list.map(g => ( + const participants = gamenight?.participants.map(p => ( ) - ) + ); + + const Join = () => { + const input = { + action: 'AddParticipant' + }; + + unpack_api_result(patch_gamenight(gamenight.id, input, props.user.jwt), props.setFlash) + .then(() => fetchGamenight()); + }; + + const Leave = () => { + const input = { + action: 'RemoveParticipant', + }; + + unpack_api_result(patch_gamenight(gamenight.id, input, props.user.jwt), props.setFlash) + .then(() => fetchGamenight()); + }; + + let join_or_leave_button; + if(gamenight?.participants.find(p => p.id === props.user.id) === undefined) { + join_or_leave_button = ( + + ) + } else { + join_or_leave_button = ( + + ) + } return (
@@ -43,10 +93,10 @@ function Gamenight(props) { - {props.gamenight.name} + {gamenight?.name} - When: {moment(props.gamenight.datetime).format('LL HH:mm')} + When: {moment(gamenight?.datetime).format('LL HH:mm')} {participants} + {join_or_leave_button}
) }