All files / src/routes games.ts

34.37% Statements 11/32
100% Branches 1/1
50% Functions 1/2
34.37% Lines 11/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 531x         1x     1x     1x 26x               26x 26x 26x 26x                                                     26x 26x  
import type { FastifyPluginAsync } from "fastify";
import type { AppConfig } from "../config.js";
import { requireUser, type IdTokenVerifier } from "../lib/firebase-auth.js";
import { proxyJsonRequest } from "../lib/http-client.js";
 
const PROXY_METHODS = ["GET", "POST", "PUT", "DELETE"] as const;
type ProxyMethod = (typeof PROXY_METHODS)[number];
 
export const gamesRoutes: FastifyPluginAsync<{
  config: AppConfig;
  verifyIdToken: IdTokenVerifier;
}> = async (app, opts) => {
  const { config, verifyIdToken } = opts;
 
  /**
   * Generic pass-through for /v1/games/* — any current or future game endpoint is
   * proxied to games-service. Identity (uid/name) is attached when a valid token
   * is present; games-service itself enforces auth where it is required. New games
   * therefore need no gateway changes.
   */
  app.route({
    method: [...PROXY_METHODS],
    url: "/*",
    handler: async (request) => {
      const headers: Record<string, string> = { "x-service-token": config.serviceToken };
      if (request.headers.authorization) {
        try {
          const user = await requireUser(request, verifyIdToken);
          headers["x-user-uid"] = user.uid;
          // Encode so non-ASCII (cyrillic) names stay header-safe; games-service decodes.
          if (user.name) headers["x-user-name"] = encodeURIComponent(user.name);
        } catch {
          // Invalid/expired token → forward without identity; the upstream returns
          // 401 for endpoints that need a signed-in user.
        }
      }
 
      const rest = (request.params as Record<string, string>)["*"] ?? "";
      const queryIndex = request.url.indexOf("?");
      const query = queryIndex >= 0 ? request.url.slice(queryIndex) : "";
      const method = request.method as ProxyMethod;
 
      return proxyJsonRequest<unknown>({
        method,
        url: `${config.gamesServiceUrl}/v1/games/${rest}${query}`,
        headers,
        body: method === "GET" || method === "DELETE" ? undefined : (request.body ?? {}),
        timeoutMs: config.upstreamTimeoutMs,
      });
    },
  });
};