@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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createKyselyAdapter, getKyselyDatabaseType } from './chunk-FUCJEA2S.js';
2
2
  import { createDefu, wildcardMatch, getOrigin, getHost, getProtocol, betterFetch, PACKAGE_VERSION, isDynamicBaseURLConfig, getBaseURL, resolveBaseURL, isRequestLike, defu } from './chunk-IQEWVHLM.js';
3
3
  import './chunk-HHNEB7YR.js';
4
- import { createMiddleware, Hono, HTTPException, tryDecode, decodeURIComponent_, createPowChallengeMiddleware, POW_VERIFY_PATH, handlePowVerify } from './chunk-ANNHE3PZ.js';
4
+ import { createMiddleware, Hono, HTTPException, tryDecode, decodeURIComponent_, createPowChallengeMiddleware, POW_VERIFY_PATH, handlePowVerify } from './chunk-LJ5BSSYE.js';
5
5
  import { createRandomStringGenerator, createAdapterFactory, withSpan, ATTR_CONTEXT, import_semantic_conventions, ATTR_HOOK_TYPE, ATTR_OPERATION_ID, generateId, safeJSONParse, getAuthTables, initGetModelName, initGetFieldName } from './chunk-GNRL67OU.js';
6
6
  import './chunk-EGRHWZRV.js';
7
7
  import { APIError2, BASE_ERROR_CODES, kAPIErrorHeaderSymbol, BetterCallError, BetterAuthError, logger, ValidationError, APIError, env, isProduction, shouldPublishLog, isDevelopment, createLogger, isTest, getBooleanEnvVar, ENV, getEnvVar } from './chunk-MNEW2V4T.js';
@@ -48,6 +48,18 @@ var errorHandler = (err, c) => {
48
48
  );
49
49
  };
50
50
 
51
+ // src/types.ts
52
+ function parseCmsMode(raw) {
53
+ if (raw === "cloudflare" || raw === "contentful") return raw;
54
+ return "hybrid";
55
+ }
56
+ function shouldPushToContentful(mode) {
57
+ return mode === "hybrid";
58
+ }
59
+ function shouldPullFromContentful(mode) {
60
+ return mode === "contentful" || mode === "hybrid";
61
+ }
62
+
51
63
  // node_modules/hono/dist/middleware/cors/index.js
