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