@notionx/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/dist/admin/index.d.ts +137 -0
  2. package/dist/admin/index.js +206 -0
  3. package/dist/admin/index.js.map +1 -0
  4. package/dist/admin/pages/index.d.ts +324 -0
  5. package/dist/admin/pages/index.js +827 -0
  6. package/dist/admin/pages/index.js.map +1 -0
  7. package/dist/auth/auth-pages/forgot-password.d.ts +20 -0
  8. package/dist/auth/auth-pages/forgot-password.js +70 -0
  9. package/dist/auth/auth-pages/forgot-password.js.map +1 -0
  10. package/dist/auth/auth-pages/index.d.ts +6 -0
  11. package/dist/auth/auth-pages/index.js +342 -0
  12. package/dist/auth/auth-pages/index.js.map +1 -0
  13. package/dist/auth/auth-pages/login.d.ts +30 -0
  14. package/dist/auth/auth-pages/login.js +125 -0
  15. package/dist/auth/auth-pages/login.js.map +1 -0
  16. package/dist/auth/auth-pages/register.d.ts +17 -0
  17. package/dist/auth/auth-pages/register.js +81 -0
  18. package/dist/auth/auth-pages/register.js.map +1 -0
  19. package/dist/auth/auth-pages/reset-password.d.ts +18 -0
  20. package/dist/auth/auth-pages/reset-password.js +72 -0
  21. package/dist/auth/auth-pages/reset-password.js.map +1 -0
  22. package/dist/auth/index.d.ts +72 -0
  23. package/dist/auth/index.js +1011 -0
  24. package/dist/auth/index.js.map +1 -0
  25. package/dist/auth/passwords.d.ts +6 -0
  26. package/dist/auth/passwords.js +79 -0
  27. package/dist/auth/passwords.js.map +1 -0
  28. package/dist/auth/rate-limit.d.ts +28 -0
  29. package/dist/auth/rate-limit.js +245 -0
  30. package/dist/auth/rate-limit.js.map +1 -0
  31. package/dist/auth/routes/google-callback.d.ts +6 -0
  32. package/dist/auth/routes/google-callback.js +404 -0
  33. package/dist/auth/routes/google-callback.js.map +1 -0
  34. package/dist/auth/routes/google.d.ts +6 -0
  35. package/dist/auth/routes/google.js +250 -0
  36. package/dist/auth/routes/google.js.map +1 -0
  37. package/dist/auth/routes/index.d.ts +22 -0
  38. package/dist/auth/routes/index.js +619 -0
  39. package/dist/auth/routes/index.js.map +1 -0
  40. package/dist/auth/routes/verify-email.d.ts +6 -0
  41. package/dist/auth/routes/verify-email.js +317 -0
  42. package/dist/auth/routes/verify-email.js.map +1 -0
  43. package/dist/auth/routes/viewer.d.ts +6 -0
  44. package/dist/auth/routes/viewer.js +372 -0
  45. package/dist/auth/routes/viewer.js.map +1 -0
  46. package/dist/auth/session.d.ts +9 -0
  47. package/dist/auth/session.js +1 -0
  48. package/dist/auth/session.js.map +1 -0
  49. package/dist/auth/turnstile.d.ts +20 -0
  50. package/dist/auth/turnstile.js +301 -0
  51. package/dist/auth/turnstile.js.map +1 -0
  52. package/dist/auth/user-session.d.ts +42 -0
  53. package/dist/auth/user-session.js +419 -0
  54. package/dist/auth/user-session.js.map +1 -0
  55. package/dist/auth/users.d.ts +112 -0
  56. package/dist/auth/users.js +558 -0
  57. package/dist/auth/users.js.map +1 -0
  58. package/dist/bootstrap-CN2g76M6.d.ts +67 -0
  59. package/dist/cache/index.d.ts +6 -0
  60. package/dist/cache/index.js +47 -0
  61. package/dist/cache/index.js.map +1 -0
  62. package/dist/content/admin-summary.d.ts +24 -0
  63. package/dist/content/admin-summary.js +36 -0
  64. package/dist/content/admin-summary.js.map +1 -0
  65. package/dist/content/index.d.ts +9 -0
  66. package/dist/content/index.js +473 -0
  67. package/dist/content/index.js.map +1 -0
  68. package/dist/content/models.d.ts +69 -0
  69. package/dist/content/models.js +24 -0
  70. package/dist/content/models.js.map +1 -0
  71. package/dist/content/prewarm.d.ts +28 -0
  72. package/dist/content/prewarm.js +56 -0
  73. package/dist/content/prewarm.js.map +1 -0
  74. package/dist/content/revalidate.d.ts +37 -0
  75. package/dist/content/revalidate.js +170 -0
  76. package/dist/content/revalidate.js.map +1 -0
  77. package/dist/content/search-index.d.ts +54 -0
  78. package/dist/content/search-index.js +172 -0
  79. package/dist/content/search-index.js.map +1 -0
  80. package/dist/content/search.d.ts +8 -0
  81. package/dist/content/search.js +57 -0
  82. package/dist/content/search.js.map +1 -0
  83. package/dist/doctor/cli.d.ts +1 -0
  84. package/dist/doctor/cli.js +360 -0
  85. package/dist/doctor/cli.js.map +1 -0
  86. package/dist/doctor/index.d.ts +139 -0
  87. package/dist/doctor/index.js +289 -0
  88. package/dist/doctor/index.js.map +1 -0
  89. package/dist/email/index.d.ts +38 -0
  90. package/dist/email/index.js +126 -0
  91. package/dist/email/index.js.map +1 -0
  92. package/dist/env-C5qu-0R-.d.ts +35 -0
  93. package/dist/hooks/index.d.ts +2 -0
  94. package/dist/hooks/index.js +1 -0
  95. package/dist/hooks/index.js.map +1 -0
  96. package/dist/i18n/index.d.ts +26 -0
  97. package/dist/i18n/index.js +73 -0
  98. package/dist/i18n/index.js.map +1 -0
  99. package/dist/index.d.ts +8 -0
  100. package/dist/index.js +1281 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/internal/admin/index.d.ts +75 -0
  103. package/dist/internal/admin/index.js +365 -0
  104. package/dist/internal/admin/index.js.map +1 -0
  105. package/dist/media/index.d.ts +24 -0
  106. package/dist/media/index.js +86 -0
  107. package/dist/media/index.js.map +1 -0
  108. package/dist/media/routes/index.d.ts +1 -0
  109. package/dist/media/routes/index.js +585 -0
  110. package/dist/media/routes/index.js.map +1 -0
  111. package/dist/media/routes/notion-media.d.ts +19 -0
  112. package/dist/media/routes/notion-media.js +588 -0
  113. package/dist/media/routes/notion-media.js.map +1 -0
  114. package/dist/middleware.d.ts +95 -0
  115. package/dist/middleware.js +79 -0
  116. package/dist/middleware.js.map +1 -0
  117. package/dist/notion/block-text.d.ts +5 -0
  118. package/dist/notion/block-text.js +37 -0
  119. package/dist/notion/block-text.js.map +1 -0
  120. package/dist/notion/blocks.d.ts +24 -0
  121. package/dist/notion/blocks.js +46 -0
  122. package/dist/notion/blocks.js.map +1 -0
  123. package/dist/notion/client.d.ts +7 -0
  124. package/dist/notion/client.js +13 -0
  125. package/dist/notion/client.js.map +1 -0
  126. package/dist/notion/config.d.ts +25 -0
  127. package/dist/notion/config.js +147 -0
  128. package/dist/notion/config.js.map +1 -0
  129. package/dist/notion/content-cache.d.ts +45 -0
  130. package/dist/notion/content-cache.js +166 -0
  131. package/dist/notion/content-cache.js.map +1 -0
  132. package/dist/notion/generic-source.d.ts +61 -0
  133. package/dist/notion/generic-source.js +408 -0
  134. package/dist/notion/generic-source.js.map +1 -0
  135. package/dist/notion/index.d.ts +13 -0
  136. package/dist/notion/index.js +1278 -0
  137. package/dist/notion/index.js.map +1 -0
  138. package/dist/notion/mappers.d.ts +1 -0
  139. package/dist/notion/mappers.js +152 -0
  140. package/dist/notion/mappers.js.map +1 -0
  141. package/dist/notion/media.d.ts +22 -0
  142. package/dist/notion/media.js +209 -0
  143. package/dist/notion/media.js.map +1 -0
  144. package/dist/notion/property-mappers.d.ts +24 -0
  145. package/dist/notion/property-mappers.js +152 -0
  146. package/dist/notion/property-mappers.js.map +1 -0
  147. package/dist/notion/routes/index.d.ts +8 -0
  148. package/dist/notion/routes/index.js +428 -0
  149. package/dist/notion/routes/index.js.map +1 -0
  150. package/dist/notion/routes/webhook.d.ts +98 -0
  151. package/dist/notion/routes/webhook.js +428 -0
  152. package/dist/notion/routes/webhook.js.map +1 -0
  153. package/dist/notion/types.d.ts +152 -0
  154. package/dist/notion/types.js +1 -0
  155. package/dist/notion/types.js.map +1 -0
  156. package/dist/notion/webhook.d.ts +83 -0
  157. package/dist/notion/webhook.js +490 -0
  158. package/dist/notion/webhook.js.map +1 -0
  159. package/dist/platform/capabilities.d.ts +34 -0
  160. package/dist/platform/capabilities.js +42 -0
  161. package/dist/platform/capabilities.js.map +1 -0
  162. package/dist/platform/current.d.ts +13 -0
  163. package/dist/platform/current.js +181 -0
  164. package/dist/platform/current.js.map +1 -0
  165. package/dist/platform/index.d.ts +5 -0
  166. package/dist/platform/index.js +269 -0
  167. package/dist/platform/index.js.map +1 -0
  168. package/dist/platform/runtime.d.ts +118 -0
  169. package/dist/platform/runtime.js +160 -0
  170. package/dist/platform/runtime.js.map +1 -0
  171. package/dist/platform/selection.d.ts +10 -0
  172. package/dist/platform/selection.js +22 -0
  173. package/dist/platform/selection.js.map +1 -0
  174. package/dist/storage/index.d.ts +17 -0
  175. package/dist/storage/index.js +218 -0
  176. package/dist/storage/index.js.map +1 -0
  177. package/dist/storage/routes/cdn.d.ts +19 -0
  178. package/dist/storage/routes/cdn.js +289 -0
  179. package/dist/storage/routes/cdn.js.map +1 -0
  180. package/dist/storage/routes/files.d.ts +27 -0
  181. package/dist/storage/routes/files.js +216 -0
  182. package/dist/storage/routes/files.js.map +1 -0
  183. package/dist/storage/routes/index.d.ts +2 -0
  184. package/dist/storage/routes/index.js +352 -0
  185. package/dist/storage/routes/index.js.map +1 -0
  186. package/dist/types-BsAcZSNX.d.ts +94 -0
  187. package/dist/types.d.ts +78 -0
  188. package/dist/types.js +1 -0
  189. package/dist/types.js.map +1 -0
  190. package/dist/util/index.d.ts +18 -0
  191. package/dist/util/index.js +48 -0
  192. package/dist/util/index.js.map +1 -0
  193. package/dist/worker/index.d.ts +6 -0
  194. package/dist/worker/index.js +1026 -0
  195. package/dist/worker/index.js.map +1 -0
  196. package/dist/worker/routes/content-prewarm.d.ts +34 -0
  197. package/dist/worker/routes/content-prewarm.js +38 -0
  198. package/dist/worker/routes/content-prewarm.js.map +1 -0
  199. package/dist/worker/routes/content-revalidate.d.ts +81 -0
  200. package/dist/worker/routes/content-revalidate.js +64 -0
  201. package/dist/worker/routes/content-revalidate.js.map +1 -0
  202. package/dist/worker/routes/health.d.ts +14 -0
  203. package/dist/worker/routes/health.js +278 -0
  204. package/dist/worker/routes/health.js.map +1 -0
  205. package/dist/worker/routes/index.d.ts +6 -0
  206. package/dist/worker/routes/index.js +373 -0
  207. package/dist/worker/routes/index.js.map +1 -0
  208. package/package.json +124 -0
