@access-dlsu/leapify 0.260605.2 → 0.260608.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.cjs +4 -2
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +4 -2
- package/dist/client/index.js.map +1 -1
- package/dist/index.cjs +42 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +42 -66
- package/dist/index.js.map +1 -1
- package/dist/routes/internal/gforms-webhook.d.ts.map +1 -1
- package/dist/services/slots.d.ts +6 -22
- package/dist/services/slots.d.ts.map +1 -1
- package/dist/worker.js +39 -63
- package/dist/worker.js.map +1 -1
- package/package.json +10 -11
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,8 @@ var LeapifyError = class extends Error {
|
|
|
23
23
|
this.code = code;
|
|
24
24
|
this.name = "LeapifyError";
|
|
25
25
|
}
|
|
26
|
+
statusCode;
|
|
27
|
+
code;
|
|
26
28
|
};
|
|
27
29
|
var unauthorized = (message = "Unauthorized") => new LeapifyError(401, "UNAUTHORIZED", message);
|
|
28
30
|
var domainRestricted = () => new LeapifyError(
|
|
@@ -317,7 +319,7 @@ function createCorsMiddleware(allowedOrigins) {
|
|
|
317
319
|
);
|
|
318
320
|
}
|
|
319
321
|
}
|
|
320
|
-
if (c.req.path.startsWith("/api/uploads
|
|
322
|
+
if (c.req.path.startsWith("/api/uploads")) {
|
|
321
323
|
c.header("Access-Control-Allow-Origin", "*");
|
|
322
324
|
c.header("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
323
325
|
if (c.req.method === "OPTIONS") {
|
|
@@ -769,6 +771,7 @@ var CacheService = class {
|
|
|
769
771
|
constructor(kv) {
|
|
770
772
|
this.kv = kv;
|
|
771
773
|
}
|
|
774
|
+
kv;
|
|
772
775
|
async get(key) {
|
|
773
776
|
return this.kv.get(key, "json");
|
|
774
777
|
}
|
|
@@ -808,71 +811,48 @@ var CacheService = class {
|
|
|
808
811
|
return `"${hashArray.map((b) => b.toString(16).padStart(2, "0")).join("")}"`;
|
|
809
812
|
}
|
|
810
813
|
};
|
|
811
|
-
var SLOT_KV_PREFIX = "slots:";
|
|
812
814
|
var SlotsService = class {
|
|
813
|
-
constructor(db
|
|
815
|
+
constructor(db) {
|
|
814
816
|
this.db = db;
|
|
815
|
-
this.cache = cache;
|
|
816
|
-
}
|
|
817
|
-
kvKey(slug) {
|
|
818
|
-
return `${SLOT_KV_PREFIX}${slug}`;
|
|
819
817
|
}
|
|
818
|
+
db;
|
|
820
819
|
/**
|
|
821
|
-
* Read current slot info
|
|
820
|
+
* Read current slot info from D1.
|
|
822
821
|
*/
|
|
823
822
|
async getSlots(slug) {
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
823
|
+
const event = await this.db.query.events.findFirst({
|
|
824
|
+
where: drizzleOrm.eq(events.slug, slug),
|
|
825
|
+
columns: { maxSlots: true, registeredSlots: true }
|
|
826
|
+
});
|
|
827
|
+
if (!event) return null;
|
|
828
|
+
return {
|
|
829
|
+
total: event.maxSlots,
|
|
830
|
+
registered: event.registeredSlots
|
|
831
|
+
};
|
|
827
832
|
}
|
|
828
833
|
/**
|
|
829
|
-
* Atomically increment registered_slots in D1
|
|
834
|
+
* Atomically increment registered_slots in D1.
|
|
830
835
|
* Called by the Google Forms Watch webhook handler.
|
|
831
836
|
*/
|
|
832
837
|
async increment(slug) {
|
|
833
838
|
await this.db.update(events).set({ registeredSlots: drizzleOrm.sql`${events.registeredSlots} + 1` }).where(drizzleOrm.eq(events.slug, slug));
|
|
834
|
-
return this.
|
|
839
|
+
return this.getSlots(slug);
|
|
835
840
|
}
|
|
836
841
|
/**
|
|
837
|
-
* Atomically decrement registered_slots in D1
|
|
842
|
+
* Atomically decrement registered_slots in D1.
|
|
838
843
|
* Used during reconciliation drift correction (not from user actions).
|
|
839
844
|
*/
|
|
840
845
|
async decrement(slug) {
|
|
841
846
|
await this.db.update(events).set({
|
|
842
847
|
registeredSlots: drizzleOrm.sql`MAX(0, ${events.registeredSlots} - 1)`
|
|
843
848
|
}).where(drizzleOrm.eq(events.slug, slug));
|
|
844
|
-
return this.
|
|
849
|
+
return this.getSlots(slug);
|
|
845
850
|
}
|
|
846
851
|
/**
|
|
847
852
|
* Set registered_slots to a specific value (used by reconciliation cron).
|
|
848
853
|
*/
|
|
849
854
|
async correctCount(slug, actualCount) {
|
|
850
855
|
await this.db.update(events).set({ registeredSlots: actualCount }).where(drizzleOrm.eq(events.slug, slug));
|
|
851
|
-
await this.invalidate(slug);
|
|
852
|
-
}
|
|
853
|
-
/**
|
|
854
|
-
* Read from D1, write to KV, and return slot info.
|
|
855
|
-
*/
|
|
856
|
-
async refreshFromDb(slug) {
|
|
857
|
-
const event = await this.db.query.events.findFirst({
|
|
858
|
-
where: drizzleOrm.eq(events.slug, slug),
|
|
859
|
-
columns: { maxSlots: true, registeredSlots: true }
|
|
860
|
-
});
|
|
861
|
-
if (!event) return null;
|
|
862
|
-
const info = {
|
|
863
|
-
total: event.maxSlots,
|
|
864
|
-
registered: event.registeredSlots,
|
|
865
|
-
available: Math.max(0, event.maxSlots - event.registeredSlots),
|
|
866
|
-
isFull: event.registeredSlots >= event.maxSlots
|
|
867
|
-
};
|
|
868
|
-
await this.cache.set(this.kvKey(slug), info);
|
|
869
|
-
return info;
|
|
870
|
-
}
|
|
871
|
-
/**
|
|
872
|
-
* Invalidate the KV cache for a specific event.
|
|
873
|
-
*/
|
|
874
|
-
async invalidate(slug) {
|
|
875
|
-
await this.cache.del(this.kvKey(slug));
|
|
876
856
|
}
|
|
877
857
|
};
|
|
878
858
|
|
|
@@ -1109,7 +1089,7 @@ var adminEventsRateLimit = createRateLimitMiddleware({
|
|
|
1109
1089
|
// src/routes/classes.ts
|
|
1110
1090
|
var EVENTS_LIST_KV_KEY = "events:list";
|
|
1111
1091
|
var EVENTS_ETAG_KV_KEY = "events:etag";
|
|
1112
|
-
var EVENTS_LIST_TTL =
|
|
1092
|
+
var EVENTS_LIST_TTL = 3600;
|
|
1113
1093
|
var createEventSchema = zod.z.object({
|
|
1114
1094
|
themeId: zod.z.string().min(1),
|
|
1115
1095
|
organizationId: zod.z.string().optional(),
|
|
@@ -1248,17 +1228,12 @@ classesRoute.get(
|
|
|
1248
1228
|
registrationClosesAt: true,
|
|
1249
1229
|
isSpotlight: true,
|
|
1250
1230
|
maxSlots: true,
|
|
1251
|
-
registeredSlots: true,
|
|
1252
1231
|
gformsUrl: true
|
|
1253
1232
|
}
|
|
1254
1233
|
}),
|
|
1255
1234
|
EVENTS_LIST_TTL
|
|
1256
1235
|
);
|
|
1257
1236
|
c.header("ETag", etag);
|
|
1258
|
-
c.header(
|
|
1259
|
-
"Cache-Control",
|
|
1260
|
-
"public, max-age=604800, stale-while-revalidate=86400"
|
|
1261
|
-
);
|
|
1262
1237
|
return c.json({ data: serializeEvents(data) });
|
|
1263
1238
|
}
|
|
1264
1239
|
);
|
|
@@ -1282,7 +1257,8 @@ classesRoute.get(
|
|
|
1282
1257
|
}
|
|
1283
1258
|
});
|
|
1284
1259
|
if (!event) throw notFound("Event");
|
|
1285
|
-
|
|
1260
|
+
const { registeredSlots: _, ...rest } = event;
|
|
1261
|
+
return c.json({ data: serializeEvent(rest) });
|
|
1286
1262
|
}
|
|
1287
1263
|
);
|
|
1288
1264
|
classesRoute.get(
|
|
@@ -1299,11 +1275,10 @@ classesRoute.get(
|
|
|
1299
1275
|
async (c) => {
|
|
1300
1276
|
const { slug } = c.req.param();
|
|
1301
1277
|
const db = createDb(c.env.DB);
|
|
1302
|
-
const
|
|
1303
|
-
const slotsService = new SlotsService(db, cache);
|
|
1278
|
+
const slotsService = new SlotsService(db);
|
|
1304
1279
|
const info = await slotsService.getSlots(slug);
|
|
1305
1280
|
if (!info) throw notFound("Event");
|
|
1306
|
-
c.header("Cache-Control", "public, max-age=
|
|
1281
|
+
c.header("Cache-Control", "public, max-age=3, stale-while-revalidate=3");
|
|
1307
1282
|
return c.json({ data: info });
|
|
1308
1283
|
}
|
|
1309
1284
|
);
|
|
@@ -1324,9 +1299,8 @@ classesRoute.post(
|
|
|
1324
1299
|
async (c) => {
|
|
1325
1300
|
const { slug } = c.req.param();
|
|
1326
1301
|
const db = createDb(c.env.DB);
|
|
1327
|
-
const cache = new CacheService(c.env.KV);
|
|
1328
1302
|
const gforms = new GFormsService(c.env.GFORMS_SERVICE_ACCOUNT_JSON);
|
|
1329
|
-
const slots = new SlotsService(db
|
|
1303
|
+
const slots = new SlotsService(db);
|
|
1330
1304
|
const event = await db.query.events.findFirst({
|
|
1331
1305
|
where: drizzleOrm.eq(events.slug, slug),
|
|
1332
1306
|
columns: { gformsId: true }
|
|
@@ -1668,7 +1642,7 @@ siteConfigRoute.patch(
|
|
|
1668
1642
|
}
|
|
1669
1643
|
);
|
|
1670
1644
|
var FAQS_KV_KEY = "faqs:active";
|
|
1671
|
-
var FAQS_TTL =
|
|
1645
|
+
var FAQS_TTL = 86400;
|
|
1672
1646
|
var faqSchema = zod.z.object({
|
|
1673
1647
|
question: zod.z.string().min(1),
|
|
1674
1648
|
answer: zod.z.string().min(1),
|
|
@@ -1693,7 +1667,8 @@ faqsRoute.get(
|
|
|
1693
1667
|
}),
|
|
1694
1668
|
FAQS_TTL
|
|
1695
1669
|
);
|
|
1696
|
-
|
|
1670
|
+
const serialized = data.map(({ sortOrder, ...rest }) => rest);
|
|
1671
|
+
return c.json({ data: serialized });
|
|
1697
1672
|
}
|
|
1698
1673
|
);
|
|
1699
1674
|
faqsRoute.post(
|
|
@@ -1800,7 +1775,6 @@ gformsWebhookRoute.post(
|
|
|
1800
1775
|
const { formId } = payload;
|
|
1801
1776
|
if (!formId) return c.json({ error: "Missing formId" }, 400);
|
|
1802
1777
|
const db = createDb(c.env.DB);
|
|
1803
|
-
const cache = new CacheService(c.env.KV);
|
|
1804
1778
|
const event = await db.query.events.findFirst({
|
|
1805
1779
|
where: drizzleOrm.eq(events.gformsId, formId),
|
|
1806
1780
|
columns: { slug: true, maxSlots: true, registeredSlots: true }
|
|
@@ -1809,7 +1783,7 @@ gformsWebhookRoute.post(
|
|
|
1809
1783
|
console.warn(`[gforms-webhook] Unknown formId: ${formId}`);
|
|
1810
1784
|
return c.json({ ok: true });
|
|
1811
1785
|
}
|
|
1812
|
-
const slotsService = new SlotsService(db
|
|
1786
|
+
const slotsService = new SlotsService(db);
|
|
1813
1787
|
const updated = await slotsService.increment(event.slug);
|
|
1814
1788
|
console.log(
|
|
1815
1789
|
`[gforms-webhook] Incremented "${event.slug}": ${updated?.registered}/${updated?.total}`
|
|
@@ -1846,7 +1820,7 @@ async function reconcileSlots(env) {
|
|
|
1846
1820
|
const db = createDb(env.DB);
|
|
1847
1821
|
const cache = new CacheService(env.KV);
|
|
1848
1822
|
const gforms = new GFormsService(env.GFORMS_SERVICE_ACCOUNT_JSON);
|
|
1849
|
-
const slots = new SlotsService(db
|
|
1823
|
+
const slots = new SlotsService(db);
|
|
1850
1824
|
const lock = await cache.get(LOCK_KEY);
|
|
1851
1825
|
if (lock) {
|
|
1852
1826
|
console.log("[reconcile-slots] Lock held, skipping.");
|
|
@@ -2080,7 +2054,7 @@ var ALLOWED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
|
2080
2054
|
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
2081
2055
|
var uploadsRoute = new hono.Hono();
|
|
2082
2056
|
uploadsRoute.get(
|
|
2083
|
-
"
|
|
2057
|
+
"/*",
|
|
2084
2058
|
honoOpenapi.describeRoute({
|
|
2085
2059
|
tags: ["Uploads"],
|
|
2086
2060
|
summary: "Serve an image from R2 storage",
|
|
@@ -2094,7 +2068,7 @@ uploadsRoute.get(
|
|
|
2094
2068
|
if (!bucket) {
|
|
2095
2069
|
throw serviceUnavailable("File storage (R2) is not configured.");
|
|
2096
2070
|
}
|
|
2097
|
-
const path = c.req.path.split("/uploads/
|
|
2071
|
+
const path = c.req.path.split("/uploads/")[1];
|
|
2098
2072
|
if (!path) throw notFound("Image");
|
|
2099
2073
|
const object = await bucket.get(path);
|
|
2100
2074
|
if (!object) throw notFound("Image");
|
|
@@ -2113,7 +2087,7 @@ uploadsRoute.get(
|
|
|
2113
2087
|
}
|
|
2114
2088
|
);
|
|
2115
2089
|
uploadsRoute.post(
|
|
2116
|
-
"/
|
|
2090
|
+
"/",
|
|
2117
2091
|
honoOpenapi.describeRoute({
|
|
2118
2092
|
tags: ["Uploads"],
|
|
2119
2093
|
summary: "Upload an image to R2 storage (admin)",
|
|
@@ -2160,7 +2134,7 @@ uploadsRoute.post(
|
|
|
2160
2134
|
customMetadata: { uploadedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
2161
2135
|
});
|
|
2162
2136
|
const url = new URL(c.req.url);
|
|
2163
|
-
url.pathname =
|
|
2137
|
+
url.pathname = `/${key}`;
|
|
2164
2138
|
url.search = "";
|
|
2165
2139
|
return c.json(
|
|
2166
2140
|
{
|
|
@@ -2217,7 +2191,8 @@ themesRoute.get(
|
|
|
2217
2191
|
async (c) => {
|
|
2218
2192
|
const db = createDb(c.env.DB);
|
|
2219
2193
|
const data = await db.select().from(themes).orderBy(drizzleOrm.asc(themes.sortOrder), drizzleOrm.asc(themes.createdAt));
|
|
2220
|
-
|
|
2194
|
+
const serialized = data.map(({ sortOrder, ...rest }) => rest);
|
|
2195
|
+
return c.json({ data: serialized });
|
|
2221
2196
|
}
|
|
2222
2197
|
);
|
|
2223
2198
|
themesRoute.post(
|
|
@@ -2464,7 +2439,7 @@ function createApp(options = {}) {
|
|
|
2464
2439
|
documentation: {
|
|
2465
2440
|
info: {
|
|
2466
2441
|
title: "Leapify API",
|
|
2467
|
-
version: "0.
|
|
2442
|
+
version: "0.260608.1" ,
|
|
2468
2443
|
description: "DLSU CSO LEAP backend API"
|
|
2469
2444
|
},
|
|
2470
2445
|
openapi: "3.1.0"
|
|
@@ -2582,6 +2557,7 @@ var SesError = class extends Error {
|
|
|
2582
2557
|
this.status = status;
|
|
2583
2558
|
this.name = "SesError";
|
|
2584
2559
|
}
|
|
2560
|
+
status;
|
|
2585
2561
|
/**
|
|
2586
2562
|
* True for errors that are permanent (not worth retrying via SES again).
|
|
2587
2563
|
* 400 BadRequest, 403 Forbidden, 404 NotFound → non-retryable.
|
|
@@ -3114,7 +3090,7 @@ function defaultGetRuntimeConfig(env) {
|
|
|
3114
3090
|
};
|
|
3115
3091
|
}
|
|
3116
3092
|
function injectConfig(html, config) {
|
|
3117
|
-
const configScript = `<script>window.__CONFIG__=${JSON.stringify(config)}
|
|
3093
|
+
const configScript = `<script>window.__CONFIG__=${JSON.stringify(config)};</script>`;
|
|
3118
3094
|
return html.replace("</head>", `${configScript}</head>`);
|
|
3119
3095
|
}
|
|
3120
3096
|
function createWorkerHandler(options) {
|
|
@@ -3236,7 +3212,7 @@ function getRuntimeConfig(env) {
|
|
|
3236
3212
|
};
|
|
3237
3213
|
}
|
|
3238
3214
|
function injectConfig2(html, config) {
|
|
3239
|
-
const configScript = `<script>window.__CONFIG__=${JSON.stringify(config)}
|
|
3215
|
+
const configScript = `<script>window.__CONFIG__=${JSON.stringify(config)};</script>`;
|
|
3240
3216
|
return html.replace("</head>", `${configScript}</head>`);
|
|
3241
3217
|
}
|
|
3242
3218
|
|
|
@@ -3263,4 +3239,4 @@ exports.themes = themes;
|
|
|
3263
3239
|
exports.themesRelations = themesRelations;
|
|
3264
3240
|
exports.users = users;
|
|
3265
3241
|
//# sourceMappingURL=index.cjs.map
|
|
3266
|
-
//# sourceMappingURL=index.cjs.
|
|
3242
|
+
//# sourceMappingURL=index.cjs.mapap
|