@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/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/images")) {
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, cache) {
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 KV first, D1 on miss.
818
+ * Read current slot info from D1.
820
819
  */
821
820
  async getSlots(slug) {
822
- const cached = await this.cache.get(this.kvKey(slug));
823
- if (cached) return cached;
824
- return this.refreshFromDb(slug);
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 and update KV.
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.refreshFromDb(slug);
837
+ return this.getSlots(slug);
833
838
  }
834
839
  /**
835
- * Atomically decrement registered_slots in D1 and update KV.
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.refreshFromDb(slug);
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 = 300;
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
- return c.json({ data: serializeEvent(event) });
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 cache = new CacheService(c.env.KV);
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=5, stale-while-revalidate=5");
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, cache);
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 = 600;
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
- return c.json({ data });
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, cache);
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, cache);
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
- "/images/*",
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/images/")[1];
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
- "/images",
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 = `${url.pathname.replace(/\/$/, "")}/${key}`;
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
- return c.json({ data });
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.260605.2" ,
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)};</script>`;
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)};</script>`;
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.mapap
3219
+ //# sourceMappingURL=index.js.map