Skip to content

Commit d6bb126

Browse files
authored
Merge pull request #21 from mojisdev/improve-version-redirect
improve version redirect
2 parents d29ba65 + 097b3ee commit d6bb126

12 files changed

+229
-173
lines changed

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
},
2121
"dependencies": {
2222
"@hono/zod-openapi": "^0.19.2",
23-
"@mojis/internal-utils": "^0.0.5",
23+
"@mojis/internal-utils": "^0.0.6",
2424
"@scalar/hono-api-reference": "^0.7.2",
2525
"hono": "^4.7.5",
2626
"zod": "^3.24.2"
@@ -29,7 +29,7 @@
2929
"@cloudflare/vitest-pool-workers": "^0.8.3",
3030
"@luxass/eslint-config": "^4.17.1",
3131
"@stoplight/spectral-cli": "^6.14.3",
32-
"eslint": "^9.22.0",
32+
"eslint": "^9.23.0",
3333
"eslint-plugin-format": "^1.0.1",
3434
"nanotar": "^0.2.0",
3535
"tsx": "^4.19.3",

pnpm-lock.yaml

+139-139
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/setup-dev/setup-worker.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const app = new Hono<{
99
}>();
1010

1111
app.post(
12-
"/upload",
12+
"/setup",
1313
async (c) => {
1414
const body = await c.req.parseBody();
1515

@@ -51,6 +51,21 @@ app.post(
5151
promises.push(c.env.EMOJI_DATA.put(normalizedEntryName, entry.text));
5252
}
5353

54+
// fetch and save the emojis.lock file
55+
const lockResponse = await fetch("https://raw.githubusercontent.com/mojisdev/emoji-data/refs/heads/main/emojis.lock", {
56+
headers: {
57+
"User-Agent": "luxass - (api.mojis.dev)",
58+
},
59+
});
60+
61+
if (!lockResponse.ok) {
62+
throw new HTTPException(500, {
63+
message: "Failed to fetch emojis.lock file",
64+
});
65+
}
66+
const lockData = await lockResponse.json();
67+
promises.push(c.env.EMOJI_DATA.put("versions.json", JSON.stringify(lockData)));
68+
5469
try {
5570
await Promise.all(promises);
5671
return c.json({

scripts/setup-dev/setup.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ async function run() {
1818
formData.append("file", blob, "emoji-data.tar.gz");
1919

2020
console.log("sending request to worker");
21-
const res = await worker.fetch("https://api.mojis.dev/upload", {
21+
const res = await worker.fetch("https://api.mojis.dev/setup", {
2222
method: "POST",
2323
// @ts-expect-error hmmm
2424
body: formData,

src/index.ts

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import type { ApiError, HonoContext } from "./types";
1+
import type { ApiError, HonoEnv } from "./types";
22
import { OpenAPIHono } from "@hono/zod-openapi";
3+
import { getAllEmojiVersions } from "@mojis/internal-utils/versions";
34
import { apiReference } from "@scalar/hono-api-reference";
45
import { env } from "hono/adapter";
56
import { HTTPException } from "hono/http-exception";
@@ -9,7 +10,7 @@ import { RANDOM_EMOJI_ROUTER } from "./routes/random-emoji";
910
import { V1_CATEGORIES_ROUTER } from "./routes/v1_categories";
1011
import { V1_VERSIONS_ROUTER } from "./routes/v1_versions";
1112

12-
const app = new OpenAPIHono<HonoContext>();
13+
const app = new OpenAPIHono<HonoEnv>();
1314

1415
app.route("/", V1_VERSIONS_ROUTER);
1516
app.route("/", V1_CATEGORIES_ROUTER);
@@ -101,4 +102,23 @@ app.notFound(async (c) => {
101102
} satisfies ApiError, 404);
102103
});
103104

104-
export default app;
105+
export default {
106+
fetch: app.fetch,
107+
scheduled: async (_, env) => {
108+
const versions = await getAllEmojiVersions();
109+
110+
const firstNonDraftVersion = versions.find((v) => !v.draft);
111+
112+
if (firstNonDraftVersion == null) {
113+
throw new Error("no non-draft version found");
114+
}
115+
116+
await env.EMOJI_DATA.put(`versions.json`, JSON.stringify({
117+
latest_version: firstNonDraftVersion.emoji_version,
118+
versions,
119+
}));
120+
121+
// eslint-disable-next-line no-console
122+
console.log("cron processed");
123+
},
124+
} satisfies ExportedHandler<HonoEnv["Bindings"]>;

src/middlewares/version.ts

+34-16
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,53 @@
1+
import type { Context } from "hono";
2+
import type { HonoEnv } from "../types";
13
import { createMiddleware } from "hono/factory";
2-
import { createError, getAvailableVersions } from "../utils";
4+
import { createError } from "../utils";
35

46
const DEFAULT_FALLBACK_VERSION = "15.1";
57

6-
export const versionMiddleware = createMiddleware(async (c, next) => {
8+
function redirectLatest(c: Context, path: string, latestVersion: string | null) {
9+
return c.redirect(path.replace("latest", latestVersion ?? DEFAULT_FALLBACK_VERSION));
10+
}
11+
12+
export const versionMiddleware = createMiddleware<HonoEnv>(async (c, next) => {
713
const version = c.req.param("version");
814
const fullPath = c.req.path;
915

1016
if (version == null) {
1117
return createError(c, 400, "missing version");
1218
}
1319

14-
// TODO(@luxass): cache the available versions for x amount of time.
15-
const availableVersions = await getAvailableVersions();
20+
try {
21+
const res = await c.env.EMOJI_DATA.get("versions.json");
1622

17-
if (version !== "latest" && !availableVersions.some((v) => v.emoji_version === version)) {
18-
return createError(c, 404, "version not found");
19-
}
23+
if (res == null) {
24+
return await next();
25+
}
26+
27+
const payload = await res.json<{
28+
latest_version: string;
29+
versions: {
30+
emoji_version: string;
31+
unicode_version: string;
32+
draft: boolean;
33+
}[];
34+
}>();
2035

21-
if (version === "latest") {
22-
// redirect to the latest version, that isn't a draft.
23-
const latestVersion = availableVersions.find((v) => !v.draft);
36+
const versions = payload.versions;
37+
const latestVersion = payload.latest_version;
2438

25-
if (latestVersion == null) {
26-
return createError(c, 404, "no versions available");
39+
if (version !== "latest" && !versions.some((v) => v.emoji_version === version)) {
40+
return createError(c, 404, "version not found");
2741
}
2842

29-
const path = fullPath.replace("latest", latestVersion.emoji_version ?? DEFAULT_FALLBACK_VERSION);
43+
if (version === "latest") {
44+
return redirectLatest(c, fullPath, latestVersion);
45+
}
3046

31-
return c.redirect(path);
32-
}
47+
return await next();
48+
} catch (err) {
49+
console.error(err);
3350

34-
await next();
51+
return redirectLatest(c, fullPath, null);
52+
}
3553
});

src/routes/gateway_github.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type { HonoContext } from "../types";
1+
import type { HonoEnv } from "../types";
22
import { OpenAPIHono } from "@hono/zod-openapi";
33
import { cache } from "../middlewares/cache";
44
import { createError } from "../utils";
55
import { GITHUB_EMOJIS_ROUTE } from "./gateway_github.openapi";
66

7-
export const GATEWAY_GITHUB_ROUTER = new OpenAPIHono<HonoContext>().basePath("/api/gateway/github");
7+
export const GATEWAY_GITHUB_ROUTER = new OpenAPIHono<HonoEnv>().basePath("/api/gateway/github");
88

99
GATEWAY_GITHUB_ROUTER.get("*", cache({
1010
cacheName: "github-emojis",

src/routes/random-emoji.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { HonoContext } from "../types";
1+
import type { HonoEnv } from "../types";
22
import { Hono } from "hono";
33
import { cache } from "../middlewares/cache";
44

5-
export const RANDOM_EMOJI_ROUTER = new Hono<HonoContext>();
5+
export const RANDOM_EMOJI_ROUTER = new Hono<HonoEnv>();
66

77
RANDOM_EMOJI_ROUTER.get(
88
"/random-emoji.png",

src/routes/v1_categories.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { HonoContext } from "../types";
1+
import type { HonoEnv } from "../types";
22
import { OpenAPIHono, z } from "@hono/zod-openapi";
33
import { HTTPException } from "hono/http-exception";
44
import { cache } from "../middlewares/cache";
@@ -7,7 +7,7 @@ import { EmojiCategorySchema } from "../schemas";
77
import { createError } from "../utils";
88
import { ALL_CATEGORIES_ROUTE, GET_CATEGORY_ROUTE } from "./v1_categories.openapi";
99

10-
export const V1_CATEGORIES_ROUTER = new OpenAPIHono<HonoContext>().basePath("/api/v1/categories/:version");
10+
export const V1_CATEGORIES_ROUTER = new OpenAPIHono<HonoEnv>().basePath("/api/v1/categories/:version");
1111

1212
V1_CATEGORIES_ROUTER.use(versionMiddleware);
1313

src/routes/v1_versions.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type { HonoContext } from "../types";
1+
import type { HonoEnv } from "../types";
22
import { OpenAPIHono } from "@hono/zod-openapi";
33
import { cache } from "../middlewares/cache";
44
import { createError, getAvailableVersions } from "../utils";
55
import { ALL_EMOJI_VERSIONS_ROUTE, DRAFT_EMOJI_VERSIONS_ROUTE, LATEST_EMOJI_VERSIONS_ROUTE } from "./v1_versions.openapi";
66

7-
export const V1_VERSIONS_ROUTER = new OpenAPIHono<HonoContext>().basePath("/api/v1/versions");
7+
export const V1_VERSIONS_ROUTER = new OpenAPIHono<HonoEnv>().basePath("/api/v1/versions");
88

99
V1_VERSIONS_ROUTER.get("*", cache({
1010
cacheName: "v1-versions",

src/types.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import type { z } from "zod";
22
import type { ApiErrorSchema, EMOJI_LOCK_SCHEMA, EmojiCategorySchema, EmojiVersionSchema } from "./schemas";
33

4-
export interface HonoContext {
4+
export interface HonoEnv {
55
Bindings: CloudflareBindings;
66
}
77

8-
export type HonoBindings = HonoContext["Bindings"];
9-
108
export type ApiError = z.infer<typeof ApiErrorSchema>;
119
export type EmojiLock = z.infer<typeof EMOJI_LOCK_SCHEMA>;
1210
export type EmojiVersion = z.infer<typeof EmojiVersionSchema>;

wrangler.jsonc

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
"bucket_name": "mojis"
2020
}
2121
],
22+
"triggers": {
23+
"crons": [
24+
"0 */12 * * *"
25+
]
26+
},
2227
"placement": { "mode": "smart" },
2328
"env": {
2429
"preview": {

0 commit comments

Comments
 (0)