feat: Add functionality to remove a user's game opinion via a new API endpoint and UI button.

This commit is contained in:
code002lover 2025-12-04 23:11:10 +01:00
parent 5bb70768e3
commit 0ac633bd34
4 changed files with 136 additions and 3 deletions

View File

@ -123,6 +123,37 @@ async fn add_opinion(
result result
} }
#[patch("/opinion", data = "<req>")]
async fn remove_opinion(
token: auth::Token,
user_list: &rocket::State<Mutex<Vec<User>>>,
game_list: &rocket::State<Mutex<Vec<Game>>>,
req: proto_utils::Proto<items::RemoveOpinionRequest>,
) -> Option<items::Person> {
let mut users = user_list.lock().await;
let games = game_list.lock().await;
let mut result = None;
if let Some(user) = users.iter_mut().find(|u| u.person.name == token.username) {
let req = req.into_inner();
if let Some(existing) = user
.person
.opinion
.iter()
.position(|o| o.title == req.game_title)
{
user.person.opinion.remove(existing);
}
result = Some(user.person.clone());
}
if result.is_some() {
save_state(&games, &users);
}
result
}
mod cached_option; mod cached_option;
use cached_option::CachedOption; use cached_option::CachedOption;
@ -280,6 +311,7 @@ async fn main() -> Result<(), std::io::Error> {
get_game, get_game,
get_games, get_games,
add_opinion, add_opinion,
remove_opinion,
add_game, add_game,
get_game_thumbnail get_game_thumbnail
], ],

View File

@ -112,6 +112,10 @@ export interface AddOpinionRequest {
wouldPlay: boolean; wouldPlay: boolean;
} }
export interface RemoveOpinionRequest {
gameTitle: string;
}
function createBasePerson(): Person { function createBasePerson(): Person {
return { name: "", opinion: [] }; return { name: "", opinion: [] };
} }
@ -1151,6 +1155,64 @@ export const AddOpinionRequest: MessageFns<AddOpinionRequest> = {
}, },
}; };
function createBaseRemoveOpinionRequest(): RemoveOpinionRequest {
return { gameTitle: "" };
}
export const RemoveOpinionRequest: MessageFns<RemoveOpinionRequest> = {
encode(message: RemoveOpinionRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
if (message.gameTitle !== "") {
writer.uint32(10).string(message.gameTitle);
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): RemoveOpinionRequest {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
const end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseRemoveOpinionRequest();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 10) {
break;
}
message.gameTitle = reader.string();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
fromJSON(object: any): RemoveOpinionRequest {
return { gameTitle: isSet(object.gameTitle) ? globalThis.String(object.gameTitle) : "" };
},
toJSON(message: RemoveOpinionRequest): unknown {
const obj: any = {};
if (message.gameTitle !== "") {
obj.gameTitle = message.gameTitle;
}
return obj;
},
create<I extends Exact<DeepPartial<RemoveOpinionRequest>, I>>(base?: I): RemoveOpinionRequest {
return RemoveOpinionRequest.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<RemoveOpinionRequest>, I>>(object: I): RemoveOpinionRequest {
const message = createBaseRemoveOpinionRequest();
message.gameTitle = object.gameTitle ?? "";
return message;
},
};
/** Authentication service */ /** Authentication service */
export interface AuthService { export interface AuthService {
Login(request: LoginRequest): Promise<LoginResponse>; Login(request: LoginRequest): Promise<LoginResponse>;

View File

@ -6,6 +6,7 @@ import {
Person as PersonProto, Person as PersonProto,
Opinion, Opinion,
AddOpinionRequest, AddOpinionRequest,
RemoveOpinionRequest,
} from "../items"; } from "../items";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { apiFetch, get_auth_status } from "./api"; import { apiFetch, get_auth_status } from "./api";
@ -458,7 +459,33 @@ export function GameList() {
<ul className="grid-container"> <ul className="grid-container">
{games.map((game) => { {games.map((game) => {
const opinion = opinions.find((op) => op.title === game.title); const opinion = opinions.find((op) => op.title === game.title);
function handleOpinion(title: string, wouldPlay: boolean): void { function handleOpinion(title: string, number: number): void {
if (number == 2) {
apiFetch("/api/opinion", {
method: "PATCH",
headers: {
"Content-Type": "application/octet-stream",
},
body: RemoveOpinionRequest.encode(
AddOpinionRequest.create({
gameTitle: title,
})
).finish(),
})
.then((res) => res.arrayBuffer())
.then((resBuffer) => {
const response = PersonProto.decode(
new Uint8Array(resBuffer)
);
setOpinions(response.opinion);
})
.catch((err) => {
console.error(err);
});
return;
}
const wouldPlay = number == 1;
apiFetch(`/api/opinion`, { apiFetch(`/api/opinion`, {
method: "POST", method: "POST",
headers: { headers: {
@ -496,6 +523,7 @@ export function GameList() {
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "center",
height: "50%",
borderColor: opinion borderColor: opinion
? opinion.wouldPlay ? opinion.wouldPlay
? "#4caf50" // would play (green) ? "#4caf50" // would play (green)
@ -525,7 +553,7 @@ export function GameList() {
}} }}
> >
<button <button
onClick={() => handleOpinion(game.title, true)} onClick={() => handleOpinion(game.title, 1)}
style={{ style={{
width: "50%", width: "50%",
borderColor: "#4caf50", borderColor: "#4caf50",
@ -534,7 +562,16 @@ export function GameList() {
Would Play Would Play
</button> </button>
<button <button
onClick={() => handleOpinion(game.title, false)} onClick={() => handleOpinion(game.title, 2)}
style={{
width: "50%",
borderColor: "#ffff00",
}}
>
Neutral
</button>
<button
onClick={() => handleOpinion(game.title, 0)}
style={{ style={{
width: "50%", width: "50%",
borderColor: "#f44336", borderColor: "#f44336",

View File

@ -72,6 +72,8 @@ message AddOpinionRequest {
bool would_play = 2; bool would_play = 2;
} }
message RemoveOpinionRequest { string game_title = 1; }
service MainService { service MainService {
rpc GetGame(GameRequest) returns (Game); rpc GetGame(GameRequest) returns (Game);
rpc GetGames(GetGamesRequest) returns (GameList); rpc GetGames(GetGamesRequest) returns (GameList);