add edit/delete game capabilities
This commit is contained in:
+67
-1
@@ -2,7 +2,7 @@ use crate::items;
|
||||
use crate::proto_utils::Proto;
|
||||
use rocket::State;
|
||||
use rocket::futures::lock::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct AuthState {
|
||||
@@ -24,6 +24,24 @@ impl Default for AuthState {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AdminState {
|
||||
pub admins: Mutex<HashSet<String>>,
|
||||
}
|
||||
|
||||
impl AdminState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
admins: Mutex::new(crate::store::load_admins()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AdminState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Token {
|
||||
@@ -62,6 +80,48 @@ impl<'r> rocket::request::FromRequest<'r> for Token {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AdminToken {
|
||||
pub token: String,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> rocket::request::FromRequest<'r> for AdminToken {
|
||||
type Error = ();
|
||||
|
||||
async fn from_request(
|
||||
request: &'r rocket::Request<'_>,
|
||||
) -> rocket::request::Outcome<Self, Self::Error> {
|
||||
let token = request.headers().get_one("Authorization");
|
||||
|
||||
match token {
|
||||
Some(token) => {
|
||||
if let Some(token) = token.strip_prefix("Bearer ") {
|
||||
let auth_state = request.guard::<&State<AuthState>>().await.unwrap();
|
||||
let tokens = auth_state.tokens.lock().await;
|
||||
|
||||
if let Some(username) = tokens.get(token) {
|
||||
let admin_state =
|
||||
request.guard::<&State<crate::AdminState>>().await.unwrap();
|
||||
let admins = admin_state.admins.lock().await;
|
||||
|
||||
if crate::store::is_admin(username, &admins) {
|
||||
return rocket::request::Outcome::Success(AdminToken {
|
||||
token: token.to_string(),
|
||||
username: username.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rocket::request::Outcome::Error((rocket::http::Status::Forbidden, ()))
|
||||
}
|
||||
None => rocket::request::Outcome::Error((rocket::http::Status::Unauthorized, ())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/login", data = "<request>")]
|
||||
pub async fn login(
|
||||
state: &State<AuthState>,
|
||||
@@ -118,22 +178,28 @@ pub async fn logout(
|
||||
#[post("/get_auth_status", data = "<request>")]
|
||||
pub async fn get_auth_status(
|
||||
state: &State<AuthState>,
|
||||
admin_state: &State<AdminState>,
|
||||
request: Proto<items::AuthStatusRequest>,
|
||||
) -> items::AuthStatusResponse {
|
||||
let req = request.into_inner();
|
||||
let tokens = state.tokens.lock().await;
|
||||
|
||||
if let Some(username) = tokens.get(&req.token) {
|
||||
let admins = admin_state.admins.lock().await;
|
||||
let is_admin = crate::store::is_admin(username, &admins);
|
||||
|
||||
items::AuthStatusResponse {
|
||||
authenticated: true,
|
||||
username: username.clone(),
|
||||
message: "Authenticated".to_string(),
|
||||
is_admin,
|
||||
}
|
||||
} else {
|
||||
items::AuthStatusResponse {
|
||||
authenticated: false,
|
||||
username: "".to_string(),
|
||||
message: "Not authenticated".to_string(),
|
||||
is_admin: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,5 @@ pub mod auth;
|
||||
pub mod proto_utils;
|
||||
pub mod store;
|
||||
|
||||
pub use auth::AdminState;
|
||||
pub use store::User;
|
||||
|
||||
@@ -103,6 +103,63 @@ async fn add_game(
|
||||
Some(game)
|
||||
}
|
||||
|
||||
#[patch("/game", data = "<game>")]
|
||||
async fn update_game(
|
||||
_token: auth::AdminToken,
|
||||
game_list: &rocket::State<Mutex<Vec<Game>>>,
|
||||
user_list: &rocket::State<Mutex<Vec<User>>>,
|
||||
game: proto_utils::Proto<items::Game>,
|
||||
) -> Option<items::Game> {
|
||||
let mut games = game_list.lock().await;
|
||||
let mut game = game.into_inner();
|
||||
|
||||
game.title = game.title.trim().to_string();
|
||||
|
||||
if game.remote_id == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut r_existing = None;
|
||||
|
||||
if let Some(existing) = games.iter_mut().find(|g| g.title == game.title) {
|
||||
existing.source = game.source;
|
||||
existing.min_players = game.min_players;
|
||||
existing.max_players = game.max_players;
|
||||
existing.price = game.price;
|
||||
existing.remote_id = game.remote_id;
|
||||
|
||||
r_existing = Some(existing.clone());
|
||||
}
|
||||
let users = user_list.lock().await;
|
||||
save_state(&games, &users);
|
||||
|
||||
r_existing
|
||||
}
|
||||
|
||||
#[delete("/game/<title>")]
|
||||
async fn delete_game(
|
||||
_token: auth::AdminToken,
|
||||
game_list: &rocket::State<Mutex<Vec<Game>>>,
|
||||
user_list: &rocket::State<Mutex<Vec<User>>>,
|
||||
title: &str,
|
||||
) -> Option<items::Game> {
|
||||
let mut games = game_list.lock().await;
|
||||
|
||||
if let Some(pos) = games
|
||||
.iter()
|
||||
.position(|g| g.title.to_lowercase() == title.to_lowercase())
|
||||
{
|
||||
let game = games.remove(pos);
|
||||
|
||||
let users = user_list.lock().await;
|
||||
save_state(&games, &users);
|
||||
|
||||
return Some(game);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[post("/opinion", data = "<req>")]
|
||||
async fn add_opinion(
|
||||
token: auth::Token,
|
||||
@@ -334,6 +391,7 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
rocket::build()
|
||||
.manage(Mutex::new(user_list))
|
||||
.manage(auth::AuthState::new())
|
||||
.manage(auth::AdminState::new())
|
||||
.manage(Mutex::new(game_list))
|
||||
.mount(
|
||||
"/api",
|
||||
@@ -345,6 +403,8 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
add_opinion,
|
||||
remove_opinion,
|
||||
add_game,
|
||||
update_game,
|
||||
delete_game,
|
||||
get_game_thumbnail,
|
||||
get_games_batch
|
||||
],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::items::{Game, Person};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
|
||||
@@ -30,6 +31,7 @@ pub struct PersistentState {
|
||||
}
|
||||
|
||||
pub const STATE_FILE: &str = "state.json";
|
||||
pub const ADMINS_FILE: &str = "admins.json";
|
||||
|
||||
pub fn save_state(games: &[Game], users: &[User]) {
|
||||
let mut games = games.to_vec();
|
||||
@@ -57,3 +59,17 @@ pub fn load_state() -> Option<(Vec<Game>, Vec<User>)> {
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn load_admins() -> HashSet<String> {
|
||||
if let Ok(file) = File::open(ADMINS_FILE) {
|
||||
let reader = BufReader::new(file);
|
||||
if let Ok(admins) = serde_json::from_reader::<_, Vec<String>>(reader) {
|
||||
return admins.into_iter().map(|s| s.to_lowercase()).collect();
|
||||
}
|
||||
}
|
||||
HashSet::new()
|
||||
}
|
||||
|
||||
pub fn is_admin(username: &str, admins: &HashSet<String>) -> bool {
|
||||
admins.contains(&username.to_lowercase())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user