@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.
- package/dist/app.d.ts.map +1 -1
- package/dist/{chunk-QARF2YFF.cjs → chunk-BFMJDSDI.cjs} +3 -2
- package/dist/chunk-BFMJDSDI.cjs.map +1 -0
- package/dist/{chunk-ANNHE3PZ.js → chunk-LJ5BSSYE.js} +3 -2
- package/dist/{chunk-QARF2YFF.cjs.map → chunk-LJ5BSSYE.js.map} +1 -1
- package/dist/{chunk-63CUZGSZ.js → chunk-MCOLCTFX.js} +3 -2
- package/dist/chunk-MCOLCTFX.js.map +1 -0
- package/dist/{chunk-YFJBE3AU.cjs → chunk-MKWVLWVJ.cjs} +3 -2
- package/dist/chunk-MKWVLWVJ.cjs.map +1 -0
- package/dist/client/index.cjs +25 -25
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.ts +17 -17
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +25 -25
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts +4 -2
- package/dist/client/types.d.ts.map +1 -1
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/schema/{events.d.ts → classes.d.ts} +3 -3
- package/dist/db/schema/classes.d.ts.map +1 -0
- package/dist/db/schema/index.d.ts +1 -1
- package/dist/db/schema/index.d.ts.map +1 -1
- package/dist/db/schema/site-config.d.ts +83 -0
- package/dist/db/schema/site-config.d.ts.map +1 -1
- package/dist/index.cjs +640 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +616 -33
- package/dist/index.js.map +1 -1
- package/dist/lib/middleware/pow-challenge.cjs +6 -6
- package/dist/lib/middleware/pow-challenge.d.ts.map +1 -1
- package/dist/lib/middleware/pow-challenge.js +1 -1
- package/dist/routes/classes.d.ts +4 -0
- package/dist/routes/classes.d.ts.map +1 -0
- package/dist/routes/contentful-sync.d.ts +4 -0
- package/dist/routes/contentful-sync.d.ts.map +1 -0
- package/dist/services/snapshot.d.ts +1 -1
- package/dist/services/snapshot.d.ts.map +1 -1
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/worker-handler.d.ts.map +1 -1
- package/dist/worker.js +605 -29
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-63CUZGSZ.js.map +0 -1
- package/dist/chunk-ANNHE3PZ.js.map +0 -1
- package/dist/chunk-YFJBE3AU.cjs.map +0 -1
- package/dist/db/schema/events.d.ts.map +0 -1
- package/dist/routes/events.d.ts +0 -4
- 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-
|
|
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/
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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/
|
|
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
|
-
"
|
|
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"))
|
|
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/
|
|
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
|