@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.js
CHANGED
|
@@ -21,6 +21,8 @@ var LeapifyError = class extends Error {
|
|
|
21
21
|
this.code = code;
|
|
22
22
|
this.name = "LeapifyError";
|
|
23
23
|
}
|
|
24
|
+
statusCode;
|
|
25
|
+
code;
|
|
24
26
|
};
|
|
25
27
|
var unauthorized = (message = "Unauthorized") => new LeapifyError(401, "UNAUTHORIZED", message);
|
|
26
28
|
var domainRestricted = () => new LeapifyError(
|
|
@@ -315,7 +317,7 @@ function createCorsMiddleware(allowedOrigins) {
|
|
|
315
317
|
);
|
|
316
318
|
}
|
|
317
319
|
}
|
|
318
|
-
if (c.req.path.startsWith("/api/uploads
|
|
320
|
+
if (c.req.path.startsWith("/api/uploads")) {
|
|
319
321
|
c.header("Access-Control-Allow-Origin", "*");
|
|
320
322
|
c.header("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
321
323
|
if (c.req.method === "OPTIONS") {
|
|
@@ -767,6 +769,7 @@ var CacheService = class {
|
|
|
767
769
|
constructor(kv) {
|
|
768
770
|
this.kv = kv;
|
|
769
771
|
}
|
|
772
|
+
kv;
|
|
770
773
|
async get(key) {
|
|
771
774
|
return this.kv.get(key, "json");
|
|
772
775
|
}
|
|
@@ -806,71 +809,48 @@ var CacheService = class {
|
|
|
806
809
|
return `"${hashArray.map((b) => b.toString(16).padStart(2, "0")).join("")}"`;
|
|
807
810
|
}
|
|
808
811
|
};
|
|
809
|
-
var SLOT_KV_PREFIX = "slots:";
|
|
810
812
|
var SlotsService = class {
|
|
811
|
-
constructor(db
|
|
813
|
+
constructor(db) {
|
|
812
814
|
this.db = db;
|
|
813
|
-
this.cache = cache;
|
|
814
|
-
}
|
|
815
|
-
kvKey(slug) {
|
|
816
|
-
return `${SLOT_KV_PREFIX}${slug}`;
|
|
817
815
|
}
|
|
816
|
+
db;
|
|
818
817
|
/**
|
|
819
|
-
* Read current slot info
|
|
818
|
+
* Read current slot info from D1.
|
|
820
819
|
*/
|
|
821
820
|
async getSlots(slug) {
|
|
822
|
-
const
|
|
823
|
-
|
|
824
|
-
|
|
821
|
+
const event = await this.db.query.events.findFirst({
|
|
822
|
+
where: eq(events.slug, slug),
|
|
823
|
+
columns: { maxSlots: true, registeredSlots: true }
|
|
824
|
+
});
|
|
825
|
+
if (!event) return null;
|
|
826
|
+
return {
|
|
827
|
+
total: event.maxSlots,
|
|
828
|
+
registered: event.registeredSlots
|
|
829
|
+
};
|
|
825
830
|
}
|
|
826
831
|
/**
|
|
827
|
-
* Atomically increment registered_slots in D1
|
|
832
|
+
* Atomically increment registered_slots in D1.
|
|
828
833
|
* Called by the Google Forms Watch webhook handler.
|
|
829
834
|
*/
|
|
830
835
|
async increment(slug) {
|
|
831
836
|
await this.db.update(events).set({ registeredSlots: sql`${events.registeredSlots} + 1` }).where(eq(events.slug, slug));
|
|
832
|
-
return this.
|
|
837
|
+
return this.getSlots(slug);
|
|
833
838
|
}
|
|
834
839
|
/**
|
|
835
|
-
* Atomically decrement registered_slots in D1
|
|
840
|
+
* Atomically decrement registered_slots in D1.
|
|
836
841
|
* Used during reconciliation drift correction (not from user actions).
|
|
837
842
|
*/
|
|
838
843
|
async decrement(slug) {
|
|
839
844
|
await this.db.update(events).set({
|
|
840
845
|
registeredSlots: sql`MAX(0, ${events.registeredSlots} - 1)`
|
|
841
846
|
}).where(eq(events.slug, slug));
|
|
842
|
-
return this.
|
|
847
|
+
return this.getSlots(slug);
|
|
843
848
|
}
|
|
844
849
|
/**
|
|
845
850
|
* Set registered_slots to a specific value (used by reconciliation cron).
|
|
846
851
|
*/
|
|
847
852
|
async correctCount(slug, actualCount) {
|
|
848
853
|
await this.db.update(events).set({ registeredSlots: actualCount }).where(eq(events.slug, slug));
|
|
849
|
-
await this.invalidate(slug);
|
|
850
|
-
}
|
|
851
|
-
/**
|
|
852
|
-
* Read from D1, write to KV, and return slot info.
|
|
853
|
-
*/
|
|
854
|
-
async refreshFromDb(slug) {
|
|
855
|
-
const event = await this.db.query.events.findFirst({
|
|
856
|
-
where: eq(events.slug, slug),
|
|
857
|
-
columns: { maxSlots: true, registeredSlots: true }
|
|
858
|
-
});
|
|
859
|
-
if (!event) return null;
|
|
860
|
-
const info = {
|
|
861
|
-
total: event.maxSlots,
|
|
862
|
-
registered: event.registeredSlots,
|
|
863
|
-
available: Math.max(0, event.maxSlots - event.registeredSlots),
|
|
864
|
-
isFull: event.registeredSlots >= event.maxSlots
|
|
865
|
-
};
|
|
866
|
-
await this.cache.set(this.kvKey(slug), info);
|
|
867
|
-
return info;
|
|
868
|
-
}
|
|
869
|
-
/**
|
|
870
|
-
* Invalidate the KV cache for a specific event.
|
|
871
|
-
*/
|
|
872
|
-
async invalidate(slug) {
|
|
873
|
-
await this.cache.del(this.kvKey(slug));
|
|
874
854
|
}
|
|
875
855
|
};
|
|
876
856
|
|
|
@@ -1107,7 +1087,7 @@ var adminEventsRateLimit = createRateLimitMiddleware({
|
|
|
1107
1087
|
// src/routes/classes.ts
|
|
1108
1088
|
var EVENTS_LIST_KV_KEY = "events:list";
|
|
1109
1089
|
var EVENTS_ETAG_KV_KEY = "events:etag";
|
|
1110
|
-
var EVENTS_LIST_TTL =
|
|
1090
|
+
var EVENTS_LIST_TTL = 3600;
|
|
1111
1091
|
var createEventSchema = z.object({
|
|
1112
1092
|
themeId: z.string().min(1),
|
|
1113
1093
|
organizationId: z.string().optional(),
|
|
@@ -1246,17 +1226,12 @@ classesRoute.get(
|
|
|
1246
1226
|
registrationClosesAt: true,
|
|
1247
1227
|
isSpotlight: true,
|
|
1248
1228
|
maxSlots: true,
|
|
1249
|
-
registeredSlots: true,
|
|
1250
1229
|
gformsUrl: true
|
|
1251
1230
|
}
|
|
1252
1231
|
}),
|
|
1253
1232
|
EVENTS_LIST_TTL
|
|
1254
1233
|
);
|
|
1255
1234
|
c.header("ETag", etag);
|
|
1256
|
-
c.header(
|
|
1257
|
-
"Cache-Control",
|
|
1258
|
-
"public, max-age=604800, stale-while-revalidate=86400"
|
|
1259
|
-
);
|
|
1260
1235
|
return c.json({ data: serializeEvents(data) });
|
|
1261
1236
|
}
|
|
1262
1237
|
);
|
|
@@ -1280,7 +1255,8 @@ classesRoute.get(
|
|
|
1280
1255
|
}
|
|
1281
1256
|
});
|
|
1282
1257
|
if (!event) throw notFound("Event");
|
|
1283
|
-
|
|
1258
|
+
const { registeredSlots: _, ...rest } = event;
|
|
1259
|
+
return c.json({ data: serializeEvent(rest) });
|
|
1284
1260
|
}
|
|
1285
1261
|
);
|
|
1286
1262
|
classesRoute.get(
|
|
@@ -1297,11 +1273,10 @@ classesRoute.get(
|
|
|
1297
1273
|
async (c) => {
|
|
1298
1274
|
const { slug } = c.req.param();
|
|
1299
1275
|
const db = createDb(c.env.DB);
|
|
1300
|
-
const
|
|
1301
|
-
const slotsService = new SlotsService(db, cache);
|
|
1276
|
+
const slotsService = new SlotsService(db);
|
|
1302
1277
|
const info = await slotsService.getSlots(slug);
|
|
1303
1278
|
if (!info) throw notFound("Event");
|
|
1304
|
-
c.header("Cache-Control", "public, max-age=
|
|
1279
|
+
c.header("Cache-Control", "public, max-age=3, stale-while-revalidate=3");
|
|
1305
1280
|
return c.json({ data: info });
|
|
1306
1281
|
}
|
|
1307
1282
|
);
|
|
@@ -1322,9 +1297,8 @@ classesRoute.post(
|
|
|
1322
1297
|
async (c) => {
|
|
1323
1298
|
const { slug } = c.req.param();
|
|
1324
1299
|
const db = createDb(c.env.DB);
|
|
1325
|
-
const cache = new CacheService(c.env.KV);
|
|
1326
1300
|
const gforms = new GFormsService(c.env.GFORMS_SERVICE_ACCOUNT_JSON);
|
|
1327
|
-
const slots = new SlotsService(db
|
|
1301
|
+
const slots = new SlotsService(db);
|
|
1328
1302
|
const event = await db.query.events.findFirst({
|
|
1329
1303
|
where: eq(events.slug, slug),
|
|
1330
1304
|
columns: { gformsId: true }
|
|
@@ -1666,7 +1640,7 @@ siteConfigRoute.patch(
|
|
|
1666
1640
|
}
|
|
1667
1641
|
);
|
|
1668
1642
|
var FAQS_KV_KEY = "faqs:active";
|
|
1669
|
-
var FAQS_TTL =
|
|
1643
|
+
var FAQS_TTL = 86400;
|
|
1670
1644
|
var faqSchema = z.object({
|
|
1671
1645
|
question: z.string().min(1),
|
|
1672
1646
|
answer: z.string().min(1),
|
|
@@ -1691,7 +1665,8 @@ faqsRoute.get(
|
|
|
1691
1665
|
}),
|
|
1692
1666
|
FAQS_TTL
|
|
1693
1667
|
);
|
|
1694
|
-
|
|
1668
|
+
const serialized = data.map(({ sortOrder, ...rest }) => rest);
|
|
1669
|
+
return c.json({ data: serialized });
|
|
1695
1670
|
}
|
|
1696
1671
|
);
|
|
1697
1672
|
faqsRoute.post(
|
|
@@ -1798,7 +1773,6 @@ gformsWebhookRoute.post(
|
|
|
1798
1773
|
const { formId } = payload;
|
|
1799
1774
|
if (!formId) return c.json({ error: "Missing formId" }, 400);
|
|
1800
1775
|
const db = createDb(c.env.DB);
|
|
1801
|
-
const cache = new CacheService(c.env.KV);
|
|
1802
1776
|
const event = await db.query.events.findFirst({
|
|
1803
1777
|
where: eq(events.gformsId, formId),
|
|
1804
1778
|
columns: { slug: true, maxSlots: true, registeredSlots: true }
|
|
@@ -1807,7 +1781,7 @@ gformsWebhookRoute.post(
|
|
|
1807
1781
|
console.warn(`[gforms-webhook] Unknown formId: ${formId}`);
|
|
1808
1782
|
return c.json({ ok: true });
|
|
1809
1783
|
}
|
|
1810
|
-
const slotsService = new SlotsService(db
|
|
1784
|
+
const slotsService = new SlotsService(db);
|
|
1811
1785
|
const updated = await slotsService.increment(event.slug);
|
|
1812
1786
|
console.log(
|
|
1813
1787
|
`[gforms-webhook] Incremented "${event.slug}": ${updated?.registered}/${updated?.total}`
|
|
@@ -1844,7 +1818,7 @@ async function reconcileSlots(env) {
|
|
|
1844
1818
|
const db = createDb(env.DB);
|
|
1845
1819
|
const cache = new CacheService(env.KV);
|
|
1846
1820
|
const gforms = new GFormsService(env.GFORMS_SERVICE_ACCOUNT_JSON);
|
|
1847
|
-
const slots = new SlotsService(db
|
|
1821
|
+
const slots = new SlotsService(db);
|
|
1848
1822
|
const lock = await cache.get(LOCK_KEY);
|
|
1849
1823
|
if (lock) {
|
|
1850
1824
|
console.log("[reconcile-slots] Lock held, skipping.");
|
|
@@ -2078,7 +2052,7 @@ var ALLOWED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
|
2078
2052
|
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
2079
2053
|
var uploadsRoute = new Hono();
|
|
2080
2054
|
uploadsRoute.get(
|
|
2081
|
-
"
|
|
2055
|
+
"/*",
|
|
2082
2056
|
describeRoute({
|
|
2083
2057
|
tags: ["Uploads"],
|
|
2084
2058
|
summary: "Serve an image from R2 storage",
|
|
@@ -2092,7 +2066,7 @@ uploadsRoute.get(
|
|
|
2092
2066
|
if (!bucket) {
|
|
2093
2067
|
throw serviceUnavailable("File storage (R2) is not configured.");
|
|
2094
2068
|
}
|
|
2095
|
-
const path = c.req.path.split("/uploads/
|
|
2069
|
+
const path = c.req.path.split("/uploads/")[1];
|
|
2096
2070
|
if (!path) throw notFound("Image");
|
|
2097
2071
|
const object = await bucket.get(path);
|
|
2098
2072
|
if (!object) throw notFound("Image");
|
|
@@ -2111,7 +2085,7 @@ uploadsRoute.get(
|
|
|
2111
2085
|
}
|
|
2112
2086
|
);
|
|
2113
2087
|
uploadsRoute.post(
|
|
2114
|
-
"/
|
|
2088
|
+
"/",
|
|
2115
2089
|
describeRoute({
|
|
2116
2090
|
tags: ["Uploads"],
|
|
2117
2091
|
summary: "Upload an image to R2 storage (admin)",
|
|
@@ -2158,7 +2132,7 @@ uploadsRoute.post(
|
|
|
2158
2132
|
customMetadata: { uploadedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
2159
2133
|
});
|
|
2160
2134
|
const url = new URL(c.req.url);
|
|
2161
|
-
url.pathname =
|
|
2135
|
+
url.pathname = `/${key}`;
|
|
2162
2136
|
url.search = "";
|
|
2163
2137
|
return c.json(
|
|
2164
2138
|
{
|
|
@@ -2215,7 +2189,8 @@ themesRoute.get(
|
|
|
2215
2189
|
async (c) => {
|
|
2216
2190
|
const db = createDb(c.env.DB);
|
|
2217
2191
|
const data = await db.select().from(themes).orderBy(asc(themes.sortOrder), asc(themes.createdAt));
|
|
2218
|
-
|
|
2192
|
+
const serialized = data.map(({ sortOrder, ...rest }) => rest);
|
|
2193
|
+
return c.json({ data: serialized });
|
|
2219
2194
|
}
|
|
2220
2195
|
);
|
|
2221
2196
|
themesRoute.post(
|
|
@@ -2462,7 +2437,7 @@ function createApp(options = {}) {
|
|
|
2462
2437
|
documentation: {
|
|
2463
2438
|
info: {
|
|
2464
2439
|
title: "Leapify API",
|
|
2465
|
-
version: "0.
|
|
2440
|
+
version: "0.260608.1" ,
|
|
2466
2441
|
description: "DLSU CSO LEAP backend API"
|
|
2467
2442
|
},
|
|
2468
2443
|
openapi: "3.1.0"
|
|
@@ -2580,6 +2555,7 @@ var SesError = class extends Error {
|
|
|
2580
2555
|
this.status = status;
|
|
2581
2556
|
this.name = "SesError";
|
|
2582
2557
|
}
|
|
2558
|
+
status;
|
|
2583
2559
|
/**
|
|
2584
2560
|
* True for errors that are permanent (not worth retrying via SES again).
|
|
2585
2561
|
* 400 BadRequest, 403 Forbidden, 404 NotFound → non-retryable.
|
|
@@ -3112,7 +3088,7 @@ function defaultGetRuntimeConfig(env) {
|
|
|
3112
3088
|
};
|
|
3113
3089
|
}
|
|
3114
3090
|
function injectConfig(html, config) {
|
|
3115
|
-
const configScript = `<script>window.__CONFIG__=${JSON.stringify(config)}
|
|
3091
|
+
const configScript = `<script>window.__CONFIG__=${JSON.stringify(config)};<\/script>`;
|
|
3116
3092
|
return html.replace("</head>", `${configScript}</head>`);
|
|
3117
3093
|
}
|
|
3118
3094
|
function createWorkerHandler(options) {
|
|
@@ -3234,10 +3210,10 @@ function getRuntimeConfig(env) {
|
|
|
3234
3210
|
};
|
|
3235
3211
|
}
|
|
3236
3212
|
function injectConfig2(html, config) {
|
|
3237
|
-
const configScript = `<script>window.__CONFIG__=${JSON.stringify(config)}
|
|
3213
|
+
const configScript = `<script>window.__CONFIG__=${JSON.stringify(config)};<\/script>`;
|
|
3238
3214
|
return html.replace("</head>", `${configScript}</head>`);
|
|
3239
3215
|
}
|
|
3240
3216
|
|
|
3241
3217
|
export { authAccount, authSession, authUser, authVerification, bookmarks, bookmarksRelations, createDb, createLeapify, createQueueHandler, createWorkerHandler, ensureDatabase, events, eventsRelations, faqs, getRuntimeConfig, injectConfig2 as injectConfig, organizations, organizationsRelations, siteConfig, themes, themesRelations, users };
|
|
3242
3218
|
//# sourceMappingURL=index.js.map
|
|
3243
|
-
//# sourceMappingURL=index.js.
|
|
3219
|
+
//# sourceMappingURL=index.js.map
|