@access-dlsu/leapify 0.260507.1 → 0.260507.4

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.
Files changed (51) hide show
  1. package/dist/app.d.ts.map +1 -1
  2. package/dist/{chunk-QARF2YFF.cjs → chunk-BFMJDSDI.cjs} +3 -2
  3. package/dist/chunk-BFMJDSDI.cjs.map +1 -0
  4. package/dist/{chunk-ANNHE3PZ.js → chunk-LJ5BSSYE.js} +3 -2
  5. package/dist/{chunk-QARF2YFF.cjs.map → chunk-LJ5BSSYE.js.map} +1 -1
  6. package/dist/{chunk-63CUZGSZ.js → chunk-MCOLCTFX.js} +3 -2
  7. package/dist/chunk-MCOLCTFX.js.map +1 -0
  8. package/dist/{chunk-YFJBE3AU.cjs → chunk-MKWVLWVJ.cjs} +3 -2
  9. package/dist/chunk-MKWVLWVJ.cjs.map +1 -0
  10. package/dist/client/index.cjs +25 -25
  11. package/dist/client/index.cjs.map +1 -1
  12. package/dist/client/index.d.ts +17 -17
  13. package/dist/client/index.d.ts.map +1 -1
  14. package/dist/client/index.js +25 -25
  15. package/dist/client/index.js.map +1 -1
  16. package/dist/client/types.d.ts +4 -2
  17. package/dist/client/types.d.ts.map +1 -1
  18. package/dist/db/migrate.d.ts.map +1 -1
  19. package/dist/db/schema/{events.d.ts → classes.d.ts} +3 -3
  20. package/dist/db/schema/classes.d.ts.map +1 -0
  21. package/dist/db/schema/index.d.ts +1 -1
  22. package/dist/db/schema/index.d.ts.map +1 -1
  23. package/dist/db/schema/site-config.d.ts +83 -0
  24. package/dist/db/schema/site-config.d.ts.map +1 -1
  25. package/dist/index.cjs +640 -52
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.ts +2 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +616 -33
  30. package/dist/index.js.map +1 -1
  31. package/dist/lib/middleware/pow-challenge.cjs +6 -6
  32. package/dist/lib/middleware/pow-challenge.d.ts.map +1 -1
  33. package/dist/lib/middleware/pow-challenge.js +1 -1
  34. package/dist/routes/classes.d.ts +4 -0
  35. package/dist/routes/classes.d.ts.map +1 -0
  36. package/dist/routes/contentful-sync.d.ts +4 -0
  37. package/dist/routes/contentful-sync.d.ts.map +1 -0
  38. package/dist/services/snapshot.d.ts +1 -1
  39. package/dist/services/snapshot.d.ts.map +1 -1
  40. package/dist/types.d.ts +19 -0
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/worker-handler.d.ts.map +1 -1
  43. package/dist/worker.js +605 -29
  44. package/dist/worker.js.map +1 -1
  45. package/package.json +1 -1
  46. package/dist/chunk-63CUZGSZ.js.map +0 -1
  47. package/dist/chunk-ANNHE3PZ.js.map +0 -1
  48. package/dist/chunk-YFJBE3AU.cjs.map +0 -1
  49. package/dist/db/schema/events.d.ts.map +0 -1
  50. package/dist/routes/events.d.ts +0 -4
  51. package/dist/routes/events.d.ts.map +0 -1
package/dist/worker.js CHANGED
@@ -25089,6 +25089,12 @@ var errorHandler2 = (err, c) => {
25089
25089
  );
25090
25090
  };
25091
25091
 
25092
+ // src/types.ts
25093
+ function parseCmsMode(raw2) {
25094
+ if (raw2 === "cloudflare" || raw2 === "contentful") return raw2;
25095
+ return "hybrid";
25096
+ }
25097
+
25092
25098
  // node_modules/hono/dist/middleware/cors/index.js
