@access-dlsu/leapify 0.260605.1 → 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(),
@@ -1237,6 +1217,7 @@ classesRoute.get(
1237
1217
  themeId: true,
1238
1218
  organizationId: true,
1239
1219
  title: true,
1220
+ description: true,
1240
1221
  venue: true,
1241
1222
  dateTime: true,
1242
1223
  price: true,
@@ -1247,19 +1228,12 @@ classesRoute.get(
1247
1228
  registrationClosesAt: true,
1248
1229
  isSpotlight: true,
1249
1230
  maxSlots: true,
1250
- registeredSlots: true,
1251
- gformsUrl: true,
1252
- gformsEditorUrl: true,
1253
- publishedAt: true
1231
+ gformsUrl: true
1254
1232
  }
1255
1233
  }),
1256
1234
  EVENTS_LIST_TTL
1257
1235
  );
1258
1236
  c.header("ETag", etag);
1259
- c.header(
1260
- "Cache-Control",
1261
- "public, max-age=604800, stale-while-revalidate=86400"
1262
- );
1263
1237
  return c.json({ data: serializeEvents(data) });
1264
1238
  }
1265
1239
  );
@@ -1283,7 +1257,8 @@ classesRoute.get(
1283
1257
  }
1284
1258
  });
1285
1259
  if (!event) throw notFound("Event");
1286
- return c.json({ data: serializeEvent(event) });
1260
+ const { registeredSlots: _, ...rest } = event;
1261
+ return c.json({ data: serializeEvent(rest) });
1287
1262
  }
1288
1263
  );
