add batch game info request

This commit is contained in:
code002lover 2025-12-18 20:04:50 +01:00
parent f6d40b8f2e
commit d916014872
5 changed files with 206 additions and 40 deletions

View File

@ -44,6 +44,19 @@ async fn get_game(
games.iter().find(|g| g.title == title).cloned() games.iter().find(|g| g.title == title).cloned()
} }
#[post("/games/batch", data = "<req>")]
async fn get_games_batch(
_token: auth::Token,
game_list: &rocket::State<Mutex<Vec<Game>>>,
req: proto_utils::Proto<items::GetGameInfoRequest>,
) -> items::GameList {
let games = game_list.lock().await;
let req = req.into_inner();
let mut games = games.clone();
games.retain(|g| req.games.contains(&g.title));
items::GameList { games }
}
#[get("/games")] #[get("/games")]
async fn get_games( async fn get_games(
_token: auth::Token, _token: auth::Token,
@ -319,7 +332,8 @@ async fn main() -> Result<(), std::io::Error> {
add_opinion, add_opinion,
remove_opinion, remove_opinion,
add_game, add_game,
get_game_thumbnail get_game_thumbnail,
get_games_batch
], ],
) )
.mount( .mount(

View File

@ -116,6 +116,14 @@ export interface RemoveOpinionRequest {
gameTitle: string; gameTitle: string;
} }
export interface GetGameInfoRequest {
games: string[];
}
export interface GameInfoResponse {
games: Game[];
}
function createBasePerson(): Person { function createBasePerson(): Person {
return { name: "", opinion: [] }; return { name: "", opinion: [] };
} }
@ -1213,6 +1221,122 @@ export const RemoveOpinionRequest: MessageFns<RemoveOpinionRequest> = {
}, },
}; };
function createBaseGetGameInfoRequest(): GetGameInfoRequest {
return { games: [] };
}
export const GetGameInfoRequest: MessageFns<GetGameInfoRequest> = {
encode(message: GetGameInfoRequest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
for (const v of message.games) {
writer.uint32(10).string(v!);
}
return writer;
},
decode(input: BinaryReader | Uint8Array, length?: number): GetGameInfoRequest {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
const end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseGetGameInfoRequest();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1: {
if (tag !== 10) {
break;
}
message.games.push(reader.string());
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
}
reader.skip(tag & 7);
}
return message;
},
fromJSON(object: any): GetGameInfoRequest {
return { games: globalThis.Array.isArray(object?.games) ? object.games.map((e: any) => globalThis.String(e)) : [] };
},
toJSON(message: GetGameInfoRequest): unknown {
const obj: any = {};
if (message.games?.length) {
obj.games = message.games;
}
return obj;
},
create<I extends Exact<DeepPartial<GetGameInfoRequest>, I>>(base?: I): GetGameInfoRequest {
return GetGameInfoRequest.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<GetGameInfoRequest>, I>>(object: I): GetGameInfoRequest {
const message = createBaseGetGameInfoRequest();
message.games = object.games?.map((e) => e) || [];
return message;
},
};
function createBaseGameInfoResponse(): GameInfoResponse {
return { games: [] };
}
export const GameInfoResponse: MessageFns<GameInfoResponse> = {
encode(message: GameInfoResponse, 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): GameInfoResponse {
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
const end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseGameInfoResponse();
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): GameInfoResponse {
return { games: globalThis.Array.isArray(object?.games) ? object.games.map((e: any) => Game.fromJSON(e)) : [] };
},
toJSON(message: GameInfoResponse): unknown {
const obj: any = {};
if (message.games?.length) {
obj.games = message.games.map((e) => Game.toJSON(e));
}
return obj;
},
create<I extends Exact<DeepPartial<GameInfoResponse>, I>>(base?: I): GameInfoResponse {
return GameInfoResponse.fromPartial(base ?? ({} as any));
},
fromPartial<I extends Exact<DeepPartial<GameInfoResponse>, I>>(object: I): GameInfoResponse {
const message = createBaseGameInfoResponse();
message.games = object.games?.map((e) => Game.fromPartial(e)) || [];
return message;
},
};
/** Authentication service */ /** Authentication service */
export interface AuthService { export interface AuthService {
Login(request: LoginRequest): Promise<LoginResponse>; Login(request: LoginRequest): Promise<LoginResponse>;
@ -1255,6 +1379,7 @@ export interface MainService {
GetGames(request: GetGamesRequest): Promise<GameList>; GetGames(request: GetGamesRequest): Promise<GameList>;
AddGame(request: Game): Promise<Game>; AddGame(request: Game): Promise<Game>;
AddOpinion(request: AddOpinionRequest): Promise<Person>; AddOpinion(request: AddOpinionRequest): Promise<Person>;
GetGameInfo(request: GetGameInfoRequest): Promise<GameInfoResponse>;
} }
export const MainServiceServiceName = "items.MainService"; export const MainServiceServiceName = "items.MainService";
@ -1268,6 +1393,7 @@ export class MainServiceClientImpl implements MainService {
this.GetGames = this.GetGames.bind(this); this.GetGames = this.GetGames.bind(this);
this.AddGame = this.AddGame.bind(this); this.AddGame = this.AddGame.bind(this);
this.AddOpinion = this.AddOpinion.bind(this); this.AddOpinion = this.AddOpinion.bind(this);
this.GetGameInfo = this.GetGameInfo.bind(this);
} }
GetGame(request: GameRequest): Promise<Game> { GetGame(request: GameRequest): Promise<Game> {
const data = GameRequest.encode(request).finish(); const data = GameRequest.encode(request).finish();
@ -1292,6 +1418,12 @@ export class MainServiceClientImpl implements MainService {
const promise = this.rpc.request(this.service, "AddOpinion", data); const promise = this.rpc.request(this.service, "AddOpinion", data);
return promise.then((data) => Person.decode(new BinaryReader(data))); return promise.then((data) => Person.decode(new BinaryReader(data)));
} }
GetGameInfo(request: GetGameInfoRequest): Promise<GameInfoResponse> {
const data = GetGameInfoRequest.encode(request).finish();
const promise = this.rpc.request(this.service, "GetGameInfo", data);
return promise.then((data) => GameInfoResponse.decode(new BinaryReader(data)));
}
} }
interface Rpc { interface Rpc {

View File

@ -3,6 +3,8 @@ import {
Person, Person,
PersonList as PersonListProto, PersonList as PersonListProto,
Game as GameProto, Game as GameProto,
GetGameInfoRequest,
GameInfoResponse,
} from "../items"; } from "../items";
import { apiFetch } from "./api"; import { apiFetch } from "./api";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
@ -72,31 +74,38 @@ export function GameFilter() {
.filter(([, players]) => players.size === 0) .filter(([, players]) => players.size === 0)
.map(([game]) => game); .map(([game]) => game);
const games = game_titles.map(async (title) => { let games = game_titles.filter((title) => metaData[title]).map((title) => metaData[title]);
if (metaData[title]) { const gamesToFetch = GetGameInfoRequest.encode(
console.log("returned cached metadata"); GetGameInfoRequest.create({
return metaData[title]; games: game_titles.filter((title) => !metaData[title]),
} })
return await apiFetch(`/api/game/${encodeURIComponent(title)}`) ).finish();
.then((res) => res.arrayBuffer())
.then((buffer) => {
const game = GameProto.decode(new Uint8Array(buffer)) as GameProto;
metaData[title] = game;
return game;
})
.catch((err) => console.error("Failed to fetch game:", err));
});
Promise.all(games).then((games) => { apiFetch("/api/games/batch", {
const filteredGames = games.filter((g) => { method: "POST",
const game = g as GameProto; headers: {
return ( "Content-Type": "application/octet-stream",
game.maxPlayers >= selectedPeople.size && },
game.minPlayers <= selectedPeople.size body: gamesToFetch,
); })
.then((res) => res.arrayBuffer())
.then((buffer) => {
const list = GameInfoResponse.decode(new Uint8Array(buffer));
games = games.concat(list.games);
games.forEach((game) => {
metaData[game.title] = game;
});
const filteredGames = games.filter((g) => {
const game = g as GameProto;
return (
game.maxPlayers >= selectedPeople.size &&
game.minPlayers <= selectedPeople.size
);
});
setFilteredGames(filteredGames.map((g) => (g as GameProto).title));
}); });
setFilteredGames(filteredGames.map((g) => (g as GameProto).title));
});
}, [selectedPeople, people, metaData]); }, [selectedPeople, people, metaData]);
const togglePerson = (name: string) => { const togglePerson = (name: string) => {

View File

@ -459,7 +459,7 @@ export function GameList({ onShowToast }: Props) {
"Content-Type": "application/octet-stream", "Content-Type": "application/octet-stream",
}, },
body: RemoveOpinionRequest.encode( body: RemoveOpinionRequest.encode(
AddOpinionRequest.create({ RemoveOpinionRequest.create({
gameTitle: title, gameTitle: title,
}) })
).finish(), ).finish(),

View File

@ -3,31 +3,32 @@ syntax = "proto3";
package items; package items;
message Person { message Person {
string name = 1; string name = 1;
repeated Opinion opinion = 2; repeated Opinion opinion = 2;
} }
message Opinion { message Opinion {
string title = 1; string title = 1;
bool would_play = 2; bool would_play = 2;
} }
message Game { message Game {
reserved 3; reserved 3;
string title = 1; string title = 1;
Source source = 2; Source source = 2;
uint32 min_players = 4; uint32 min_players = 4;
uint32 max_players = 5; uint32 max_players = 5;
uint32 price = 6; uint32 price = 6;
uint64 remote_id = 7; uint64 remote_id = 7;
} }
enum Source { enum Source {
STEAM = 0; STEAM = 0;
ROBLOX = 1; ROBLOX = 1;
} }
message PersonList { repeated Person person = 1; } message PersonList { repeated Person person = 1; }
message GameList { repeated Game games = 1; } message GameList { repeated Game games = 1; }
// Authentication messages // Authentication messages
@ -37,24 +38,24 @@ message LoginRequest {
} }
message LoginResponse { message LoginResponse {
string token = 1; string token = 1;
bool success = 2; bool success = 2;
string message = 3; string message = 3;
} }
message LogoutRequest { string token = 1; } message LogoutRequest { string token = 1; }
message LogoutResponse { message LogoutResponse {
bool success = 1; bool success = 1;
string message = 2; string message = 2;
} }
message AuthStatusRequest { string token = 1; } message AuthStatusRequest { string token = 1; }
message AuthStatusResponse { message AuthStatusResponse {
bool authenticated = 1; bool authenticated = 1;
string username = 2; string username = 2;
string message = 3; string message = 3;
} }
// Authentication service // Authentication service
@ -65,18 +66,28 @@ service AuthService {
} }
message GameRequest { string title = 1; } message GameRequest { string title = 1; }
message GetGamesRequest {} message GetGamesRequest {}
message AddOpinionRequest { message AddOpinionRequest {
string game_title = 1; string game_title = 1;
bool would_play = 2; bool would_play = 2;
} }
message RemoveOpinionRequest { string game_title = 1; } message RemoveOpinionRequest { string game_title = 1; }
message GetGameInfoRequest {
repeated string games = 1;
}
message GameInfoResponse {
repeated Game games = 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);
rpc AddGame(Game) returns (Game); rpc AddGame(Game) returns (Game);
rpc AddOpinion(AddOpinionRequest) returns (Person); rpc AddOpinion(AddOpinionRequest) returns (Person);
rpc GetGameInfo(GetGameInfoRequest) returns (GameInfoResponse);
} }