25093
25099
  var cors = (options) => {
25094
25100
  const opts = {
@@ -25399,6 +25405,7 @@ function createPowChallengeMiddleware() {
25399
25405
  return createMiddleware(async (c, next) => {
25400
25406
  if (c.req.path === POW_VERIFY_PATH) return next();
25401
25407
  if (EXEMPT_PATHS.some((p) => c.req.path.startsWith(p))) return next();
25408
+ if (c.req.method === "OPTIONS") return next();
25402
25409
  if (c.req.header("Authorization")) return next();
25403
25410
  const cookieHeader = c.req.header("Cookie") ?? "";
25404
25411
  const cookieMatch = cookieHeader.match(
@@ -62511,12 +62518,14 @@ function drizzle(client, config3 = {}) {
62511
62518
  // src/db/schema/index.ts
62512
62519
  var schema_exports = {};
62513
62520
  __export(schema_exports, {
62521
+ CONTENTFUL_CONFIG_KEYS: () => CONTENTFUL_CONFIG_KEYS,
62514
62522
  authAccount: () => authAccount,
62515
62523
  authSession: () => authSession,
62516
62524
  authUser: () => authUser,
62517
62525
  authVerification: () => authVerification,
62518
62526
  bookmarks: () => bookmarks,
62519
62527
  bookmarksRelations: () => bookmarksRelations,
62528
+ contentfulConfig: () => contentfulConfig,
62520
62529
  events: () => events,
62521
62530
  eventsRelations: () => eventsRelations,
62522
62531
  faqs: () => faqs,
@@ -62568,7 +62577,7 @@ var organizationsRelations = relations(organizations, ({ many }) => ({
62568
62577
  events: many(events)
62569
62578
  }));
62570
62579
 
62571
- // src/db/schema/events.ts
62580
+ // src/db/schema/classes.ts
62572
62581
  var events = sqliteTable(
62573
62582
  "events",
62574
62583
  {
@@ -62593,7 +62602,7 @@ var events = sqliteTable(
62593
62602
  // start time string
62594
62603
  endTime: text("end_time"),
62595
62604
  // end time string
62596
- isMajor: integer2("is_major", { mode: "boolean" }).notNull().default(false),
62605
+ isSpotlight: integer2("is_spotlight", { mode: "boolean" }).notNull().default(false),
62597
62606
  // Slot tracking (local counter — NOT polled from Google Forms)
62598
62607
  maxSlots: integer2("max_slots").notNull().default(0),
62599
62608
  registeredSlots: integer2("registered_slots").notNull().default(0),
@@ -62650,6 +62659,18 @@ var siteConfig = sqliteTable("site_config", {
62650
62659
  // JSON-serializable string
62651
62660
  updatedAt: integer2("updated_at").notNull().default(sql2`(unixepoch())`)
62652
62661
  });
62662
+ var CONTENTFUL_CONFIG_KEYS = {
62663
+ ENABLED: "contentful.enabled",
62664
+ SPACE_ID: "contentful.spaceId",
62665
+ MANAGEMENT_TOKEN: "contentful.managementToken",
62666
+ DEFAULT_SPACE_ID: "dlsu-events"
62667
+ };
62668
+ var contentfulConfig = sqliteTable("contentful_config", {
62669
+ space_id: text("space_id"),
62670
+ contentful_enabled: integer2("contentful_enabled").notNull().default(0),
62671
+ last_sync_at: integer2("last_sync_at"),
62672
+ updated_at: integer2("updated_at").default(sql2`(unixepoch())`).notNull()
62673
+ });
62653
62674
 
62654
62675
  // src/db/schema/faqs.ts
62655
62676
  var faqs = sqliteTable("faqs", {
@@ -63819,7 +63840,7 @@ var adminEventsRateLimit = createRateLimitMiddleware({
63819
63840
  identifier: "uid"
63820
63841
  });
63821
63842
 
63822
- // src/routes/events.ts
63843
+ // src/routes/classes.ts
63823
63844
  var EVENTS_LIST_KV_KEY = "events:list";
63824
63845
  var EVENTS_ETAG_KV_KEY = "events:etag";
63825
63846
  var EVENTS_LIST_TTL = 300;
@@ -63835,7 +63856,7 @@ async function pushEventToContentful(env2, event) {
63835
63856
  const fields = {
63836
63857
  title: ContentfulManagement.locale(event.title),
63837
63858
  slug: ContentfulManagement.locale(event.slug),
63838
- isMajor: ContentfulManagement.locale(event.isMajor),
63859
+ isSpotlight: ContentfulManagement.locale(event.isSpotlight),
63839
63860
  maxSlots: ContentfulManagement.locale(event.maxSlots)
63840
63861
  };
63841
63862
  if (event.themeId) fields.theme = ContentfulManagement.entryRef(event.themeId);
@@ -63898,7 +63919,7 @@ var createEventSchema = external_exports.object({
63898
63919
  startTime: external_exports.string().optional(),
63899
63920
  endTime: external_exports.string().optional(),
63900
63921
  registrationClosesAt: external_exports.number().optional(),
63901
- isMajor: external_exports.boolean().default(false),
63922
+ isSpotlight: external_exports.boolean().default(false),
63902
63923
  maxSlots: external_exports.number().int().min(0).default(0),
63903
63924
  gformsId: external_exports.string().optional(),
63904
63925
  gformsUrl: external_exports.string().url().optional(),
@@ -63907,11 +63928,11 @@ var createEventSchema = external_exports.object({
63907
63928
  contentfulEntryId: external_exports.string().optional(),
63908
63929
  status: external_exports.enum(["draft", "queued", "published"]).default("draft")
63909
63930
  });
63910
- var eventsRoute = new Hono2();
63931
+ var classesRoute = new Hono2();
63911
63932
  function generateSlug(title) {
63912
63933
  return title.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
63913
63934
  }
63914
- eventsRoute.get("/admin", authMiddleware, adminMiddleware, async (c) => {
63935
+ classesRoute.get("/admin", authMiddleware, adminMiddleware, async (c) => {
63915
63936
  const db = createDb(c.env.DB);
63916
63937
  const data = await db.query.events.findMany({
63917
63938
  with: { theme: true, organization: true },
@@ -63919,7 +63940,7 @@ eventsRoute.get("/admin", authMiddleware, adminMiddleware, async (c) => {
63919
63940
  });
63920
63941
  return c.json({ data });
63921
63942
  });
63922
- eventsRoute.post("/admin/publish", authMiddleware, adminMiddleware, async (c) => {
63943
+ classesRoute.post("/admin/publish", authMiddleware, adminMiddleware, async (c) => {
63923
63944
  const body = await c.req.json();
63924
63945
  const db = createDb(c.env.DB);
63925
63946
  const cache3 = new CacheService(c.env.KV);
@@ -63947,7 +63968,7 @@ eventsRoute.post("/admin/publish", authMiddleware, adminMiddleware, async (c) =>
63947
63968
  ]);
63948
63969
  return c.json({ data: { updated: body.ids.length } });
63949
63970
  });
63950
- eventsRoute.get("/", eventsListRateLimit, async (c) => {
63971
+ classesRoute.get("/", eventsListRateLimit, async (c) => {
63951
63972
  const db = createDb(c.env.DB);
63952
63973
  const cache3 = new CacheService(c.env.KV);
63953
63974
  const [latest] = await db.select({ max: events.publishedAt }).from(events).where(eq(events.status, "published")).limit(1);
@@ -63958,7 +63979,7 @@ eventsRoute.get("/", eventsListRateLimit, async (c) => {
63958
63979
  );
63959
63980
  const ifNoneMatch = c.req.header("If-None-Match");
63960
63981
  if (ifNoneMatch === etag) {
63961
- return c.newResponse(null, 304);
63982
+ return c.body(null, 304);
63962
63983
  }
63963
63984
  const data = await cache3.getOrSet(
63964
63985
  EVENTS_LIST_KV_KEY,
@@ -63982,7 +64003,7 @@ eventsRoute.get("/", eventsListRateLimit, async (c) => {
63982
64003
  startTime: true,
63983
64004
  endTime: true,
63984
64005
  registrationClosesAt: true,
63985
- isMajor: true,
64006
+ isSpotlight: true,
63986
64007
  maxSlots: true,
63987
64008
  registeredSlots: true,
63988
64009
  gformsUrl: true,
@@ -63999,7 +64020,7 @@ eventsRoute.get("/", eventsListRateLimit, async (c) => {
63999
64020
  );
64000
64021
  return c.json({ data });
64001
64022
  });
64002
- eventsRoute.get("/:slug", async (c) => {
64023
+ classesRoute.get("/:slug", async (c) => {
64003
64024
  const { slug } = c.req.param();
64004
64025
  const db = createDb(c.env.DB);
64005
64026
  const event = await db.query.events.findFirst({
@@ -64011,7 +64032,7 @@ eventsRoute.get("/:slug", async (c) => {
64011
64032
  if (!event) throw notFound("Event");
64012
64033
  return c.json({ data: event });
64013
64034
  });
64014
- eventsRoute.get("/:slug/slots", eventsSlotsRateLimit, async (c) => {
64035
+ classesRoute.get("/:slug/slots", eventsSlotsRateLimit, async (c) => {
64015
64036
  const { slug } = c.req.param();
64016
64037
  const db = createDb(c.env.DB);
64017
64038
  const cache3 = new CacheService(c.env.KV);
@@ -64021,7 +64042,7 @@ eventsRoute.get("/:slug/slots", eventsSlotsRateLimit, async (c) => {
64021
64042
  c.header("Cache-Control", "public, max-age=5, stale-while-revalidate=5");
64022
64043
  return c.json({ data: info2 });
64023
64044
  });
64024
- eventsRoute.post(
64045
+ classesRoute.post(
64025
64046
  "/",
64026
64047
  authMiddleware,
64027
64048
  adminMiddleware,
@@ -64056,11 +64077,13 @@ eventsRoute.post(
64056
64077
  cache3.del(EVENTS_LIST_KV_KEY),
64057
64078
  cache3.del(EVENTS_ETAG_KV_KEY)
64058
64079
  ]);
64059
- c.executionCtx.waitUntil(pushEventToContentful(c.env, created));
64080
+ if (c.get("cmsMode") === "hybrid") {
64081
+ c.executionCtx.waitUntil(pushEventToContentful(c.env, created));
64082
+ }
64060
64083
  return c.json({ data: created }, 201);
64061
64084
  }
64062
64085
  );
64063
- eventsRoute.patch("/:slug", authMiddleware, adminMiddleware, async (c) => {
64086
+ classesRoute.patch("/:slug", authMiddleware, adminMiddleware, async (c) => {
64064
64087
  const { slug } = c.req.param();
64065
64088
  const body = await c.req.json();
64066
64089
  const db = createDb(c.env.DB);
@@ -64075,10 +64098,12 @@ eventsRoute.patch("/:slug", authMiddleware, adminMiddleware, async (c) => {
64075
64098
  cache3.del(EVENTS_LIST_KV_KEY),
64076
64099
  cache3.del(EVENTS_ETAG_KV_KEY)
64077
64100
  ]);
64078
- c.executionCtx.waitUntil(pushEventToContentful(c.env, updated));
64101
+ if (c.get("cmsMode") === "hybrid") {
64102
+ c.executionCtx.waitUntil(pushEventToContentful(c.env, updated));
64103
+ }
64079
64104
  return c.json({ data: updated });
64080
64105
  });
64081
- eventsRoute.delete("/:slug", authMiddleware, adminMiddleware, async (c) => {
64106
+ classesRoute.delete("/:slug", authMiddleware, adminMiddleware, async (c) => {
64082
64107
  const { slug } = c.req.param();
64083
64108
  const db = createDb(c.env.DB);
64084
64109
  const cache3 = new CacheService(c.env.KV);
@@ -64177,7 +64202,447 @@ usersRoute.delete("/me/bookmarks/:eventId", authMiddleware, async (c) => {
64177
64202
  return c.json({ data: { bookmarked: false } });
64178
64203
  });
64179
64204
 
64205
+ // src/services/contentful.ts
64206
+ var CONTENTFUL_CDN = "https://cdn.contentful.com";
64207
+ var ContentfulService = class _ContentfulService {
64208
+ spaceId;
64209
+ accessToken;
64210
+ environment;
64211
+ constructor(spaceId, accessToken, environment = "master") {
64212
+ this.spaceId = spaceId;
64213
+ this.accessToken = accessToken;
64214
+ this.environment = environment;
64215
+ }
64216
+ /**
64217
+ * Returns true if the required Contentful credentials are configured.
64218
+ */
64219
+ static isConfigured(spaceId, accessToken) {
64220
+ return !!(spaceId && accessToken);
64221
+ }
64222
+ // ─── Entries ─────────────────────────────────────────────────────────────
64223
+ /**
64224
+ * Fetch all entries of a given content type.
64225
+ * Handles pagination automatically (100 per page, Contentful max).
64226
+ */
64227
+ async getEntries(contentTypeId) {
64228
+ const allItems = [];
64229
+ let skip = 0;
64230
+ const limit = 100;
64231
+ do {
64232
+ const url2 = this.buildUrl(`/entries`, {
64233
+ content_type: contentTypeId,
64234
+ skip: String(skip),
64235
+ limit: String(limit),
64236
+ include: "2"
64237
+ // resolve up to 2 levels of linked entries/assets
64238
+ });
64239
+ const res = await fetch(url2, { headers: this.headers() });
64240
+ if (!res.ok) {
64241
+ throw new Error(`Contentful entries error: ${res.status} ${await res.text()}`);
64242
+ }
64243
+ const data = await res.json();
64244
+ allItems.push(...data.items);
64245
+ skip += limit;
64246
+ if (allItems.length >= data.total) break;
64247
+ } while (true);
64248
+ return allItems;
64249
+ }
64250
+ /**
64251
+ * Fetch all assets. Handles pagination.
64252
+ */
64253
+ async getAssets() {
64254
+ const allItems = [];
64255
+ let skip = 0;
64256
+ const limit = 100;
64257
+ do {
64258
+ const url2 = this.buildUrl(`/assets`, {
64259
+ skip: String(skip),
64260
+ limit: String(limit)
64261
+ });
64262
+ const res = await fetch(url2, { headers: this.headers() });
64263
+ if (!res.ok) {
64264
+ throw new Error(`Contentful assets error: ${res.status} ${await res.text()}`);
64265
+ }
64266
+ const data = await res.json();
64267
+ allItems.push(...data.items);
64268
+ skip += limit;
64269
+ if (allItems.length >= data.total) break;
64270
+ } while (true);
64271
+ return allItems;
64272
+ }
64273
+ // ─── Asset file download ─────────────────────────────────────────────────
64274
+ /**
64275
+ * Download an asset file from Contentful's CDN.
64276
+ * Returns the raw ArrayBuffer and content type.
64277
+ */
64278
+ async downloadAsset(assetUrl) {
64279
+ const url2 = assetUrl.startsWith("//") ? `https:${assetUrl}` : assetUrl;
64280
+ const res = await fetch(url2);
64281
+ if (!res.ok) {
64282
+ throw new Error(`Failed to download asset: ${res.status}`);
64283
+ }
64284
+ const contentType = res.headers.get("Content-Type") ?? "application/octet-stream";
64285
+ const data = await res.arrayBuffer();
64286
+ return { data, contentType };
64287
+ }
64288
+ // ─── Helpers ─────────────────────────────────────────────────────────────
64289
+ /**
64290
+ * Extract a field value from a Contentful entry, handling locale wrapping.
64291
+ * Contentful fields are often `{ "en-US": value }` — this unwraps them.
64292
+ */
64293
+ static getField(entry, fieldName) {
64294
+ const raw2 = entry.fields[fieldName];
64295
+ if (raw2 === void 0 || raw2 === null) return void 0;
64296
+ if (typeof raw2 === "object" && !Array.isArray(raw2) && "en-US" in raw2) {
64297
+ return raw2["en-US"];
64298
+ }
64299
+ return raw2;
64300
+ }
64301
+ /**
64302
+ * Extract a linked entry/sys reference ID from a reference field.
64303
+ */
64304
+ static getRefId(entry, fieldName) {
64305
+ const ref = _ContentfulService.getField(entry, fieldName);
64306
+ return ref?.sys?.id;
64307
+ }
64308
+ /**
64309
+ * Extract an asset URL from a linked asset field.
64310
+ */
64311
+ static getAssetUrl(entry, fieldName) {
64312
+ const asset = _ContentfulService.getField(entry, fieldName);
64313
+ return asset?.sys?.id;
64314
+ }
64315
+ /**
64316
+ * Resolve an asset URL by ID from a list of fetched assets.
64317
+ */
64318
+ static resolveAssetUrl(assets, assetId) {
64319
+ const asset = assets.find((a) => a.sys.id === assetId);
64320
+ return asset?.fields?.file?.url;
64321
+ }
64322
+ // ─── Private ─────────────────────────────────────────────────────────────
64323
+ headers() {
64324
+ return {
64325
+ Authorization: `Bearer ${this.accessToken}`,
64326
+ "Content-Type": "application/json"
64327
+ };
64328
+ }
64329
+ buildUrl(path, params = {}) {
64330
+ const url2 = new URL(
64331
+ `/spaces/${this.spaceId}/environments/${this.environment}${path}`,
64332
+ CONTENTFUL_CDN
64333
+ );
64334
+ for (const [key, value] of Object.entries(params)) {
64335
+ url2.searchParams.set(key, value);
64336
+ }
64337
+ return url2.toString();
64338
+ }
64339
+ };
64340
+
64180
64341
  // src/services/snapshot.ts
64342
+ var DEFAULT_FIELDS = {
64343
+ event: {
64344
+ title: "title",
64345
+ slug: "slug",
64346
+ theme: "theme",
64347
+ organization: "organization",
64348
+ venue: "venue",
64349
+ date: "date",
64350
+ startTime: "startTime",
64351
+ endTime: "endTime",
64352
+ price: "price",
64353
+ image: "image",
64354
+ isSpotlight: "isSpotlight",
64355
+ maxSlots: "maxSlots",
64356
+ gformsUrl: "gformsUrl",
64357
+ gformsEditorUrl: "gformsEditorUrl",
64358
+ registrationClosesAt: "registrationClosesAt",
64359
+ classCode: "classCode"
64360
+ },
64361
+ theme: {
64362
+ name: "name",
64363
+ path: "path"
64364
+ },
64365
+ faq: {
64366
+ question: "question",
64367
+ answer: "answer",
64368
+ category: "category",
64369
+ sortOrder: "sortOrder"
64370
+ },
64371
+ organization: {
64372
+ name: "name",
64373
+ acronym: "acronym",
64374
+ logoUrl: "logoUrl",
64375
+ link: "link"
64376
+ },
64377
+ siteConfig: {
64378
+ key: "key",
64379
+ value: "value"
64380
+ }
64381
+ };
64382
+ var CONTENTFUL_CACHE_PREFIX = "contentful:cache";
64383
+ var CONTENTFUL_CACHE_TTL = 300;
64384
+ async function snapshotAllContent(db, bucket, contentful, config3 = {}, kv) {
64385
+ const mergedConfig = {
64386
+ eventTypeId: config3.eventTypeId ?? "event",
64387
+ themeTypeId: config3.themeTypeId ?? "theme",
64388
+ faqTypeId: config3.faqTypeId ?? "faq",
64389
+ organizationTypeId: config3.organizationTypeId ?? "organization",
64390
+ fields: {
64391
+ event: { ...DEFAULT_FIELDS.event, ...config3.fields?.event },
64392
+ theme: { ...DEFAULT_FIELDS.theme, ...config3.fields?.theme },
64393
+ faq: { ...DEFAULT_FIELDS.faq, ...config3.fields?.faq },
64394
+ organization: { ...DEFAULT_FIELDS.organization, ...config3.fields?.organization },
64395
+ siteConfig: { ...DEFAULT_FIELDS.siteConfig, ...config3.fields?.siteConfig }
64396
+ }
64397
+ };
64398
+ const result = {
64399
+ themesSynced: 0,
64400
+ eventsSynced: 0,
64401
+ faqsSynced: 0,
64402
+ organizationsSynced: 0,
64403
+ imagesUploaded: 0,
64404
+ imagesSkipped: 0,
64405
+ errors: []
64406
+ };
64407
+ let allAssets = [];
64408
+ if (bucket) {
64409
+ try {
64410
+ allAssets = await contentful.getAssets();
64411
+ } catch (err) {
64412
+ result.errors.push(`Failed to fetch assets: ${err}`);
64413
+ }
64414
+ }
64415
+ try {
64416
+ const cacheKey = `${CONTENTFUL_CACHE_PREFIX}:themes`;
64417
+ let themeEntries;
64418
+ if (kv) {
64419
+ const cached2 = await kv.get(cacheKey, "json");
64420
+ if (cached2) {
64421
+ themeEntries = cached2;
64422
+ } else {
64423
+ themeEntries = await contentful.getEntries(mergedConfig.themeTypeId);
64424
+ await kv.put(cacheKey, JSON.stringify(themeEntries), { expirationTtl: CONTENTFUL_CACHE_TTL });
64425
+ }
64426
+ } else {
64427
+ themeEntries = await contentful.getEntries(mergedConfig.themeTypeId);
64428
+ }
64429
+ result.themesSynced = await syncThemes(db, themeEntries, mergedConfig);
64430
+ } catch (err) {
64431
+ result.errors.push(`Themes sync failed: ${err}`);
64432
+ }
64433
+ try {
64434
+ const cacheKey = `${CONTENTFUL_CACHE_PREFIX}:organizations`;
64435
+ let orgEntries;
64436
+ if (kv) {
64437
+ const cached2 = await kv.get(cacheKey, "json");
64438
+ if (cached2) {
64439
+ orgEntries = cached2;
64440
+ } else {
64441
+ orgEntries = await contentful.getEntries(mergedConfig.organizationTypeId);
64442
+ await kv.put(cacheKey, JSON.stringify(orgEntries), { expirationTtl: CONTENTFUL_CACHE_TTL });
64443
+ }
64444
+ } else {
64445
+ orgEntries = await contentful.getEntries(mergedConfig.organizationTypeId);
64446
+ }
64447
+ await syncOrganizations(db, orgEntries, mergedConfig);
64448
+ } catch (err) {
64449
+ result.errors.push(`Organizations sync failed: ${err}`);
64450
+ }
64451
+ try {
64452
+ const cacheKey = `${CONTENTFUL_CACHE_PREFIX}:events`;
64453
+ let eventEntries;
64454
+ if (kv) {
64455
+ const cached2 = await kv.get(cacheKey, "json");
64456
+ if (cached2) {
64457
+ eventEntries = cached2;
64458
+ } else {
64459
+ eventEntries = await contentful.getEntries(mergedConfig.eventTypeId);
64460
+ await kv.put(cacheKey, JSON.stringify(eventEntries), { expirationTtl: CONTENTFUL_CACHE_TTL });
64461
+ }
64462
+ } else {
64463
+ eventEntries = await contentful.getEntries(mergedConfig.eventTypeId);
64464
+ }
64465
+ result.eventsSynced = await syncEvents(db, bucket, contentful, allAssets, eventEntries, mergedConfig, result);
64466
+ } catch (err) {
64467
+ result.errors.push(`Events sync failed: ${err}`);
64468
+ }
64469
+ try {
64470
+ const cacheKey = `${CONTENTFUL_CACHE_PREFIX}:faqs`;
64471
+ let faqEntries;
64472
+ if (kv) {
64473
+ const cached2 = await kv.get(cacheKey, "json");
64474
+ if (cached2) {
64475
+ faqEntries = cached2;
64476
+ } else {
64477
+ faqEntries = await contentful.getEntries(mergedConfig.faqTypeId);
64478
+ await kv.put(cacheKey, JSON.stringify(faqEntries), { expirationTtl: CONTENTFUL_CACHE_TTL });
64479
+ }
64480
+ } else {
64481
+ faqEntries = await contentful.getEntries(mergedConfig.faqTypeId);
64482
+ }
64483
+ result.faqsSynced = await syncFaqs(db, faqEntries, mergedConfig);
64484
+ } catch (err) {
64485
+ result.errors.push(`FAQs sync failed: ${err}`);
64486
+ }
64487
+ console.log(
64488
+ `[Snapshot] Complete: ${result.themesSynced} themes, ${result.organizationsSynced} organizations, ${result.eventsSynced} events, ${result.faqsSynced} FAQs, ${result.imagesUploaded} images uploaded, ${result.imagesSkipped} images skipped, ${result.errors.length} errors`
64489
+ );
64490
+ return result;
64491
+ }
64492
+ async function syncThemes(db, entries, config3) {
64493
+ let count2 = 0;
64494
+ for (const entry of entries) {
64495
+ const cfId = entry.sys.id;
64496
+ const name = ContentfulService.getField(entry, config3.fields.theme.name);
64497
+ const path = ContentfulService.getField(entry, config3.fields.theme.path);
64498
+ if (!name || !path) continue;
64499
+ await db.insert(themes).values({ id: cfId, name, path }).onConflictDoUpdate({
64500
+ target: themes.id,
64501
+ set: { name, path }
64502
+ });
64503
+ count2++;
64504
+ }
64505
+ return count2;
64506
+ }
64507
+ async function syncOrganizations(db, entries, config3) {
64508
+ let count2 = 0;
64509
+ for (const entry of entries) {
64510
+ const cfId = entry.sys.id;
64511
+ const f = config3.fields.organization;
64512
+ const name = ContentfulService.getField(entry, f.name);
64513
+ const acronym = ContentfulService.getField(entry, f.acronym);
64514
+ if (!name || !acronym) continue;
64515
+ const logoUrl = ContentfulService.getField(entry, f.logoUrl) ?? null;
64516
+ const link = ContentfulService.getField(entry, f.link) ?? null;
64517
+ await db.insert(organizations).values({ id: cfId, name, acronym, logoUrl, link }).onConflictDoUpdate({
64518
+ target: organizations.id,
64519
+ set: { name, acronym, logoUrl, link }
64520
+ });
64521
+ count2++;
64522
+ }
64523
+ return count2;
64524
+ }
64525
+ async function syncEvents(db, bucket, contentful, allAssets, entries, config3, result) {
64526
+ let count2 = 0;
64527
+ for (const entry of entries) {
64528
+ try {
64529
+ const cfId = entry.sys.id;
64530
+ const f = config3.fields.event;
64531
+ const title = ContentfulService.getField(entry, f.title);
64532
+ if (!title) {
64533
+ result.errors.push(`Event ${cfId}: missing title, skipping`);
64534
+ continue;
64535
+ }
64536
+ const slug = ContentfulService.getField(entry, f.slug) ?? slugify2(title);
64537
+ const themeRef = ContentfulService.getField(entry, f.theme);
64538
+ const themeId = themeRef?.sys?.id ?? null;
64539
+ const orgRef = ContentfulService.getField(entry, f.organization);
64540
+ const organizationId = orgRef?.sys?.id ?? null;
64541
+ let backgroundImageUrl = null;
64542
+ if (bucket) {
64543
+ const imageRef = ContentfulService.getField(entry, f.image);
64544
+ const assetId = imageRef?.sys?.id;
64545
+ if (assetId) {
64546
+ const assetUrl = ContentfulService.resolveAssetUrl(allAssets, assetId);
64547
+ if (assetUrl) {
64548
+ const r2Key = `contentful/${assetId}`;
64549
+ const uploaded = await uploadAssetIfChanged(bucket, contentful, assetUrl, r2Key);
64550
+ if (uploaded.skipped) {
64551
+ result.imagesSkipped++;
64552
+ } else {
64553
+ result.imagesUploaded++;
64554
+ }
64555
+ backgroundImageUrl = `/uploads/images/${r2Key}`;
64556
+ }
64557
+ }
64558
+ }
64559
+ const values = {
64560
+ id: cfId,
64561
+ contentfulEntryId: cfId,
64562
+ title,
64563
+ slug,
64564
+ themeId,
64565
+ organizationId,
64566
+ updatedAt: entry.sys.updatedAt
64567
+ };
64568
+ const venue = ContentfulService.getField(entry, f.venue);
64569
+ if (venue !== void 0) values.venue = venue;
64570
+ const date5 = ContentfulService.getField(entry, f.date);
64571
+ if (date5 !== void 0) values.dateTime = date5;
64572
+ const price = ContentfulService.getField(entry, f.price);
64573
+ if (price !== void 0) values.price = price;
64574
+ if (backgroundImageUrl) values.backgroundImageUrl = backgroundImageUrl;
64575
+ const isSpotlight = ContentfulService.getField(entry, f.isSpotlight);
64576
+ if (isSpotlight !== void 0) values.isSpotlight = isSpotlight;
64577
+ const maxSlots = ContentfulService.getField(entry, f.maxSlots);
64578
+ if (maxSlots !== void 0) values.maxSlots = maxSlots;
64579
+ const gformsUrl = ContentfulService.getField(entry, f.gformsUrl);
64580
+ if (gformsUrl !== void 0) values.gformsUrl = gformsUrl;
64581
+ const gformsEditorUrl = ContentfulService.getField(entry, f.gformsEditorUrl);
64582
+ if (gformsEditorUrl !== void 0) values.gformsEditorUrl = gformsEditorUrl;
64583
+ const classCode = ContentfulService.getField(entry, f.classCode);
64584
+ if (classCode !== void 0) values.classCode = classCode;
64585
+ const startTime = ContentfulService.getField(entry, f.startTime);
64586
+ if (startTime !== void 0) values.startTime = startTime;
64587
+ const endTime = ContentfulService.getField(entry, f.endTime);
64588
+ if (endTime !== void 0) values.endTime = endTime;
64589
+ const regCloseRaw = ContentfulService.getField(entry, f.registrationClosesAt);
64590
+ if (regCloseRaw) {
64591
+ const ms = Date.parse(regCloseRaw);
64592
+ if (!Number.isNaN(ms)) values.registrationClosesAt = Math.floor(ms / 1e3);
64593
+ }
64594
+ await db.insert(events).values(values).onConflictDoUpdate({
64595
+ target: events.id,
64596
+ set: values
64597
+ });
64598
+ count2++;
64599
+ } catch (err) {
64600
+ result.errors.push(`Event ${entry.sys.id}: ${err}`);
64601
+ }
64602
+ }
64603
+ return count2;
64604
+ }
64605
+ async function syncFaqs(db, entries, config3) {
64606
+ let count2 = 0;
64607
+ for (const entry of entries) {
64608
+ const cfId = entry.sys.id;
64609
+ const f = config3.fields.faq;
64610
+ const question = ContentfulService.getField(entry, f.question);
64611
+ const answer = ContentfulService.getField(entry, f.answer);
64612
+ if (!question || !answer) continue;
64613
+ const category = ContentfulService.getField(entry, f.category) ?? null;
64614
+ const sortOrder = ContentfulService.getField(entry, f.sortOrder) ?? 0;
64615
+ await db.insert(faqs).values({
64616
+ id: cfId,
64617
+ question,
64618
+ answer,
64619
+ category,
64620
+ sortOrder
64621
+ }).onConflictDoUpdate({
64622
+ target: faqs.id,
64623
+ set: { question, answer, category, sortOrder }
64624
+ });
64625
+ count2++;
64626
+ }
64627
+ return count2;
64628
+ }
64629
+ async function uploadAssetIfChanged(bucket, contentful, assetUrl, r2Key) {
64630
+ const { data, contentType } = await contentful.downloadAsset(assetUrl);
64631
+ const sha = await computeSha256(data);
64632
+ const existing = await bucket.head(r2Key);
64633
+ if (existing?.customMetadata?.sha256 === sha) {
64634
+ return { skipped: true };
64635
+ }
64636
+ await bucket.put(r2Key, data, {
64637
+ httpMetadata: { contentType },
64638
+ customMetadata: {
64639
+ sha256: sha,
64640
+ source: "contentful",
64641
+ syncedAt: (/* @__PURE__ */ new Date()).toISOString()
64642
+ }
64643
+ });
64644
+ return { skipped: false };
64645
+ }
64181
64646
  async function batchRun(items, fn, concurrency = 5) {
64182
64647
  const results = [];
64183
64648
  for (let i = 0; i < items.length; i += concurrency) {
@@ -64187,6 +64652,13 @@ async function batchRun(items, fn, concurrency = 5) {
64187
64652
  }
64188
64653
  return results;
64189
64654
  }
64655
+ async function computeSha256(data) {
64656
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
64657
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
64658
+ }
64659
+ function slugify2(text2) {
64660
+ return text2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
64661
+ }
64190
64662
  async function ensureContentTypes(mgmt, config3 = {}) {
64191
64663
  const eventTypeId = config3.eventTypeId ?? "event";
64192
64664
  const themeTypeId = config3.themeTypeId ?? "theme";
@@ -64207,7 +64679,7 @@ async function ensureContentTypes(mgmt, config3 = {}) {
64207
64679
  { id: "endTime", name: "End Time", type: "Symbol" },
64208
64680
  { id: "price", name: "Price", type: "Symbol" },
64209
64681
  { id: "image", name: "Image", type: "Link", linkType: "Asset" },
64210
- { id: "isMajor", name: "Major Event", type: "Boolean" },
64682
+ { id: "isSpotlight", name: "Spotlight", type: "Boolean" },
64211
64683
  { id: "maxSlots", name: "Max Slots", type: "Integer" },
64212
64684
  { id: "gformsUrl", name: "Google Forms URL", type: "Symbol" },
64213
64685
  { id: "gformsEditorUrl", name: "Google Forms Editor URL", type: "Symbol" },
@@ -64292,7 +64764,7 @@ async function pushToContentful(db, mgmt, config3 = {}, kv, forceFull = false) {
64292
64764
  const fields = {
64293
64765
  title: ContentfulManagement.locale(event.title),
64294
64766
  slug: ContentfulManagement.locale(event.slug),
64295
- isMajor: ContentfulManagement.locale(event.isMajor),
64767
+ isSpotlight: ContentfulManagement.locale(event.isSpotlight),
64296
64768
  maxSlots: ContentfulManagement.locale(event.maxSlots)
64297
64769
  };
64298
64770
  if (event.themeId) fields.theme = ContentfulManagement.entryRef(event.themeId);
@@ -64363,6 +64835,9 @@ siteConfigRoute.get("/", async (c) => {
64363
64835
  siteName: config3.site_name ?? null,
64364
64836
  registrationGloballyOpen: config3.registration_globally_open ?? true,
64365
64837
  maintenanceMode: config3.maintenance_mode ?? false,
64838
+ // Prefer the D1-persisted value over the env-var default so that
64839
+ // PATCH /config/cms_mode changes are reflected immediately.
64840
+ cmsMode: config3.cms_mode ?? c.get("cmsMode"),
64366
64841
  now: Math.floor(Date.now() / 1e3)
64367
64842
  }
64368
64843
  });
@@ -64384,6 +64859,10 @@ siteConfigRoute.patch("/:key", authMiddleware, adminMiddleware, async (c) => {
64384
64859
  var SYNC_LOCK_KEY = "contentful:sync:lock";
64385
64860
  var SYNC_LOCK_TTL = 60;
64386
64861
  siteConfigRoute.post("/sync-content", authMiddleware, adminMiddleware, async (c) => {
64862
+ const cmsMode = c.get("cmsMode");
64863
+ if (cmsMode === "cloudflare") {
64864
+ throw serviceUnavailable("Contentful sync is not available in Cloudflare-only mode.");
64865
+ }
64387
64866
  if (!ContentfulManagement.isConfigured(c.env.CONTENTFUL_SPACE_ID, c.env.CONTENTFUL_MANAGEMENT_TOKEN)) {
64388
64867
  throw serviceUnavailable("Contentful Management API credentials not configured.");
64389
64868
  }
@@ -64470,7 +64949,9 @@ faqsRoute.post(
64470
64949
  const cache3 = new CacheService(c.env.KV);
64471
64950
  const [created] = await db.insert(faqs).values(body).returning();
64472
64951
  await cache3.del(FAQS_KV_KEY);
64473
- c.executionCtx.waitUntil(pushFaqToContentful(c.env, created));
64952
+ if (c.get("cmsMode") === "hybrid") {
64953
+ c.executionCtx.waitUntil(pushFaqToContentful(c.env, created));
64954
+ }
64474
64955
  return c.json({ data: created }, 201);
64475
64956
  }
64476
64957
  );
@@ -64483,7 +64964,9 @@ faqsRoute.patch("/:id", authMiddleware, adminMiddleware, async (c) => {
64483
64964
  const [updated] = await db.update(faqs).set({ ...body, updatedAt: now2 }).where(eq(faqs.id, id)).returning();
64484
64965
  if (!updated) throw notFound("FAQ");
64485
64966
  await cache3.del(FAQS_KV_KEY);
64486
- c.executionCtx.waitUntil(pushFaqToContentful(c.env, updated));
64967
+ if (c.get("cmsMode") === "hybrid") {
64968
+ c.executionCtx.waitUntil(pushFaqToContentful(c.env, updated));
64969
+ }
64487
64970
  return c.json({ data: updated });
64488
64971
  });
64489
64972
  faqsRoute.delete("/:id", authMiddleware, adminMiddleware, async (c) => {
@@ -64759,7 +65242,9 @@ themesRoute.post(
64759
65242
  const db = createDb(c.env.DB);
64760
65243
  try {
64761
65244
  const [created] = await db.insert(themes).values(body).returning();
64762
- c.executionCtx.waitUntil(pushThemeToContentful(c.env, created));
65245
+ if (c.get("cmsMode") === "hybrid") {
65246
+ c.executionCtx.waitUntil(pushThemeToContentful(c.env, created));
65247
+ }
64763
65248
  return c.json({ data: created }, 201);
64764
65249
  } catch (err) {
64765
65250
  if (err.message && err.message.includes("UNIQUE constraint failed")) {
@@ -64780,7 +65265,9 @@ themesRoute.patch(
64780
65265
  try {
64781
65266
  const [updated] = await db.update(themes).set(body).where(eq(themes.id, id)).returning();
64782
65267
  if (!updated) throw notFound("Theme");
64783
- c.executionCtx.waitUntil(pushThemeToContentful(c.env, updated));
65268
+ if (c.get("cmsMode") === "hybrid") {
65269
+ c.executionCtx.waitUntil(pushThemeToContentful(c.env, updated));
65270
+ }
64784
65271
  return c.json({ data: updated });
64785
65272
  } catch (err) {
64786
65273
  if (err.message && err.message.includes("UNIQUE constraint failed")) {
@@ -64795,7 +65282,9 @@ themesRoute.delete("/:id", authMiddleware, adminMiddleware, async (c) => {
64795
65282
  const db = createDb(c.env.DB);
64796
65283
  const [deleted] = await db.delete(themes).where(eq(themes.id, id)).returning();
64797
65284
  if (!deleted) throw notFound("Theme");
64798
- c.executionCtx.waitUntil(deleteThemeFromContentful(c.env, id));
65285
+ if (c.get("cmsMode") === "hybrid") {
65286
+ c.executionCtx.waitUntil(deleteThemeFromContentful(c.env, id));
65287
+ }
64799
65288
  return c.body(null, 204);
64800
65289
  });
64801
65290
 
@@ -64859,6 +65348,83 @@ organizationsRoute.delete("/:id", authMiddleware, adminMiddleware, async (c) =>
64859
65348
  return c.body(null, 204);
64860
65349
  });
64861
65350
 
65351
+ // src/routes/contentful-sync.ts
65352
+ var contentfulSyncRoute = new Hono2();
65353
+ contentfulSyncRoute.post(
65354
+ "/trigger",
65355
+ authMiddleware,
65356
+ adminMiddleware,
65357
+ async (c) => {
65358
+ const env2 = c.env;
65359
+ const { CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_ENVIRONMENT } = env2;
65360
+ if (!CONTENTFUL_SPACE_ID || !CONTENTFUL_ACCESS_TOKEN) {
65361
+ return c.json({ error: { code: "NOT_CONFIGURED", message: "Contentful credentials missing" } }, 400);
65362
+ }
65363
+ const db = createDb(env2.DB);
65364
+ const contentful = new ContentfulService(CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_ENVIRONMENT);
65365
+ try {
65366
+ if (CONTENTFUL_MANAGEMENT_TOKEN) {
65367
+ const mgmt = new ContentfulManagement(CONTENTFUL_SPACE_ID, CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_ENVIRONMENT);
65368
+ await ensureContentTypes(mgmt);
65369
+ }
65370
+ c.executionCtx.waitUntil(
65371
+ snapshotAllContent(db, env2.FILES, contentful, {}, env2.KV).then((r) => console.log("[Contentful] Snapshot complete:", JSON.stringify(r))).catch((err) => console.error("[Contentful] Snapshot failed:", err))
65372
+ );
65373
+ return c.json({ message: "Contentful sync triggered", triggeredAt: Math.floor(Date.now() / 1e3) });
65374
+ } catch (err) {
65375
+ console.error("[Contentful] Sync trigger failed:", err);
65376
+ return c.json({ error: { code: "SYNC_FAILED", message: err?.message ?? "Failed to trigger sync" } }, 500);
65377
+ }
65378
+ }
65379
+ );
65380
+ contentfulSyncRoute.post(
65381
+ "/push",
65382
+ authMiddleware,
65383
+ adminMiddleware,
65384
+ async (c) => {
65385
+ const env2 = c.env;
65386
+ const { CONTENTFUL_SPACE_ID, CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_ENVIRONMENT } = env2;
65387
+ if (!CONTENTFUL_SPACE_ID || !CONTENTFUL_MANAGEMENT_TOKEN) {
65388
+ return c.json({ error: { code: "NOT_CONFIGURED", message: "Contentful management credentials missing" } }, 400);
65389
+ }
65390
+ const db = createDb(env2.DB);
65391
+ const mgmt = new ContentfulManagement(CONTENTFUL_SPACE_ID, CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_ENVIRONMENT);
65392
+ try {
65393
+ c.executionCtx.waitUntil(
65394
+ pushToContentful(db, mgmt, {}, env2.KV).then((r) => console.log("[Contentful] Push complete:", JSON.stringify(r))).catch((err) => console.error("[Contentful] Push failed:", err))
65395
+ );
65396
+ return c.json({ message: "Contentful push triggered", triggeredAt: Math.floor(Date.now() / 1e3) });
65397
+ } catch (err) {
65398
+ console.error("[Contentful] Push trigger failed:", err);
65399
+ return c.json({ error: { code: "SYNC_FAILED", message: err?.message ?? "Failed to trigger push" } }, 500);
65400
+ }
65401
+ }
65402
+ );
65403
+ contentfulSyncRoute.get(
65404
+ "/status",
65405
+ authMiddleware,
65406
+ adminMiddleware,
65407
+ async (c) => {
65408
+ const env2 = c.env;
65409
+ const db = createDb(env2.DB);
65410
+ const [[{ themes: themes2 }], [{ events: events2 }], [{ faqs: faqs2 }], [{ orgs }]] = await Promise.all([
65411
+ db.all(sql2`SELECT count(*) as themes FROM themes`),
65412
+ db.all(sql2`SELECT count(*) as events FROM events`),
65413
+ db.all(sql2`SELECT count(*) as faqs FROM faqs`),
65414
+ db.all(sql2`SELECT count(*) as orgs FROM organizations`)
65415
+ ]);
65416
+ const isConfigured = !!env2.CONTENTFUL_SPACE_ID && !!env2.CONTENTFUL_ACCESS_TOKEN;
65417
+ const canPush = !!env2.CONTENTFUL_SPACE_ID && !!env2.CONTENTFUL_MANAGEMENT_TOKEN;
65418
+ return c.json({
65419
+ isConfigured,
65420
+ canPush,
65421
+ cmsMode: c.get("cmsMode"),
65422
+ spaceId: env2.CONTENTFUL_SPACE_ID ?? null,
65423
+ totals: { themes: themes2, events: events2, faqs: faqs2, organizations: orgs }
65424
+ });
65425
+ }
65426
+ );
65427
+
64862
65428
  // src/app.ts
64863
65429
  function createApp(options = {}) {
64864
65430
  const app2 = new Hono2();
@@ -64872,6 +65438,12 @@ function createApp(options = {}) {
64872
65438
  app2.use("*", createCorsMiddleware(options.allowedOrigins ?? ["*"]));
64873
65439
  app2.use("*", createPowChallengeMiddleware());
64874
65440
  app2.use("*", createRefererGuard(options.allowedOrigins ?? ["*"]));
65441
+ app2.use("*", async (c, next) => {
65442
+ const overrideRaw = await c.env.KV.get("config:cms_mode").catch(() => null);
65443
+ const override = overrideRaw ? JSON.parse(overrideRaw) : null;
65444
+ c.set("cmsMode", parseCmsMode(override ?? c.env.CMS_MODE));
65445
+ return next();
65446
+ });
64875
65447
  app2.on(["POST", "GET"], "/api/auth/*", (c) => {
64876
65448
  const auth = createAuth(c.env);
64877
65449
  const req = c.req.raw;
@@ -64883,7 +65455,7 @@ function createApp(options = {}) {
64883
65455
  return auth.handler(new Request(req.url, {
64884
65456
  method: req.method,
64885
65457
  headers: newHeaders,
64886
- body: req.method === "GET" ? null : req.body,
65458
+ body: req.method === "GET" || req.method === "HEAD" || req.method === "OPTIONS" ? null : req.body,
64887
65459
  redirect: req.redirect
64888
65460
  }));
64889
65461
  }
@@ -64904,12 +65476,13 @@ function createApp(options = {}) {
64904
65476
  app2.post(POW_VERIFY_PATH, handlePowVerify);
64905
65477
  app2.route("/health", healthRoute);
64906
65478
  app2.route("/api/config", siteConfigRoute);
64907
- app2.route("/api/events", eventsRoute);
65479
+ app2.route("/api/classes", classesRoute);
64908
65480
  app2.route("/api/themes", themesRoute);
64909
65481
  app2.route("/api/users", usersRoute);
64910
65482
  app2.route("/api/organizations", organizationsRoute);
64911
65483
  app2.route("/api/faqs", faqsRoute);
64912
65484
  app2.route("/api/uploads", uploadsRoute);
65485
+ app2.route("/api/contentful", contentfulSyncRoute);
64913
65486
  app2.route("/internal/gforms-webhook", gformsWebhookRoute);
64914
65487
  app2.onError(errorHandler2);
64915
65488
  app2.notFound(
@@ -65543,6 +66116,7 @@ var PATCH_STATEMENTS = [
65543
66116
  `ALTER TABLE "events" ADD COLUMN "class_code" text`,
65544
66117
  `ALTER TABLE "events" ADD COLUMN "start_time" text`,
65545
66118
  `ALTER TABLE "events" ADD COLUMN "end_time" text`,
66119
+ `ALTER TABLE "events" RENAME COLUMN "is_major" TO "is_spotlight"`,
65546
66120
  `CREATE INDEX IF NOT EXISTS "idx_events_organization_id" ON "events" ("organization_id")`
65547
66121
  ];
65548
66122
  var CREATE_STATEMENTS = [
@@ -65646,7 +66220,7 @@ var CREATE_STATEMENTS = [
65646
66220
  "class_code" text,
65647
66221
  "start_time" text,
65648
66222
  "end_time" text,
65649
- "is_major" integer DEFAULT false NOT NULL,
66223
+ "is_spotlight" integer DEFAULT false NOT NULL,
65650
66224
  "max_slots" integer DEFAULT 0 NOT NULL,
65651
66225
  "registered_slots" integer DEFAULT 0 NOT NULL,
65652
66226
  "gforms_id" text,
@@ -65708,7 +66282,9 @@ async function ensureDatabase(d1) {
65708
66282
  try {
65709
66283
  await d1.prepare(sql3).run();
65710
66284
  } catch (err) {
65711
- if (err?.message?.includes("duplicate column")) continue;
66285
+ if (err?.message?.includes("duplicate column") || err?.message?.includes("no such column: is_major")) {
66286
+ continue;
66287
+ }
65712
66288
  throw err;
65713
66289
  }
65714
66290
  }