add refresh state from file

This commit is contained in:
code002lover 2026-01-11 19:13:56 +01:00
parent db417e50d9
commit 0eea1b1ff4
6 changed files with 179 additions and 3 deletions

View File

@ -160,6 +160,31 @@ async fn delete_game(
None None
} }
#[post("/refresh")]
async fn refresh_state(
_token: auth::AdminToken,
game_list: &rocket::State<Mutex<Vec<Game>>>,
user_list: &rocket::State<Mutex<Vec<User>>>,
) -> items::RefreshResponse {
if let Some((new_games, new_users)) = store::load_state() {
let mut games = game_list.lock().await;
let mut users = user_list.lock().await;
*games = new_games;
*users = new_users;
items::RefreshResponse {
success: true,
message: "State refreshed from file".to_string(),
}
} else {
items::RefreshResponse {
success: false,
message: "Failed to load state from file".to_string(),
}
}
}
#[post("/opinion", data = "<req>")] #[post("/opinion", data = "<req>")]
async fn add_opinion( async fn add_opinion(
token: auth::Token, token: auth::Token,
@ -405,6 +430,7 @@ async fn main() -> Result<(), std::io::Error> {
add_game, add_game,
update_game, update_game,
delete_game, delete_game,
refresh_state,
get_game_thumbnail, get_game_thumbnail,
get_games_batch get_games_batch
], ],

View File

@ -90,6 +90,11 @@ export interface LogoutResponse {
message: string; message: string;
} }
export interface RefreshResponse {
success: boolean;
message: string;
}
export interface AuthStatusRequest { export interface AuthStatusRequest {
token: string; token: string;
} }
@ -837,6 +842,82 @@ export const LogoutResponse: MessageFns<LogoutResponse> = {
}, },
}; };
function createBaseRefreshResponse(): RefreshResponse {
return { success: false, message: "" };
}
export const RefreshResponse: MessageFns<RefreshResponse> = {
encode(message: RefreshResponse, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.success !== false) {
writer.uint32(8).bool(message.success);
}
if (message.message !== "") {
writer.uint32(18).string(message.message);
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): RefreshResponse {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
const end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseRefreshResponse();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 8) {
break;
}
message.success = reader.bool();
continue;
}
case 2: {
if (tag !== 18) {
break;
}
message.message = reader.string();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
fromJSON(object: any): RefreshResponse {
return {
success: isSet(object.success) ? globalThis.Boolean(object.success) : false,
message: isSet(object.message) ? globalThis.String(object.message) : "",
};
},
toJSON(message: RefreshResponse): unknown {
const obj: any = {};
if (message.success !== false) {
obj.success = message.success;
}
if (message.message !== "") {
obj.message = message.message;
}
return obj;
},
create<I extends Exact<DeepPartial<RefreshResponse>, I>>(base?: I): RefreshResponse {
return RefreshResponse.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<RefreshResponse>, I>>(object: I): RefreshResponse {
const message = createBaseRefreshResponse();
message.success = object.success ?? false;
message.message = object.message ?? "";
return message;
},
};
function createBaseAuthStatusRequest(): AuthStatusRequest { function createBaseAuthStatusRequest(): AuthStatusRequest {
return { token: "" }; return { token: "" };
} }

View File

@ -172,7 +172,7 @@ function App() {
</div> </div>
<ShaderBackground theme={theme} /> <ShaderBackground theme={theme} />
<Routes> <Routes>
<Route path="/" element={<PersonList people={people} />} /> <Route path="/" element={<PersonList people={people} onShowToast={addToast} />} />
<Route path="/games" element={<GameList onShowToast={addToast} />} /> <Route path="/games" element={<GameList onShowToast={addToast} />} />
<Route path="/filter" element={<GameFilter />} /> <Route path="/filter" element={<GameFilter />} />
<Route path="/person/:name" element={<PersonDetails />} /> <Route path="/person/:name" element={<PersonDetails />} />

View File

@ -1,20 +1,41 @@
import { Person } from "../items"; import { Person } from "../items";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useState } from "react"; import { useState } from "react";
import { get_auth_status } from "./api"; import { get_auth_status, refresh_state, get_is_admin } from "./api";
import type { ToastType } from "./Toast";
import "./PersonList.css" import "./PersonList.css"
interface Props { interface Props {
people: Person[]; people: Person[];
onShowToast?: (message: string, type?: ToastType) => void;
} }
export const PersonList = ({ people }: Props) => { export const PersonList = ({ people, onShowToast }: Props) => {
const [current_user, set_current_user] = useState<string>(""); const [current_user, set_current_user] = useState<string>("");
const [isRefreshing, setIsRefreshing] = useState(false);
get_auth_status().then((res) => { get_auth_status().then((res) => {
if (res) { if (res) {
set_current_user(res.username); set_current_user(res.username);
} }
}); });
const handleRefresh = async () => {
setIsRefreshing(true);
try {
await refresh_state();
onShowToast?.("State refreshed from file successfully", "success");
window.location.reload();
} catch (err) {
console.error(err);
onShowToast?.("Failed to refresh state from file", "error");
} finally {
setIsRefreshing(false);
}
};
const isAdmin = get_is_admin();
return ( return (
<div> <div>
<div <div
@ -26,6 +47,43 @@ export const PersonList = ({ people }: Props) => {
}} }}
> >
<h2>People List</h2> <h2>People List</h2>
{isAdmin && (
<button
onClick={handleRefresh}
disabled={isRefreshing}
className="btn-secondary"
style={{
padding: "0.5rem 1rem",
fontSize: "0.9rem",
display: "flex",
alignItems: "center",
gap: "0.5rem",
opacity: isRefreshing ? 0.7 : 1,
cursor: isRefreshing ? "not-allowed" : "pointer",
}}
>
{isRefreshing ? (
<>
<span
style={{
width: "14px",
height: "14px",
border: "2px solid rgba(255,255,255,0.3)",
borderTopColor: "currentColor",
borderRadius: "50%",
animation: "spin 0.8s linear infinite",
}}
></span>
Refreshing...
</>
) : (
<>
<span>🔄</span>
Refresh from File
</>
)}
</button>
)}
</div> </div>
<div className="grid-container"> <div className="grid-container">
{people.map((person, index) => { {people.map((person, index) => {

View File

@ -55,3 +55,9 @@ export const get_auth_status = async (): Promise<AuthStatusResponse | null> => {
export const get_is_admin = (): boolean => { export const get_is_admin = (): boolean => {
return localStorage.getItem("isAdmin") === "true"; return localStorage.getItem("isAdmin") === "true";
}; };
export const refresh_state = async (): Promise<void> => {
await apiFetch("/api/refresh", {
method: "POST",
});
};

View File

@ -50,6 +50,11 @@ message LogoutResponse {
string message = 2; string message = 2;
} }
message RefreshResponse {
bool success = 1;
string message = 2;
}
message AuthStatusRequest { string token = 1; } message AuthStatusRequest { string token = 1; }
message AuthStatusResponse { message AuthStatusResponse {