feat: implement game listing API and integrate into frontend for display and selection.
This commit is contained in:
parent
e192829fdd
commit
0c3c9e61b6
@ -92,6 +92,14 @@ fn get_game(
|
||||
games.iter().find(|g| g.title == title).cloned()
|
||||
}
|
||||
|
||||
#[get("/games")]
|
||||
fn get_games(_token: auth::Token, game_list: &rocket::State<Mutex<Vec<Game>>>) -> items::GameList {
|
||||
let games = game_list.lock().unwrap();
|
||||
items::GameList {
|
||||
games: games.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/game", data = "<game>")]
|
||||
fn add_game(
|
||||
_token: auth::Token,
|
||||
@ -203,7 +211,14 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
.manage(Mutex::new(game_list))
|
||||
.mount(
|
||||
"/api",
|
||||
routes![get_users, get_user, get_game, add_opinion, add_game],
|
||||
routes![
|
||||
get_users,
|
||||
get_user,
|
||||
get_game,
|
||||
get_games,
|
||||
add_opinion,
|
||||
add_game
|
||||
],
|
||||
)
|
||||
.mount(
|
||||
"/auth",
|
||||
|
||||
@ -66,6 +66,10 @@ export interface PersonList {
|
||||
person: Person[];
|
||||
}
|
||||
|
||||
export interface GameList {
|
||||
games: Game[];
|
||||
}
|
||||
|
||||
/** Authentication messages */
|
||||
export interface LoginRequest {
|
||||
username: string;
|
||||
@ -101,6 +105,9 @@ export interface GameRequest {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface GetGamesRequest {
|
||||
}
|
||||
|
||||
export interface AddOpinionRequest {
|
||||
gameTitle: string;
|
||||
wouldPlay: boolean;
|
||||
@ -474,6 +481,64 @@ export const PersonList: MessageFns<PersonList> = {
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseGameList(): GameList {
|
||||
return { games: [] };
|
||||
}
|
||||
|
||||
export const GameList: MessageFns<GameList> = {
|
||||
encode(message: GameList, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
for (const v of message.games) {
|
||||
Game.encode(v!, writer.uint32(10).fork()).join();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): GameList {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseGameList();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1: {
|
||||
if (tag !== 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.games.push(Game.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): GameList {
|
||||
return { games: globalThis.Array.isArray(object?.games) ? object.games.map((e: any) => Game.fromJSON(e)) : [] };
|
||||
},
|
||||
|
||||
toJSON(message: GameList): unknown {
|
||||
const obj: any = {};
|
||||
if (message.games?.length) {
|
||||
obj.games = message.games.map((e) => Game.toJSON(e));
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<GameList>, I>>(base?: I): GameList {
|
||||
return GameList.fromPartial(base ?? ({} as any));
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<GameList>, I>>(object: I): GameList {
|
||||
const message = createBaseGameList();
|
||||
message.games = object.games?.map((e) => Game.fromPartial(e)) || [];
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseLoginRequest(): LoginRequest {
|
||||
return { username: "", password: "" };
|
||||
}
|
||||
@ -984,6 +1049,49 @@ export const GameRequest: MessageFns<GameRequest> = {
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseGetGamesRequest(): GetGamesRequest {
|
||||
return {};
|
||||
}
|
||||
|
||||
export const GetGamesRequest: MessageFns<GetGamesRequest> = {
|
||||
encode(_: GetGamesRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: BinaryReader | Uint8Array, length?: number): GetGamesRequest {
|
||||
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseGetGamesRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
}
|
||||
reader.skip(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(_: any): GetGamesRequest {
|
||||
return {};
|
||||
},
|
||||
|
||||
toJSON(_: GetGamesRequest): unknown {
|
||||
const obj: any = {};
|
||||
return obj;
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<GetGamesRequest>, I>>(base?: I): GetGamesRequest {
|
||||
return GetGamesRequest.fromPartial(base ?? ({} as any));
|
||||
},
|
||||
fromPartial<I extends Exact<DeepPartial<GetGamesRequest>, I>>(_: I): GetGamesRequest {
|
||||
const message = createBaseGetGamesRequest();
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseAddOpinionRequest(): AddOpinionRequest {
|
||||
return { gameTitle: "", wouldPlay: false };
|
||||
}
|
||||
@ -1099,6 +1207,8 @@ export class AuthServiceClientImpl implements AuthService {
|
||||
|
||||
export interface MainService {
|
||||
GetGame(request: GameRequest): Promise<Game>;
|
||||
GetGames(request: GetGamesRequest): Promise<GameList>;
|
||||
AddGame(request: Game): Promise<Game>;
|
||||
AddOpinion(request: AddOpinionRequest): Promise<Person>;
|
||||
}
|
||||
|
||||
@ -1110,6 +1220,8 @@ export class MainServiceClientImpl implements MainService {
|
||||
this.service = opts?.service || MainServiceServiceName;
|
||||
this.rpc = rpc;
|
||||
this.GetGame = this.GetGame.bind(this);
|
||||
this.GetGames = this.GetGames.bind(this);
|
||||
this.AddGame = this.AddGame.bind(this);
|
||||
this.AddOpinion = this.AddOpinion.bind(this);
|
||||
}
|
||||
GetGame(request: GameRequest): Promise<Game> {
|
||||
@ -1118,6 +1230,18 @@ export class MainServiceClientImpl implements MainService {
|
||||
return promise.then((data) => Game.decode(new BinaryReader(data)));
|
||||
}
|
||||
|
||||
GetGames(request: GetGamesRequest): Promise<GameList> {
|
||||
const data = GetGamesRequest.encode(request).finish();
|
||||
const promise = this.rpc.request(this.service, "GetGames", data);
|
||||
return promise.then((data) => GameList.decode(new BinaryReader(data)));
|
||||
}
|
||||
|
||||
AddGame(request: Game): Promise<Game> {
|
||||
const data = Game.encode(request).finish();
|
||||
const promise = this.rpc.request(this.service, "AddGame", data);
|
||||
return promise.then((data) => Game.decode(new BinaryReader(data)));
|
||||
}
|
||||
|
||||
AddOpinion(request: AddOpinionRequest): Promise<Person> {
|
||||
const data = AddOpinionRequest.encode(request).finish();
|
||||
const promise = this.rpc.request(this.service, "AddOpinion", data);
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { Game, Source } from "../items";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Game, Source, GameList as GameListProto } from "../items";
|
||||
import { apiFetch } from "./api";
|
||||
|
||||
export function GameList() {
|
||||
const [games, setGames] = useState<Game[]>([]);
|
||||
const [title, setTitle] = useState("");
|
||||
const [source, setSource] = useState<Source>(Source.STEAM);
|
||||
const [multiplayer, setMultiplayer] = useState(false);
|
||||
@ -12,6 +13,24 @@ export function GameList() {
|
||||
const [remoteId, setRemoteId] = useState(0);
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
const fetchGames = () => {
|
||||
apiFetch("/api/games")
|
||||
.then((res) => res.arrayBuffer())
|
||||
.then((buffer) => {
|
||||
try {
|
||||
const list = GameListProto.decode(new Uint8Array(buffer));
|
||||
setGames(list.games);
|
||||
} catch (e) {
|
||||
console.error("Failed to decode games:", e);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchGames();
|
||||
}, []);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const game = {
|
||||
@ -37,6 +56,7 @@ export function GameList() {
|
||||
if (res.ok) {
|
||||
setMessage("Game added successfully!");
|
||||
setTitle("");
|
||||
fetchGames();
|
||||
// Reset other fields if needed
|
||||
} else {
|
||||
setMessage("Failed to add game.");
|
||||
@ -121,6 +141,17 @@ export function GameList() {
|
||||
</label>
|
||||
<button type="submit">Add Game</button>
|
||||
</form>
|
||||
|
||||
<div style={{ marginTop: "2rem" }}>
|
||||
<h3>Existing Games</h3>
|
||||
<ul>
|
||||
{games.map((game) => (
|
||||
<li key={game.title}>
|
||||
{game.title} ({game.source === Source.STEAM ? "Steam" : "Roblox"})
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,14 +1,32 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Person, AddOpinionRequest } from "../items";
|
||||
import { Person, AddOpinionRequest, Game, GameList } from "../items";
|
||||
import { apiFetch } from "./api";
|
||||
|
||||
export const PersonDetails = () => {
|
||||
const { name } = useParams<{ name: string }>();
|
||||
const [person, setPerson] = useState<Person | null>(null);
|
||||
const [games, setGames] = useState<Game[]>([]);
|
||||
const [gameTitle, setGameTitle] = useState("");
|
||||
const [wouldPlay, setWouldPlay] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
apiFetch("/api/games")
|
||||
.then((res) => res.arrayBuffer())
|
||||
.then((buffer) => {
|
||||
try {
|
||||
const list = GameList.decode(new Uint8Array(buffer));
|
||||
setGames(list.games);
|
||||
if (list.games.length > 0) {
|
||||
setGameTitle(list.games[0].title);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to decode games:", e);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (name) {
|
||||
apiFetch(`/api/${name}`)
|
||||
@ -76,12 +94,16 @@ export const PersonDetails = () => {
|
||||
>
|
||||
<h3>Add Opinion</h3>
|
||||
<div style={{ display: "flex", gap: "1rem", alignItems: "center" }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Game Title"
|
||||
<select
|
||||
value={gameTitle}
|
||||
onChange={(e) => setGameTitle(e.target.value)}
|
||||
/>
|
||||
>
|
||||
{games.map((g) => (
|
||||
<option key={g.title} value={g.title}>
|
||||
{g.title}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@ -28,6 +28,8 @@ enum Source {
|
||||
}
|
||||
|
||||
message PersonList { repeated Person person = 1; }
|
||||
message GameList { repeated Game games = 1; }
|
||||
|
||||
// Authentication messages
|
||||
message LoginRequest {
|
||||
string username = 1;
|
||||
@ -63,6 +65,7 @@ service AuthService {
|
||||
}
|
||||
|
||||
message GameRequest { string title = 1; }
|
||||
message GetGamesRequest {}
|
||||
|
||||
message AddOpinionRequest {
|
||||
string game_title = 1;
|
||||
@ -71,6 +74,7 @@ message AddOpinionRequest {
|
||||
|
||||
service MainService {
|
||||
rpc GetGame(GameRequest) returns (Game);
|
||||
rpc GetGames(GetGamesRequest) returns (GameList);
|
||||
rpc AddGame(Game) returns (Game);
|
||||
rpc AddOpinion(AddOpinionRequest) returns (Person);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user