@@ -0,0 +1,301 @@
1
+ // src/util/env.ts
2
+ import { env } from "cloudflare:workers";
3
+ var workerEnv = env;
4
+
5
+ // src/internal/admin/settings.ts
6
+ import { cache } from "react";
7
+
8
+ // src/platform/runtime.ts
9
+ function cacheRequestForKey(key) {
10
+ return new Request(key, { method: "GET" });
11
+ }
12
+ function createCloudflarePublicCacheAdapter(cache2) {
13
+ return {
14
+ kind: "cloudflare-cache",
15
+ async match(key) {
16
+ return await cache2.match(cacheRequestForKey(key)) ?? null;
17
+ },
18
+ put(key, response) {
19
+ return cache2.put(cacheRequestForKey(key), response);
20
+ },
21
+ delete(key) {
22
+ return cache2.delete(cacheRequestForKey(key));
23
+ }
24
+ };
25
+ }
26
+ function createCloudflareKeyValueCacheAdapter(namespace) {
27
+ return {
28
+ kind: "workers-kv",
29
+ async get(key, options) {
30
+ return await namespace.get(key, {
31
+ type: "json",
32
+ cacheTtl: options?.cacheTtl
33
+ });
34
+ },
35
+ async put(key, value, options) {
36
+ await namespace.put(key, JSON.stringify(value), {
37
+ expirationTtl: options?.expirationTtl,
38
+ metadata: options?.metadata
39
+ });
40
+ },
41
+ delete(key) {
42
+ return namespace.delete(key);
43
+ },
44
+ async list(options) {
45
+ const result = await namespace.list({
46
+ prefix: options?.prefix,
47
+ limit: options?.limit,
48
+ cursor: options?.cursor
49
+ });
50
+ return {
51
+ keys: result.keys.map((key) => ({ name: key.name })),
52
+ cursor: result.list_complete ? void 0 : result.cursor,
53
+ listComplete: result.list_complete
54
+ };
55
+ }
56
+ };
57
+ }
58
+ function r2ObjectToStoredObject(object) {
59
+ return {
60
+ body: object.body,
61
+ size: object.size,
62
+ etag: object.etag,
63
+ contentType: object.httpMetadata?.contentType
64
+ };
65
+ }
66
+ function createCloudflareRuntimePlatform(env2, options) {
67
+ const database = env2.DB ? {
68
+ kind: "d1",
69
+ prepare(query) {
70
+ return env2.DB.prepare(query);
71
+ },
72
+ async batch(statements) {
73
+ return await env2.DB.batch(
74
+ statements
75
+ );
76
+ }
77
+ } : null;
78
+ const objectStorage = env2.ASSETS_BUCKET ? {
79
+ kind: "r2",
80
+ async get(key) {
81
+ const object = await env2.ASSETS_BUCKET?.get(key);
82
+ return object ? r2ObjectToStoredObject(object) : null;
83
+ },
84
+ async put(key, value, options2) {
85
+ await env2.ASSETS_BUCKET?.put(key, value, {
86
+ httpMetadata: {
87
+ contentType: options2?.contentType,
88
+ cacheControl: options2?.cacheControl
89
+ },
90
+ customMetadata: options2?.metadata
91
+ });
92
+ },
93
+ async delete(key) {
94
+ await env2.ASSETS_BUCKET?.delete(key);
95
+ },
96
+ async list(options2) {
97
+ const listed = await env2.ASSETS_BUCKET?.list({
98
+ prefix: options2?.prefix,
99
+ limit: options2?.limit
100
+ });
101
+ return listed?.objects.map((object) => ({
102
+ key: object.key,
103
+ size: object.size,
104
+ uploaded: object.uploaded
105
+ })) ?? [];
106
+ }
107
+ } : null;
108
+ const imageTransformer = env2.IMAGES ? {
109
+ kind: "cloudflare-images",
110
+ async transform(body, options2) {
111
+ const result = await env2.IMAGES.input(body).transform(options2.width ? { width: options2.width } : {}).output({
112
+ format: options2.format,
113
+ quality: options2.quality
114
+ });
115
+ return {
116
+ body: result.image(),
117
+ contentType: result.contentType(),
118
+ response: () => result.response()
119
+ };
120
+ }
121
+ } : null;
122
+ const keyValueCache = env2.CONTENT_CACHE ? createCloudflareKeyValueCacheAdapter(env2.CONTENT_CACHE) : null;
123
+ return {
124
+ id: "cloudflare-workers",
125
+ database,
126
+ objectStorage,
127
+ imageTransformer,
128
+ keyValueCache,
129
+ publicCache: options?.publicCache ? createCloudflarePublicCacheAdapter(options.publicCache) : null
130
+ };
131
+ }
132
+
133
+ // src/platform/cloudflare-runtime.ts
134
+ function getDefaultCloudflareCache() {
135
+ const globalWithCaches = globalThis;
136
+ return globalWithCaches.caches?.default ?? null;
137
+ }
138
+ function getRuntimePlatform() {
139
+ return createCloudflareRuntimePlatform(workerEnv, {
140
+ publicCache: getDefaultCloudflareCache()
141
+ });
142
+ }
143
+
144
+ // src/platform/current.ts
145
+ function getRuntimePlatform2() {
146
+ return getRuntimePlatform();
147
+ }
148
+ function getDatabase() {
149
+ const platform = getRuntimePlatform2();
150
+ const database = platform.database;
151
+ if (!database) {
152
+ throw new Error(`SQL database adapter not configured for ${platform.id}`);
153
+ }
154
+ return database;
155
+ }
156
+
157
+ // src/internal/admin/schema-guard.ts
158
+ var DEFAULT_TURNSTILE_PUBLIC_CONFIG = {
159
+ enabled: false,
160
+ siteKey: null,
161
+ secretConfigured: false
162
+ };
163
+ function isSchemaDriftError(error) {
164
+ const message = error instanceof Error ? error.message : String(error ?? "");
165
+ return message.includes("no such column") || message.includes("no such table");
166
+ }
167
+ function buildTurnstilePublicConfig(settings, envLike) {
168
+ const envSiteKey = envLike.TURNSTILE_SITE_KEY?.trim() || null;
169
+ const siteKey = settings.turnstile_site_key?.trim() || envSiteKey || null;
170
+ const secretConfigured = Boolean(envLike.TURNSTILE_SECRET_KEY?.trim());
171
+ const enabled = (settings.turnstile_enabled === 1 || Boolean(envSiteKey)) && Boolean(siteKey) && secretConfigured;
172
+ return {
173
+ enabled,
174
+ siteKey,
175
+ secretConfigured
176
+ };
177
+ }
178
+
179
+ // src/internal/admin/settings.ts
180
+ var DEFAULT_ADMIN_EMAIL = "zhaofilms@gmail.com";
181
+ function rowToSettings(r) {
182
+ return {
183
+ site_title: r.site_title,
184
+ google_enabled: r.google_enabled === 1 ? 1 : 0,
185
+ google_client_id: r.google_client_id,
186
+ google_client_secret: r.google_client_secret,
187
+ google_updated_at: r.google_updated_at,
188
+ turnstile_enabled: r.turnstile_enabled === 1 ? 1 : 0,
189
+ turnstile_site_key: r.turnstile_site_key,
190
+ turnstile_updated_at: r.turnstile_updated_at,
191
+ admin_email: r.admin_email,
192
+ updated_at: r.updated_at
193
+ };
194
+ }
195
+ var getAppSettingsCached = cache(async () => {
196
+ const row = await getDatabase().prepare(
197
+ `SELECT site_title, google_enabled, google_client_id, google_client_secret,
198
+ google_updated_at, turnstile_enabled, turnstile_site_key,
199
+ turnstile_updated_at, admin_email, updated_at
200
+ FROM app_settings WHERE id = 1`
201
+ ).first();
202
+ if (!row) {
203
+ return {
204
+ site_title: "vinext Blog",
205
+ google_enabled: 0,
206
+ google_client_id: null,
207
+ google_client_secret: null,
208
+ google_updated_at: null,
209
+ turnstile_enabled: 0,
210
+ turnstile_site_key: null,
211
+ turnstile_updated_at: null,
212
+ admin_email: DEFAULT_ADMIN_EMAIL,
213
+ updated_at: ""
214
+ };
215
+ }
216
+ return rowToSettings(row);
217
+ });
218
+ async function getAppSettings() {
219
+ return getAppSettingsCached();
220
+ }
221
+ async function getTurnstilePublicConfig() {
222
+ try {
223
+ const s = await getAppSettings();
224
+ return buildTurnstilePublicConfig(s, workerEnv);
225
+ } catch (error) {
226
+ if (isSchemaDriftError(error)) {
227
+ console.error(
228
+ "[settings] turnstile config unavailable due to schema drift; falling back to disabled state",
229
+ error
230
+ );
231
+ return { ...DEFAULT_TURNSTILE_PUBLIC_CONFIG };
232
+ }
233
+ throw error;
234
+ }
235
+ }
236
+
237
+ // src/auth/turnstile.ts
238
+ var SITEVERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
239
+ async function getTurnstileRuntimeConfig() {
240
+ const pub = await getTurnstilePublicConfig();
241
+ const secretKey = workerEnv.TURNSTILE_SECRET_KEY?.trim();
242
+ if (!pub.enabled || !pub.siteKey || !secretKey) return null;
243
+ return { enabled: true, siteKey: pub.siteKey, secretKey };
244
+ }
245
+ async function verifyTurnstileToken(token, remoteIp) {
246
+ const config = await getTurnstileRuntimeConfig();
247
+ if (!config) {
248
+ console.error("[DEBUG-TURNSTILE] runtime config missing", {
249
+ tokenLength: token.length,
250
+ remoteIpPresent: Boolean(remoteIp)
251
+ });
252
+ return { ok: true };
253
+ }
254
+ if (!token) {
255
+ console.error("[DEBUG-TURNSTILE] missing token", {
256
+ siteKeySuffix: config.siteKey.slice(-6),
257
+ remoteIpPresent: Boolean(remoteIp)
258
+ });
259
+ return { ok: false, codes: ["missing-input-response"] };
260
+ }
261
+ const body = new URLSearchParams();
262
+ body.set("secret", config.secretKey);
263
+ body.set("response", token);
264
+ if (remoteIp) body.set("remoteip", remoteIp);
265
+ const res = await fetch(SITEVERIFY_URL, {
266
+ method: "POST",
267
+ headers: { "content-type": "application/x-www-form-urlencoded" },
268
+ body
269
+ });
270
+ if (!res.ok) {
271
+ console.error("[DEBUG-TURNSTILE] siteverify http error", {
272
+ siteKeySuffix: config.siteKey.slice(-6),
273
+ tokenLength: token.length,
274
+ status: res.status
275
+ });
276
+ return { ok: false, codes: [`http-${res.status}`] };
277
+ }
278
+ const data = await res.json();
279
+ if (data.success) return { ok: true };
280
+ console.error("[DEBUG-TURNSTILE] siteverify failed", {
281
+ siteKeySuffix: config.siteKey.slice(-6),
282
+ tokenLength: token.length,
283
+ codes: data["error-codes"] ?? ["verification-failed"]
284
+ });
285
+ return { ok: false, codes: data["error-codes"] ?? ["verification-failed"] };
286
+ }
287
+ async function verifyTurnstileFromForm(formData, remoteIp) {
288
+ const token = String(formData.get("cf-turnstile-response") ?? "").trim();
289
+ console.error("[DEBUG-TURNSTILE] form token read", {
290
+ tokenLength: token.length,
291
+ remoteIpPresent: Boolean(remoteIp)
292
+ });
293
+ const result = await verifyTurnstileToken(token, remoteIp);
294
+ return result.ok ? { ok: true } : { ok: false };
295
+ }
296
+ export {
297
+ getTurnstileRuntimeConfig,
298
+ verifyTurnstileFromForm,
299
+ verifyTurnstileToken
300
+ };
301
+ //# sourceMappingURL=turnstile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/util/env.ts","../../src/internal/admin/settings.ts","../../src/platform/runtime.ts","../../src/platform/cloudflare-runtime.ts","../../src/platform/current.ts","../../src/internal/admin/schema-guard.ts","../../src/auth/turnstile.ts"],"sourcesContent":["// lib/env.ts - 集中获取 Cloudflare bindings\n// 用 cloudflare:workers 模块(workerd 内置),作为平台 adapter 的绑定入口\n\n/// <reference types=\"@cloudflare/workers-types\" />\nimport { env } from \"cloudflare:workers\";\n\nexport type AppEnv = {\n DB: D1Database;\n ASSETS: Fetcher;\n IMAGES: ImagesBinding;\n ASSETS_BUCKET?: R2Bucket;\n CONTENT_CACHE?: KVNamespace;\n ADMIN_PASSWORD: string;\n ADMIN_EMAIL?: string;\n SITE_URL?: string;\n RESEND_API_KEY?: string;\n RESEND_FROM?: string;\n // Google OAuth 仍然兼容 Cloudflare Secret 作为兜底。\n // 实际生效值以 app_settings.google_client_id / google_client_secret 为准。\n GOOGLE_CLIENT_ID?: string;\n GOOGLE_CLIENT_SECRET?: string;\n /** Turnstile site key fallback when not stored in app_settings */\n TURNSTILE_SITE_KEY?: string;\n /** Turnstile secret — set via `wrangler secret put TURNSTILE_SECRET_KEY` */\n TURNSTILE_SECRET_KEY?: string;\n /** Notion integration token for the blog data source */\n NOTION_TOKEN?: string;\n /** Notion data source ID used by dataSources.query */\n NOTION_DATA_SOURCE_ID?: string;\n /** Notion data source ID for the public movie catalog */\n NOTION_MOVIES_DATA_SOURCE_ID?: string;\n /** Notion data source ID for localized movie copy */\n NOTION_MOVIE_TRANSLATIONS_DATA_SOURCE_ID?: string;\n /** Optional Notion API base URL for tests or proxies */\n NOTION_API_BASE_URL?: string;\n /** Optional Notion edit URL for admin handoff screens */\n NOTION_EDIT_BASE_URL?: string;\n /** Optional webhook verification token for Notion invalidation */\n NOTION_WEBHOOK_VERIFICATION_TOKEN?: string;\n};\n\n// 强制类型:vinext 把 env 类型放在 env.d.ts(interface VinextEnv extends Env),\n// 但 TS server 经常解析不到。运行时一定有 DB,类型断言保证编译通过。\nexport const workerEnv = env as unknown as AppEnv;\n","// lib/settings.ts - 读取和更新后台系统设置(单管理员模型)\n// 数据保存在 SQL 表 app_settings,目前固定为 1 行。\n//\n// Internal to the package — not exposed via package.json exports. The\n// auth helpers (turnstile.ts, users.ts) call into the read functions;\n// admin pages in the starter import the update functions through a\n// re-export shim at `apps/moviebluebook/lib/settings.ts`.\n\nimport { cache } from \"react\";\nimport { workerEnv } from \"../../util/env\";\nimport { getDatabase } from \"../../platform/current\";\nimport {\n buildTurnstilePublicConfig,\n DEFAULT_TURNSTILE_PUBLIC_CONFIG,\n isSchemaDriftError,\n} from \"./schema-guard\";\n\nexport type AppSettings = {\n site_title: string;\n google_enabled: 0 | 1;\n google_client_id: string | null;\n google_client_secret: string | null;\n google_updated_at: string | null;\n turnstile_enabled: 0 | 1;\n turnstile_site_key: string | null;\n turnstile_updated_at: string | null;\n admin_email: string;\n updated_at: string;\n};\n\ntype Row = {\n site_title: string;\n google_enabled: number;\n google_client_id: string | null;\n google_client_secret: string | null;\n google_updated_at: string | null;\n turnstile_enabled: number;\n turnstile_site_key: string | null;\n turnstile_updated_at: string | null;\n admin_email: string;\n updated_at: string;\n};\n\nconst DEFAULT_ADMIN_EMAIL = \"zhaofilms@gmail.com\";\n\nfunction rowToSettings(r: Row): AppSettings {\n return {\n site_title: r.site_title,\n google_enabled: r.google_enabled === 1 ? 1 : 0,\n google_client_id: r.google_client_id,\n google_client_secret: r.google_client_secret,\n google_updated_at: r.google_updated_at,\n turnstile_enabled: r.turnstile_enabled === 1 ? 1 : 0,\n turnstile_site_key: r.turnstile_site_key,\n turnstile_updated_at: r.turnstile_updated_at,\n admin_email: r.admin_email,\n updated_at: r.updated_at,\n };\n}\n\nconst getAppSettingsCached = cache(async (): Promise<AppSettings> => {\n const row = await getDatabase().prepare(\n `SELECT site_title, google_enabled, google_client_id, google_client_secret,\n google_updated_at, turnstile_enabled, turnstile_site_key,\n turnstile_updated_at, admin_email, updated_at\n FROM app_settings WHERE id = 1`\n ).first<Row>();\n if (!row) {\n // 极端情况:迁移未执行\n return {\n site_title: \"vinext Blog\",\n google_enabled: 0,\n google_client_id: null,\n google_client_secret: null,\n google_updated_at: null,\n turnstile_enabled: 0,\n turnstile_site_key: null,\n turnstile_updated_at: null,\n admin_email: DEFAULT_ADMIN_EMAIL,\n updated_at: \"\",\n };\n }\n return rowToSettings(row);\n});\n\nexport async function getAppSettings(): Promise<AppSettings> {\n return getAppSettingsCached();\n}\n\n/** Turnstile 前端可见配置(site key 公开;secret 在 env)。 */\nexport async function getTurnstilePublicConfig(): Promise<{\n enabled: boolean;\n siteKey: string | null;\n secretConfigured: boolean;\n}> {\n try {\n const s = await getAppSettings();\n return buildTurnstilePublicConfig(s, workerEnv);\n } catch (error) {\n if (isSchemaDriftError(error)) {\n console.error(\n \"[settings] turnstile config unavailable due to schema drift; falling back to disabled state\",\n error\n );\n return { ...DEFAULT_TURNSTILE_PUBLIC_CONFIG };\n }\n throw error;\n }\n}\n\nexport async function updateTurnstileConfig(input: {\n enabled: boolean;\n siteKey: string;\n}): Promise<void> {\n const enabled = input.enabled ? 1 : 0;\n await getDatabase().prepare(\n `UPDATE app_settings\n SET turnstile_enabled = ?,\n turnstile_site_key = ?,\n turnstile_updated_at = datetime('now'),\n updated_at = datetime('now')\n WHERE id = 1`\n )\n .bind(enabled, input.siteKey || null)\n .run();\n}\n\nexport async function disableTurnstileConfig(): Promise<void> {\n await getDatabase().prepare(\n `UPDATE app_settings\n SET turnstile_enabled = 0,\n turnstile_updated_at = datetime('now'),\n updated_at = datetime('now')\n WHERE id = 1`\n ).run();\n}\n\n/** Google 登录实际配置:只有 enabled 且 id+secret 都存在才认为可用 */\nexport async function getGoogleOAuthConfig(): Promise<{\n enabled: boolean;\n clientId: string;\n clientSecret: string;\n} | null> {\n const s = await getAppSettings();\n if (!s.google_enabled) return null;\n if (!s.google_client_id || !s.google_client_secret) return null;\n return {\n enabled: true,\n clientId: s.google_client_id,\n clientSecret: s.google_client_secret,\n };\n}\n\nexport async function updateGoogleOAuthConfig(input: {\n enabled: boolean;\n clientId: string;\n clientSecret: string;\n}): Promise<void> {\n const enabled = input.enabled ? 1 : 0;\n await getDatabase().prepare(\n `UPDATE app_settings\n SET google_enabled = ?,\n google_client_id = ?,\n google_client_secret = ?,\n google_updated_at = datetime('now'),\n updated_at = datetime('now')\n WHERE id = 1`\n )\n .bind(enabled, input.clientId, input.clientSecret)\n .run();\n}\n\nexport async function clearGoogleOAuthConfig(): Promise<void> {\n await getDatabase().prepare(\n `UPDATE app_settings\n SET google_enabled = 0,\n google_client_id = NULL,\n google_client_secret = NULL,\n google_updated_at = datetime('now'),\n updated_at = datetime('now')\n WHERE id = 1`\n ).run();\n}\n\nexport async function updateSiteTitle(title: string): Promise<void> {\n await getDatabase().prepare(\n `UPDATE app_settings SET site_title = ?, updated_at = datetime('now') WHERE id = 1`\n )\n .bind(title)\n .run();\n}\n","import type { AppEnv } from \"../util/env\";\n\nexport type PlatformBindingEnv = Pick<\n AppEnv,\n \"ASSETS_BUCKET\" | \"CONTENT_CACHE\" | \"DB\" | \"IMAGES\"\n>;\n\nexport type StoredObject = {\n body: ReadableStream;\n size: number;\n etag?: string;\n contentType?: string;\n};\n\nexport type ObjectStoragePutOptions = {\n contentType?: string;\n cacheControl?: string;\n metadata?: Record<string, string>;\n};\n\nexport type ObjectStorageListItem = {\n key: string;\n size: number;\n uploaded: Date;\n};\n\nexport type ObjectStorageAdapter = {\n kind: \"r2\";\n get(key: string): Promise<StoredObject | null>;\n put(\n key: string,\n value: ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob,\n options?: ObjectStoragePutOptions\n ): Promise<void>;\n delete(key: string): Promise<void>;\n list(\n options?: { prefix?: string; limit?: number }\n ): Promise<ObjectStorageListItem[]>;\n};\n\nexport type ImageTransformOptions = {\n width?: number;\n format: \"image/avif\" | \"image/webp\";\n quality: number;\n};\n\nexport type ImageTransformResult = {\n body: ReadableStream;\n contentType: string;\n response(): Response;\n};\n\nexport type ImageTransformerAdapter = {\n kind: \"cloudflare-images\" | \"external\";\n transform(\n body: ReadableStream,\n options: ImageTransformOptions\n ): Promise<ImageTransformResult>;\n};\n\nexport type PublicCacheAdapter = {\n kind: \"cloudflare-cache\" | \"noop\" | \"external\";\n match(key: string): Promise<Response | null>;\n put(key: string, response: Response): Promise<void>;\n delete(key: string): Promise<boolean>;\n};\n\nexport type KeyValueCacheGetOptions = {\n cacheTtl?: number;\n};\n\nexport type KeyValueCachePutOptions = {\n expirationTtl?: number;\n metadata?: Record<string, string | number | boolean | null>;\n};\n\nexport type KeyValueCacheListOptions = {\n prefix?: string;\n limit?: number;\n cursor?: string;\n};\n\nexport type KeyValueCacheListResult = {\n keys: Array<{ name: string }>;\n cursor?: string;\n listComplete: boolean;\n};\n\nexport type KeyValueCacheAdapter = {\n kind: \"workers-kv\" | \"noop\" | \"external\";\n get<T = unknown>(\n key: string,\n options?: KeyValueCacheGetOptions\n ): Promise<T | null>;\n put<T = unknown>(\n key: string,\n value: T,\n options?: KeyValueCachePutOptions\n ): Promise<void>;\n delete(key: string): Promise<void>;\n list(options?: KeyValueCacheListOptions): Promise<KeyValueCacheListResult>;\n};\n\nexport type SqlValue = string | number | boolean | null;\n\nexport type SqlResult<T = Record<string, unknown>> = {\n results?: T[];\n success?: boolean;\n meta?: {\n changes?: number;\n duration?: number;\n last_row_id?: number;\n rows_read?: number;\n rows_written?: number;\n [key: string]: unknown;\n };\n};\n\nexport type SqlPreparedStatement = {\n bind(...values: SqlValue[]): SqlPreparedStatement;\n all<T = Record<string, unknown>>(): Promise<SqlResult<T>>;\n first<T = Record<string, unknown>>(columnName?: string): Promise<T | null>;\n run<T = Record<string, unknown>>(): Promise<SqlResult<T>>;\n};\n\nexport type SqlDatabaseAdapter = {\n kind: \"d1\";\n prepare(query: string): SqlPreparedStatement;\n batch<T = Record<string, unknown>>(\n statements: SqlPreparedStatement[]\n ): Promise<SqlResult<T>[]>;\n};\n\nexport type RuntimePlatform = {\n id: \"cloudflare-workers\";\n database: SqlDatabaseAdapter | null;\n objectStorage: ObjectStorageAdapter | null;\n imageTransformer: ImageTransformerAdapter | null;\n publicCache: PublicCacheAdapter | null;\n keyValueCache: KeyValueCacheAdapter | null;\n};\n\ntype CloudflareCacheLike = Pick<Cache, \"match\" | \"put\" | \"delete\">;\ntype CloudflareKvLike = Pick<KVNamespace, \"get\" | \"put\" | \"delete\" | \"list\">;\n\nfunction cacheRequestForKey(key: string) {\n return new Request(key, { method: \"GET\" });\n}\n\nexport function createCloudflarePublicCacheAdapter(\n cache: CloudflareCacheLike\n): PublicCacheAdapter {\n return {\n kind: \"cloudflare-cache\",\n async match(key) {\n return (await cache.match(cacheRequestForKey(key))) ?? null;\n },\n put(key, response) {\n return cache.put(cacheRequestForKey(key), response);\n },\n delete(key) {\n return cache.delete(cacheRequestForKey(key));\n },\n };\n}\n\nexport function createNoopPublicCacheAdapter(kind: \"noop\" = \"noop\"): PublicCacheAdapter {\n return {\n kind,\n async match() {\n return null;\n },\n async put() {},\n async delete() {\n return false;\n },\n };\n}\n\nexport function createCloudflareKeyValueCacheAdapter(\n namespace: CloudflareKvLike\n): KeyValueCacheAdapter {\n return {\n kind: \"workers-kv\",\n async get<T = unknown>(\n key: string,\n options?: KeyValueCacheGetOptions\n ): Promise<T | null> {\n return (await namespace.get(key, {\n type: \"json\",\n cacheTtl: options?.cacheTtl,\n })) as T | null;\n },\n async put(key, value, options) {\n await namespace.put(key, JSON.stringify(value), {\n expirationTtl: options?.expirationTtl,\n metadata: options?.metadata,\n });\n },\n delete(key) {\n return namespace.delete(key);\n },\n async list(options) {\n const result = await namespace.list({\n prefix: options?.prefix,\n limit: options?.limit,\n cursor: options?.cursor,\n });\n return {\n keys: result.keys.map((key) => ({ name: key.name })),\n cursor: result.list_complete ? undefined : result.cursor,\n listComplete: result.list_complete,\n };\n },\n };\n}\n\nexport function createNoopKeyValueCacheAdapter(\n kind: \"noop\" = \"noop\"\n): KeyValueCacheAdapter {\n return {\n kind,\n async get() {\n return null;\n },\n async put() {},\n async delete() {},\n async list() {\n return { keys: [], listComplete: true };\n },\n };\n}\n\nfunction r2ObjectToStoredObject(object: R2ObjectBody): StoredObject {\n return {\n body: object.body,\n size: object.size,\n etag: object.etag,\n contentType: object.httpMetadata?.contentType,\n };\n}\n\nexport function createCloudflareRuntimePlatform(\n env: PlatformBindingEnv,\n options?: { publicCache?: CloudflareCacheLike | null }\n): RuntimePlatform {\n const database: SqlDatabaseAdapter | null = env.DB\n ? ({\n kind: \"d1\",\n prepare(query: string) {\n return env.DB.prepare(query) as unknown as SqlPreparedStatement;\n },\n async batch(statements: SqlPreparedStatement[]) {\n return (await env.DB.batch(\n statements as unknown as D1PreparedStatement[]\n )) as unknown as SqlResult<Record<string, unknown>>[];\n },\n } as unknown as SqlDatabaseAdapter)\n : null;\n\n const objectStorage: ObjectStorageAdapter | null = env.ASSETS_BUCKET\n ? {\n kind: \"r2\",\n async get(key) {\n const object = await env.ASSETS_BUCKET?.get(key);\n return object ? r2ObjectToStoredObject(object) : null;\n },\n async put(key, value, options) {\n await env.ASSETS_BUCKET?.put(key, value, {\n httpMetadata: {\n contentType: options?.contentType,\n cacheControl: options?.cacheControl,\n },\n customMetadata: options?.metadata,\n });\n },\n async delete(key) {\n await env.ASSETS_BUCKET?.delete(key);\n },\n async list(options) {\n const listed = await env.ASSETS_BUCKET?.list({\n prefix: options?.prefix,\n limit: options?.limit,\n });\n return (\n listed?.objects.map((object) => ({\n key: object.key,\n size: object.size,\n uploaded: object.uploaded,\n })) ?? []\n );\n },\n }\n : null;\n\n const imageTransformer: ImageTransformerAdapter | null = env.IMAGES\n ? {\n kind: \"cloudflare-images\",\n async transform(body, options) {\n const result = await env.IMAGES.input(body)\n .transform(options.width ? { width: options.width } : {})\n .output({\n format: options.format,\n quality: options.quality,\n });\n return {\n body: result.image(),\n contentType: result.contentType(),\n response: () => result.response(),\n };\n },\n }\n : null;\n\n const keyValueCache: KeyValueCacheAdapter | null = env.CONTENT_CACHE\n ? createCloudflareKeyValueCacheAdapter(env.CONTENT_CACHE)\n : null;\n\n return {\n id: \"cloudflare-workers\",\n database,\n objectStorage,\n imageTransformer,\n keyValueCache,\n publicCache: options?.publicCache\n ? createCloudflarePublicCacheAdapter(options.publicCache)\n : null,\n };\n}\n","import { workerEnv } from \"../util/env\";\nimport {\n createCloudflarePublicCacheAdapter,\n createCloudflareRuntimePlatform,\n} from \"./runtime\";\n\nfunction getDefaultCloudflareCache() {\n const globalWithCaches = globalThis as typeof globalThis & {\n caches?: CacheStorage & { default?: Cache };\n };\n return globalWithCaches.caches?.default ?? null;\n}\n\nexport function getRuntimePlatform() {\n return createCloudflareRuntimePlatform(workerEnv, {\n publicCache: getDefaultCloudflareCache(),\n });\n}\n\nexport function getDatabase() {\n const database = getRuntimePlatform().database;\n if (!database) {\n throw new Error(\"SQL database binding not configured\");\n }\n return database;\n}\n\nexport function getPublicCache() {\n const cache = getDefaultCloudflareCache();\n if (!cache) {\n throw new Error(\"Cloudflare cache binding not configured\");\n }\n return createCloudflarePublicCacheAdapter(cache);\n}\n","import {\n getPublicCache as getCloudflarePublicCache,\n getRuntimePlatform as getCloudflareRuntimePlatform,\n} from \"./cloudflare-runtime\";\nimport { currentRuntimeId } from \"./selection\";\n\nexport function getRuntimePlatform() {\n return getCloudflareRuntimePlatform();\n}\n\nexport function getDatabase() {\n const platform = getRuntimePlatform();\n const database = platform.database;\n if (!database) {\n throw new Error(`SQL database adapter not configured for ${platform.id}`);\n }\n return database;\n}\n\nexport function getPublicCache() {\n return getCloudflarePublicCache();\n}\n\nexport function getKeyValueCache() {\n return getRuntimePlatform().keyValueCache;\n}\n\nexport const runtimeSelection = {\n currentRuntimeId,\n};\n","export const REQUIRED_SCHEMA_CHECKS = [\n {\n key: \"app_settings.turnstile_enabled\",\n sql: \"SELECT turnstile_enabled FROM app_settings LIMIT 1\",\n },\n {\n key: \"users.session_rev\",\n sql: \"SELECT session_rev FROM users LIMIT 1\",\n },\n {\n key: \"auth_rate_limits\",\n sql: \"SELECT 1 FROM auth_rate_limits LIMIT 1\",\n },\n];\n\nexport const DEFAULT_TURNSTILE_PUBLIC_CONFIG = {\n enabled: false,\n siteKey: null,\n secretConfigured: false,\n};\n\nexport function isSchemaDriftError(error: unknown): boolean {\n const message = error instanceof Error ? error.message : String(error ?? \"\");\n return (\n message.includes(\"no such column\") || message.includes(\"no such table\")\n );\n}\n\nexport function buildTurnstilePublicConfig(\n settings: { turnstile_enabled: number; turnstile_site_key: string | null },\n envLike: { TURNSTILE_SITE_KEY?: string; TURNSTILE_SECRET_KEY?: string }\n): { enabled: boolean; siteKey: string | null; secretConfigured: boolean } {\n const envSiteKey = envLike.TURNSTILE_SITE_KEY?.trim() || null;\n const siteKey = settings.turnstile_site_key?.trim() || envSiteKey || null;\n const secretConfigured = Boolean(envLike.TURNSTILE_SECRET_KEY?.trim());\n const enabled =\n (settings.turnstile_enabled === 1 || Boolean(envSiteKey)) &&\n Boolean(siteKey) &&\n secretConfigured;\n\n return {\n enabled,\n siteKey,\n secretConfigured,\n };\n}\n\nexport async function runSchemaHealthChecks(db: {\n prepare: (sql: string) => { first: () => Promise<unknown> };\n}): Promise<{ ok: boolean; missing: string[]; errors: string[] }> {\n const missing: string[] = [];\n const errors: string[] = [];\n\n for (const check of REQUIRED_SCHEMA_CHECKS) {\n try {\n await db.prepare(check.sql).first();\n } catch (error) {\n if (isSchemaDriftError(error)) {\n missing.push(check.key);\n } else {\n const message = error instanceof Error ? error.message : String(error);\n errors.push(`${check.key}: ${message}`);\n }\n }\n }\n\n return {\n ok: missing.length === 0 && errors.length === 0,\n missing,\n errors,\n };\n}\n","// auth/turnstile.ts - Cloudflare Turnstile server-side token verification.\n// Widget: https://dash.cloudflare.com/?to=/:account/turnstile\n// Same Cloudflare account as Workers, but created as a separate Turnstile product.\n\nimport { workerEnv } from \"../util/env\";\nimport { getTurnstilePublicConfig } from \"../internal/admin/settings\";\n\nconst SITEVERIFY_URL =\n \"https://challenges.cloudflare.com/turnstile/v0/siteverify\";\n\nexport type TurnstileRuntimeConfig = {\n enabled: boolean;\n siteKey: string;\n secretKey: string;\n};\n\n/** 是否应对当前请求执行 Turnstile 校验(开关 + site key + secret 齐全)。 */\nexport async function getTurnstileRuntimeConfig(): Promise<TurnstileRuntimeConfig | null> {\n const pub = await getTurnstilePublicConfig();\n const secretKey = workerEnv.TURNSTILE_SECRET_KEY?.trim();\n if (!pub.enabled || !pub.siteKey || !secretKey) return null;\n return { enabled: true, siteKey: pub.siteKey, secretKey };\n}\n\ntype SiteverifyResponse = {\n success: boolean;\n \"error-codes\"?: string[];\n};\n\nexport async function verifyTurnstileToken(\n token: string,\n remoteIp: string | null\n): Promise<{ ok: true } | { ok: false; codes: string[] }> {\n const config = await getTurnstileRuntimeConfig();\n if (!config) {\n // #region debug-point C:runtime-config-missing\n console.error(\"[DEBUG-TURNSTILE] runtime config missing\", {\n tokenLength: token.length,\n remoteIpPresent: Boolean(remoteIp),\n });\n // #endregion\n return { ok: true };\n }\n\n if (!token) {\n // #region debug-point B:missing-token\n console.error(\"[DEBUG-TURNSTILE] missing token\", {\n siteKeySuffix: config.siteKey.slice(-6),\n remoteIpPresent: Boolean(remoteIp),\n });\n // #endregion\n return { ok: false, codes: [\"missing-input-response\"] };\n }\n\n const body = new URLSearchParams();\n body.set(\"secret\", config.secretKey);\n body.set(\"response\", token);\n if (remoteIp) body.set(\"remoteip\", remoteIp);\n\n const res = await fetch(SITEVERIFY_URL, {\n method: \"POST\",\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n\n if (!res.ok) {\n // #region debug-point C:siteverify-http-error\n console.error(\"[DEBUG-TURNSTILE] siteverify http error\", {\n siteKeySuffix: config.siteKey.slice(-6),\n tokenLength: token.length,\n status: res.status,\n });\n // #endregion\n return { ok: false, codes: [`http-${res.status}`] };\n }\n\n const data = (await res.json()) as SiteverifyResponse;\n if (data.success) return { ok: true };\n // #region debug-point C:siteverify-failed\n console.error(\"[DEBUG-TURNSTILE] siteverify failed\", {\n siteKeySuffix: config.siteKey.slice(-6),\n tokenLength: token.length,\n codes: data[\"error-codes\"] ?? [\"verification-failed\"],\n });\n // #endregion\n return { ok: false, codes: data[\"error-codes\"] ?? [\"verification-failed\"] };\n}\n\nexport async function verifyTurnstileFromForm(\n formData: FormData,\n remoteIp: string | null\n): Promise<{ ok: true } | { ok: false }> {\n const token = String(formData.get(\"cf-turnstile-response\") ?? \"\").trim();\n // #region debug-point B:form-token-read\n console.error(\"[DEBUG-TURNSTILE] form token read\", {\n tokenLength: token.length,\n remoteIpPresent: Boolean(remoteIp),\n });\n // #endregion\n const result = await verifyTurnstileToken(token, remoteIp);\n return result.ok ? { ok: true } : { ok: false };\n}\n"],"mappings":"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","names":["cache","env","options","getRuntimePlatform"]}
@@ -0,0 +1,42 @@
1
+ export { getDatabase } from '../platform/current.js';
2
+ import { SessionUser } from './session.js';
3
+ import '../platform/runtime.js';
4
+ import '../env-C5qu-0R-.js';
5
+ import '../platform/selection.js';
6
+
7
+ /** Cookie name holding the OAuth (Google) user session. */
8
+ declare const USER_COOKIE = "vinext_user_session";
9
+ /** Cookie name holding the admin-password session. */
10
+ declare const ADMIN_COOKIE = "vinext_admin_session";
11
+ /** 给 OAuth 用户发 token:HMAC 签名 + base64-encoded JSON payload */
12
+ declare function signUserToken(user: SessionUser): Promise<string>;
13
+ declare function setUserSessionCookie(user: SessionUser): Promise<void>;
14
+ /** 校验用户 session token:签名、过期、session_rev 是否与数据库一致 */
15
+ declare function verifyUserToken(token: string | undefined): Promise<SessionUser | null>;
16
+ /** 当前 OAuth 用户(如果登录了),admin 密码登录时返 null */
17
+ declare function getCurrentUser(): Promise<SessionUser | null>;
18
+ declare function clearUserSessionCookie(): Promise<void>;
19
+ declare function checkPassword(input: string): Promise<boolean>;
20
+ declare function setSessionCookie(): Promise<void>;
21
+ declare function clearSessionCookie(): Promise<void>;
22
+ type AuthViewer = {
23
+ email: string;
24
+ user: SessionUser | null;
25
+ role: "user" | "vip" | "admin";
26
+ isAdmin: boolean;
27
+ isVip: boolean;
28
+ canViewVipContent: boolean;
29
+ };
30
+ /**
31
+ * Combined viewer: returns an `AuthViewer` for the current request
32
+ * regardless of which login method (admin password vs OAuth) the
33
+ * user used. `null` means the request is unauthenticated.
34
+ */
35
+ declare function getAuthViewer(): Promise<AuthViewer | null>;
36
+ /**
37
+ * Extended isAuthenticated: admin password login or OAuth login both
38
+ * count as authenticated.
39
+ */
40
+ declare function isAuthenticated(): Promise<boolean>;
41
+
42
+ export { ADMIN_COOKIE, type AuthViewer, USER_COOKIE, checkPassword, clearSessionCookie, clearUserSessionCookie, getAuthViewer, getCurrentUser, isAuthenticated, setSessionCookie, setUserSessionCookie, signUserToken, verifyUserToken };