1289
1264
  classesRoute.get(
@@ -1300,11 +1275,10 @@ classesRoute.get(
1300
1275
  async (c) => {
1301
1276
  const { slug } = c.req.param();
1302
1277
  const db = createDb(c.env.DB);
1303
- const cache = new CacheService(c.env.KV);
1304
- const slotsService = new SlotsService(db, cache);
1278
+ const slotsService = new SlotsService(db);
1305
1279
  const info = await slotsService.getSlots(slug);
1306
1280
  if (!info) throw notFound("Event");
1307
- 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");
1308
1282
  return c.json({ data: info });
1309
1283
  }
1310
1284
  );
@@ -1325,9 +1299,8 @@ classesRoute.post(
1325
1299
  async (c) => {
1326
1300
  const { slug } = c.req.param();
1327
1301
  const db = createDb(c.env.DB);
1328
- const cache = new CacheService(c.env.KV);
1329
1302
  const gforms = new GFormsService(c.env.GFORMS_SERVICE_ACCOUNT_JSON);
1330
- const slots = new SlotsService(db, cache);
1303
+ const slots = new SlotsService(db);
1331
1304
  const event = await db.query.events.findFirst({
1332
1305
  where: drizzleOrm.eq(events.slug, slug),
1333
1306
  columns: { gformsId: true }
@@ -1669,7 +1642,7 @@ siteConfigRoute.patch(
1669
1642
  }
1670
1643
  );
1671
1644
  var FAQS_KV_KEY = "faqs:active";
1672
- var FAQS_TTL = 600;
1645
+ var FAQS_TTL = 86400;
1673
1646
  var faqSchema = zod.z.object({
1674
1647
  question: zod.z.string().min(1),
1675
1648
  answer: zod.z.string().min(1),
@@ -1694,7 +1667,8 @@ faqsRoute.get(
1694
1667
  }),
1695
1668
  FAQS_TTL
1696
1669
  );
1697
- return c.json({ data });
1670
+ const serialized = data.map(({ sortOrder, ...rest }) => rest);
1671
+ return c.json({ data: serialized });
1698
1672
  }
1699
1673
  );
1700
1674
  faqsRoute.post(
@@ -1801,7 +1775,6 @@ gformsWebhookRoute.post(
1801
1775
  const { formId } = payload;
1802
1776
  if (!formId) return c.json({ error: "Missing formId" }, 400);
1803
1777
  const db = createDb(c.env.DB);
1804
- const cache = new CacheService(c.env.KV);
1805
1778
  const event = await db.query.events.findFirst({
1806
1779
  where: drizzleOrm.eq(events.gformsId, formId),
1807
1780
  columns: { slug: true, maxSlots: true, registeredSlots: true }
@@ -1810,7 +1783,7 @@ gformsWebhookRoute.post(
1810
1783
  console.warn(`[gforms-webhook] Unknown formId: ${formId}`);
1811
1784
  return c.json({ ok: true });
1812
1785
  }
1813
- const slotsService = new SlotsService(db, cache);
1786
+ const slotsService = new SlotsService(db);
1814
1787
  const updated = await slotsService.increment(event.slug);
1815
1788
  console.log(
1816
1789
  `[gforms-webhook] Incremented "${event.slug}": ${updated?.registered}/${updated?.total}`
@@ -1847,7 +1820,7 @@ async function reconcileSlots(env) {
1847
1820
  const db = createDb(env.DB);
1848
1821
  const cache = new CacheService(env.KV);
1849
1822
  const gforms = new GFormsService(env.GFORMS_SERVICE_ACCOUNT_JSON);
1850
- const slots = new SlotsService(db, cache);
1823
+ const slots = new SlotsService(db);
1851
1824
  const lock = await cache.get(LOCK_KEY);
1852
1825
  if (lock) {
1853
1826
  console.log("[reconcile-slots] Lock held, skipping.");
@@ -2081,7 +2054,7 @@ var ALLOWED_MIME_TYPES = /* @__PURE__ */ new Set([
2081
2054
  var MAX_FILE_SIZE = 10 * 1024 * 1024;
2082
2055
  var uploadsRoute = new hono.Hono();
2083
2056
  uploadsRoute.get(
2084
- "/images/*",
2057
+ "/*",
2085
2058
  honoOpenapi.describeRoute({
2086
2059
  tags: ["Uploads"],
2087
2060
  summary: "Serve an image from R2 storage",
@@ -2095,7 +2068,7 @@ uploadsRoute.get(
2095
2068
  if (!bucket) {
2096
2069
  throw serviceUnavailable("File storage (R2) is not configured.");
2097
2070
  }
2098
- const path = c.req.path.split("/uploads/images/")[1];
2071
+ const path = c.req.path.split("/uploads/")[1];
2099
2072
  if (!path) throw notFound("Image");
2100
2073
  const object = await bucket.get(path);
2101
2074
  if (!object) throw notFound("Image");
@@ -2114,7 +2087,7 @@ uploadsRoute.get(
2114
2087
  }
2115
2088
  );
2116
2089
  uploadsRoute.post(
2117
- "/images",
2090
+ "/",
2118
2091
  honoOpenapi.describeRoute({
2119
2092
  tags: ["Uploads"],
2120
2093
  summary: "Upload an image to R2 storage (admin)",
@@ -2161,7 +2134,7 @@ uploadsRoute.post(
2161
2134
  customMetadata: { uploadedAt: (/* @__PURE__ */ new Date()).toISOString() }
2162
2135
  });
2163
2136
  const url = new URL(c.req.url);
2164
- url.pathname = `${url.pathname.replace(/\/$/, "")}/${key}`;
2137
+ url.pathname = `/${key}`;
2165
2138
  url.search = "";
2166
2139
  return c.json(
2167
2140
  {
@@ -2218,7 +2191,8 @@ themesRoute.get(
2218
2191
  async (c) => {
2219
2192
  const db = createDb(c.env.DB);
2220
2193
  const data = await db.select().from(themes).orderBy(drizzleOrm.asc(themes.sortOrder), drizzleOrm.asc(themes.createdAt));
2221
- return c.json({ data });
2194
+ const serialized = data.map(({ sortOrder, ...rest }) => rest);
2195
+ return c.json({ data: serialized });
2222
2196
  }
2223
2197
  );
2224
2198
  themesRoute.post(
@@ -2465,7 +2439,7 @@ function createApp(options = {}) {
2465
2439
  documentation: {
2466
2440
  info: {
2467
2441
  title: "Leapify API",
2468
- version: "0.260605.1" ,
2442
+ version: "0.260608.1" ,
2469
2443
  description: "DLSU CSO LEAP backend API"
2470
2444
  },
2471
2445
  openapi: "3.1.0"
@@ -2583,6 +2557,7 @@ var SesError = class extends Error {
2583
2557
  this.status = status;
2584
2558
  this.name = "SesError";
2585
2559
  }
2560
+ status;
2586
2561
  /**
2587
2562
  * True for errors that are permanent (not worth retrying via SES again).
2588
2563
  * 400 BadRequest, 403 Forbidden, 404 NotFound → non-retryable.
@@ -3115,7 +3090,7 @@ function defaultGetRuntimeConfig(env) {
3115
3090
  };
3116
3091
  }
3117
3092
  function injectConfig(html, config) {
3118
- const configScript = `<script>window.__CONFIG__=${JSON.stringify(config)};<\/script>`;
3093
+ const configScript = `<script>window.__CONFIG__=${JSON.stringify(config)};</script>`;
3119
3094
  return html.replace("</head>", `${configScript}</head>`);
3120
3095
  }
3121
3096
  function createWorkerHandler(options) {
@@ -3237,7 +3212,7 @@ function getRuntimeConfig(env) {
3237
3212
  };
3238
3213
  }
3239
3214
  function injectConfig2(html, config) {
3240
- const configScript = `<script>window.__CONFIG__=${JSON.stringify(config)};<\/script>`;
3215
+ const configScript = `<script>window.__CONFIG__=${JSON.stringify(config)};</script>`;
3241
3216
  return html.replace("</head>", `${configScript}</head>`);
3242
3217
  }
3243
3218
 
@@ -3264,4 +3239,4 @@ exports.themes = themes;
3264
3239
  exports.themesRelations = themesRelations;
3265
3240
  exports.users = users;
3266
3241
  //# sourceMappingURL=index.cjs.map
3267
- //# sourceMappingURL=index.cjs.map
3242
+ //# sourceMappingURL=index.cjs.mapap