@access-dlsu/leapify 0.260507.1 → 0.260507.5
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/auth/auth.d.ts.map +1 -1
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/{chunk-ANNHE3PZ.js → chunk-5YYVBPAE.js} +21 -5
- package/dist/chunk-5YYVBPAE.js.map +1 -0
- package/dist/{chunk-QARF2YFF.cjs → chunk-LVKPYSXI.cjs} +21 -5
- package/dist/chunk-LVKPYSXI.cjs.map +1 -0
- package/dist/{chunk-63CUZGSZ.js → chunk-OZ6HZKR5.js} +21 -5
- package/dist/chunk-OZ6HZKR5.js.map +1 -0
- package/dist/{chunk-YFJBE3AU.cjs → chunk-S5DBMZVP.cjs} +21 -5
- package/dist/chunk-S5DBMZVP.cjs.map +1 -0
- package/dist/client/auth.d.ts +1 -13
- package/dist/client/auth.d.ts.map +1 -1
- 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 +679 -59
- 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 +655 -40
- package/dist/index.js.map +1 -1
- package/dist/lib/middleware/cors.d.ts.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 +662 -39
- package/dist/worker.js.map +1 -1
- package/package.json +153 -153
- package/dist/chunk-63CUZGSZ.js.map +0 -1
- package/dist/chunk-ANNHE3PZ.js.map +0 -1
- package/dist/chunk-QARF2YFF.cjs.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.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var chunkOK6RVPEH_cjs = require('./chunk-OK6RVPEH.cjs');
|
|
4
4
|
var chunkRFP2X2FA_cjs = require('./chunk-RFP2X2FA.cjs');
|
|
5
5
|
require('./chunk-JPVIXCF5.cjs');
|
|
6
|
-
var
|
|
6
|
+
var chunkLVKPYSXI_cjs = require('./chunk-LVKPYSXI.cjs');
|
|
7
7
|
var chunk5JKLV7IE_cjs = require('./chunk-5JKLV7IE.cjs');
|
|
8
8
|
require('./chunk-NKIQRCOM.cjs');
|
|
9
9
|
var chunk4DPT2KQR_cjs = require('./chunk-4DPT2KQR.cjs');
|
|
@@ -50,13 +50,28 @@ var errorHandler = (err, c) => {
|
|
|
50
50
|
);
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
// src/types.ts
|
|
54
|
+
function parseCmsMode(raw) {
|
|
55
|
+
if (raw === "cloudflare" || raw === "contentful") return raw;
|
|
56
|
+
return "hybrid";
|
|
57
|
+
}
|
|
58
|
+
function shouldPushToContentful(mode) {
|
|
59
|
+
return mode === "hybrid";
|
|
60
|
+
}
|
|
61
|
+
function shouldPullFromContentful(mode) {
|
|
62
|
+
return mode === "contentful" || mode === "hybrid";
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
// node_modules/hono/dist/middleware/cors/index.js
|
|
54
66
|
var cors = (options) => {
|
|
55
|
-
const
|
|
67
|
+
const defaults = {
|
|
56
68
|
origin: "*",
|
|
57
69
|
allowMethods: ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH"],
|
|
58
70
|
allowHeaders: [],
|
|
59
|
-
exposeHeaders: []
|
|
71
|
+
exposeHeaders: []
|
|
72
|
+
};
|
|
73
|
+
const opts = {
|
|
74
|
+
...defaults,
|
|
60
75
|
...options
|
|
61
76
|
};
|
|
62
77
|
const findAllowOrigin = ((optsOrigin) => {
|
|
@@ -147,9 +162,22 @@ function createCorsMiddleware(allowedOrigins) {
|
|
|
147
162
|
});
|
|
148
163
|
return async (c, next) => {
|
|
149
164
|
const origin = c.req.header("origin");
|
|
165
|
+
if (c.req.path.startsWith("/api/uploads/images")) {
|
|
166
|
+
c.header("Access-Control-Allow-Origin", "*");
|
|
167
|
+
c.header("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
168
|
+
if (c.req.method === "OPTIONS") {
|
|
169
|
+
return c.body(null, 204);
|
|
170
|
+
}
|
|
171
|
+
return next();
|
|
172
|
+
}
|
|
150
173
|
if (!c.req.path.startsWith("/health") && !c.req.path.startsWith("/api/auth") && !c.req.path.startsWith("/internal") && origin && !allowedOrigins.includes("*") && !allowedOrigins.includes(origin)) {
|
|
151
174
|
return c.json(
|
|
152
|
-
{
|
|
175
|
+
{
|
|
176
|
+
error: {
|
|
177
|
+
code: "DOMAIN_RESTRICTED",
|
|
178
|
+
message: `Origin ${origin} is not allowed`
|
|
179
|
+
}
|
|
180
|
+
},
|
|
153
181
|
403
|
|
154
182
|
);
|
|
155
183
|
}
|
|
@@ -161,7 +189,7 @@ function createCorsMiddleware(allowedOrigins) {
|
|
|
161
189
|
function createRefererGuard(allowedOrigins) {
|
|
162
190
|
const MUTATION_METHODS = /* @__PURE__ */ new Set(["POST", "PATCH", "PUT", "DELETE"]);
|
|
163
191
|
const SKIP_PREFIXES = ["/health", "/internal", "/api/auth", "/.well-known"];
|
|
164
|
-
return
|
|
192
|
+
return chunkLVKPYSXI_cjs.createMiddleware(async (c, next) => {
|
|
165
193
|
if (!MUTATION_METHODS.has(c.req.method)) return next();
|
|
166
194
|
if (SKIP_PREFIXES.some((p) => c.req.path.startsWith(p))) return next();
|
|
167
195
|
if (allowedOrigins.includes("*")) return next();
|
|
@@ -36332,12 +36360,14 @@ function drizzle(client, config3 = {}) {
|
|
|
36332
36360
|
// src/db/schema/index.ts
|
|
36333
36361
|
var schema_exports = {};
|
|
36334
36362
|
chunkEMMSS5I5_cjs.__export(schema_exports, {
|
|
36363
|
+
CONTENTFUL_CONFIG_KEYS: () => CONTENTFUL_CONFIG_KEYS,
|
|
36335
36364
|
authAccount: () => authAccount,
|
|
36336
36365
|
authSession: () => authSession,
|
|
36337
36366
|
authUser: () => authUser,
|
|
36338
36367
|
authVerification: () => authVerification,
|
|
36339
36368
|
bookmarks: () => bookmarks,
|
|
36340
36369
|
bookmarksRelations: () => bookmarksRelations,
|
|
36370
|
+
contentfulConfig: () => contentfulConfig,
|
|
36341
36371
|
events: () => events,
|
|
36342
36372
|
eventsRelations: () => eventsRelations,
|
|
36343
36373
|
faqs: () => faqs,
|
|
@@ -36389,7 +36419,7 @@ var organizationsRelations = relations(organizations, ({ many }) => ({
|
|
|
36389
36419
|
events: many(events)
|
|
36390
36420
|
}));
|
|
36391
36421
|
|
|
36392
|
-
// src/db/schema/
|
|
36422
|
+
// src/db/schema/classes.ts
|
|
36393
36423
|
var events = sqliteTable(
|
|
36394
36424
|
"events",
|
|
36395
36425
|
{
|
|
@@ -36414,7 +36444,7 @@ var events = sqliteTable(
|
|
|
36414
36444
|
// start time string
|
|
36415
36445
|
endTime: text("end_time"),
|
|
36416
36446
|
// end time string
|
|
36417
|
-
|
|
36447
|
+
isSpotlight: integer2("is_spotlight", { mode: "boolean" }).notNull().default(false),
|
|
36418
36448
|
// Slot tracking (local counter — NOT polled from Google Forms)
|
|
36419
36449
|
maxSlots: integer2("max_slots").notNull().default(0),
|
|
36420
36450
|
registeredSlots: integer2("registered_slots").notNull().default(0),
|
|
@@ -36471,6 +36501,18 @@ var siteConfig = sqliteTable("site_config", {
|
|
|
36471
36501
|
// JSON-serializable string
|
|
36472
36502
|
updatedAt: integer2("updated_at").notNull().default(sql2`(unixepoch())`)
|
|
36473
36503
|
});
|
|
36504
|
+
var CONTENTFUL_CONFIG_KEYS = {
|
|
36505
|
+
ENABLED: "contentful.enabled",
|
|
36506
|
+
SPACE_ID: "contentful.spaceId",
|
|
36507
|
+
MANAGEMENT_TOKEN: "contentful.managementToken",
|
|
36508
|
+
DEFAULT_SPACE_ID: "dlsu-events"
|
|
36509
|
+
};
|
|
36510
|
+
var contentfulConfig = sqliteTable("contentful_config", {
|
|
36511
|
+
space_id: text("space_id"),
|
|
36512
|
+
contentful_enabled: integer2("contentful_enabled").notNull().default(0),
|
|
36513
|
+
last_sync_at: integer2("last_sync_at"),
|
|
36514
|
+
updated_at: integer2("updated_at").default(sql2`(unixepoch())`).notNull()
|
|
36515
|
+
});
|
|
36474
36516
|
|
|
36475
36517
|
// src/db/schema/faqs.ts
|
|
36476
36518
|
var faqs = sqliteTable("faqs", {
|
|
@@ -36640,11 +36682,21 @@ function createAuth(env2) {
|
|
|
36640
36682
|
};
|
|
36641
36683
|
if (isFirstUser) {
|
|
36642
36684
|
await db.insert(users).values(base).onConflictDoUpdate({
|
|
36643
|
-
target: users.
|
|
36644
|
-
set: {
|
|
36685
|
+
target: users.email,
|
|
36686
|
+
set: {
|
|
36687
|
+
betterAuthId: user.id,
|
|
36688
|
+
role: "super_admin",
|
|
36689
|
+
name: user.name ?? user.email.split("@")[0]
|
|
36690
|
+
}
|
|
36645
36691
|
});
|
|
36646
36692
|
} else {
|
|
36647
|
-
await db.insert(users).values(base).
|
|
36693
|
+
await db.insert(users).values(base).onConflictDoUpdate({
|
|
36694
|
+
target: users.email,
|
|
36695
|
+
set: {
|
|
36696
|
+
betterAuthId: user.id,
|
|
36697
|
+
name: user.name ?? user.email.split("@")[0]
|
|
36698
|
+
}
|
|
36699
|
+
});
|
|
36648
36700
|
}
|
|
36649
36701
|
}
|
|
36650
36702
|
}
|
|
@@ -36673,7 +36725,13 @@ async function resolveUser(env2, betterAuthUserId, betterAuthUserEmail, betterAu
|
|
|
36673
36725
|
betterAuthId: betterAuthUserId,
|
|
36674
36726
|
email: betterAuthUserEmail,
|
|
36675
36727
|
name: betterAuthUserName ?? betterAuthUserEmail.split("@")[0]
|
|
36676
|
-
}).
|
|
36728
|
+
}).onConflictDoUpdate({
|
|
36729
|
+
target: users.email,
|
|
36730
|
+
set: {
|
|
36731
|
+
betterAuthId: betterAuthUserId,
|
|
36732
|
+
name: betterAuthUserName ?? betterAuthUserEmail.split("@")[0]
|
|
36733
|
+
}
|
|
36734
|
+
}).returning();
|
|
36677
36735
|
dbUser = created;
|
|
36678
36736
|
}
|
|
36679
36737
|
if (!dbUser) throw unauthorized("Failed to resolve user record");
|
|
@@ -36686,7 +36744,7 @@ async function resolveUser(env2, betterAuthUserId, betterAuthUserEmail, betterAu
|
|
|
36686
36744
|
emailVerified: betterAuthEmailVerified
|
|
36687
36745
|
};
|
|
36688
36746
|
}
|
|
36689
|
-
var authMiddleware =
|
|
36747
|
+
var authMiddleware = chunkLVKPYSXI_cjs.createMiddleware(
|
|
36690
36748
|
async (c, next) => {
|
|
36691
36749
|
const rawToken = extractRawToken(c);
|
|
36692
36750
|
if (rawToken) {
|
|
@@ -36728,7 +36786,7 @@ var authMiddleware = chunkQARF2YFF_cjs.createMiddleware(
|
|
|
36728
36786
|
return next();
|
|
36729
36787
|
}
|
|
36730
36788
|
);
|
|
36731
|
-
var optionalAuthMiddleware =
|
|
36789
|
+
var optionalAuthMiddleware = chunkLVKPYSXI_cjs.createMiddleware(async (c, next) => {
|
|
36732
36790
|
const rawToken = extractRawToken(c);
|
|
36733
36791
|
if (!rawToken) {
|
|
36734
36792
|
c.set("user", null);
|
|
@@ -36736,7 +36794,7 @@ var optionalAuthMiddleware = chunkQARF2YFF_cjs.createMiddleware(async (c, next)
|
|
|
36736
36794
|
}
|
|
36737
36795
|
return authMiddleware(c, next);
|
|
36738
36796
|
});
|
|
36739
|
-
var adminMiddleware =
|
|
36797
|
+
var adminMiddleware = chunkLVKPYSXI_cjs.createMiddleware(
|
|
36740
36798
|
async (c, next) => {
|
|
36741
36799
|
const user = c.get("user");
|
|
36742
36800
|
if (!user || !["admin", "super_admin"].includes(user.role)) {
|
|
@@ -36745,7 +36803,7 @@ var adminMiddleware = chunkQARF2YFF_cjs.createMiddleware(
|
|
|
36745
36803
|
return next();
|
|
36746
36804
|
}
|
|
36747
36805
|
);
|
|
36748
|
-
var internalMiddleware =
|
|
36806
|
+
var internalMiddleware = chunkLVKPYSXI_cjs.createMiddleware(async (c, next) => {
|
|
36749
36807
|
const secret = c.req.header("X-Internal-Secret");
|
|
36750
36808
|
if (!secret || secret !== c.env.INTERNAL_API_SECRET) {
|
|
36751
36809
|
throw forbidden("Invalid internal secret");
|
|
@@ -36754,7 +36812,7 @@ var internalMiddleware = chunkQARF2YFF_cjs.createMiddleware(async (c, next) => {
|
|
|
36754
36812
|
});
|
|
36755
36813
|
|
|
36756
36814
|
// src/routes/health.ts
|
|
36757
|
-
var healthRoute = new
|
|
36815
|
+
var healthRoute = new chunkLVKPYSXI_cjs.Hono();
|
|
36758
36816
|
async function probeResend(apiKey) {
|
|
36759
36817
|
const start = Date.now();
|
|
36760
36818
|
try {
|
|
@@ -36983,7 +37041,7 @@ var parse4 = (cookie, name) => {
|
|
|
36983
37041
|
cookieValue = cookieValue.slice(1, -1);
|
|
36984
37042
|
}
|
|
36985
37043
|
if (validCookieValueRegEx.test(cookieValue)) {
|
|
36986
|
-
parsedCookie[cookieName] = cookieValue.indexOf("%") !== -1 ?
|
|
37044
|
+
parsedCookie[cookieName] = cookieValue.indexOf("%") !== -1 ? chunkLVKPYSXI_cjs.tryDecode(cookieValue, chunkLVKPYSXI_cjs.decodeURIComponent_) : cookieValue;
|
|
36987
37045
|
}
|
|
36988
37046
|
}
|
|
36989
37047
|
return parsedCookie;
|
|
@@ -37026,7 +37084,7 @@ var validator = (target, validationFunc) => {
|
|
|
37026
37084
|
value = await c.req.json();
|
|
37027
37085
|
} catch {
|
|
37028
37086
|
const message2 = "Malformed JSON in request body";
|
|
37029
|
-
throw new
|
|
37087
|
+
throw new chunkLVKPYSXI_cjs.HTTPException(400, { message: message2 });
|
|
37030
37088
|
}
|
|
37031
37089
|
break;
|
|
37032
37090
|
case "form": {
|
|
@@ -37044,7 +37102,7 @@ var validator = (target, validationFunc) => {
|
|
|
37044
37102
|
} catch (e) {
|
|
37045
37103
|
let message2 = "Malformed FormData request.";
|
|
37046
37104
|
message2 += e instanceof Error ? ` ${e.message}` : ` ${String(e)}`;
|
|
37047
|
-
throw new
|
|
37105
|
+
throw new chunkLVKPYSXI_cjs.HTTPException(400, { message: message2 });
|
|
37048
37106
|
}
|
|
37049
37107
|
}
|
|
37050
37108
|
const form = /* @__PURE__ */ Object.create(null);
|
|
@@ -37593,7 +37651,7 @@ var ContentfulManagement = class {
|
|
|
37593
37651
|
// src/lib/middleware/rate-limit.ts
|
|
37594
37652
|
function createRateLimitMiddleware(config3) {
|
|
37595
37653
|
const { endpoint, limit, windowSec, identifier } = config3;
|
|
37596
|
-
return
|
|
37654
|
+
return chunkLVKPYSXI_cjs.createMiddleware(async (c, next) => {
|
|
37597
37655
|
if (c.req.path === "/.well-known/leapify/pow/verify") return next();
|
|
37598
37656
|
const id = identifier === "uid" ? c.get("user")?.uid ?? c.req.header("CF-Connecting-IP") ?? "unknown" : c.req.header("CF-Connecting-IP") ?? "unknown";
|
|
37599
37657
|
const key = `rl:${endpoint}:${id}`;
|
|
@@ -37640,7 +37698,7 @@ var adminEventsRateLimit = createRateLimitMiddleware({
|
|
|
37640
37698
|
identifier: "uid"
|
|
37641
37699
|
});
|
|
37642
37700
|
|
|
37643
|
-
// src/routes/
|
|
37701
|
+
// src/routes/classes.ts
|
|
37644
37702
|
var EVENTS_LIST_KV_KEY = "events:list";
|
|
37645
37703
|
var EVENTS_ETAG_KV_KEY = "events:etag";
|
|
37646
37704
|
var EVENTS_LIST_TTL = 300;
|
|
@@ -37656,7 +37714,7 @@ async function pushEventToContentful(env2, event) {
|
|
|
37656
37714
|
const fields = {
|
|
37657
37715
|
title: ContentfulManagement.locale(event.title),
|
|
37658
37716
|
slug: ContentfulManagement.locale(event.slug),
|
|
37659
|
-
|
|
37717
|
+
isSpotlight: ContentfulManagement.locale(event.isSpotlight),
|
|
37660
37718
|
maxSlots: ContentfulManagement.locale(event.maxSlots)
|
|
37661
37719
|
};
|
|
37662
37720
|
if (event.themeId) fields.theme = ContentfulManagement.entryRef(event.themeId);
|
|
@@ -37719,7 +37777,7 @@ var createEventSchema = external_exports.object({
|
|
|
37719
37777
|
startTime: external_exports.string().optional(),
|
|
37720
37778
|
endTime: external_exports.string().optional(),
|
|
37721
37779
|
registrationClosesAt: external_exports.number().optional(),
|
|
37722
|
-
|
|
37780
|
+
isSpotlight: external_exports.boolean().default(false),
|
|
37723
37781
|
maxSlots: external_exports.number().int().min(0).default(0),
|
|
37724
37782
|
gformsId: external_exports.string().optional(),
|
|
37725
37783
|
gformsUrl: external_exports.string().url().optional(),
|
|
@@ -37728,11 +37786,11 @@ var createEventSchema = external_exports.object({
|
|
|
37728
37786
|
contentfulEntryId: external_exports.string().optional(),
|
|
37729
37787
|
status: external_exports.enum(["draft", "queued", "published"]).default("draft")
|
|
37730
37788
|
});
|
|
37731
|
-
var
|
|
37789
|
+
var classesRoute = new chunkLVKPYSXI_cjs.Hono();
|
|
37732
37790
|
function generateSlug(title) {
|
|
37733
37791
|
return title.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
37734
37792
|
}
|
|
37735
|
-
|
|
37793
|
+
classesRoute.get("/admin", authMiddleware, adminMiddleware, async (c) => {
|
|
37736
37794
|
const db = createDb(c.env.DB);
|
|
37737
37795
|
const data = await db.query.events.findMany({
|
|
37738
37796
|
with: { theme: true, organization: true },
|
|
@@ -37740,7 +37798,7 @@ eventsRoute.get("/admin", authMiddleware, adminMiddleware, async (c) => {
|
|
|
37740
37798
|
});
|
|
37741
37799
|
return c.json({ data });
|
|
37742
37800
|
});
|
|
37743
|
-
|
|
37801
|
+
classesRoute.post("/admin/publish", authMiddleware, adminMiddleware, async (c) => {
|
|
37744
37802
|
const body = await c.req.json();
|
|
37745
37803
|
const db = createDb(c.env.DB);
|
|
37746
37804
|
const cache3 = new CacheService(c.env.KV);
|
|
@@ -37768,7 +37826,7 @@ eventsRoute.post("/admin/publish", authMiddleware, adminMiddleware, async (c) =>
|
|
|
37768
37826
|
]);
|
|
37769
37827
|
return c.json({ data: { updated: body.ids.length } });
|
|
37770
37828
|
});
|
|
37771
|
-
|
|
37829
|
+
classesRoute.get("/", eventsListRateLimit, async (c) => {
|
|
37772
37830
|
const db = createDb(c.env.DB);
|
|
37773
37831
|
const cache3 = new CacheService(c.env.KV);
|
|
37774
37832
|
const [latest] = await db.select({ max: events.publishedAt }).from(events).where(eq(events.status, "published")).limit(1);
|
|
@@ -37779,7 +37837,7 @@ eventsRoute.get("/", eventsListRateLimit, async (c) => {
|
|
|
37779
37837
|
);
|
|
37780
37838
|
const ifNoneMatch = c.req.header("If-None-Match");
|
|
37781
37839
|
if (ifNoneMatch === etag) {
|
|
37782
|
-
return c.
|
|
37840
|
+
return c.body(null, 304);
|
|
37783
37841
|
}
|
|
37784
37842
|
const data = await cache3.getOrSet(
|
|
37785
37843
|
EVENTS_LIST_KV_KEY,
|
|
@@ -37803,7 +37861,7 @@ eventsRoute.get("/", eventsListRateLimit, async (c) => {
|
|
|
37803
37861
|
startTime: true,
|
|
37804
37862
|
endTime: true,
|
|
37805
37863
|
registrationClosesAt: true,
|
|
37806
|
-
|
|
37864
|
+
isSpotlight: true,
|
|
37807
37865
|
maxSlots: true,
|
|
37808
37866
|
registeredSlots: true,
|
|
37809
37867
|
gformsUrl: true,
|
|
@@ -37820,7 +37878,7 @@ eventsRoute.get("/", eventsListRateLimit, async (c) => {
|
|
|
37820
37878
|
);
|
|
37821
37879
|
return c.json({ data });
|
|
37822
37880
|
});
|
|
37823
|
-
|
|
37881
|
+
classesRoute.get("/:slug", async (c) => {
|
|
37824
37882
|
const { slug } = c.req.param();
|
|
37825
37883
|
const db = createDb(c.env.DB);
|
|
37826
37884
|
const event = await db.query.events.findFirst({
|
|
@@ -37832,7 +37890,7 @@ eventsRoute.get("/:slug", async (c) => {
|
|
|
37832
37890
|
if (!event) throw notFound("Event");
|
|
37833
37891
|
return c.json({ data: event });
|
|
37834
37892
|
});
|
|
37835
|
-
|
|
37893
|
+
classesRoute.get("/:slug/slots", eventsSlotsRateLimit, async (c) => {
|
|
37836
37894
|
const { slug } = c.req.param();
|
|
37837
37895
|
const db = createDb(c.env.DB);
|
|
37838
37896
|
const cache3 = new CacheService(c.env.KV);
|
|
@@ -37842,7 +37900,7 @@ eventsRoute.get("/:slug/slots", eventsSlotsRateLimit, async (c) => {
|
|
|
37842
37900
|
c.header("Cache-Control", "public, max-age=5, stale-while-revalidate=5");
|
|
37843
37901
|
return c.json({ data: info2 });
|
|
37844
37902
|
});
|
|
37845
|
-
|
|
37903
|
+
classesRoute.post(
|
|
37846
37904
|
"/",
|
|
37847
37905
|
authMiddleware,
|
|
37848
37906
|
adminMiddleware,
|
|
@@ -37877,11 +37935,13 @@ eventsRoute.post(
|
|
|
37877
37935
|
cache3.del(EVENTS_LIST_KV_KEY),
|
|
37878
37936
|
cache3.del(EVENTS_ETAG_KV_KEY)
|
|
37879
37937
|
]);
|
|
37880
|
-
c.
|
|
37938
|
+
if (c.get("cmsMode") === "hybrid") {
|
|
37939
|
+
c.executionCtx.waitUntil(pushEventToContentful(c.env, created));
|
|
37940
|
+
}
|
|
37881
37941
|
return c.json({ data: created }, 201);
|
|
37882
37942
|
}
|
|
37883
37943
|
);
|
|
37884
|
-
|
|
37944
|
+
classesRoute.patch("/:slug", authMiddleware, adminMiddleware, async (c) => {
|
|
37885
37945
|
const { slug } = c.req.param();
|
|
37886
37946
|
const body = await c.req.json();
|
|
37887
37947
|
const db = createDb(c.env.DB);
|
|
@@ -37896,10 +37956,12 @@ eventsRoute.patch("/:slug", authMiddleware, adminMiddleware, async (c) => {
|
|
|
37896
37956
|
cache3.del(EVENTS_LIST_KV_KEY),
|
|
37897
37957
|
cache3.del(EVENTS_ETAG_KV_KEY)
|
|
37898
37958
|
]);
|
|
37899
|
-
c.
|
|
37959
|
+
if (c.get("cmsMode") === "hybrid") {
|
|
37960
|
+
c.executionCtx.waitUntil(pushEventToContentful(c.env, updated));
|
|
37961
|
+
}
|
|
37900
37962
|
return c.json({ data: updated });
|
|
37901
37963
|
});
|
|
37902
|
-
|
|
37964
|
+
classesRoute.delete("/:slug", authMiddleware, adminMiddleware, async (c) => {
|
|
37903
37965
|
const { slug } = c.req.param();
|
|
37904
37966
|
const db = createDb(c.env.DB);
|
|
37905
37967
|
const cache3 = new CacheService(c.env.KV);
|
|
@@ -37914,7 +37976,7 @@ eventsRoute.delete("/:slug", authMiddleware, adminMiddleware, async (c) => {
|
|
|
37914
37976
|
|
|
37915
37977
|
// src/routes/users.ts
|
|
37916
37978
|
var VALID_ROLES = ["student", "admin", "super_admin"];
|
|
37917
|
-
var usersRoute = new
|
|
37979
|
+
var usersRoute = new chunkLVKPYSXI_cjs.Hono();
|
|
37918
37980
|
usersRoute.get("/", authMiddleware, adminMiddleware, async (c) => {
|
|
37919
37981
|
const db = createDb(c.env.DB);
|
|
37920
37982
|
const data = await db.select().from(users);
|
|
@@ -37998,7 +38060,447 @@ usersRoute.delete("/me/bookmarks/:eventId", authMiddleware, async (c) => {
|
|
|
37998
38060
|
return c.json({ data: { bookmarked: false } });
|
|
37999
38061
|
});
|
|
38000
38062
|
|
|
38063
|
+
// src/services/contentful.ts
|
|
38064
|
+
var CONTENTFUL_CDN = "https://cdn.contentful.com";
|
|
38065
|
+
var ContentfulService = class _ContentfulService {
|
|
38066
|
+
spaceId;
|
|
38067
|
+
accessToken;
|
|
38068
|
+
environment;
|
|
38069
|
+
constructor(spaceId, accessToken, environment = "master") {
|
|
38070
|
+
this.spaceId = spaceId;
|
|
38071
|
+
this.accessToken = accessToken;
|
|
38072
|
+
this.environment = environment;
|
|
38073
|
+
}
|
|
38074
|
+
/**
|
|
38075
|
+
* Returns true if the required Contentful credentials are configured.
|
|
38076
|
+
*/
|
|
38077
|
+
static isConfigured(spaceId, accessToken) {
|
|
38078
|
+
return !!(spaceId && accessToken);
|
|
38079
|
+
}
|
|
38080
|
+
// ─── Entries ─────────────────────────────────────────────────────────────
|
|
38081
|
+
/**
|
|
38082
|
+
* Fetch all entries of a given content type.
|
|
38083
|
+
* Handles pagination automatically (100 per page, Contentful max).
|
|
38084
|
+
*/
|
|
38085
|
+
async getEntries(contentTypeId) {
|
|
38086
|
+
const allItems = [];
|
|
38087
|
+
let skip = 0;
|
|
38088
|
+
const limit = 100;
|
|
38089
|
+
do {
|
|
38090
|
+
const url2 = this.buildUrl(`/entries`, {
|
|
38091
|
+
content_type: contentTypeId,
|
|
38092
|
+
skip: String(skip),
|
|
38093
|
+
limit: String(limit),
|
|
38094
|
+
include: "2"
|
|
38095
|
+
// resolve up to 2 levels of linked entries/assets
|
|
38096
|
+
});
|
|
38097
|
+
const res = await fetch(url2, { headers: this.headers() });
|
|
38098
|
+
if (!res.ok) {
|
|
38099
|
+
throw new Error(`Contentful entries error: ${res.status} ${await res.text()}`);
|
|
38100
|
+
}
|
|
38101
|
+
const data = await res.json();
|
|
38102
|
+
allItems.push(...data.items);
|
|
38103
|
+
skip += limit;
|
|
38104
|
+
if (allItems.length >= data.total) break;
|
|
38105
|
+
} while (true);
|
|
38106
|
+
return allItems;
|
|
38107
|
+
}
|
|
38108
|
+
/**
|
|
38109
|
+
* Fetch all assets. Handles pagination.
|
|
38110
|
+
*/
|
|
38111
|
+
async getAssets() {
|
|
38112
|
+
const allItems = [];
|
|
38113
|
+
let skip = 0;
|
|
38114
|
+
const limit = 100;
|
|
38115
|
+
do {
|
|
38116
|
+
const url2 = this.buildUrl(`/assets`, {
|
|
38117
|
+
skip: String(skip),
|
|
38118
|
+
limit: String(limit)
|
|
38119
|
+
});
|
|
38120
|
+
const res = await fetch(url2, { headers: this.headers() });
|
|
38121
|
+
if (!res.ok) {
|
|
38122
|
+
throw new Error(`Contentful assets error: ${res.status} ${await res.text()}`);
|
|
38123
|
+
}
|
|
38124
|
+
const data = await res.json();
|
|
38125
|
+
allItems.push(...data.items);
|
|
38126
|
+
skip += limit;
|
|
38127
|
+
if (allItems.length >= data.total) break;
|
|
38128
|
+
} while (true);
|
|
38129
|
+
return allItems;
|
|
38130
|
+
}
|
|
38131
|
+
// ─── Asset file download ─────────────────────────────────────────────────
|
|
38132
|
+
/**
|
|
38133
|
+
* Download an asset file from Contentful's CDN.
|
|
38134
|
+
* Returns the raw ArrayBuffer and content type.
|
|
38135
|
+
*/
|
|
38136
|
+
async downloadAsset(assetUrl) {
|
|
38137
|
+
const url2 = assetUrl.startsWith("//") ? `https:${assetUrl}` : assetUrl;
|
|
38138
|
+
const res = await fetch(url2);
|
|
38139
|
+
if (!res.ok) {
|
|
38140
|
+
throw new Error(`Failed to download asset: ${res.status}`);
|
|
38141
|
+
}
|
|
38142
|
+
const contentType = res.headers.get("Content-Type") ?? "application/octet-stream";
|
|
38143
|
+
const data = await res.arrayBuffer();
|
|
38144
|
+
return { data, contentType };
|
|
38145
|
+
}
|
|
38146
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
38147
|
+
/**
|
|
38148
|
+
* Extract a field value from a Contentful entry, handling locale wrapping.
|
|
38149
|
+
* Contentful fields are often `{ "en-US": value }` — this unwraps them.
|
|
38150
|
+
*/
|
|
38151
|
+
static getField(entry, fieldName) {
|
|
38152
|
+
const raw = entry.fields[fieldName];
|
|
38153
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
38154
|
+
if (typeof raw === "object" && !Array.isArray(raw) && "en-US" in raw) {
|
|
38155
|
+
return raw["en-US"];
|
|
38156
|
+
}
|
|
38157
|
+
return raw;
|
|
38158
|
+
}
|
|
38159
|
+
/**
|
|
38160
|
+
* Extract a linked entry/sys reference ID from a reference field.
|
|
38161
|
+
*/
|
|
38162
|
+
static getRefId(entry, fieldName) {
|
|
38163
|
+
const ref = _ContentfulService.getField(entry, fieldName);
|
|
38164
|
+
return ref?.sys?.id;
|
|
38165
|
+
}
|
|
38166
|
+
/**
|
|
38167
|
+
* Extract an asset URL from a linked asset field.
|
|
38168
|
+
*/
|
|
38169
|
+
static getAssetUrl(entry, fieldName) {
|
|
38170
|
+
const asset = _ContentfulService.getField(entry, fieldName);
|
|
38171
|
+
return asset?.sys?.id;
|
|
38172
|
+
}
|
|
38173
|
+
/**
|
|
38174
|
+
* Resolve an asset URL by ID from a list of fetched assets.
|
|
38175
|
+
*/
|
|
38176
|
+
static resolveAssetUrl(assets, assetId) {
|
|
38177
|
+
const asset = assets.find((a) => a.sys.id === assetId);
|
|
38178
|
+
return asset?.fields?.file?.url;
|
|
38179
|
+
}
|
|
38180
|
+
// ─── Private ─────────────────────────────────────────────────────────────
|
|
38181
|
+
headers() {
|
|
38182
|
+
return {
|
|
38183
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
38184
|
+
"Content-Type": "application/json"
|
|
38185
|
+
};
|
|
38186
|
+
}
|
|
38187
|
+
buildUrl(path, params = {}) {
|
|
38188
|
+
const url2 = new URL(
|
|
38189
|
+
`/spaces/${this.spaceId}/environments/${this.environment}${path}`,
|
|
38190
|
+
CONTENTFUL_CDN
|
|
38191
|
+
);
|
|
38192
|
+
for (const [key, value] of Object.entries(params)) {
|
|
38193
|
+
url2.searchParams.set(key, value);
|
|
38194
|
+
}
|
|
38195
|
+
return url2.toString();
|
|
38196
|
+
}
|
|
38197
|
+
};
|
|
38198
|
+
|
|
38001
38199
|
// src/services/snapshot.ts
|
|
38200
|
+
var DEFAULT_FIELDS = {
|
|
38201
|
+
event: {
|
|
38202
|
+
title: "title",
|
|
38203
|
+
slug: "slug",
|
|
38204
|
+
theme: "theme",
|
|
38205
|
+
organization: "organization",
|
|
38206
|
+
venue: "venue",
|
|
38207
|
+
date: "date",
|
|
38208
|
+
startTime: "startTime",
|
|
38209
|
+
endTime: "endTime",
|
|
38210
|
+
price: "price",
|
|
38211
|
+
image: "image",
|
|
38212
|
+
isSpotlight: "isSpotlight",
|
|
38213
|
+
maxSlots: "maxSlots",
|
|
38214
|
+
gformsUrl: "gformsUrl",
|
|
38215
|
+
gformsEditorUrl: "gformsEditorUrl",
|
|
38216
|
+
registrationClosesAt: "registrationClosesAt",
|
|
38217
|
+
classCode: "classCode"
|
|
38218
|
+
},
|
|
38219
|
+
theme: {
|
|
38220
|
+
name: "name",
|
|
38221
|
+
path: "path"
|
|
38222
|
+
},
|
|
38223
|
+
faq: {
|
|
38224
|
+
question: "question",
|
|
38225
|
+
answer: "answer",
|
|
38226
|
+
category: "category",
|
|
38227
|
+
sortOrder: "sortOrder"
|
|
38228
|
+
},
|
|
38229
|
+
organization: {
|
|
38230
|
+
name: "name",
|
|
38231
|
+
acronym: "acronym",
|
|
38232
|
+
logoUrl: "logoUrl",
|
|
38233
|
+
link: "link"
|
|
38234
|
+
},
|
|
38235
|
+
siteConfig: {
|
|
38236
|
+
key: "key",
|
|
38237
|
+
value: "value"
|
|
38238
|
+
}
|
|
38239
|
+
};
|
|
38240
|
+
var CONTENTFUL_CACHE_PREFIX = "contentful:cache";
|
|
38241
|
+
var CONTENTFUL_CACHE_TTL = 300;
|
|
38242
|
+
async function snapshotAllContent(db, bucket, contentful, config3 = {}, kv) {
|
|
38243
|
+
const mergedConfig = {
|
|
38244
|
+
eventTypeId: config3.eventTypeId ?? "event",
|
|
38245
|
+
themeTypeId: config3.themeTypeId ?? "theme",
|
|
38246
|
+
faqTypeId: config3.faqTypeId ?? "faq",
|
|
38247
|
+
organizationTypeId: config3.organizationTypeId ?? "organization",
|
|
38248
|
+
fields: {
|
|
38249
|
+
event: { ...DEFAULT_FIELDS.event, ...config3.fields?.event },
|
|
38250
|
+
theme: { ...DEFAULT_FIELDS.theme, ...config3.fields?.theme },
|
|
38251
|
+
faq: { ...DEFAULT_FIELDS.faq, ...config3.fields?.faq },
|
|
38252
|
+
organization: { ...DEFAULT_FIELDS.organization, ...config3.fields?.organization },
|
|
38253
|
+
siteConfig: { ...DEFAULT_FIELDS.siteConfig, ...config3.fields?.siteConfig }
|
|
38254
|
+
}
|
|
38255
|
+
};
|
|
38256
|
+
const result = {
|
|
38257
|
+
themesSynced: 0,
|
|
38258
|
+
eventsSynced: 0,
|
|
38259
|
+
faqsSynced: 0,
|
|
38260
|
+
organizationsSynced: 0,
|
|
38261
|
+
imagesUploaded: 0,
|
|
38262
|
+
imagesSkipped: 0,
|
|
38263
|
+
errors: []
|
|
38264
|
+
};
|
|
38265
|
+
let allAssets = [];
|
|
38266
|
+
if (bucket) {
|
|
38267
|
+
try {
|
|
38268
|
+
allAssets = await contentful.getAssets();
|
|
38269
|
+
} catch (err) {
|
|
38270
|
+
result.errors.push(`Failed to fetch assets: ${err}`);
|
|
38271
|
+
}
|
|
38272
|
+
}
|
|
38273
|
+
try {
|
|
38274
|
+
const cacheKey = `${CONTENTFUL_CACHE_PREFIX}:themes`;
|
|
38275
|
+
let themeEntries;
|
|
38276
|
+
if (kv) {
|
|
38277
|
+
const cached2 = await kv.get(cacheKey, "json");
|
|
38278
|
+
if (cached2) {
|
|
38279
|
+
themeEntries = cached2;
|
|
38280
|
+
} else {
|
|
38281
|
+
themeEntries = await contentful.getEntries(mergedConfig.themeTypeId);
|
|
38282
|
+
await kv.put(cacheKey, JSON.stringify(themeEntries), { expirationTtl: CONTENTFUL_CACHE_TTL });
|
|
38283
|
+
}
|
|
38284
|
+
} else {
|
|
38285
|
+
themeEntries = await contentful.getEntries(mergedConfig.themeTypeId);
|
|
38286
|
+
}
|
|
38287
|
+
result.themesSynced = await syncThemes(db, themeEntries, mergedConfig);
|
|
38288
|
+
} catch (err) {
|
|
38289
|
+
result.errors.push(`Themes sync failed: ${err}`);
|
|
38290
|
+
}
|
|
38291
|
+
try {
|
|
38292
|
+
const cacheKey = `${CONTENTFUL_CACHE_PREFIX}:organizations`;
|
|
38293
|
+
let orgEntries;
|
|
38294
|
+
if (kv) {
|
|
38295
|
+
const cached2 = await kv.get(cacheKey, "json");
|
|
38296
|
+
if (cached2) {
|
|
38297
|
+
orgEntries = cached2;
|
|
38298
|
+
} else {
|
|
38299
|
+
orgEntries = await contentful.getEntries(mergedConfig.organizationTypeId);
|
|
38300
|
+
await kv.put(cacheKey, JSON.stringify(orgEntries), { expirationTtl: CONTENTFUL_CACHE_TTL });
|
|
38301
|
+
}
|
|
38302
|
+
} else {
|
|
38303
|
+
orgEntries = await contentful.getEntries(mergedConfig.organizationTypeId);
|
|
38304
|
+
}
|
|
38305
|
+
await syncOrganizations(db, orgEntries, mergedConfig);
|
|
38306
|
+
} catch (err) {
|
|
38307
|
+
result.errors.push(`Organizations sync failed: ${err}`);
|
|
38308
|
+
}
|
|
38309
|
+
try {
|
|
38310
|
+
const cacheKey = `${CONTENTFUL_CACHE_PREFIX}:events`;
|
|
38311
|
+
let eventEntries;
|
|
38312
|
+
if (kv) {
|
|
38313
|
+
const cached2 = await kv.get(cacheKey, "json");
|
|
38314
|
+
if (cached2) {
|
|
38315
|
+
eventEntries = cached2;
|
|
38316
|
+
} else {
|
|
38317
|
+
eventEntries = await contentful.getEntries(mergedConfig.eventTypeId);
|
|
38318
|
+
await kv.put(cacheKey, JSON.stringify(eventEntries), { expirationTtl: CONTENTFUL_CACHE_TTL });
|
|
38319
|
+
}
|
|
38320
|
+
} else {
|
|
38321
|
+
eventEntries = await contentful.getEntries(mergedConfig.eventTypeId);
|
|
38322
|
+
}
|
|
38323
|
+
result.eventsSynced = await syncEvents(db, bucket, contentful, allAssets, eventEntries, mergedConfig, result);
|
|
38324
|
+
} catch (err) {
|
|
38325
|
+
result.errors.push(`Events sync failed: ${err}`);
|
|
38326
|
+
}
|
|
38327
|
+
try {
|
|
38328
|
+
const cacheKey = `${CONTENTFUL_CACHE_PREFIX}:faqs`;
|
|
38329
|
+
let faqEntries;
|
|
38330
|
+
if (kv) {
|
|
38331
|
+
const cached2 = await kv.get(cacheKey, "json");
|
|
38332
|
+
if (cached2) {
|
|
38333
|
+
faqEntries = cached2;
|
|
38334
|
+
} else {
|
|
38335
|
+
faqEntries = await contentful.getEntries(mergedConfig.faqTypeId);
|
|
38336
|
+
await kv.put(cacheKey, JSON.stringify(faqEntries), { expirationTtl: CONTENTFUL_CACHE_TTL });
|
|
38337
|
+
}
|
|
38338
|
+
} else {
|
|
38339
|
+
faqEntries = await contentful.getEntries(mergedConfig.faqTypeId);
|
|
38340
|
+
}
|
|
38341
|
+
result.faqsSynced = await syncFaqs(db, faqEntries, mergedConfig);
|
|
38342
|
+
} catch (err) {
|
|
38343
|
+
result.errors.push(`FAQs sync failed: ${err}`);
|
|
38344
|
+
}
|
|
38345
|
+
console.log(
|
|
38346
|
+
`[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`
|
|
38347
|
+
);
|
|
38348
|
+
return result;
|
|
38349
|
+
}
|
|
38350
|
+
async function syncThemes(db, entries, config3) {
|
|
38351
|
+
let count2 = 0;
|
|
38352
|
+
for (const entry of entries) {
|
|
38353
|
+
const cfId = entry.sys.id;
|
|
38354
|
+
const name = ContentfulService.getField(entry, config3.fields.theme.name);
|
|
38355
|
+
const path = ContentfulService.getField(entry, config3.fields.theme.path);
|
|
38356
|
+
if (!name || !path) continue;
|
|
38357
|
+
await db.insert(themes).values({ id: cfId, name, path }).onConflictDoUpdate({
|
|
38358
|
+
target: themes.id,
|
|
38359
|
+
set: { name, path }
|
|
38360
|
+
});
|
|
38361
|
+
count2++;
|
|
38362
|
+
}
|
|
38363
|
+
return count2;
|
|
38364
|
+
}
|
|
38365
|
+
async function syncOrganizations(db, entries, config3) {
|
|
38366
|
+
let count2 = 0;
|
|
38367
|
+
for (const entry of entries) {
|
|
38368
|
+
const cfId = entry.sys.id;
|
|
38369
|
+
const f = config3.fields.organization;
|
|
38370
|
+
const name = ContentfulService.getField(entry, f.name);
|
|
38371
|
+
const acronym = ContentfulService.getField(entry, f.acronym);
|
|
38372
|
+
if (!name || !acronym) continue;
|
|
38373
|
+
const logoUrl = ContentfulService.getField(entry, f.logoUrl) ?? null;
|
|
38374
|
+
const link = ContentfulService.getField(entry, f.link) ?? null;
|
|
38375
|
+
await db.insert(organizations).values({ id: cfId, name, acronym, logoUrl, link }).onConflictDoUpdate({
|
|
38376
|
+
target: organizations.id,
|
|
38377
|
+
set: { name, acronym, logoUrl, link }
|
|
38378
|
+
});
|
|
38379
|
+
count2++;
|
|
38380
|
+
}
|
|
38381
|
+
return count2;
|
|
38382
|
+
}
|
|
38383
|
+
async function syncEvents(db, bucket, contentful, allAssets, entries, config3, result) {
|
|
38384
|
+
let count2 = 0;
|
|
38385
|
+
for (const entry of entries) {
|
|
38386
|
+
try {
|
|
38387
|
+
const cfId = entry.sys.id;
|
|
38388
|
+
const f = config3.fields.event;
|
|
38389
|
+
const title = ContentfulService.getField(entry, f.title);
|
|
38390
|
+
if (!title) {
|
|
38391
|
+
result.errors.push(`Event ${cfId}: missing title, skipping`);
|
|
38392
|
+
continue;
|
|
38393
|
+
}
|
|
38394
|
+
const slug = ContentfulService.getField(entry, f.slug) ?? slugify2(title);
|
|
38395
|
+
const themeRef = ContentfulService.getField(entry, f.theme);
|
|
38396
|
+
const themeId = themeRef?.sys?.id ?? null;
|
|
38397
|
+
const orgRef = ContentfulService.getField(entry, f.organization);
|
|
38398
|
+
const organizationId = orgRef?.sys?.id ?? null;
|
|
38399
|
+
let backgroundImageUrl = null;
|
|
38400
|
+
if (bucket) {
|
|
38401
|
+
const imageRef = ContentfulService.getField(entry, f.image);
|
|
38402
|
+
const assetId = imageRef?.sys?.id;
|
|
38403
|
+
if (assetId) {
|
|
38404
|
+
const assetUrl = ContentfulService.resolveAssetUrl(allAssets, assetId);
|
|
38405
|
+
if (assetUrl) {
|
|
38406
|
+
const r2Key = `contentful/${assetId}`;
|
|
38407
|
+
const uploaded = await uploadAssetIfChanged(bucket, contentful, assetUrl, r2Key);
|
|
38408
|
+
if (uploaded.skipped) {
|
|
38409
|
+
result.imagesSkipped++;
|
|
38410
|
+
} else {
|
|
38411
|
+
result.imagesUploaded++;
|
|
38412
|
+
}
|
|
38413
|
+
backgroundImageUrl = `/uploads/images/${r2Key}`;
|
|
38414
|
+
}
|
|
38415
|
+
}
|
|
38416
|
+
}
|
|
38417
|
+
const values = {
|
|
38418
|
+
id: cfId,
|
|
38419
|
+
contentfulEntryId: cfId,
|
|
38420
|
+
title,
|
|
38421
|
+
slug,
|
|
38422
|
+
themeId,
|
|
38423
|
+
organizationId,
|
|
38424
|
+
updatedAt: entry.sys.updatedAt
|
|
38425
|
+
};
|
|
38426
|
+
const venue = ContentfulService.getField(entry, f.venue);
|
|
38427
|
+
if (venue !== void 0) values.venue = venue;
|
|
38428
|
+
const date5 = ContentfulService.getField(entry, f.date);
|
|
38429
|
+
if (date5 !== void 0) values.dateTime = date5;
|
|
38430
|
+
const price = ContentfulService.getField(entry, f.price);
|
|
38431
|
+
if (price !== void 0) values.price = price;
|
|
38432
|
+
if (backgroundImageUrl) values.backgroundImageUrl = backgroundImageUrl;
|
|
38433
|
+
const isSpotlight = ContentfulService.getField(entry, f.isSpotlight);
|
|
38434
|
+
if (isSpotlight !== void 0) values.isSpotlight = isSpotlight;
|
|
38435
|
+
const maxSlots = ContentfulService.getField(entry, f.maxSlots);
|
|
38436
|
+
if (maxSlots !== void 0) values.maxSlots = maxSlots;
|
|
38437
|
+
const gformsUrl = ContentfulService.getField(entry, f.gformsUrl);
|
|
38438
|
+
if (gformsUrl !== void 0) values.gformsUrl = gformsUrl;
|
|
38439
|
+
const gformsEditorUrl = ContentfulService.getField(entry, f.gformsEditorUrl);
|
|
38440
|
+
if (gformsEditorUrl !== void 0) values.gformsEditorUrl = gformsEditorUrl;
|
|
38441
|
+
const classCode = ContentfulService.getField(entry, f.classCode);
|
|
38442
|
+
if (classCode !== void 0) values.classCode = classCode;
|
|
38443
|
+
const startTime = ContentfulService.getField(entry, f.startTime);
|
|
38444
|
+
if (startTime !== void 0) values.startTime = startTime;
|
|
38445
|
+
const endTime = ContentfulService.getField(entry, f.endTime);
|
|
38446
|
+
if (endTime !== void 0) values.endTime = endTime;
|
|
38447
|
+
const regCloseRaw = ContentfulService.getField(entry, f.registrationClosesAt);
|
|
38448
|
+
if (regCloseRaw) {
|
|
38449
|
+
const ms = Date.parse(regCloseRaw);
|
|
38450
|
+
if (!Number.isNaN(ms)) values.registrationClosesAt = Math.floor(ms / 1e3);
|
|
38451
|
+
}
|
|
38452
|
+
await db.insert(events).values(values).onConflictDoUpdate({
|
|
38453
|
+
target: events.id,
|
|
38454
|
+
set: values
|
|
38455
|
+
});
|
|
38456
|
+
count2++;
|
|
38457
|
+
} catch (err) {
|
|
38458
|
+
result.errors.push(`Event ${entry.sys.id}: ${err}`);
|
|
38459
|
+
}
|
|
38460
|
+
}
|
|
38461
|
+
return count2;
|
|
38462
|
+
}
|
|
38463
|
+
async function syncFaqs(db, entries, config3) {
|
|
38464
|
+
let count2 = 0;
|
|
38465
|
+
for (const entry of entries) {
|
|
38466
|
+
const cfId = entry.sys.id;
|
|
38467
|
+
const f = config3.fields.faq;
|
|
38468
|
+
const question = ContentfulService.getField(entry, f.question);
|
|
38469
|
+
const answer = ContentfulService.getField(entry, f.answer);
|
|
38470
|
+
if (!question || !answer) continue;
|
|
38471
|
+
const category = ContentfulService.getField(entry, f.category) ?? null;
|
|
38472
|
+
const sortOrder = ContentfulService.getField(entry, f.sortOrder) ?? 0;
|
|
38473
|
+
await db.insert(faqs).values({
|
|
38474
|
+
id: cfId,
|
|
38475
|
+
question,
|
|
38476
|
+
answer,
|
|
38477
|
+
category,
|
|
38478
|
+
sortOrder
|
|
38479
|
+
}).onConflictDoUpdate({
|
|
38480
|
+
target: faqs.id,
|
|
38481
|
+
set: { question, answer, category, sortOrder }
|
|
38482
|
+
});
|
|
38483
|
+
count2++;
|
|
38484
|
+
}
|
|
38485
|
+
return count2;
|
|
38486
|
+
}
|
|
38487
|
+
async function uploadAssetIfChanged(bucket, contentful, assetUrl, r2Key) {
|
|
38488
|
+
const { data, contentType } = await contentful.downloadAsset(assetUrl);
|
|
38489
|
+
const sha = await computeSha256(data);
|
|
38490
|
+
const existing = await bucket.head(r2Key);
|
|
38491
|
+
if (existing?.customMetadata?.sha256 === sha) {
|
|
38492
|
+
return { skipped: true };
|
|
38493
|
+
}
|
|
38494
|
+
await bucket.put(r2Key, data, {
|
|
38495
|
+
httpMetadata: { contentType },
|
|
38496
|
+
customMetadata: {
|
|
38497
|
+
sha256: sha,
|
|
38498
|
+
source: "contentful",
|
|
38499
|
+
syncedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
38500
|
+
}
|
|
38501
|
+
});
|
|
38502
|
+
return { skipped: false };
|
|
38503
|
+
}
|
|
38002
38504
|
async function batchRun(items, fn, concurrency = 5) {
|
|
38003
38505
|
const results = [];
|
|
38004
38506
|
for (let i = 0; i < items.length; i += concurrency) {
|
|
@@ -38008,6 +38510,13 @@ async function batchRun(items, fn, concurrency = 5) {
|
|
|
38008
38510
|
}
|
|
38009
38511
|
return results;
|
|
38010
38512
|
}
|
|
38513
|
+
async function computeSha256(data) {
|
|
38514
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
38515
|
+
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
38516
|
+
}
|
|
38517
|
+
function slugify2(text2) {
|
|
38518
|
+
return text2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
38519
|
+
}
|
|
38011
38520
|
async function ensureContentTypes(mgmt, config3 = {}) {
|
|
38012
38521
|
const eventTypeId = config3.eventTypeId ?? "event";
|
|
38013
38522
|
const themeTypeId = config3.themeTypeId ?? "theme";
|
|
@@ -38028,7 +38537,7 @@ async function ensureContentTypes(mgmt, config3 = {}) {
|
|
|
38028
38537
|
{ id: "endTime", name: "End Time", type: "Symbol" },
|
|
38029
38538
|
{ id: "price", name: "Price", type: "Symbol" },
|
|
38030
38539
|
{ id: "image", name: "Image", type: "Link", linkType: "Asset" },
|
|
38031
|
-
{ id: "
|
|
38540
|
+
{ id: "isSpotlight", name: "Spotlight", type: "Boolean" },
|
|
38032
38541
|
{ id: "maxSlots", name: "Max Slots", type: "Integer" },
|
|
38033
38542
|
{ id: "gformsUrl", name: "Google Forms URL", type: "Symbol" },
|
|
38034
38543
|
{ id: "gformsEditorUrl", name: "Google Forms Editor URL", type: "Symbol" },
|
|
@@ -38113,7 +38622,7 @@ async function pushToContentful(db, mgmt, config3 = {}, kv, forceFull = false) {
|
|
|
38113
38622
|
const fields = {
|
|
38114
38623
|
title: ContentfulManagement.locale(event.title),
|
|
38115
38624
|
slug: ContentfulManagement.locale(event.slug),
|
|
38116
|
-
|
|
38625
|
+
isSpotlight: ContentfulManagement.locale(event.isSpotlight),
|
|
38117
38626
|
maxSlots: ContentfulManagement.locale(event.maxSlots)
|
|
38118
38627
|
};
|
|
38119
38628
|
if (event.themeId) fields.theme = ContentfulManagement.entryRef(event.themeId);
|
|
@@ -38170,7 +38679,7 @@ async function pushToContentful(db, mgmt, config3 = {}, kv, forceFull = false) {
|
|
|
38170
38679
|
}
|
|
38171
38680
|
|
|
38172
38681
|
// src/routes/site-config.ts
|
|
38173
|
-
var siteConfigRoute = new
|
|
38682
|
+
var siteConfigRoute = new chunkLVKPYSXI_cjs.Hono();
|
|
38174
38683
|
siteConfigRoute.get("/", async (c) => {
|
|
38175
38684
|
const db = createDb(c.env.DB);
|
|
38176
38685
|
const rows = await db.query.siteConfig.findMany();
|
|
@@ -38184,6 +38693,9 @@ siteConfigRoute.get("/", async (c) => {
|
|
|
38184
38693
|
siteName: config3.site_name ?? null,
|
|
38185
38694
|
registrationGloballyOpen: config3.registration_globally_open ?? true,
|
|
38186
38695
|
maintenanceMode: config3.maintenance_mode ?? false,
|
|
38696
|
+
// Prefer the D1-persisted value over the env-var default so that
|
|
38697
|
+
// PATCH /config/cms_mode changes are reflected immediately.
|
|
38698
|
+
cmsMode: config3.cms_mode ?? c.get("cmsMode"),
|
|
38187
38699
|
now: Math.floor(Date.now() / 1e3)
|
|
38188
38700
|
}
|
|
38189
38701
|
});
|
|
@@ -38205,6 +38717,10 @@ siteConfigRoute.patch("/:key", authMiddleware, adminMiddleware, async (c) => {
|
|
|
38205
38717
|
var SYNC_LOCK_KEY = "contentful:sync:lock";
|
|
38206
38718
|
var SYNC_LOCK_TTL = 60;
|
|
38207
38719
|
siteConfigRoute.post("/sync-content", authMiddleware, adminMiddleware, async (c) => {
|
|
38720
|
+
const cmsMode = c.get("cmsMode");
|
|
38721
|
+
if (cmsMode === "cloudflare") {
|
|
38722
|
+
throw serviceUnavailable("Contentful sync is not available in Cloudflare-only mode.");
|
|
38723
|
+
}
|
|
38208
38724
|
if (!ContentfulManagement.isConfigured(c.env.CONTENTFUL_SPACE_ID, c.env.CONTENTFUL_MANAGEMENT_TOKEN)) {
|
|
38209
38725
|
throw serviceUnavailable("Contentful Management API credentials not configured.");
|
|
38210
38726
|
}
|
|
@@ -38238,7 +38754,7 @@ var faqSchema = external_exports.object({
|
|
|
38238
38754
|
category: external_exports.string().optional(),
|
|
38239
38755
|
sortOrder: external_exports.number().int().default(0)
|
|
38240
38756
|
});
|
|
38241
|
-
var faqsRoute = new
|
|
38757
|
+
var faqsRoute = new chunkLVKPYSXI_cjs.Hono();
|
|
38242
38758
|
async function pushFaqToContentful(env2, faq) {
|
|
38243
38759
|
console.log("[Contentful] pushFaqToContentful called for FAQ:", faq.id);
|
|
38244
38760
|
if (!ContentfulManagement.isConfigured(env2.CONTENTFUL_SPACE_ID, env2.CONTENTFUL_MANAGEMENT_TOKEN)) {
|
|
@@ -38291,7 +38807,9 @@ faqsRoute.post(
|
|
|
38291
38807
|
const cache3 = new CacheService(c.env.KV);
|
|
38292
38808
|
const [created] = await db.insert(faqs).values(body).returning();
|
|
38293
38809
|
await cache3.del(FAQS_KV_KEY);
|
|
38294
|
-
c.
|
|
38810
|
+
if (c.get("cmsMode") === "hybrid") {
|
|
38811
|
+
c.executionCtx.waitUntil(pushFaqToContentful(c.env, created));
|
|
38812
|
+
}
|
|
38295
38813
|
return c.json({ data: created }, 201);
|
|
38296
38814
|
}
|
|
38297
38815
|
);
|
|
@@ -38304,7 +38822,9 @@ faqsRoute.patch("/:id", authMiddleware, adminMiddleware, async (c) => {
|
|
|
38304
38822
|
const [updated] = await db.update(faqs).set({ ...body, updatedAt: now2 }).where(eq(faqs.id, id)).returning();
|
|
38305
38823
|
if (!updated) throw notFound("FAQ");
|
|
38306
38824
|
await cache3.del(FAQS_KV_KEY);
|
|
38307
|
-
c.
|
|
38825
|
+
if (c.get("cmsMode") === "hybrid") {
|
|
38826
|
+
c.executionCtx.waitUntil(pushFaqToContentful(c.env, updated));
|
|
38827
|
+
}
|
|
38308
38828
|
return c.json({ data: updated });
|
|
38309
38829
|
});
|
|
38310
38830
|
faqsRoute.delete("/:id", authMiddleware, adminMiddleware, async (c) => {
|
|
@@ -38318,7 +38838,7 @@ faqsRoute.delete("/:id", authMiddleware, adminMiddleware, async (c) => {
|
|
|
38318
38838
|
});
|
|
38319
38839
|
|
|
38320
38840
|
// src/routes/internal/gforms-webhook.ts
|
|
38321
|
-
var gformsWebhookRoute = new
|
|
38841
|
+
var gformsWebhookRoute = new chunkLVKPYSXI_cjs.Hono();
|
|
38322
38842
|
gformsWebhookRoute.post("/", internalMiddleware, async (c) => {
|
|
38323
38843
|
const rawBody = await c.req.text();
|
|
38324
38844
|
const signature = c.req.header("X-Goog-Signature");
|
|
@@ -38390,7 +38910,7 @@ var ALLOWED_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
|
38390
38910
|
"image/svg+xml"
|
|
38391
38911
|
]);
|
|
38392
38912
|
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
38393
|
-
var uploadsRoute = new
|
|
38913
|
+
var uploadsRoute = new chunkLVKPYSXI_cjs.Hono();
|
|
38394
38914
|
uploadsRoute.get("/images/*", async (c) => {
|
|
38395
38915
|
const bucket = c.env.FILES;
|
|
38396
38916
|
if (!bucket) {
|
|
@@ -38564,7 +39084,7 @@ var createThemeSchema = external_exports.object({
|
|
|
38564
39084
|
name: external_exports.string().min(1),
|
|
38565
39085
|
path: external_exports.string().min(1)
|
|
38566
39086
|
});
|
|
38567
|
-
var themesRoute = new
|
|
39087
|
+
var themesRoute = new chunkLVKPYSXI_cjs.Hono();
|
|
38568
39088
|
themesRoute.get("/", async (c) => {
|
|
38569
39089
|
const db = createDb(c.env.DB);
|
|
38570
39090
|
const data = await db.select().from(themes);
|
|
@@ -38580,7 +39100,9 @@ themesRoute.post(
|
|
|
38580
39100
|
const db = createDb(c.env.DB);
|
|
38581
39101
|
try {
|
|
38582
39102
|
const [created] = await db.insert(themes).values(body).returning();
|
|
38583
|
-
c.
|
|
39103
|
+
if (c.get("cmsMode") === "hybrid") {
|
|
39104
|
+
c.executionCtx.waitUntil(pushThemeToContentful(c.env, created));
|
|
39105
|
+
}
|
|
38584
39106
|
return c.json({ data: created }, 201);
|
|
38585
39107
|
} catch (err) {
|
|
38586
39108
|
if (err.message && err.message.includes("UNIQUE constraint failed")) {
|
|
@@ -38601,7 +39123,9 @@ themesRoute.patch(
|
|
|
38601
39123
|
try {
|
|
38602
39124
|
const [updated] = await db.update(themes).set(body).where(eq(themes.id, id)).returning();
|
|
38603
39125
|
if (!updated) throw notFound("Theme");
|
|
38604
|
-
c.
|
|
39126
|
+
if (c.get("cmsMode") === "hybrid") {
|
|
39127
|
+
c.executionCtx.waitUntil(pushThemeToContentful(c.env, updated));
|
|
39128
|
+
}
|
|
38605
39129
|
return c.json({ data: updated });
|
|
38606
39130
|
} catch (err) {
|
|
38607
39131
|
if (err.message && err.message.includes("UNIQUE constraint failed")) {
|
|
@@ -38616,7 +39140,9 @@ themesRoute.delete("/:id", authMiddleware, adminMiddleware, async (c) => {
|
|
|
38616
39140
|
const db = createDb(c.env.DB);
|
|
38617
39141
|
const [deleted] = await db.delete(themes).where(eq(themes.id, id)).returning();
|
|
38618
39142
|
if (!deleted) throw notFound("Theme");
|
|
38619
|
-
c.
|
|
39143
|
+
if (c.get("cmsMode") === "hybrid") {
|
|
39144
|
+
c.executionCtx.waitUntil(deleteThemeFromContentful(c.env, id));
|
|
39145
|
+
}
|
|
38620
39146
|
return c.body(null, 204);
|
|
38621
39147
|
});
|
|
38622
39148
|
|
|
@@ -38627,7 +39153,7 @@ var createOrganizationSchema = external_exports.object({
|
|
|
38627
39153
|
logoUrl: external_exports.string().url().nullable().optional(),
|
|
38628
39154
|
link: external_exports.string().url().nullable().optional()
|
|
38629
39155
|
});
|
|
38630
|
-
var organizationsRoute = new
|
|
39156
|
+
var organizationsRoute = new chunkLVKPYSXI_cjs.Hono();
|
|
38631
39157
|
organizationsRoute.get("/", async (c) => {
|
|
38632
39158
|
const db = createDb(c.env.DB);
|
|
38633
39159
|
const data = await db.select().from(organizations);
|
|
@@ -38680,9 +39206,86 @@ organizationsRoute.delete("/:id", authMiddleware, adminMiddleware, async (c) =>
|
|
|
38680
39206
|
return c.body(null, 204);
|
|
38681
39207
|
});
|
|
38682
39208
|
|
|
39209
|
+
// src/routes/contentful-sync.ts
|
|
39210
|
+
var contentfulSyncRoute = new chunkLVKPYSXI_cjs.Hono();
|
|
39211
|
+
contentfulSyncRoute.post(
|
|
39212
|
+
"/trigger",
|
|
39213
|
+
authMiddleware,
|
|
39214
|
+
adminMiddleware,
|
|
39215
|
+
async (c) => {
|
|
39216
|
+
const env2 = c.env;
|
|
39217
|
+
const { CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_ENVIRONMENT } = env2;
|
|
39218
|
+
if (!CONTENTFUL_SPACE_ID || !CONTENTFUL_ACCESS_TOKEN) {
|
|
39219
|
+
return c.json({ error: { code: "NOT_CONFIGURED", message: "Contentful credentials missing" } }, 400);
|
|
39220
|
+
}
|
|
39221
|
+
const db = createDb(env2.DB);
|
|
39222
|
+
const contentful = new ContentfulService(CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_ENVIRONMENT);
|
|
39223
|
+
try {
|
|
39224
|
+
if (CONTENTFUL_MANAGEMENT_TOKEN) {
|
|
39225
|
+
const mgmt = new ContentfulManagement(CONTENTFUL_SPACE_ID, CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_ENVIRONMENT);
|
|
39226
|
+
await ensureContentTypes(mgmt);
|
|
39227
|
+
}
|
|
39228
|
+
c.executionCtx.waitUntil(
|
|
39229
|
+
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))
|
|
39230
|
+
);
|
|
39231
|
+
return c.json({ message: "Contentful sync triggered", triggeredAt: Math.floor(Date.now() / 1e3) });
|
|
39232
|
+
} catch (err) {
|
|
39233
|
+
console.error("[Contentful] Sync trigger failed:", err);
|
|
39234
|
+
return c.json({ error: { code: "SYNC_FAILED", message: err?.message ?? "Failed to trigger sync" } }, 500);
|
|
39235
|
+
}
|
|
39236
|
+
}
|
|
39237
|
+
);
|
|
39238
|
+
contentfulSyncRoute.post(
|
|
39239
|
+
"/push",
|
|
39240
|
+
authMiddleware,
|
|
39241
|
+
adminMiddleware,
|
|
39242
|
+
async (c) => {
|
|
39243
|
+
const env2 = c.env;
|
|
39244
|
+
const { CONTENTFUL_SPACE_ID, CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_ENVIRONMENT } = env2;
|
|
39245
|
+
if (!CONTENTFUL_SPACE_ID || !CONTENTFUL_MANAGEMENT_TOKEN) {
|
|
39246
|
+
return c.json({ error: { code: "NOT_CONFIGURED", message: "Contentful management credentials missing" } }, 400);
|
|
39247
|
+
}
|
|
39248
|
+
const db = createDb(env2.DB);
|
|
39249
|
+
const mgmt = new ContentfulManagement(CONTENTFUL_SPACE_ID, CONTENTFUL_MANAGEMENT_TOKEN, CONTENTFUL_ENVIRONMENT);
|
|
39250
|
+
try {
|
|
39251
|
+
c.executionCtx.waitUntil(
|
|
39252
|
+
pushToContentful(db, mgmt, {}, env2.KV).then((r) => console.log("[Contentful] Push complete:", JSON.stringify(r))).catch((err) => console.error("[Contentful] Push failed:", err))
|
|
39253
|
+
);
|
|
39254
|
+
return c.json({ message: "Contentful push triggered", triggeredAt: Math.floor(Date.now() / 1e3) });
|
|
39255
|
+
} catch (err) {
|
|
39256
|
+
console.error("[Contentful] Push trigger failed:", err);
|
|
39257
|
+
return c.json({ error: { code: "SYNC_FAILED", message: err?.message ?? "Failed to trigger push" } }, 500);
|
|
39258
|
+
}
|
|
39259
|
+
}
|
|
39260
|
+
);
|
|
39261
|
+
contentfulSyncRoute.get(
|
|
39262
|
+
"/status",
|
|
39263
|
+
authMiddleware,
|
|
39264
|
+
adminMiddleware,
|
|
39265
|
+
async (c) => {
|
|
39266
|
+
const env2 = c.env;
|
|
39267
|
+
const db = createDb(env2.DB);
|
|
39268
|
+
const [[{ themes: themes2 }], [{ events: events2 }], [{ faqs: faqs2 }], [{ orgs }]] = await Promise.all([
|
|
39269
|
+
db.all(sql2`SELECT count(*) as themes FROM themes`),
|
|
39270
|
+
db.all(sql2`SELECT count(*) as events FROM events`),
|
|
39271
|
+
db.all(sql2`SELECT count(*) as faqs FROM faqs`),
|
|
39272
|
+
db.all(sql2`SELECT count(*) as orgs FROM organizations`)
|
|
39273
|
+
]);
|
|
39274
|
+
const isConfigured = !!env2.CONTENTFUL_SPACE_ID && !!env2.CONTENTFUL_ACCESS_TOKEN;
|
|
39275
|
+
const canPush = !!env2.CONTENTFUL_SPACE_ID && !!env2.CONTENTFUL_MANAGEMENT_TOKEN;
|
|
39276
|
+
return c.json({
|
|
39277
|
+
isConfigured,
|
|
39278
|
+
canPush,
|
|
39279
|
+
cmsMode: c.get("cmsMode"),
|
|
39280
|
+
spaceId: env2.CONTENTFUL_SPACE_ID ?? null,
|
|
39281
|
+
totals: { themes: themes2, events: events2, faqs: faqs2, organizations: orgs }
|
|
39282
|
+
});
|
|
39283
|
+
}
|
|
39284
|
+
);
|
|
39285
|
+
|
|
38683
39286
|
// src/app.ts
|
|
38684
39287
|
function createApp(options = {}) {
|
|
38685
|
-
const app = new
|
|
39288
|
+
const app = new chunkLVKPYSXI_cjs.Hono();
|
|
38686
39289
|
if (options.gformsWebhookUrl) {
|
|
38687
39290
|
const webhookUrl = `${options.gformsWebhookUrl.replace(/\/$/, "")}/internal/gforms-webhook`;
|
|
38688
39291
|
app.use("*", async (c, next) => {
|
|
@@ -38691,8 +39294,14 @@ function createApp(options = {}) {
|
|
|
38691
39294
|
});
|
|
38692
39295
|
}
|
|
38693
39296
|
app.use("*", createCorsMiddleware(options.allowedOrigins ?? ["*"]));
|
|
38694
|
-
app.use("*",
|
|
39297
|
+
app.use("*", chunkLVKPYSXI_cjs.createPowChallengeMiddleware());
|
|
38695
39298
|
app.use("*", createRefererGuard(options.allowedOrigins ?? ["*"]));
|
|
39299
|
+
app.use("*", async (c, next) => {
|
|
39300
|
+
const overrideRaw = await c.env.KV.get("config:cms_mode").catch(() => null);
|
|
39301
|
+
const override = overrideRaw ? JSON.parse(overrideRaw) : null;
|
|
39302
|
+
c.set("cmsMode", parseCmsMode(override ?? c.env.CMS_MODE));
|
|
39303
|
+
return next();
|
|
39304
|
+
});
|
|
38696
39305
|
app.on(["POST", "GET"], "/api/auth/*", (c) => {
|
|
38697
39306
|
const auth = createAuth(c.env);
|
|
38698
39307
|
const req = c.req.raw;
|
|
@@ -38704,7 +39313,7 @@ function createApp(options = {}) {
|
|
|
38704
39313
|
return auth.handler(new Request(req.url, {
|
|
38705
39314
|
method: req.method,
|
|
38706
39315
|
headers: newHeaders,
|
|
38707
|
-
body: req.method === "GET" ? null : req.body,
|
|
39316
|
+
body: req.method === "GET" || req.method === "HEAD" || req.method === "OPTIONS" ? null : req.body,
|
|
38708
39317
|
redirect: req.redirect
|
|
38709
39318
|
}));
|
|
38710
39319
|
}
|
|
@@ -38722,15 +39331,16 @@ function createApp(options = {}) {
|
|
|
38722
39331
|
}
|
|
38723
39332
|
return next();
|
|
38724
39333
|
});
|
|
38725
|
-
app.post(
|
|
39334
|
+
app.post(chunkLVKPYSXI_cjs.POW_VERIFY_PATH, chunkLVKPYSXI_cjs.handlePowVerify);
|
|
38726
39335
|
app.route("/health", healthRoute);
|
|
38727
39336
|
app.route("/api/config", siteConfigRoute);
|
|
38728
|
-
app.route("/api/
|
|
39337
|
+
app.route("/api/classes", classesRoute);
|
|
38729
39338
|
app.route("/api/themes", themesRoute);
|
|
38730
39339
|
app.route("/api/users", usersRoute);
|
|
38731
39340
|
app.route("/api/organizations", organizationsRoute);
|
|
38732
39341
|
app.route("/api/faqs", faqsRoute);
|
|
38733
39342
|
app.route("/api/uploads", uploadsRoute);
|
|
39343
|
+
app.route("/api/contentful", contentfulSyncRoute);
|
|
38734
39344
|
app.route("/internal/gforms-webhook", gformsWebhookRoute);
|
|
38735
39345
|
app.onError(errorHandler);
|
|
38736
39346
|
app.notFound(
|
|
@@ -39364,6 +39974,7 @@ var PATCH_STATEMENTS = [
|
|
|
39364
39974
|
`ALTER TABLE "events" ADD COLUMN "class_code" text`,
|
|
39365
39975
|
`ALTER TABLE "events" ADD COLUMN "start_time" text`,
|
|
39366
39976
|
`ALTER TABLE "events" ADD COLUMN "end_time" text`,
|
|
39977
|
+
`ALTER TABLE "events" RENAME COLUMN "is_major" TO "is_spotlight"`,
|
|
39367
39978
|
`CREATE INDEX IF NOT EXISTS "idx_events_organization_id" ON "events" ("organization_id")`
|
|
39368
39979
|
];
|
|
39369
39980
|
var CREATE_STATEMENTS = [
|
|
@@ -39467,7 +40078,7 @@ var CREATE_STATEMENTS = [
|
|
|
39467
40078
|
"class_code" text,
|
|
39468
40079
|
"start_time" text,
|
|
39469
40080
|
"end_time" text,
|
|
39470
|
-
"
|
|
40081
|
+
"is_spotlight" integer DEFAULT false NOT NULL,
|
|
39471
40082
|
"max_slots" integer DEFAULT 0 NOT NULL,
|
|
39472
40083
|
"registered_slots" integer DEFAULT 0 NOT NULL,
|
|
39473
40084
|
"gforms_id" text,
|
|
@@ -39529,7 +40140,9 @@ async function ensureDatabase(d1) {
|
|
|
39529
40140
|
try {
|
|
39530
40141
|
await d1.prepare(sql3).run();
|
|
39531
40142
|
} catch (err) {
|
|
39532
|
-
if (err?.message?.includes("duplicate column"))
|
|
40143
|
+
if (err?.message?.includes("duplicate column") || err?.message?.includes("no such column") && err?.message?.includes("is_major")) {
|
|
40144
|
+
continue;
|
|
40145
|
+
}
|
|
39533
40146
|
throw err;
|
|
39534
40147
|
}
|
|
39535
40148
|
}
|
|
@@ -39538,12 +40151,14 @@ async function ensureDatabase(d1) {
|
|
|
39538
40151
|
// src/worker-handler.ts
|
|
39539
40152
|
var API_PREFIXES = [
|
|
39540
40153
|
"/api/auth/",
|
|
39541
|
-
"/api/
|
|
40154
|
+
"/api/classes",
|
|
39542
40155
|
"/api/users",
|
|
40156
|
+
"/api/organizations",
|
|
39543
40157
|
"/api/faqs",
|
|
39544
40158
|
"/api/themes",
|
|
39545
40159
|
"/api/config",
|
|
39546
40160
|
"/api/uploads",
|
|
40161
|
+
"/api/contentful",
|
|
39547
40162
|
"/health",
|
|
39548
40163
|
"/internal/",
|
|
39549
40164
|
"/.well-known/"
|
|
@@ -39582,7 +40197,7 @@ function createWorkerHandler(options) {
|
|
|
39582
40197
|
return getLeapify(env2).fetch(request, env2, ctx);
|
|
39583
40198
|
}
|
|
39584
40199
|
let response = await options.serveFrontend(request, env2, ctx);
|
|
39585
|
-
if ((!response || response.status === 404) && !pathname.includes(".")) {
|
|
40200
|
+
if ((!response || response.status === 404) && !pathname.includes(".") && request.method === "GET") {
|
|
39586
40201
|
const indexRequest = new Request(new URL("/", request.url), request);
|
|
39587
40202
|
response = await options.serveFrontend(indexRequest, env2, ctx);
|
|
39588
40203
|
}
|
|
@@ -39690,6 +40305,7 @@ function injectConfig2(html2, config3) {
|
|
|
39690
40305
|
(*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) *)
|
|
39691
40306
|
*/
|
|
39692
40307
|
|
|
40308
|
+
exports.CONTENTFUL_CONFIG_KEYS = CONTENTFUL_CONFIG_KEYS;
|
|
39693
40309
|
exports.ContentfulManagement = ContentfulManagement;
|
|
39694
40310
|
exports.authAccount = authAccount;
|
|
39695
40311
|
exports.authSession = authSession;
|
|
@@ -39697,6 +40313,7 @@ exports.authUser = authUser;
|
|
|
39697
40313
|
exports.authVerification = authVerification;
|
|
39698
40314
|
exports.bookmarks = bookmarks;
|
|
39699
40315
|
exports.bookmarksRelations = bookmarksRelations;
|
|
40316
|
+
exports.contentfulConfig = contentfulConfig;
|
|
39700
40317
|
exports.createDb = createDb;
|
|
39701
40318
|
exports.createLeapify = createLeapify;
|
|
39702
40319
|
exports.createQueueHandler = createQueueHandler;
|
|
@@ -39710,6 +40327,9 @@ exports.getRuntimeConfig = getRuntimeConfig;
|
|
|
39710
40327
|
exports.injectConfig = injectConfig2;
|
|
39711
40328
|
exports.organizations = organizations;
|
|
39712
40329
|
exports.organizationsRelations = organizationsRelations;
|
|
40330
|
+
exports.parseCmsMode = parseCmsMode;
|
|
40331
|
+
exports.shouldPullFromContentful = shouldPullFromContentful;
|
|
40332
|
+
exports.shouldPushToContentful = shouldPushToContentful;
|
|
39713
40333
|
exports.siteConfig = siteConfig;
|
|
39714
40334
|
exports.themes = themes;
|
|
39715
40335
|
exports.themesRelations = themesRelations;
|