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()
|
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>")]
|
#[post("/game", data = "<game>")]
|
||||||
fn add_game(
|
fn add_game(
|
||||||
_token: auth::Token,
|
_token: auth::Token,
|
||||||
@ -203,7 +211,14 @@ async fn main() -> Result<(), std::io::Error> {
|
|||||||
.manage(Mutex::new(game_list))
|
.manage(Mutex::new(game_list))
|
||||||
.mount(
|
.mount(
|
||||||
"/api",
|
"/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(
|
.mount(
|
||||||
"/auth",
|
"/auth",
|
||||||
|
|||||||
@ -66,6 +66,10 @@ export interface PersonList {
|
|||||||
person: Person[];
|
person: Person[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GameList {
|
||||||
|
games: Game[];
|
||||||
|
}
|
||||||
|
|
||||||
/** Authentication messages */
|
/** Authentication messages */
|
||||||
export interface LoginRequest {
|
export interface LoginRequest {
|
||||||
username: string;
|
username: string;
|
||||||
@ -101,6 +105,9 @@ export interface GameRequest {
|
|||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GetGamesRequest {
|
||||||
|
}
|
||||||
|
|
||||||
export interface AddOpinionRequest {
|
export interface AddOpinionRequest {
|
||||||
gameTitle: string;
|
gameTitle: string;
|
||||||
wouldPlay: boolean;
|
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 {
|
function createBaseLoginRequest(): LoginRequest {
|
||||||
return { username: "", password: "" };
|
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 {
|
function createBaseAddOpinionRequest(): AddOpinionRequest {
|
||||||
return { gameTitle: "", wouldPlay: false };
|
return { gameTitle: "", wouldPlay: false };
|
||||||
}
|
}
|
||||||
@ -1099,6 +1207,8 @@ export class AuthServiceClientImpl implements AuthService {
|
|||||||
|
|
||||||
export interface MainService {
|
export interface MainService {
|
||||||
GetGame(request: GameRequest): Promise<Game>;
|
GetGame(request: GameRequest): Promise<Game>;
|
||||||
|
GetGames(request: GetGamesRequest): Promise<GameList>;
|
||||||
|
AddGame(request: Game): Promise<Game>;
|
||||||
AddOpinion(request: AddOpinionRequest): Promise<Person>;
|
AddOpinion(request: AddOpinionRequest): Promise<Person>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1110,6 +1220,8 @@ export class MainServiceClientImpl implements MainService {
|
|||||||
this.service = opts?.service || MainServiceServiceName;
|
this.service = opts?.service || MainServiceServiceName;
|
||||||
this.rpc = rpc;
|
this.rpc = rpc;
|
||||||
this.GetGame = this.GetGame.bind(this);
|
this.GetGame = this.GetGame.bind(this);
|
||||||
|
this.GetGames = this.GetGames.bind(this);
|
||||||
|
this.AddGame = this.AddGame.bind(this);
|
||||||
this.AddOpinion = this.AddOpinion.bind(this);
|
this.AddOpinion = this.AddOpinion.bind(this);
|
||||||
}
|
}
|
||||||
GetGame(request: GameRequest): Promise<Game> {
|
GetGame(request: GameRequest): Promise<Game> {
|
||||||
@ -1118,6 +1230,18 @@ export class MainServiceClientImpl implements MainService {
|
|||||||
return promise.then((data) => Game.decode(new BinaryReader(data)));
|
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> {
|
AddOpinion(request: AddOpinionRequest): Promise<Person> {
|
||||||
const data = AddOpinionRequest.encode(request).finish();
|
const data = AddOpinionRequest.encode(request).finish();
|
||||||
const promise = this.rpc.request(this.service, "AddOpinion", data);
|
const promise = this.rpc.request(this.service, "AddOpinion", data);
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Game, Source } from "../items";
|
import { Game, Source, GameList as GameListProto } from "../items";
|
||||||
import { apiFetch } from "./api";
|
import { apiFetch } from "./api";
|
||||||
|
|
||||||
export function GameList() {
|
export function GameList() {
|
||||||
|
const [games, setGames] = useState<Game[]>([]);
|
||||||
const [title, setTitle] = useState("");
|
const [title, setTitle] = useState("");
|
||||||
const [source, setSource] = useState<Source>(Source.STEAM);
|
const [source, setSource] = useState<Source>(Source.STEAM);
|
||||||
const [multiplayer, setMultiplayer] = useState(false);
|
const [multiplayer, setMultiplayer] = useState(false);
|
||||||
@ -12,6 +13,24 @@ export function GameList() {
|
|||||||
const [remoteId, setRemoteId] = useState(0);
|
const [remoteId, setRemoteId] = useState(0);
|
||||||
const [message, setMessage] = useState("");
|
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) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const game = {
|
const game = {
|
||||||
@ -37,6 +56,7 @@ export function GameList() {
|
|||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
setMessage("Game added successfully!");
|
setMessage("Game added successfully!");
|
||||||
setTitle("");
|
setTitle("");
|
||||||
|
fetchGames();
|
||||||
// Reset other fields if needed
|
// Reset other fields if needed
|
||||||
} else {
|
} else {
|
||||||
setMessage("Failed to add game.");
|
setMessage("Failed to add game.");
|
||||||
@ -121,6 +141,17 @@ export function GameList() {
|
|||||||
</label>
|
</label>
|
||||||
<button type="submit">Add Game</button>
|
<button type="submit">Add Game</button>
|
||||||
</form>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,32 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { Person, AddOpinionRequest } from "../items";
|
import { Person, AddOpinionRequest, Game, GameList } from "../items";
|
||||||
import { apiFetch } from "./api";
|
import { apiFetch } from "./api";
|
||||||
|
|
||||||
export const PersonDetails = () => {
|
export const PersonDetails = () => {
|
||||||
const { name } = useParams<{ name: string }>();
|
const { name } = useParams<{ name: string }>();
|
||||||
const [person, setPerson] = useState<Person | null>(null);
|
const [person, setPerson] = useState<Person | null>(null);
|
||||||
|
const [games, setGames] = useState<Game[]>([]);
|
||||||
const [gameTitle, setGameTitle] = useState("");
|
const [gameTitle, setGameTitle] = useState("");
|
||||||
const [wouldPlay, setWouldPlay] = useState(false);
|
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(() => {
|
useEffect(() => {
|
||||||
if (name) {
|
if (name) {
|
||||||
apiFetch(`/api/${name}`)
|
apiFetch(`/api/${name}`)
|
||||||
@ -76,12 +94,16 @@ export const PersonDetails = () => {
|
|||||||
>
|
>
|
||||||
<h3>Add Opinion</h3>
|
<h3>Add Opinion</h3>
|
||||||
<div style={{ display: "flex", gap: "1rem", alignItems: "center" }}>
|
<div style={{ display: "flex", gap: "1rem", alignItems: "center" }}>
|
||||||
<input
|
<select
|
||||||
type="text"
|
|
||||||
placeholder="Game Title"
|
|
||||||
value={gameTitle}
|
value={gameTitle}
|
||||||
onChange={(e) => setGameTitle(e.target.value)}
|
onChange={(e) => setGameTitle(e.target.value)}
|
||||||
/>
|
>
|
||||||
|
{games.map((g) => (
|
||||||
|
<option key={g.title} value={g.title}>
|
||||||
|
{g.title}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|||||||
@ -28,6 +28,8 @@ enum Source {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message PersonList { repeated Person person = 1; }
|
message PersonList { repeated Person person = 1; }
|
||||||
|
message GameList { repeated Game games = 1; }
|
||||||
|
|
||||||
// Authentication messages
|
// Authentication messages
|
||||||
message LoginRequest {
|
message LoginRequest {
|
||||||
string username = 1;
|
string username = 1;
|
||||||
@ -63,6 +65,7 @@ service AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message GameRequest { string title = 1; }
|
message GameRequest { string title = 1; }
|
||||||
|
message GetGamesRequest {}
|
||||||
|
|
||||||
message AddOpinionRequest {
|
message AddOpinionRequest {
|
||||||
string game_title = 1;
|
string game_title = 1;
|
||||||
@ -71,6 +74,7 @@ message AddOpinionRequest {
|
|||||||
|
|
||||||
service MainService {
|
service MainService {
|
||||||
rpc GetGame(GameRequest) returns (Game);
|
rpc GetGame(GameRequest) returns (Game);
|
||||||
|
rpc GetGames(GetGamesRequest) returns (GameList);
|
||||||
rpc AddGame(Game) returns (Game);
|
rpc AddGame(Game) returns (Game);
|
||||||
rpc AddOpinion(AddOpinionRequest) returns (Person);
|
rpc AddOpinion(AddOpinionRequest) returns (Person);
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user