52
64
  var cors = (options) => {
53
65
  const opts = {
@@ -36330,12 +36342,14 @@ function drizzle(client, config3 = {}) {
36330
36342
  // src/db/schema/index.ts
36331
36343
  var schema_exports = {};
36332
36344
  __export(schema_exports, {
36345
+ CONTENTFUL_CONFIG_KEYS: () => CONTENTFUL_CONFIG_KEYS,
36333
36346
  authAccount: () => authAccount,
36334
36347
  authSession: () => authSession,
36335
36348
  authUser: () => authUser,
36336
36349
  authVerification: () => authVerification,
36337
36350
  bookmarks: () => bookmarks,
36338
36351
  bookmarksRelations: () => bookmarksRelations,
36352
+ contentfulConfig: () => contentfulConfig,
36339
36353
  events: () => events,
36340
36354
  eventsRelations: () => eventsRelations,
36341
36355
  faqs: () => faqs,
@@ -36387,7 +36401,7 @@ var organizationsRelations = relations(organizations, ({ many }) => ({
36387
36401
  events: many(events)
36388
36402
  }));
36389
36403
 
36390
- // src/db/schema/events.ts
36404
+ // src/db/schema/classes.ts
36391
36405
  var events = sqliteTable(
36392
36406
  "events",
36393
36407
  {
@@ -36412,7 +36426,7 @@ var events = sqliteTable(
36412
36426
  // start time string
36413
36427
  endTime: text("end_time"),
36414
36428
  // end time string
36415
- isMajor: integer2("is_major", { mode: "boolean" }).notNull().default(false),
36429
+ isSpotlight: integer2("is_spotlight", { mode: "boolean" }).notNull().default(false),
36416
36430
  // Slot tracking (local counter — NOT polled from Google Forms)
36417
36431
  maxSlots: integer2("max_slots").notNull().default(0),
36418
36432
  registeredSlots: integer2("registered_slots").notNull().default(0),
@@ -36469,6 +36483,18 @@ var siteConfig = sqliteTable("site_config", {
36469
36483
  // JSON-serializable string
36470
36484
  updatedAt: integer2("updated_at").notNull().default(sql2`(unixepoch())`)
36471
36485
  });
36486
+ var CONTENTFUL_CONFIG_KEYS = {
36487
+ ENABLED: "contentful.enabled",
36488
+ SPACE_ID: "contentful.spaceId",
36489
+ MANAGEMENT_TOKEN: "contentful.managementToken",
36490
+ DEFAULT_SPACE_ID: "dlsu-events"
36491
+ };
36492
+ var contentfulConfig = sqliteTable("contentful_config", {
36493
+ space_id: text("space_id"),
36494
+ contentful_enabled: integer2("contentful_enabled").notNull().default(0),
36495
+ last_sync_at: integer2("last_sync_at"),
36496
+ updated_at: integer2("updated_at").default(sql2`(unixepoch())`).notNull()
36497
+ });
36472
36498
 
36473
36499
  // src/db/schema/faqs.ts
36474
36500
  var faqs = sqliteTable("faqs", {
@@ -37638,7 +37664,7 @@ var adminEventsRateLimit = createRateLimitMiddleware({
37638
37664
  identifier: "uid"
37639
37665
  });
37640
37666
 
37641
- // src/routes/events.ts
37667
+ // src/routes/classes.ts
37642
37668
  var EVENTS_LIST_KV_KEY = "events:list";
37643
37669
  var EVENTS_ETAG_KV_KEY = "events:etag";
37644
37670
  var EVENTS_LIST_TTL = 300;
@@ -37654,7 +37680,7 @@ async function pushEventToContentful(env2, event) {
37654
37680
  const fields = {
37655
37681
  title: ContentfulManagement.locale(event.title),
37656
37682
  slug: ContentfulManagement.locale(event.slug),
37657
- isMajor: ContentfulManagement.locale(event.isMajor),
37683
+ isSpotlight: ContentfulManagement.locale(event.isSpotlight),
37658
37684
  maxSlots: ContentfulManagement.locale(event.maxSlots)
37659
37685
  };
37660
37686
  if (event.themeId) fields.theme = ContentfulManagement.entryRef(event.themeId);
@@ -37717,7 +37743,7 @@ var createEventSchema = external_exports.object({
37717
37743
  startTime: external_exports.string().optional(),
37718
37744
  endTime: external_exports.string().optional(),
37719
37745
  registrationClosesAt: external_exports.number().optional(),
37720
- isMajor: external_exports.boolean().default(false),
37746
+ isSpotlight: external_exports.boolean().default(false),
37721
37747
  maxSlots: external_exports.number().int().min(0).default(0),
37722
37748
  gformsId: external_exports.string().optional(),
37723
37749
  gformsUrl: external_exports.string().url().optional(),
@@ -37726,11 +37752,11 @@ var createEventSchema = external_exports.object({
37726
37752
  contentfulEntryId: external_exports.string().optional(),
37727
37753
  status: external_exports.enum(["draft", "queued", "published"]).default("draft")
37728
37754
  });
37729
- var eventsRoute = new Hono();
37755
+ var classesRoute = new Hono();
37730
37756
  function generateSlug(title) {
37731
37757
  return title.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
37732
37758
  }
37733
- eventsRoute.get("/admin", authMiddleware, adminMiddleware, async (c) => {
37759
+ classesRoute.get("/admin", authMiddleware, adminMiddleware, async (c) => {
37734
37760
  const db = createDb(c.env.DB);
37735
37761
  const data = await db.query.events.findMany({
37736
37762
  with: { theme: true, organization: true },
@@ -37738,7 +37764,7 @@ eventsRoute.get("/admin", authMiddleware, adminMiddleware, async (c) => {
37738
37764
  });
37739
37765
  return c.json({ data });
37740
37766
  });
37741
- eventsRoute.post("/admin/publish", authMiddleware, adminMiddleware, async (c) => {
37767
+ classesRoute.post("/admin/publish", authMiddleware, adminMiddleware, async (c) => {
37742
37768
  const body = await c.req.json();
37743
37769
  const db = createDb(c.env.DB);
37744
37770
  const cache3 = new CacheService(c.env.KV);
@@ -37766,7 +37792,7 @@ eventsRoute.post("/admin/publish", authMiddleware, adminMiddleware, async (c) =>
37766
37792
  ]);
37767
37793
  return c.json({ data: { updated: body.ids.length } });
37768
37794
  });
37769
- eventsRoute.get("/", eventsListRateLimit, async (c) => {
37795
+ classesRoute.get("/", eventsListRateLimit, async (c) => {
37770
37796
  const db = createDb(c.env.DB);
37771
37797
  const cache3 = new CacheService(c.env.KV);
37772
37798
  const [latest] = await db.select({ max: events.publishedAt }).from(events).where(eq(events.status, "published")).limit(1);
@@ -37777,7 +37803,7 @@ eventsRoute.get("/", eventsListRateLimit, async (c) => {
37777
37803
  );
37778
37804
  const ifNoneMatch = c.req.header("If-None-Match");
37779
37805
  if (ifNoneMatch === etag) {
37780
- return c.newResponse(null, 304);
37806
+ return c.body(null, 304);
37781
37807
  }
37782
37808
  const data = await cache3.getOrSet(
37783
37809
  EVENTS_LIST_KV_KEY,
@@ -37801,7 +37827,7 @@ eventsRoute.get("/", eventsListRateLimit, async (c) => {
37801
37827
  startTime: true,
37802
37828
  endTime: true,
37803
37829
  registrationClosesAt: true,
37804
- isMajor: true,
37830
+ isSpotlight: true,
37805
37831
  maxSlots: true,
37806
37832
  registeredSlots: true,
37807
37833
  gformsUrl: true,
@@ -37818,7 +37844,7 @@ eventsRoute.get("/", eventsListRateLimit, async (c) => {
37818
37844
  );
37819
37845
  return c.json({ data });
37820
37846
  });
37821
- eventsRoute.get("/:slug", async (c) => {
37847
+ classesRoute.get("/:slug", async (c) => {
37822
37848
  const { slug } = c.req.param();
37823
37849
  const db = createDb(c.env.DB);
37824
37850
  const event = await db.query.events.findFirst({
@@ -37830,7 +37856,7 @@ eventsRoute.get("/:slug", async (c) => {
37830
37856
  if (!event) throw notFound("Event");
37831
37857
  return c.json({ data: event });
37832
37858
  });
37833
- eventsRoute.get("/:slug/slots", eventsSlotsRateLimit, async (c) => {
37859
+ classesRoute.get("/:slug/slots", eventsSlotsRateLimit, async (c) => {
37834
37860
  const { slug } = c.req.param();
37835
37861
  const db = createDb(c.env.DB);
37836
37862
  const cache3 = new CacheService(c.env.KV);
@@ -37840,7 +37866,7 @@ eventsRoute.get("/:slug/slots", eventsSlotsRateLimit, async (c) => {
37840
37866
  c.header("Cache-Control", "public, max-age=5, stale-while-revalidate=5");
37841
37867
  return c.json({ data: info2 });
37842
37868
  });
37843
- eventsRoute.post(
37869
+ classesRoute.post(
37844
37870
  "/",
37845
37871
  authMiddleware,
37846
37872
  adminMiddleware,
@@ -37875,11 +37901,13 @@ eventsRoute.post(
37875
37901
  cache3.del(EVENTS_LIST_KV_KEY),
37876
37902
  cache3.del(EVENTS_ETAG_KV_KEY)
37877
37903
  ]);
37878
- c.executionCtx.waitUntil(pushEventToContentful(c.env, created));
37904
+ if (c.get("cmsMode") === "hybrid") {
37905
+ c.executionCtx.waitUntil(pushEventToContentful(c.env, created));
37906
+ }
37879
37907
  return c.json({ data: created }, 201);
37880
37908
  }
37881
37909
  );
37882
- eventsRoute.patch("/:slug", authMiddleware, adminMiddleware, async (c) => {
37910
+ classesRoute.patch("/:slug", authMiddleware, adminMiddleware, async (c) => {
37883
37911
  const { slug } = c.req.param();
37884
37912
  const body = await c.req.json();
37885
37913
  const db = createDb(c.env.DB);
@@ -37894,10 +37922,12 @@ eventsRoute.patch("/:slug", authMiddleware, adminMiddleware, async (c) => {
37894
37922
  cache3.del(EVENTS_LIST_KV_KEY),
37895
37923
  cache3.del(EVENTS_ETAG_KV_KEY)
37896
37924
  ]);
37897
- c.executionCtx.waitUntil(pushEventToContentful(c.env, updated));
37925
+ if (c.get("cmsMode") === "hybrid") {
37926
+ c.executionCtx.waitUntil(pushEventToContentful(c.env, updated));
37927
+ }
37898
37928
  return c.json({ data: updated });
37899
37929
  });
37900
- eventsRoute.delete("/:slug", authMiddleware, adminMiddleware, async (c) => {
37930
+ classesRoute.delete("/:slug", authMiddleware, adminMiddleware, async (c) => {
37901
37931
  const { slug } = c.req.param();
37902
37932
  const db = createDb(c.env.DB);
37903
37933
  const cache3 = new CacheService(c.env.KV);
@@ -37996,7 +38026,447 @@ usersRoute.delete("/me/bookmarks/:eventId", authMiddleware, async (c) => {
37996
38026
  return c.json({ data: { bookmarked: false } });
37997
38027
  });
37998
38028
 
38029
+ // src/services/contentful.ts
38030
+ var CONTENTFUL_CDN = "https://cdn.contentful.com";
38031
+ var ContentfulService = class _ContentfulService {
38032
+ spaceId;
38033
+ accessToken;
38034
+ environment;
38035
+ constructor(spaceId, accessToken, environment = "master") {
38036
+ this.spaceId = spaceId;
38037
+ this.accessToken = accessToken;
38038
+ this.environment = environment;
38039
+ }
38040
+ /**
38041
+ * Returns true if the required Contentful credentials are configured.
38042
+ */
38043
+ static isConfigured(spaceId, accessToken) {
38044
+ return !!(spaceId && accessToken);
38045
+ }
38046
+ // ─── Entries ─────────────────────────────────────────────────────────────
38047
+ /**
38048
+ * Fetch all entries of a given content type.
38049
+ * Handles pagination automatically (100 per page, Contentful max).
38050
+ */
38051
+ async getEntries(contentTypeId) {
38052
+ const allItems = [];
38053
+ let skip = 0;
38054
+ const limit = 100;
38055
+ do {
38056
+ const url2 = this.buildUrl(`/entries`, {
38057
+ content_type: contentTypeId,
38058
+ skip: String(skip),
38059
+ limit: String(limit),
38060
+ include: "2"
38061
+ // resolve up to 2 levels of linked entries/assets
38062
+ });
38063
+ const res = await fetch(url2, { headers: this.headers() });
38064
+ if (!res.ok) {
38065
+ throw new Error(`Contentful entries error: ${res.status} ${await res.text()}`);
38066
+ }
38067
+ const data = await res.json();
38068
+ allItems.push(...data.items);
38069
+ skip += limit;
38070
+ if (allItems.length >= data.total) break;
38071
+ } while (true);
38072
+ return allItems;
38073
+ }
38074
+ /**
38075
+ * Fetch all assets. Handles pagination.
38076
+ */
38077
+ async getAssets() {
38078
+ const allItems = [];
38079
+ let skip = 0;
38080
+ const limit = 100;
38081
+ do {
38082
+ const url2 = this.buildUrl(`/assets`, {
38083
+ skip: String(skip),
38084
+ limit: String(limit)
38085
+ });
38086
+ const res = await fetch(url2, { headers: this.headers() });
38087
+ if (!res.ok) {
38088
+ throw new Error(`Contentful assets error: ${res.status} ${await res.text()}`);
38089
+ }
38090
+ const data = await res.json();
38091
+ allItems.push(...data.items);
38092
+ skip += limit;
38093
+ if (allItems.length >= data.total) break;
38094
+ } while (true);
38095
+ return allItems;
38096
+ }
38097
+ // ─── Asset file download ─────────────────────────────────────────────────
38098
+ /**
38099
+ * Download an asset file from Contentful's CDN.
38100
+ * Returns the raw ArrayBuffer and content type.
38101
+ */
38102
+ async downloadAsset(assetUrl) {
38103
+ const url2 = assetUrl.startsWith("//") ? `https:${assetUrl}` : assetUrl;
38104
+ const res = await fetch(url2);
38105
+ if (!res.ok) {
38106
+ throw new Error(`Failed to download asset: ${res.status}`);
38107
+ }
38108
+ const contentType = res.headers.get("Content-Type") ?? "application/octet-stream";
38109
+ const data = await res.arrayBuffer();
38110
+ return { data, contentType };
38111
+ }
38112
+ // ─── Helpers ─────────────────────────────────────────────────────────────
38113
+ /**
38114
+ * Extract a field value from a Contentful entry, handling locale wrapping.
38115
+ * Contentful fields are often `{ "en-US": value }` — this unwraps them.
38116
+ */
38117
+ static getField(entry, fieldName) {
38118
+ const raw = entry.fields[fieldName];
38119
+ if (raw === void 0 || raw === null) return void 0;
38120
+ if (typeof raw === "object" && !Array.isArray(raw) && "en-US" in raw) {
38121
+ return raw["en-US"];
38122
+ }
38123
+ return raw;
38124
+ }
38125
+ /**
38126
+ * Extract a linked entry/sys reference ID from a reference field.
38127
+ */
38128
+ static getRefId(entry, fieldName) {
38129
+ const ref = _ContentfulService.getField(entry, fieldName);
38130
+ return ref?.sys?.id;
38131
+ }
38132
+ /**
38133
+ * Extract an asset URL from a linked asset field.
38134
+ */
38135
+ static getAssetUrl(entry, fieldName) {
38136
+ const asset = _ContentfulService.getField(entry, fieldName);
38137
+ return asset?.sys?.id;
38138
+ }
38139
+ /**
38140
+ * Resolve an asset URL by ID from a list of fetched assets.
38141
+ */
38142
+ static resolveAssetUrl(assets, assetId) {
38143
+ const asset = assets.find((a) => a.sys.id === assetId);
38144
+ return asset?.fields?.file?.url;
38145
+ }
38146
+ // ─── Private ─────────────────────────────────────────────────────────────
38147
+ headers() {
38148
+ return {
38149
+ Authorization: `Bearer ${this.accessToken}`,
38150
+ "Content-Type": "application/json"
38151
+ };
38152
+ }
38153
+ buildUrl(path, params = {}) {
38154
+ const url2 = new URL(
38155
+ `/spaces/${this.spaceId}/environments/${this.environment}${path}`,
38156
+ CONTENTFUL_CDN
38157
+ );
38158
+ for (const [key, value] of Object.entries(params)) {
38159
+ url2.searchParams.set(key, value);
38160
+ }
38161
+ return url2.toString();
38162
+ }
38163
+ };
38164
+
37999
38165
  // src/services/snapshot.ts
38166
+ var DEFAULT_FIELDS = {
38167
+ event: {
38168
+ title: "title",
38169
+ slug: "slug",
38170
+ theme: "theme",
38171
+ organization: "organization",
38172
+ venue: "venue",
38173
+ date: "date",
38174
+ startTime: "startTime",
38175
+ endTime: "endTime",
38176
+ price: "price",
38177
+ image: "image",
38178
+ isSpotlight: "isSpotlight",
38179
+ maxSlots: "maxSlots",
38180
+ gformsUrl: "gformsUrl",
38181
+ gformsEditorUrl: "gformsEditorUrl",
38182
+ registrationClosesAt: "registrationClosesAt",
38183
+ classCode: "classCode"
38184
+ },
38185
+ theme: {
38186
+ name: "name",
38187
+ path: "path"
38188
+ },
38189
+ faq: {
38190
+ question: "question",
38191
+ answer: "answer",
38192
+ category: "category",
38193
+ sortOrder: "sortOrder"
38194
+ },
38195
+ organization: {
38196
+ name: "name",
38197
+ acronym: "acronym",
38198
+ logoUrl: "logoUrl",
38199
+ link: "link"
38200
+ },
38201
+ siteConfig: {
38202
+ key: "key",
38203
+ value: "value"
38204
+ }
38205
+ };
38206
+ var CONTENTFUL_CACHE_PREFIX = "contentful:cache";
38207
+ var CONTENTFUL_CACHE_TTL = 300;
38208
+ async function snapshotAllContent(db, bucket, contentful, config3 = {}, kv) {
38209
+ const mergedConfig = {
38210
+ eventTypeId: config3.eventTypeId ?? "event",
38211
+ themeTypeId: config3.themeTypeId ?? "theme",
38212
+ faqTypeId: config3.faqTypeId ?? "faq",
38213
+ organizationTypeId: config3.organizationTypeId ?? "organization",
38214
+ fields: {
38215
+ event: { ...DEFAULT_FIELDS.event, ...config3.fields?.event },
38216
+ theme: { ...DEFAULT_FIELDS.theme, ...config3.fields?.theme },
38217
+ faq: { ...DEFAULT_FIELDS.faq, ...config3.fields?.faq },
38218
+ organization: { ...DEFAULT_FIELDS.organization, ...config3.fields?.organization },
38219
+ siteConfig: { ...DEFAULT_FIELDS.siteConfig, ...config3.fields?.siteConfig }
38220
+ }
38221
+ };
38222
+ const result = {
38223
+ themesSynced: 0,
38224
+ eventsSynced: 0,
38225
+ faqsSynced: 0,
38226
+ organizationsSynced: 0,
38227
+ imagesUploaded: 0,
38228
+ imagesSkipped: 0,
38229
+ errors: []
38230
+ };
38231
+ let allAssets = [];
38232
+ if (bucket) {
38233
+ try {
38234
+ allAssets = await contentful.getAssets();
38235
+ } catch (err) {
38236
+ result.errors.push(`Failed to fetch assets: ${err}`);
38237
+ }
38238
+ }
38239
+ try {
38240
+ const cacheKey = `${CONTENTFUL_CACHE_PREFIX}:themes`;
38241
+ let themeEntries;
38242
+ if (kv) {
38243
+ const cached2 = await kv.get(cacheKey, "json");
38244
+ if (cached2) {
38245
+ themeEntries = cached2;
38246
+ } else {
38247
+ themeEntries = await contentful.getEntries(mergedConfig.themeTypeId);
38248
+ await kv.put(cacheKey, JSON.stringify(themeEntries), { expirationTtl: CONTENTFUL_CACHE_TTL });
38249
+ }
38250
+ } else {
38251
+ themeEntries = await contentful.getEntries(mergedConfig.themeTypeId);
38252
+ }
38253
+ result.themesSynced = await syncThemes(db, themeEntries, mergedConfig);
38254
+ } catch (err) {
38255
+ result.errors.push(`Themes sync failed: ${err}`);
38256
+ }
38257
+ try {
38258
+ const cacheKey = `${CONTENTFUL_CACHE_PREFIX}:organizations`;
38259
+ let orgEntries;
38260
+ if (kv) {
38261
+ const cached2 = await kv.get(cacheKey, "json");
38262
+ if (cached2) {
38263
+ orgEntries = cached2;
38264
+ } else {
38265
+ orgEntries = await contentful.getEntries(mergedConfig.organizationTypeId);
38266
+ await kv.put(cacheKey, JSON.stringify(orgEntries), { expirationTtl: CONTENTFUL_CACHE_TTL });
38267
+ }
38268
+ } else {
38269
+ orgEntries = await contentful.getEntries(mergedConfig.organizationTypeId);
38270
+ }
38271
+ await syncOrganizations(db, orgEntries, mergedConfig);
38272
+ } catch (err) {
38273
+ result.errors.push(`Organizations sync failed: ${err}`);
38274
+ }
38275
+ try {
38276
+ const cacheKey = `${CONTENTFUL_CACHE_PREFIX}:events`;
38277
+ let eventEntries;
38278
+ if (kv) {
38279
+ const cached2 = await kv.get(cacheKey, "json");
38280
+ if (cached2) {
38281
+ eventEntries = cached2;
38282
+ } else {
38283
+ eventEntries = await contentful.getEntries(mergedConfig.eventTypeId);
38284
+ await kv.put(cacheKey, JSON.stringify(eventEntries), { expirationTtl: CONTENTFUL_CACHE_TTL });
38285
+ }
38286
+ } else {
38287
+ eventEntries = await contentful.getEntries(mergedConfig.eventTypeId);
38288
+ }
38289
+ result.eventsSynced = await syncEvents(db, bucket, contentful, allAssets, eventEntries, mergedConfig, result);
38290
+ } catch (err) {
38291
+ result.errors.push(`Events sync failed: ${err}`);
38292
+ }
38293
+ try {
38294
+ const cacheKey = `${CONTENTFUL_CACHE_PREFIX}:faqs`;
38295
+ let faqEntries;
38296
+ if (kv) {
38297
+ const cached2 = await kv.get(cacheKey, "json");
38298
+ if (cached2) {
38299
+ faqEntries = cached2;
38300
+ } else {
38301
+ faqEntries = await contentful.getEntries(mergedConfig.faqTypeId);
38302
+ await kv.put(cacheKey, JSON.stringify(faqEntries), { expirationTtl: CONTENTFUL_CACHE_TTL });
38303
+ }
38304
+ } else {
38305
+ faqEntries = await contentful.getEntries(mergedConfig.faqTypeId);
38306
+ }
38307
+ result.faqsSynced = await syncFaqs(db, faqEntries, mergedConfig);
38308
+ } catch (err) {
38309
+ result.errors.push(`FAQs sync failed: ${err}`);
38310
+ }
38311
+ console.log(
38312
+ `[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`
38313
+ );
38314
+ return result;
38315
+ }
38316
+ async function syncThemes(db, entries, config3) {
38317
+ let count2 = 0;
38318
+ for (const entry of entries) {
38319
+ const cfId = entry.sys.id;
38320
+ const name = ContentfulService.getField(entry, config3.fields.theme.name);
38321
+ const path = ContentfulService.getField(entry, config3.fields.theme.path);
38322
+ if (!name || !path) continue;
38323
+ await db.insert(themes).values({ id: cfId, name, path }).onConflictDoUpdate({
38324
+ target: themes.id,
38325
+ set: { name, path }
38326
+ });
38327
+ count2++;
38328
+ }
38329
+ return count2;
38330
+ }
38331
+ async function syncOrganizations(db, entries, config3) {
38332
+ let count2 = 0;
38333
+ for (const entry of entries) {
38334
+ const cfId = entry.sys.id;
38335
+ const f = config3.fields.organization;
38336
+ const name = ContentfulService.getField(entry, f.name);
38337
+ const acronym = ContentfulService.getField(entry, f.acronym);
38338
+ if (!name || !acronym) continue;
38339
+ const logoUrl = ContentfulService.getField(entry, f.logoUrl) ?? null;
38340
+ const link = ContentfulService.getField(entry, f.link) ?? null;
38341
+ await db.insert(organizations).values({ id: cfId, name, acronym, logoUrl, link }).onConflictDoUpdate({
38342
+ target: organizations.id,
38343
+ set: { name, acronym, logoUrl, link }
38344
+ });
38345
+ count2++;
38346
+ }
38347
+ return count2;
38348
+ }
38349
+ async function syncEvents(db, bucket, contentful, allAssets, entries, config3, result) {
38350
+ let count2 = 0;
38351
+ for (const entry of entries) {
38352
+ try {
38353
+ const cfId = entry.sys.id;
38354
+ const f = config3.fields.event;
38355
+ const title = ContentfulService.getField(entry, f.title);
38356
+ if (!title) {
38357
+ result.errors.push(`Event ${cfId}: missing title, skipping`);
38358
+ continue;
38359
+ }
38360
+ const slug = ContentfulService.getField(entry, f.slug) ?? slugify2(title);
38361
+ const themeRef = ContentfulService.getField(entry, f.theme);
38362
+ const themeId = themeRef?.sys?.id ?? null;
38363
+ const orgRef = ContentfulService.getField(entry, f.organization);
38364
+ const organizationId = orgRef?.sys?.id ?? null;
38365
+ let backgroundImageUrl = null;
38366
+ if (bucket) {
38367
+ const imageRef = ContentfulService.getField(entry, f.image);
38368
+ const assetId = imageRef?.sys?.id;
38369
+ if (assetId) {
38370
+ const assetUrl = ContentfulService.resolveAssetUrl(allAssets, assetId);
38371
+ if (assetUrl) {
38372
+ const r2Key = `contentful/${assetId}`;
38373
+ const uploaded = await uploadAssetIfChanged(bucket, contentful, assetUrl, r2Key);
38374
+ if (uploaded.skipped) {
38375
+ result.imagesSkipped++;
38376
+ } else {
38377
+ result.imagesUploaded++;
38378
+ }
38379
+ backgroundImageUrl = `/uploads/images/${r2Key}`;
38380
+ }
38381
+ }
38382
+ }
38383
+ const values = {
38384
+ id: cfId,
38385
+ contentfulEntryId: cfId,
38386
+ title,
38387
+ slug,
38388
+ themeId,
38389
+ organizationId,
38390
+ updatedAt: entry.sys.updatedAt
38391
+ };
38392
+ const venue = ContentfulService.getField(entry, f.venue);
38393
+ if (venue !== void 0) values.venue = venue;
38394
+ const date5 = ContentfulService.getField(entry, f.date);
38395
+ if (date5 !== void 0) values.dateTime = date5;
38396
+ const price = ContentfulService.getField(entry, f.price);
38397
+ if (price !== void 0) values.price = price;
38398
+ if (backgroundImageUrl) values.backgroundImageUrl = backgroundImageUrl;
38399
+ const isSpotlight = ContentfulService.getField(entry, f.isSpotlight);
38400
+ if (isSpotlight !== void 0) values.isSpotlight = isSpotlight;
38401
+ const maxSlots = ContentfulService.getField(entry, f.maxSlots);
38402
+ if (maxSlots !== void 0) values.maxSlots = maxSlots;
38403
+ const gformsUrl = ContentfulService.getField(entry, f.gformsUrl);
38404
+ if (gformsUrl !== void 0) values.gformsUrl = gformsUrl;
38405
+ const gformsEditorUrl = ContentfulService.getField(entry, f.gformsEditorUrl);
38406
+ if (gformsEditorUrl !== void 0) values.gformsEditorUrl = gformsEditorUrl;
38407
+ const classCode = ContentfulService.getField(entry, f.classCode);
38408
+ if (classCode !== void 0) values.classCode = classCode;
38409
+ const startTime = ContentfulService.getField(entry, f.startTime);
38410
+ if (startTime !== void 0) values.startTime = startTime;
38411
+ const endTime = ContentfulService.getField(entry, f.endTime);
38412
+ if (endTime !== void 0) values.endTime = endTime;
38413
+ const regCloseRaw = ContentfulService.getField(entry, f.registrationClosesAt);
38414
+ if (regCloseRaw) {
38415
+ const ms = Date.parse(regCloseRaw);
38416
+ if (!Number.isNaN(ms)) values.registrationClosesAt = Math.floor(ms / 1e3);
38417
+ }
38418
+ await db.insert(events).values(values).onConflictDoUpdate({
38419
+ target: events.id,
38420
+ set: values
38421
+ });
38422
+ count2++;
38423
+ } catch (err) {
38424
+ result.errors.push(`Event ${entry.sys.id}: ${err}`);
38425
+ }
38426
+ }
38427
+ return count2;
38428
+ }
38429
+ async function syncFaqs(db, entries, config3) {
38430
+ let count2 = 0;
38431
+ for (const entry of entries) {
38432
+ const cfId = entry.sys.id;
38433
+ const f = config3.fields.faq;
38434
+ const question = ContentfulService.getField(entry, f.question);
38435
+ const answer = ContentfulService.getField(entry, f.answer);
38436
+ if (!question || !answer) continue;
38437
+ const category = ContentfulService.getField(entry, f.category) ?? null;
38438
+ const sortOrder = ContentfulService.getField(entry, f.sortOrder) ?? 0;
38439
+ await db.insert(faqs).values({
38440
+ id: cfId,
38441
+ question,
38442
+ answer,
38443
+ category,
38444
+ sortOrder
38445
+ }).onConflictDoUpdate({
38446
+ target: faqs.id,
38447
+ set: { question, answer, category, sortOrder }
38448
+ });
38449
+ count2++;
38450
+ }
38451
+ return count2;
38452
+ }
38453
+ async function uploadAssetIfChanged(bucket, contentful, assetUrl, r2Key) {
38454
+ const { data, contentType } = await contentful.downloadAsset(assetUrl);
38455
+ const sha = await computeSha256(data);
38456
+ const existing = await bucket.head(r2Key);
38457
+ if (existing?.customMetadata?.sha256 === sha) {
38458
+ return { skipped: true };
38459
+ }
38460
+ await bucket.put(r2Key, data, {
38461
+ httpMetadata: { contentType },
38462
+ customMetadata: {
38463
+ sha256: sha,
38464
+ source: "contentful",
38465
+ syncedAt: (/* @__PURE__ */ new Date()).toISOString()
38466
+ }
38467
+ });
38468
+ return { skipped: false };
38469
+ }
38000
38470
  async function batchRun(items, fn, concurrency = 5) {
38001
38471
  const results = [];
38002
38472
  for (let i = 0; i < items.length; i += concurrency) {
@@ -38006,6 +38476,13 @@ async function batchRun(items, fn, concurrency = 5) {
38006
38476
  }
38007
38477
  return results;
38008
38478
  }
38479
+ async function computeSha256(data) {
38480
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
38481
+ return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
38482
+ }
38483
+ function slugify2(text2) {
38484
+ return text2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
38485
+ }
38009
38486
  async function ensureContentTypes(mgmt, config3 = {}) {
38010
38487
  const eventTypeId = config3.eventTypeId ?? "event";
38011
38488
  const themeTypeId = config3.themeTypeId ?? "theme";
@@ -38026,7 +38503,7 @@ async function ensureContentTypes(mgmt, config3 = {}) {
38026
38503
  { id: "endTime", name: "End Time", type: "Symbol" },
38027
38504
  { id: "price", name: "Price", type: "Symbol" },
38028
38505
  { id: "image", name: "Image", type: "Link", linkType: "Asset" },
38029
- { id: "isMajor", name: "Major Event", type: "Boolean" },
38506
+ { id: "isSpotlight", name: "Spotlight", type: "Boolean" },
38030
38507
  { id: "maxSlots", name: "Max Slots", type: "Integer" },
38031
38508
  { id: "gformsUrl", name: "Google Forms URL", type: "Symbol" },
38032
38509
  { id: "gformsEditorUrl", name: "Google Forms Editor URL", type: "Symbol" },
@@ -38111,7 +38588,7 @@ async function pushToContentful(db, mgmt, config3 = {}, kv, forceFull = false) {
38111
38588
  const fields = {
38112
38589
  title: ContentfulManagement.locale(event.title),
38113
38590
  slug: ContentfulManagement.locale(event.slug),
38114
- isMajor: ContentfulManagement.locale(event.isMajor),
38591
+ isSpotlight: ContentfulManagement.locale(event.isSpotlight),
38115
38592
  maxSlots: ContentfulManagement.locale(event.maxSlots)
38116
38593
  };
38117
38594
  if (event.themeId) fields.theme = ContentfulManagement.entryRef(event.themeId);
@@ -38182,6 +38659,9 @@ siteConfigRoute.get("/", async (c) => {
38182
38659
  siteName: config3.site_name ?? null,
38183
38660
  registrationGloballyOpen: config3.registration_globally_open ?? true,
38184
38661
  maintenanceMode: config3.maintenance_mode ?? false,
38662
+ // Prefer the D1-persisted value over the env-var default so that
38663
+ // PATCH /config/cms_mode changes are reflected immediately.
38664
+ cmsMode: config3.cms_mode ?? c.get("cmsMode"),
38185
38665
  now: Math.floor(Date.now() / 1e3)
38186
38666
  }
38187
38667
  });
@@ -38203,6 +38683,10 @@ siteConfigRoute.patch("/:key", authMiddleware, adminMiddleware, async (c) => {
38203
38683
  var SYNC_LOCK_KEY = "contentful:sync:lock";
38204
38684
  var SYNC_LOCK_TTL = 60;
38205
38685
  siteConfigRoute.post("/sync-content", authMiddleware, adminMiddleware, async (c) => {
38686
+ const cmsMode = c.get("cmsMode");
38687
+ if (cmsMode === "cloudflare") {
38688
+ throw serviceUnavailable("Contentful sync is not available in Cloudflare-only mode.");
38689
+ }
38206
38690
  if (!ContentfulManagement.isConfigured(c.env.CONTENTFUL_SPACE_ID, c.env.CONTENTFUL_MANAGEMENT_TOKEN)) {
38207
38691
  throw serviceUnavailable("Contentful Management API credentials not configured.");
38208
38692
  }
@@ -38289,7 +38773,9 @@ faqsRoute.post(
38289
38773
  const cache3 = new CacheService(c.env.KV);
38290
38774
  const [created] = await db.insert(faqs).values(body).returning();
38291
38775
  await cache3.del(FAQS_KV_KEY);
38292
- c.executionCtx.waitUntil(pushFaqToContentful(c.env, created));
38776
+ if (c.get("cmsMode") === "hybrid") {
38777
+ c.executionCtx.waitUntil(pushFaqToContentful(c.env, created));
38778
+ }
38293
38779
  return c.json({ data: created }, 201);
38294
38780
  }
38295
38781
  );
@@ -38302,7 +38788,9 @@ faqsRoute.patch("/:id", authMiddleware, adminMiddleware, async (c) => {
38302
38788
  const [updated] = await db.update(faqs).set({ ...body, updatedAt: now2 }).where(eq(faqs.id, id)).returning();
38303
38789
  if (!updated) throw notFound("FAQ");
38304
38790
  await cache3.del(FAQS_KV_KEY);
38305
- c.executionCtx.waitUntil(pushFaqToContentful(c.env, updated));
38791
+ if (c.get("cmsMode") === "hybrid") {
38792
+ c.executionCtx.waitUntil(pushFaqToContentful(c.env, updated));
38793
+ }
38306
38794
  return c.json({ data: updated });
38307
38795
  });
38308
38796
  faqsRoute.delete("/:id", authMiddleware, adminMiddleware, async (c) => {
@@ -38578,7 +39066,9 @@ themesRoute.post(
38578
39066
  const db = createDb(c.env.DB);
38579
39067
  try {
38580
39068
  const [created] = await db.insert(themes).values(body).returning();
38581
- c.executionCtx.waitUntil(pushThemeToContentful(c.env, created));
39069
+ if (c.get("cmsMode") === "hybrid") {
39070
+ c.executionCtx.waitUntil(pushThemeToContentful(c.env, created));
39071
+ }
38582
39072
  return c.json({ data: created }, 201);
38583
39073
  } catch (err) {
38584
39074
  if (err.message && err.message.includes("UNIQUE constraint failed")) {
@@ -38599,7 +39089,9 @@ themesRoute.patch(
38599
39089
  try {
38600
39090
  const [updated] = await db.update(themes).set(body).where(eq(themes.id, id)).returning();
38601
39091
  if (!updated) throw notFound("Theme");
38602
- c.executionCtx.waitUntil(pushThemeToContentful(c.env, updated));
39092
+ if (c.get("cmsMode") === "hybrid") {
39093
+ c.executionCtx.waitUntil(pushThemeToContentful(c.env, updated));
39094
+ }
38603
39095
  return c.json({ data: updated });
38604
39096
  } catch (err) {
38605
39097
  if (err.message && err.message.includes("UNIQUE constraint failed")) {
@@ -38614,7 +39106,9 @@ themesRoute.delete("/:id", authMiddleware, adminMiddleware, async (c) => {
38614
39106
  const db = createDb(c.env.DB);
38615
39107
  const [deleted] = await db.delete(themes).where(eq(themes.id, id)).returning();
38616
39108
  if (!deleted) throw notFound("Theme");
38617
- c.executionCtx.waitUntil(deleteThemeFromContentful(c.env, id));
39109
+ if (c.get("cmsMode") === "hybrid") {
39110
+ c.executionCtx.waitUntil(deleteThemeFromContentful(c.env, id));
39111
+ }
38618
39112
  return c.body(null, 204);
38619
39113
  });
38620
39114
 
@@ -38678,6 +39172,83 @@ organizationsRoute.delete("/:id", authMiddleware, adminMiddleware, async (c) =>
38678
39172
  return c.body(null, 204);
38679
39173
  });
38680
39174
 
39175
+ // src/routes/contentful-sync.ts
39176
+ var contentfulSyncRoute = new Hono();
39177
+ contentfulSyncRoute.post(
39178
+ "/trigger",
39179
+ authMiddleware,
39180
+ adminMiddleware,
39181
+ async (c) => {
39182
+ const env2 = c.env;
39183
+ const { CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_ENVIRONMENT } = env2;
39184
+ if (!CONTENTFUL_SPACE_ID || !CONTENTFUL_ACCESS_TOKEN) {
39185
+ return c.json({ error: { code: "NOT_CONFIGURED", message: "Contentful credentials missing" } }, 400);
39186
+ }
39187
+ const db = createDb(env2.DB);
39188
+ const contentful = new ContentfulService(CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_ENVIRONMENT);
39189
+ try {
39190
+ if (CONTENTFUL_MANAGEMENT_TOKEN) {
39191
+ const mgmt = new ContentfulManagement(CONTENTFUL_SPACE_ID, CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_ENVIRONMENT);
39192
+ await ensureContentTypes(mgmt);
39193
+ }
39194
+ c.executionCtx.waitUntil(
39195
+ 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))
39196
+ );
39197
+ return c.json({ message: "Contentful sync triggered", triggeredAt: Math.floor(Date.now() / 1e3) });
39198
+ } catch (err) {
39199
+ console.error("[Contentful] Sync trigger failed:", err);
39200
+ return c.json({ error: { code: "SYNC_FAILED", message: err?.message ?? "Failed to trigger sync" } }, 500);
39201
+ }
39202
+ }
39203
+ );
39204
+ contentfulSyncRoute.post(
39205
+ "/push",
39206
+ authMiddleware,
39207
+ adminMiddleware,
39208
+ async (c) => {
39209
+ const env2 = c.env;
39210
+ const { CONTENTFUL_SPACE_ID, CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_ENVIRONMENT } = env2;
39211
+ if (!CONTENTFUL_SPACE_ID || !CONTENTFUL_MANAGEMENT_TOKEN) {
39212
+ return c.json({ error: { code: "NOT_CONFIGURED", message: "Contentful management credentials missing" } }, 400);
39213
+ }
39214
+ const db = createDb(env2.DB);
39215
+ const mgmt = new ContentfulManagement(CONTENTFUL_SPACE_ID, CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_ENVIRONMENT);
39216
+ try {
39217
+ c.executionCtx.waitUntil(
39218
+ pushToContentful(db, mgmt, {}, env2.KV).then((r) => console.log("[Contentful] Push complete:", JSON.stringify(r))).catch((err) => console.error("[Contentful] Push failed:", err))
39219
+ );
39220
+ return c.json({ message: "Contentful push triggered", triggeredAt: Math.floor(Date.now() / 1e3) });
39221
+ } catch (err) {
39222
+ console.error("[Contentful] Push trigger failed:", err);
39223
+ return c.json({ error: { code: "SYNC_FAILED", message: err?.message ?? "Failed to trigger push" } }, 500);
39224
+ }
39225
+ }
39226
+ );
39227
+ contentfulSyncRoute.get(
39228
+ "/status",
39229
+ authMiddleware,
39230
+ adminMiddleware,
39231
+ async (c) => {
39232
+ const env2 = c.env;
39233
+ const db = createDb(env2.DB);
39234
+ const [[{ themes: themes2 }], [{ events: events2 }], [{ faqs: faqs2 }], [{ orgs }]] = await Promise.all([
39235
+ db.all(sql2`SELECT count(*) as themes FROM themes`),
39236
+ db.all(sql2`SELECT count(*) as events FROM events`),
39237
+ db.all(sql2`SELECT count(*) as faqs FROM faqs`),
39238
+ db.all(sql2`SELECT count(*) as orgs FROM organizations`)
39239
+ ]);
39240
+ const isConfigured = !!env2.CONTENTFUL_SPACE_ID && !!env2.CONTENTFUL_ACCESS_TOKEN;
39241
+ const canPush = !!env2.CONTENTFUL_SPACE_ID && !!env2.CONTENTFUL_MANAGEMENT_TOKEN;
39242
+ return c.json({
39243
+ isConfigured,
39244
+ canPush,
39245
+ cmsMode: c.get("cmsMode"),
39246
+ spaceId: env2.CONTENTFUL_SPACE_ID ?? null,
39247
+ totals: { themes: themes2, events: events2, faqs: faqs2, organizations: orgs }
39248
+ });
39249
+ }
39250
+ );
39251
+
38681
39252
  // src/app.ts
38682
39253
  function createApp(options = {}) {
38683
39254
  const app = new Hono();
@@ -38691,6 +39262,12 @@ function createApp(options = {}) {
38691
39262
  app.use("*", createCorsMiddleware(options.allowedOrigins ?? ["*"]));
38692
39263
  app.use("*", createPowChallengeMiddleware());
38693
39264
  app.use("*", createRefererGuard(options.allowedOrigins ?? ["*"]));
39265
+ app.use("*", async (c, next) => {
39266
+ const overrideRaw = await c.env.KV.get("config:cms_mode").catch(() => null);
39267
+ const override = overrideRaw ? JSON.parse(overrideRaw) : null;
39268
+ c.set("cmsMode", parseCmsMode(override ?? c.env.CMS_MODE));
39269
+ return next();
39270
+ });
38694
39271
  app.on(["POST", "GET"], "/api/auth/*", (c) => {
38695
39272
  const auth = createAuth(c.env);
38696
39273
  const req = c.req.raw;
@@ -38702,7 +39279,7 @@ function createApp(options = {}) {
38702
39279
  return auth.handler(new Request(req.url, {
38703
39280
  method: req.method,
38704
39281
  headers: newHeaders,
38705
- body: req.method === "GET" ? null : req.body,
39282
+ body: req.method === "GET" || req.method === "HEAD" || req.method === "OPTIONS" ? null : req.body,
38706
39283
  redirect: req.redirect
38707
39284
  }));
38708
39285
  }
@@ -38723,12 +39300,13 @@ function createApp(options = {}) {
38723
39300
  app.post(POW_VERIFY_PATH, handlePowVerify);
38724
39301
  app.route("/health", healthRoute);
38725
39302
  app.route("/api/config", siteConfigRoute);
38726
- app.route("/api/events", eventsRoute);
39303
+ app.route("/api/classes", classesRoute);
38727
39304
  app.route("/api/themes", themesRoute);
38728
39305
  app.route("/api/users", usersRoute);
38729
39306
  app.route("/api/organizations", organizationsRoute);
38730
39307
  app.route("/api/faqs", faqsRoute);
38731
39308
  app.route("/api/uploads", uploadsRoute);
39309
+ app.route("/api/contentful", contentfulSyncRoute);
38732
39310
  app.route("/internal/gforms-webhook", gformsWebhookRoute);
38733
39311
  app.onError(errorHandler);
38734
39312
  app.notFound(
@@ -39362,6 +39940,7 @@ var PATCH_STATEMENTS = [
39362
39940
  `ALTER TABLE "events" ADD COLUMN "class_code" text`,
39363
39941
  `ALTER TABLE "events" ADD COLUMN "start_time" text`,
39364
39942
  `ALTER TABLE "events" ADD COLUMN "end_time" text`,
39943
+ `ALTER TABLE "events" RENAME COLUMN "is_major" TO "is_spotlight"`,
39365
39944
  `CREATE INDEX IF NOT EXISTS "idx_events_organization_id" ON "events" ("organization_id")`
39366
39945
  ];
39367
39946
  var CREATE_STATEMENTS = [
@@ -39465,7 +40044,7 @@ var CREATE_STATEMENTS = [
39465
40044
  "class_code" text,
39466
40045
  "start_time" text,
39467
40046
  "end_time" text,
39468
- "is_major" integer DEFAULT false NOT NULL,
40047
+ "is_spotlight" integer DEFAULT false NOT NULL,
39469
40048
  "max_slots" integer DEFAULT 0 NOT NULL,
39470
40049
  "registered_slots" integer DEFAULT 0 NOT NULL,
39471
40050
  "gforms_id" text,
@@ -39527,7 +40106,9 @@ async function ensureDatabase(d1) {
39527
40106
  try {
39528
40107
  await d1.prepare(sql3).run();
39529
40108
  } catch (err) {
39530
- if (err?.message?.includes("duplicate column")) continue;
40109
+ if (err?.message?.includes("duplicate column") || err?.message?.includes("no such column: is_major")) {
40110
+ continue;
40111
+ }
39531
40112
  throw err;
39532
40113
  }
39533
40114
  }
@@ -39536,12 +40117,14 @@ async function ensureDatabase(d1) {
39536
40117
  // src/worker-handler.ts
39537
40118
  var API_PREFIXES = [
39538
40119
  "/api/auth/",
39539
- "/api/events",
40120
+ "/api/classes",
39540
40121
  "/api/users",
40122
+ "/api/organizations",
39541
40123
  "/api/faqs",
39542
40124
  "/api/themes",
39543
40125
  "/api/config",
39544
40126
  "/api/uploads",
40127
+ "/api/contentful",
39545
40128
  "/health",
39546
40129
  "/internal/",
39547
40130
  "/.well-known/"
@@ -39580,7 +40163,7 @@ function createWorkerHandler(options) {
39580
40163
  return getLeapify(env2).fetch(request, env2, ctx);
39581
40164
  }
39582
40165
  let response = await options.serveFrontend(request, env2, ctx);
39583
- if ((!response || response.status === 404) && !pathname.includes(".")) {
40166
+ if ((!response || response.status === 404) && !pathname.includes(".") && request.method === "GET") {
39584
40167
  const indexRequest = new Request(new URL("/", request.url), request);
39585
40168
  response = await options.serveFrontend(indexRequest, env2, ctx);
39586
40169
  }
@@ -39688,6 +40271,6 @@ function injectConfig2(html2, config3) {
39688
40271
  (*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) *)
39689
40272
  */
39690
40273
 
39691
- export { ContentfulManagement, authAccount, authSession, authUser, authVerification, bookmarks, bookmarksRelations, createDb, createLeapify, createQueueHandler, createWorkerHandler, ensureContentTypes, ensureDatabase, events, eventsRelations, faqs, getRuntimeConfig, injectConfig2 as injectConfig, organizations, organizationsRelations, siteConfig, themes, themesRelations, users };
40274
+ export { CONTENTFUL_CONFIG_KEYS, ContentfulManagement, authAccount, authSession, authUser, authVerification, bookmarks, bookmarksRelations, contentfulConfig, createDb, createLeapify, createQueueHandler, createWorkerHandler, ensureContentTypes, ensureDatabase, events, eventsRelations, faqs, getRuntimeConfig, injectConfig2 as injectConfig, organizations, organizationsRelations, parseCmsMode, shouldPullFromContentful, shouldPushToContentful, siteConfig, themes, themesRelations, users };
39692
40275
  //# sourceMappingURL=index.js.map
39693
40276
  //# sourceMappingURL=index.js.map