Skip to content

Commit

Permalink
rt: replace custom router
Browse files Browse the repository at this point in the history
  • Loading branch information
EthanThatOneKid committed May 28, 2024
1 parent b0706cc commit 9cdf651
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 305 deletions.
4 changes: 1 addition & 3 deletions deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
"ngrok": "ngrok http 8080",
"dnt": "deno run -A tasks/dnt/main.ts"
},
"imports": {
"lc-dailies/": "./"
},
"imports": { "@fartlabs/rt": "jsr:@fartlabs/rt@^0.0.3", "lc-dailies/": "./" },
"fmt": {
"exclude": ["./npm"]
},
Expand Down
15 changes: 15 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

137 changes: 91 additions & 46 deletions lib/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { createRouter } from "@fartlabs/rt";
import * as discord from "lc-dailies/lib/discord/mod.ts";
import * as leaderboard from "lc-dailies/lib/leaderboard/mod.ts";
import * as router from "lc-dailies/lib/router/mod.ts";
import * as discord_app from "./discord_app/mod.ts";
import {
makeSeasonGetHandler,
makeSeasonsGetHandler,
makeSeasonTxtGetHandler,
} from "./seasons.ts";
import type { Season } from "./types.ts";

/**
* APIRouterOptions are the options for the API router.
*/
export interface APIRouterOptions {
discordApplicationID: string;
discordPublicKey: string;
Expand All @@ -20,42 +19,69 @@ export interface APIRouterOptions {
* LC-Dailies API.
*/
export function makeAPIRouter(options: APIRouterOptions) {
return new router.Router()
return createRouter()
.post(
new URLPattern({ pathname: "/" }),
discord_app.withErrorResponse(
discord_app.makeDiscordAppHandler(
options.leaderboardClient,
options.discordPublicKey,
options.discordChannelID,
),
),
"/",
(ctx) =>
discord_app.withErrorResponse(
discord_app.makeDiscordAppHandler(
options.leaderboardClient,
options.discordPublicKey,
options.discordChannelID,
),
)(ctx.request),
)
.get(
new URLPattern({ pathname: "/invite" }),
() =>
Promise.resolve(
Response.redirect(makeInviteURL(options.discordApplicationID)),
),
"/invite",
() => Response.redirect(makeInviteURL(options.discordApplicationID)),
)
.get(
new URLPattern({ pathname: "/source" }),
() =>
Promise.resolve(
Response.redirect("https://github.com/acmcsufoss/lc-dailies"),
),
"/source",
() => Response.redirect("https://github.com/acmcsufoss/lc-dailies"),
)
.get(
new URLPattern({ pathname: "/seasons" }),
withCORS(makeSeasonsGetHandler(options.leaderboardClient)),
"/seasons",
async () => {
const seasons = await options.leaderboardClient.listSeasons();
return withCORS(new Response(JSON.stringify(seasons)));
},
)
.get(
new URLPattern({ pathname: "/seasons/:season_id.txt" }),
withCORS(makeSeasonTxtGetHandler(options.leaderboardClient)),
.get<"season_id">(
"/seasons/:season_id.txt",
async (ctx) => {
const seasonID = ctx.params["season_id"];
if (!seasonID) {
return new Response("Missing season ID", { status: 400 });
}

const season = await getSeasonByIDOrLatest(
options.leaderboardClient,
seasonID,
);
if (!season) {
return new Response("Season not found", { status: 404 });
}

const text = leaderboard.formatScores(season);
return withCORS(
new Response(text, { headers: { "Content-Type": "text/plain" } }),
);
},
)
.get(
new URLPattern({ pathname: "/seasons/:season_id" }),
withCORS(makeSeasonGetHandler(options.leaderboardClient)),
.get<"season_id">(
"/seasons",
async (ctx) => {
const seasonID = ctx.params["season_id"];
if (!seasonID) {
return new Response("Missing season ID", { status: 400 });
}

const season = await getSeasonByIDOrLatest(
options.leaderboardClient,
seasonID,
);
return withCORS(new Response(JSON.stringify(season)));
},
);
}

Expand Down Expand Up @@ -103,19 +129,38 @@ function makeInviteURL(applicationID: string) {
}

/**
* withCORS wraps a handler with common CORS headers.
* withCORS wraps a Response with common CORS headers.
*/
function withCORS(
handle: router.RouterHandler["handle"],
): router.RouterHandler["handle"] {
return async function (request: router.RouterRequest) {
const response = await handle(request);
response.headers.set("Access-Control-Allow-Origin", "*");
response.headers.set("Access-Control-Allow-Methods", "GET, POST");
response.headers.set(
"Access-Control-Allow-Headers",
"Content-Type, Authorization",
function withCORS(response: Response): Response {
response.headers.set("Access-Control-Allow-Origin", "*");
response.headers.set("Access-Control-Allow-Methods", "GET, POST");
response.headers.set(
"Access-Control-Allow-Headers",
"Content-Type, Authorization",
);

return response;
}

/**
* getSeasonByIDOrLatest gets a season by ID or the latest season.
*/
async function getSeasonByIDOrLatest(
leaderboardClient: leaderboard.LeaderboardClient,
seasonID: string | undefined,
): Promise<Season | null> {
const season = !seasonID || seasonID === "latest"
? await leaderboardClient.getLatestSeason()
: await leaderboardClient.getSeason(seasonID);
if (season && !season.scores) {
season.scores = await leaderboard.calculateScores(
leaderboard.makeDefaultCalculateScoresOptions(
season.players,
season.questions,
season.submissions,
),
);
return response;
};
}

return season;
}
13 changes: 5 additions & 8 deletions lib/api/discord_app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
MessageFlags,
Utils,
} from "lc-dailies/deps.ts";
import * as router from "lc-dailies/lib/router/mod.ts";
import * as discord from "lc-dailies/lib/discord/mod.ts";
import * as leaderboard from "lc-dailies/lib/leaderboard/mod.ts";
import {
Expand Down Expand Up @@ -49,11 +48,11 @@ export function makeDiscordAppHandler(
discordChannelID: string,
) {
return async function handleDiscordApp(
request: router.RouterRequest,
request: Request,
): Promise<Response> {
// Verify the request is coming from Discord.
const { error, body } = await discord.verify(
request.request,
request,
discordPublicKey,
);
if (error !== null) {
Expand Down Expand Up @@ -180,11 +179,9 @@ async function handleSyncSubcommand(
* and return a response using the error message.
*/
export function withErrorResponse(
oldHandle: router.RouterHandler["handle"],
): router.RouterHandler["handle"] {
return async function handle(
request: router.RouterRequest,
): Promise<Response> {
oldHandle: (request: Request) => Promise<Response>,
): (request: Request) => Promise<Response> {
return async function handle(request: Request): Promise<Response> {
return await oldHandle(request)
.catch((error) => {
if (!(error instanceof Error)) {
Expand Down
90 changes: 0 additions & 90 deletions lib/api/seasons.ts

This file was deleted.

1 change: 0 additions & 1 deletion lib/router/mod.ts

This file was deleted.

Loading

1 comment on commit 9cdf651

@deno-deploy
Copy link

@deno-deploy deno-deploy bot commented on 9cdf651 May 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed to deploy:

UNCAUGHT_EXCEPTION

MissingEnvVarsError: The following variables were defined in the example file but are not present in the environment:
  KV_URL

Make sure to add them to your env file.

If you expect any of these variables to be empty, you can set the allowEmptyValues option to true.
    at assertSafe (https://deno.land/[email protected]/dotenv/mod.ts:363:11)
    at load (https://deno.land/[email protected]/dotenv/mod.ts:295:5)
    at eventLoopTick (ext:core/01_core.js:168:7)
    at async file:///src/env.ts:3:1

Please sign in to comment.