@leadertechie/personal-site-kit 0.0.0 → 0.1.0-alpha.10

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 (197) hide show
  1. package/dist/api/__tests__/info.test.d.ts +2 -0
  2. package/dist/api/__tests__/info.test.d.ts.map +1 -0
  3. package/dist/api/__tests__/utils.test.d.ts +2 -0
  4. package/dist/api/__tests__/utils.test.d.ts.map +1 -0
  5. package/dist/api/content-utils.d.ts +27 -0
  6. package/dist/api/content-utils.d.ts.map +1 -0
  7. package/dist/api/handlers/{aboutme.d.ts → about-me.d.ts} +1 -1
  8. package/dist/api/handlers/about-me.d.ts.map +1 -0
  9. package/dist/api/handlers/auth-handler.d.ts +2 -0
  10. package/dist/api/handlers/auth-handler.d.ts.map +1 -0
  11. package/dist/api/handlers/auth.d.ts +23 -0
  12. package/dist/api/handlers/auth.d.ts.map +1 -0
  13. package/dist/api/handlers/content-api.d.ts +0 -1
  14. package/dist/api/handlers/content-api.d.ts.map +1 -1
  15. package/dist/api/handlers/content.d.ts.map +1 -1
  16. package/dist/api/handlers/home.d.ts.map +1 -1
  17. package/dist/api/handlers/{staticdetails.d.ts → static-details.d.ts} +1 -1
  18. package/dist/api/handlers/static-details.d.ts.map +1 -0
  19. package/dist/api/index.d.ts +7 -8
  20. package/dist/api/index.d.ts.map +1 -1
  21. package/dist/api/website-api.d.ts +10 -0
  22. package/dist/api/website-api.d.ts.map +1 -0
  23. package/dist/api.d.ts +2 -0
  24. package/dist/api.js +19 -589
  25. package/dist/assets/logo-placeholder.svg +21 -0
  26. package/dist/chunks/index-CGvOrVf8.js +213 -0
  27. package/dist/chunks/index-_AMi6ort.js +2690 -0
  28. package/dist/chunks/site-store-Vqmjjz9c.js +86 -0
  29. package/dist/chunks/template-C1tMqlPY.js +597 -0
  30. package/dist/chunks/website-api-CuyeBej-.js +920 -0
  31. package/dist/index.d.ts +5 -2
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +48 -352
  34. package/dist/prerender/__tests__/page-content.test.d.ts +2 -0
  35. package/dist/prerender/__tests__/page-content.test.d.ts.map +1 -0
  36. package/dist/prerender/__tests__/template.test.d.ts +2 -0
  37. package/dist/prerender/__tests__/template.test.d.ts.map +1 -0
  38. package/dist/prerender/data-fetcher.d.ts +19 -0
  39. package/dist/prerender/data-fetcher.d.ts.map +1 -0
  40. package/dist/prerender/index.d.ts +5 -4
  41. package/dist/prerender/index.d.ts.map +1 -1
  42. package/dist/prerender/{pageContent.d.ts → page-content.d.ts} +1 -1
  43. package/dist/prerender/page-content.d.ts.map +1 -0
  44. package/dist/prerender/page-generators/about.d.ts +16 -0
  45. package/dist/prerender/page-generators/about.d.ts.map +1 -0
  46. package/dist/prerender/page-generators/base.d.ts +26 -0
  47. package/dist/prerender/page-generators/base.d.ts.map +1 -0
  48. package/dist/prerender/page-generators/blog-detail.d.ts +15 -0
  49. package/dist/prerender/page-generators/blog-detail.d.ts.map +1 -0
  50. package/dist/prerender/page-generators/blogs-list.d.ts +17 -0
  51. package/dist/prerender/page-generators/blogs-list.d.ts.map +1 -0
  52. package/dist/prerender/page-generators/home.d.ts +19 -0
  53. package/dist/prerender/page-generators/home.d.ts.map +1 -0
  54. package/dist/prerender/page-generators/index.d.ts +9 -0
  55. package/dist/prerender/page-generators/index.d.ts.map +1 -0
  56. package/dist/prerender/page-generators/not-found.d.ts +14 -0
  57. package/dist/prerender/page-generators/not-found.d.ts.map +1 -0
  58. package/dist/prerender/page-generators/stories-list.d.ts +17 -0
  59. package/dist/prerender/page-generators/stories-list.d.ts.map +1 -0
  60. package/dist/prerender/page-generators/story-detail.d.ts +15 -0
  61. package/dist/prerender/page-generators/story-detail.d.ts.map +1 -0
  62. package/dist/prerender/website-prerender.d.ts +22 -0
  63. package/dist/prerender/website-prerender.d.ts.map +1 -0
  64. package/dist/prerender.d.ts +2 -0
  65. package/dist/prerender.js +163 -151
  66. package/dist/shared/config/index.d.ts +1 -0
  67. package/dist/shared/config/index.d.ts.map +1 -1
  68. package/dist/shared/core/__tests__/theme-toggle.test.d.ts +2 -0
  69. package/dist/shared/core/__tests__/theme-toggle.test.d.ts.map +1 -0
  70. package/dist/shared/core/site-store.d.ts +1 -0
  71. package/dist/shared/core/site-store.d.ts.map +1 -1
  72. package/dist/shared/index.d.ts +5 -3
  73. package/dist/shared/index.d.ts.map +1 -1
  74. package/dist/shared/interfaces/{iFooterLink.d.ts → ifooter-link.d.ts} +1 -1
  75. package/dist/shared/interfaces/ifooter-link.d.ts.map +1 -0
  76. package/dist/shared/interfaces/{iRoute.d.ts → iroute.d.ts} +1 -1
  77. package/dist/shared/interfaces/iroute.d.ts.map +1 -0
  78. package/dist/shared/{pageContent.d.ts → page-content.d.ts} +4 -3
  79. package/dist/shared/page-content.d.ts.map +1 -0
  80. package/dist/shared/router.d.ts +23 -0
  81. package/dist/shared/router.d.ts.map +1 -0
  82. package/dist/shared/runtime.d.ts +6 -6
  83. package/dist/shared/runtime.d.ts.map +1 -1
  84. package/dist/shared/website-ui.d.ts +32 -0
  85. package/dist/shared/website-ui.d.ts.map +1 -0
  86. package/dist/shared.js +13 -8
  87. package/dist/ui/about-me/api.d.ts.map +1 -0
  88. package/dist/ui/{aboutme → about-me}/index.d.ts +2 -10
  89. package/dist/ui/about-me/index.d.ts.map +1 -0
  90. package/dist/ui/about-me/styles.d.ts.map +1 -0
  91. package/dist/ui/admin/api.d.ts +16 -0
  92. package/dist/ui/admin/api.d.ts.map +1 -0
  93. package/dist/ui/admin/components/AboutMeSection.d.ts +7 -0
  94. package/dist/ui/admin/components/AboutMeSection.d.ts.map +1 -0
  95. package/dist/ui/admin/components/AdminSection.d.ts +13 -0
  96. package/dist/ui/admin/components/AdminSection.d.ts.map +1 -0
  97. package/dist/ui/admin/components/BlogsSection.d.ts +7 -0
  98. package/dist/ui/admin/components/BlogsSection.d.ts.map +1 -0
  99. package/dist/ui/admin/components/HomeSection.d.ts +7 -0
  100. package/dist/ui/admin/components/HomeSection.d.ts.map +1 -0
  101. package/dist/ui/admin/components/ImagesSection.d.ts +7 -0
  102. package/dist/ui/admin/components/ImagesSection.d.ts.map +1 -0
  103. package/dist/ui/admin/components/LoginForm.d.ts +9 -0
  104. package/dist/ui/admin/components/LoginForm.d.ts.map +1 -0
  105. package/dist/ui/admin/components/LogoSection.d.ts +7 -0
  106. package/dist/ui/admin/components/LogoSection.d.ts.map +1 -0
  107. package/dist/ui/admin/components/ProfileSection.d.ts +7 -0
  108. package/dist/ui/admin/components/ProfileSection.d.ts.map +1 -0
  109. package/dist/ui/admin/components/StaticSection.d.ts +9 -0
  110. package/dist/ui/admin/components/StaticSection.d.ts.map +1 -0
  111. package/dist/ui/admin/components/StoriesSection.d.ts +7 -0
  112. package/dist/ui/admin/components/StoriesSection.d.ts.map +1 -0
  113. package/dist/ui/admin/components/index.d.ts +11 -0
  114. package/dist/ui/admin/components/index.d.ts.map +1 -0
  115. package/dist/ui/admin/index.d.ts +27 -26
  116. package/dist/ui/admin/index.d.ts.map +1 -1
  117. package/dist/ui/admin/styles.d.ts.map +1 -1
  118. package/dist/ui/admin/types.d.ts +24 -0
  119. package/dist/ui/admin/types.d.ts.map +1 -0
  120. package/dist/ui/banner/styles.d.ts.map +1 -1
  121. package/dist/ui/blog-viewer/__tests__/blogviewer.test.d.ts +2 -0
  122. package/dist/ui/blog-viewer/__tests__/blogviewer.test.d.ts.map +1 -0
  123. package/dist/ui/blog-viewer/index.d.ts +25 -0
  124. package/dist/ui/blog-viewer/index.d.ts.map +1 -0
  125. package/dist/ui/blog-viewer/styles.d.ts +2 -0
  126. package/dist/ui/blog-viewer/styles.d.ts.map +1 -0
  127. package/dist/ui/footer/index.d.ts +1 -1
  128. package/dist/ui/footer/index.d.ts.map +1 -1
  129. package/dist/ui/footer/styles.d.ts.map +1 -1
  130. package/dist/ui/index.d.ts +7 -0
  131. package/dist/ui/index.d.ts.map +1 -0
  132. package/dist/ui/story-viewer/__tests__/storyviewer.test.d.ts +2 -0
  133. package/dist/ui/story-viewer/__tests__/storyviewer.test.d.ts.map +1 -0
  134. package/dist/ui/story-viewer/index.d.ts +25 -0
  135. package/dist/ui/story-viewer/index.d.ts.map +1 -0
  136. package/dist/ui/story-viewer/styles.d.ts +2 -0
  137. package/dist/ui/story-viewer/styles.d.ts.map +1 -0
  138. package/dist/ui.d.ts +1 -1
  139. package/dist/ui.js +17 -818
  140. package/package.json +35 -12
  141. package/public/assets/logo-placeholder.svg +21 -0
  142. package/dist/api/handlers/aboutme.d.ts.map +0 -1
  143. package/dist/api/handlers/staticdetails.d.ts.map +0 -1
  144. package/dist/prerender/pageContent.d.ts.map +0 -1
  145. package/dist/shared/interfaces/iFooterLink.d.ts.map +0 -1
  146. package/dist/shared/interfaces/iRoute.d.ts.map +0 -1
  147. package/dist/shared/pageContent.d.ts.map +0 -1
  148. package/dist/ui/aboutme/api.d.ts.map +0 -1
  149. package/dist/ui/aboutme/index.d.ts.map +0 -1
  150. package/dist/ui/aboutme/renderer.d.ts +0 -5
  151. package/dist/ui/aboutme/renderer.d.ts.map +0 -1
  152. package/dist/ui/aboutme/styles.d.ts.map +0 -1
  153. package/src/api/__tests__/info.test.ts +0 -44
  154. package/src/api/__tests__/utils.test.ts +0 -78
  155. package/src/api/handlers/aboutme.ts +0 -99
  156. package/src/api/handlers/content-api.ts +0 -268
  157. package/src/api/handlers/content.ts +0 -72
  158. package/src/api/handlers/home.ts +0 -79
  159. package/src/api/handlers/info.ts +0 -12
  160. package/src/api/handlers/logo.ts +0 -55
  161. package/src/api/handlers/staticdetails.ts +0 -48
  162. package/src/api/index.ts +0 -125
  163. package/src/api/utils.ts +0 -16
  164. package/src/prerender/__tests__/pageContent.test.ts +0 -54
  165. package/src/prerender/__tests__/template.test.ts +0 -54
  166. package/src/prerender/index.ts +0 -138
  167. package/src/prerender/pageContent.ts +0 -263
  168. package/src/prerender/prerender.ts +0 -25
  169. package/src/prerender/template.ts +0 -65
  170. package/src/shared/config/api.ts +0 -16
  171. package/src/shared/config/index.ts +0 -41
  172. package/src/shared/config/types.ts +0 -16
  173. package/src/shared/core/__tests__/theme-toggle.test.ts +0 -204
  174. package/src/shared/core/site-store.ts +0 -38
  175. package/src/shared/core/theme-toggle.ts +0 -118
  176. package/src/shared/index.ts +0 -15
  177. package/src/shared/interfaces/iFooterLink.ts +0 -4
  178. package/src/shared/interfaces/iRoute.ts +0 -4
  179. package/src/shared/models/theme-variables.css +0 -25
  180. package/src/shared/pageContent.ts +0 -209
  181. package/src/shared/runtime.ts +0 -11
  182. package/src/shared/template.ts +0 -35
  183. package/src/styles/markdown.css +0 -129
  184. package/src/ui/aboutme/api.ts +0 -12
  185. package/src/ui/aboutme/index.ts +0 -155
  186. package/src/ui/aboutme/renderer.ts +0 -7
  187. package/src/ui/aboutme/styles.ts +0 -10
  188. package/src/ui/admin/index.ts +0 -492
  189. package/src/ui/admin/styles.ts +0 -317
  190. package/src/ui/banner/index.ts +0 -38
  191. package/src/ui/banner/styles.ts +0 -10
  192. package/src/ui/footer/index.ts +0 -37
  193. package/src/ui/footer/styles.ts +0 -9
  194. /package/{src/shared → dist}/styles/markdown.css +0 -0
  195. /package/{src → dist}/styles/theme.css +0 -0
  196. /package/dist/ui/{aboutme → about-me}/api.d.ts +0 -0
  197. /package/dist/ui/{aboutme → about-me}/styles.d.ts +0 -0
@@ -0,0 +1,920 @@
1
+ import { R2ContentLoader } from "@leadertechie/r2tohtml";
2
+ function createJSONResponse(data, status = 200) {
3
+ return new Response(JSON.stringify(data), {
4
+ status,
5
+ headers: { "Content-Type": "application/json" }
6
+ });
7
+ }
8
+ function createErrorResponse(message, status = 500) {
9
+ return createJSONResponse({ error: message }, status);
10
+ }
11
+ let loader$1 = null;
12
+ function getLoader$1(env) {
13
+ if (!env?.CONTENT_BUCKET) {
14
+ return null;
15
+ }
16
+ if (!loader$1) {
17
+ loader$1 = new R2ContentLoader(
18
+ {
19
+ bucket: env.CONTENT_BUCKET,
20
+ cacheTTL: 5 * 60 * 1e3
21
+ },
22
+ {
23
+ md2html: {
24
+ imagePathPrefix: "images/",
25
+ styleOptions: {
26
+ classPrefix: "md-",
27
+ addHeadingIds: true
28
+ }
29
+ }
30
+ }
31
+ );
32
+ }
33
+ return loader$1;
34
+ }
35
+ function clearContentCache$1() {
36
+ loader$1?.clearCache();
37
+ }
38
+ async function handleAboutMe(env) {
39
+ try {
40
+ console.log("handleAboutMe: env?.CONTENT_BUCKET =", !!env?.CONTENT_BUCKET);
41
+ if (!env?.CONTENT_BUCKET) {
42
+ return new Response(JSON.stringify({ error: "Content bucket not configured", env: !!env }), {
43
+ status: 500,
44
+ headers: { "Content-Type": "application/json" }
45
+ });
46
+ }
47
+ const r2 = getLoader$1(env);
48
+ if (!r2) {
49
+ return new Response(JSON.stringify({ error: "Content bucket not configured" }), {
50
+ status: 500,
51
+ headers: { "Content-Type": "application/json" }
52
+ });
53
+ }
54
+ console.log("handleAboutMe: r2 created, fetching data");
55
+ const [profileObj, rendered] = await Promise.all([
56
+ r2.getObject("profile.json"),
57
+ r2.getRendered("about-me.md")
58
+ ]);
59
+ if (!rendered) {
60
+ return new Response(JSON.stringify({ error: "About-me content not found. Please run seed." }), {
61
+ status: 404,
62
+ headers: { "Content-Type": "application/json" }
63
+ });
64
+ }
65
+ let profile = {
66
+ name: "Your Name",
67
+ title: "Professional",
68
+ experience: "Experienced",
69
+ profileImageUrl: ""
70
+ };
71
+ if (profileObj) {
72
+ profile = await profileObj.json();
73
+ }
74
+ console.log("handleAboutMe: profile loaded:", profile.name);
75
+ const responseData = {
76
+ profile,
77
+ contentNodes: [],
78
+ processedMarkdown: rendered.content
79
+ };
80
+ return new Response(JSON.stringify(responseData), {
81
+ headers: { "Content-Type": "application/json" }
82
+ });
83
+ } catch (error) {
84
+ console.error("Error serving aboutme content:", error);
85
+ return new Response(JSON.stringify({ error: "Content not available", message: String(error) }), {
86
+ status: 500,
87
+ headers: { "Content-Type": "application/json" }
88
+ });
89
+ }
90
+ }
91
+ let loader = null;
92
+ function getLoader(env) {
93
+ if (!loader && env?.CONTENT_BUCKET) {
94
+ loader = new R2ContentLoader(
95
+ {
96
+ bucket: env.CONTENT_BUCKET,
97
+ cacheTTL: 5 * 60 * 1e3
98
+ },
99
+ {
100
+ md2html: {
101
+ imagePathPrefix: "images/",
102
+ styleOptions: {
103
+ classPrefix: "md-",
104
+ addHeadingIds: true
105
+ }
106
+ }
107
+ }
108
+ );
109
+ }
110
+ return loader;
111
+ }
112
+ function clearContentCache() {
113
+ loader?.clearCache();
114
+ }
115
+ async function handleHome(env) {
116
+ try {
117
+ if (!env?.CONTENT_BUCKET) {
118
+ return new Response(JSON.stringify({ error: "Content bucket not configured" }), {
119
+ status: 500,
120
+ headers: { "Content-Type": "application/json" }
121
+ });
122
+ }
123
+ const r2 = getLoader(env);
124
+ let astResult = await r2.getWithAST("pages/home.md");
125
+ let renderedResult = await r2.getRendered("pages/home.md");
126
+ if (!astResult || !renderedResult) {
127
+ return new Response(JSON.stringify({
128
+ contentNodes: [],
129
+ processedMarkdown: "",
130
+ content: ""
131
+ }), {
132
+ headers: { "Content-Type": "application/json" }
133
+ });
134
+ }
135
+ const responseData = {
136
+ contentNodes: astResult.contentNodes,
137
+ processedMarkdown: "",
138
+ content: renderedResult.content
139
+ };
140
+ return new Response(JSON.stringify(responseData), {
141
+ headers: { "Content-Type": "application/json" }
142
+ });
143
+ } catch (error) {
144
+ console.error("Error serving home content:", error);
145
+ return new Response(JSON.stringify({ error: "Content not available" }), {
146
+ status: 500,
147
+ headers: { "Content-Type": "application/json" }
148
+ });
149
+ }
150
+ }
151
+ async function handleInfo() {
152
+ return createJSONResponse({
153
+ name: "TechieLeader",
154
+ version: "1.0.0",
155
+ description: "TechieLeader API",
156
+ endpoints: [
157
+ { path: "/info", method: "GET", description: "Get API information" }
158
+ ]
159
+ });
160
+ }
161
+ const AUTH_KV = "auth_store";
162
+ const RATE_LIMIT_KV = "rate_limit";
163
+ const MAX_ATTEMPTS = 5;
164
+ const BASE_DELAY_MS = 1e3;
165
+ const MAX_DELAY_MS = 6e4;
166
+ async function hashPassword(password, salt) {
167
+ const encoder = new TextEncoder();
168
+ const keyMaterial = await crypto.subtle.importKey(
169
+ "raw",
170
+ encoder.encode(password),
171
+ "PBKDF2",
172
+ false,
173
+ ["deriveBits"]
174
+ );
175
+ const saltBuffer = encoder.encode(salt);
176
+ const derivedBits = await crypto.subtle.deriveBits(
177
+ {
178
+ name: "PBKDF2",
179
+ salt: saltBuffer,
180
+ iterations: 1e5,
181
+ hash: "SHA-256"
182
+ },
183
+ keyMaterial,
184
+ 256
185
+ );
186
+ return btoa(String.fromCharCode(...new Uint8Array(derivedBits)));
187
+ }
188
+ async function generateSalt() {
189
+ const saltBytes = crypto.getRandomValues(new Uint8Array(16));
190
+ return btoa(String.fromCharCode(...saltBytes));
191
+ }
192
+ async function checkRateLimit(env, ip) {
193
+ const kvKey = `rate:${ip}`;
194
+ const entry = await env.KV.get(kvKey, "json");
195
+ if (!entry) {
196
+ return { allowed: true, delayMs: 0 };
197
+ }
198
+ const now = Date.now();
199
+ const windowMs = 15 * 60 * 1e3;
200
+ if (now - entry.firstAttempt > windowMs) {
201
+ return { allowed: true, delayMs: 0 };
202
+ }
203
+ if (entry.attempts >= MAX_ATTEMPTS) {
204
+ const delayMs = Math.min(BASE_DELAY_MS * Math.pow(2, entry.attempts - MAX_ATTEMPTS), MAX_DELAY_MS);
205
+ const timeSinceLast = now - entry.lastAttempt;
206
+ if (timeSinceLast < delayMs) {
207
+ return { allowed: false, delayMs: delayMs - timeSinceLast };
208
+ }
209
+ }
210
+ return { allowed: true, delayMs: 0 };
211
+ }
212
+ async function recordFailedAttempt(env, ip) {
213
+ const kvKey = `rate:${ip}`;
214
+ const entry = await env.KV.get(kvKey, "json");
215
+ const now = Date.now();
216
+ const windowMs = 15 * 60 * 1e3;
217
+ if (!entry || now - entry.firstAttempt > windowMs) {
218
+ await env.KV.put(kvKey, JSON.stringify({
219
+ attempts: 1,
220
+ firstAttempt: now,
221
+ lastAttempt: now
222
+ }), { expirationTtl: Math.ceil(windowMs / 1e3) + 60 });
223
+ } else {
224
+ entry.attempts++;
225
+ entry.lastAttempt = now;
226
+ await env.KV.put(kvKey, JSON.stringify(entry), {
227
+ expirationTtl: Math.ceil(windowMs / 1e3) + 60
228
+ });
229
+ }
230
+ }
231
+ async function clearRateLimit(env, ip) {
232
+ const kvKey = `rate:${ip}`;
233
+ await env.KV.delete(kvKey);
234
+ }
235
+ async function getAuthStore(env) {
236
+ const store = await env.KV.get(AUTH_KV, "json");
237
+ return store;
238
+ }
239
+ async function setupAuth(env, username, password) {
240
+ const salt = await generateSalt();
241
+ const passwordHash = await hashPassword(password, salt);
242
+ await env.KV.put(AUTH_KV, JSON.stringify({
243
+ username,
244
+ passwordHash,
245
+ salt
246
+ }));
247
+ }
248
+ async function verifyCredentials(env, username, password) {
249
+ const store = await getAuthStore(env);
250
+ if (!store) {
251
+ return false;
252
+ }
253
+ if (username !== store.username) {
254
+ return false;
255
+ }
256
+ const hash = await hashPassword(password, store.salt);
257
+ return hash === store.passwordHash;
258
+ }
259
+ function getClientIP(request) {
260
+ return request.headers.get("CF-Connecting-IP") || request.headers.get("X-Forwarded-For")?.split(",")[0]?.trim() || "unknown";
261
+ }
262
+ function getSessionToken(request) {
263
+ const cookieHeader = request.headers.get("Cookie");
264
+ if (!cookieHeader) return null;
265
+ const match = cookieHeader.split(";").find((c) => c.trim().startsWith("session="));
266
+ return match?.split("=")[1] || null;
267
+ }
268
+ async function handleContent(request, env, subpath) {
269
+ const bucket = env.CONTENT_BUCKET;
270
+ if (!bucket) {
271
+ return createErrorResponse("Content bucket not configured", 500);
272
+ }
273
+ const method = request.method;
274
+ const clientIP = getClientIP(request);
275
+ const rateCheck = await checkRateLimit(env, clientIP);
276
+ if (!rateCheck.allowed) {
277
+ return new Response(JSON.stringify({
278
+ error: "Too many failed attempts. Please wait.",
279
+ retryAfter: Math.ceil(rateCheck.delayMs / 1e3)
280
+ }), {
281
+ status: 429,
282
+ headers: {
283
+ "Content-Type": "application/json",
284
+ "Retry-After": String(Math.ceil(rateCheck.delayMs / 1e3))
285
+ }
286
+ });
287
+ }
288
+ if (method === "GET" && subpath.startsWith("images/")) {
289
+ return handleGet(request, bucket, subpath);
290
+ }
291
+ const store = await getAuthStore(env);
292
+ if (!store) {
293
+ if (method === "GET") {
294
+ return handleGet(request, bucket, subpath);
295
+ }
296
+ return createErrorResponse("Admin not configured. Use POST /auth/setup to configure.", 401);
297
+ }
298
+ const sessionToken = getSessionToken(request) || request.headers.get("X-Session-Token");
299
+ let isAuthenticated = false;
300
+ if (sessionToken) {
301
+ const session = await env.KV.get(`session:${sessionToken}`, "json");
302
+ if (session && session.expiresAt > Date.now()) {
303
+ isAuthenticated = true;
304
+ }
305
+ }
306
+ const authHeader = request.headers.get("Authorization");
307
+ if (!isAuthenticated && authHeader?.startsWith("Basic ")) {
308
+ try {
309
+ const credentials = atob(authHeader.slice(6));
310
+ const [username, password] = credentials.split(":");
311
+ if (await verifyCredentials(env, username, password)) {
312
+ isAuthenticated = true;
313
+ }
314
+ } catch (e) {
315
+ }
316
+ }
317
+ if (!isAuthenticated) {
318
+ await recordFailedAttempt(env, clientIP);
319
+ return createErrorResponse("Unauthorized", 401);
320
+ }
321
+ await clearRateLimit(env, clientIP);
322
+ if (method === "GET") {
323
+ return handleGet(request, bucket, subpath);
324
+ }
325
+ return handleWrite(request, bucket, subpath, env, method);
326
+ }
327
+ async function handleGet(request, bucket, subpath) {
328
+ if (request.method === "GET" && (!subpath || subpath === "/")) {
329
+ try {
330
+ const list = await bucket.list();
331
+ return createJSONResponse(list.objects.map((o) => ({
332
+ key: o.key,
333
+ size: o.size,
334
+ uploaded: o.uploaded,
335
+ httpEtag: o.httpEtag
336
+ })));
337
+ } catch (e) {
338
+ return createErrorResponse("Failed to list content: " + e.message, 500);
339
+ }
340
+ }
341
+ if (request.method === "GET" && subpath) {
342
+ try {
343
+ const object = await bucket.get(subpath);
344
+ if (!object) {
345
+ return createErrorResponse("Content not found", 404);
346
+ }
347
+ const headers = new Headers();
348
+ object.writeHttpMetadata(headers);
349
+ headers.set("etag", object.httpEtag);
350
+ return new Response(object.body, { headers });
351
+ } catch (e) {
352
+ return createErrorResponse("Failed to get content: " + e.message, 500);
353
+ }
354
+ }
355
+ return createErrorResponse("Method not allowed", 405);
356
+ }
357
+ async function handleWrite(request, bucket, subpath, env, method) {
358
+ if (method === "PUT" && subpath) {
359
+ try {
360
+ const contentType = request.headers.get("Content-Type");
361
+ const options = {};
362
+ if (contentType) {
363
+ options.httpMetadata = { contentType };
364
+ }
365
+ await bucket.put(subpath, request.body, options);
366
+ clearContentCache();
367
+ clearContentCache$1();
368
+ return createJSONResponse({ success: true, key: subpath });
369
+ } catch (e) {
370
+ return createErrorResponse("Failed to upload content: " + e.message, 500);
371
+ }
372
+ }
373
+ if (method === "DELETE" && subpath) {
374
+ try {
375
+ await bucket.delete(subpath);
376
+ clearContentCache();
377
+ clearContentCache$1();
378
+ return createJSONResponse({ success: true, key: subpath });
379
+ } catch (e) {
380
+ return createErrorResponse("Failed to delete content: " + e.message, 500);
381
+ }
382
+ }
383
+ return createErrorResponse("Method not allowed", 405);
384
+ }
385
+ function createSessionCookie(token, origin) {
386
+ const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toUTCString();
387
+ const isSecure = origin.startsWith("https://");
388
+ const hostname = new URL(origin).hostname;
389
+ const SameSite = isSecure ? "Strict" : "Lax";
390
+ let cookie = `session=${token}; HttpOnly; SameSite=${SameSite}; Path=/; Expires=${expires}`;
391
+ if (isSecure) {
392
+ cookie += "; Secure";
393
+ }
394
+ cookie += `; Domain=${hostname}`;
395
+ return cookie;
396
+ }
397
+ async function handleAuth(request, env, subpath) {
398
+ request.method;
399
+ const clientIP = getClientIP(request);
400
+ const path = subpath.replace(/^\//, "").split("/")[0];
401
+ const origin = request.headers.get("Origin") || new URL(request.url).origin;
402
+ const rateCheck = await checkRateLimit(env, clientIP);
403
+ if (!rateCheck.allowed) {
404
+ return new Response(JSON.stringify({
405
+ error: "Too many failed attempts. Please wait.",
406
+ retryAfter: Math.ceil(rateCheck.delayMs / 1e3)
407
+ }), {
408
+ status: 429,
409
+ headers: {
410
+ "Content-Type": "application/json",
411
+ "Retry-After": String(Math.ceil(rateCheck.delayMs / 1e3))
412
+ }
413
+ });
414
+ }
415
+ switch (path) {
416
+ case "setup":
417
+ return handleSetup(request, env, clientIP, origin);
418
+ case "status":
419
+ return handleStatus(env);
420
+ case "login":
421
+ return handleLogin(request, env, clientIP, origin);
422
+ case "logout":
423
+ return handleLogout(request, env);
424
+ default:
425
+ return createErrorResponse("Not found", 404);
426
+ }
427
+ }
428
+ async function handleSetup(request, env, clientIP, origin) {
429
+ console.log("handleSetup: starting setup, env.KV exists:", !!env.KV);
430
+ if (request.method !== "POST") {
431
+ return createErrorResponse("Method not allowed", 405);
432
+ }
433
+ try {
434
+ const existing = await getAuthStore(env);
435
+ console.log("handleSetup: existing store check:", !!existing);
436
+ if (existing) {
437
+ return createErrorResponse("Admin already configured. Use /auth/login to login.", 400);
438
+ }
439
+ const body = await request.json();
440
+ const { username, password } = body;
441
+ console.log("handleSetup: body parsed, username:", username);
442
+ if (!username || !password) {
443
+ return createErrorResponse("Username and password required", 400);
444
+ }
445
+ if (username.length < 3 || password.length < 8) {
446
+ return createErrorResponse("Username must be 3+ chars, password must be 8+ chars", 400);
447
+ }
448
+ console.log("handleSetup: calling setupAuth");
449
+ await setupAuth(env, username, password);
450
+ console.log("handleSetup: setupAuth successful");
451
+ await clearRateLimit(env, clientIP);
452
+ const token = crypto.randomUUID();
453
+ console.log("handleSetup: session token generated");
454
+ await env.KV.put(`session:${token}`, JSON.stringify({
455
+ createdAt: Date.now(),
456
+ expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1e3
457
+ }), { expirationTtl: 7 * 24 * 60 * 60 });
458
+ console.log("handleSetup: session stored in KV");
459
+ const headers = {
460
+ "Content-Type": "application/json",
461
+ "Set-Cookie": createSessionCookie(token, origin),
462
+ "X-Session-Token": token
463
+ };
464
+ return new Response(JSON.stringify({
465
+ success: true,
466
+ message: "Admin configured successfully"
467
+ }), {
468
+ status: 201,
469
+ headers
470
+ });
471
+ } catch (e) {
472
+ console.error("handleSetup: error occurred:", e);
473
+ return createErrorResponse("Internal server error: " + e.message, 500);
474
+ }
475
+ }
476
+ async function handleStatus(env) {
477
+ const store = await getAuthStore(env);
478
+ return createJSONResponse({
479
+ configured: !!store,
480
+ username: store?.username || null
481
+ });
482
+ }
483
+ async function handleLogin(request, env, clientIP, origin) {
484
+ if (request.method !== "POST") {
485
+ return createErrorResponse("Method not allowed", 405);
486
+ }
487
+ const store = await getAuthStore(env);
488
+ if (!store) {
489
+ return createErrorResponse("Admin not configured. Use POST /auth/setup first.", 401);
490
+ }
491
+ try {
492
+ const body = await request.json();
493
+ const { username, password } = body;
494
+ if (!username || !password) {
495
+ return createErrorResponse("Username and password required", 400);
496
+ }
497
+ if (await verifyCredentials(env, username, password)) {
498
+ await clearRateLimit(env, clientIP);
499
+ const token = crypto.randomUUID();
500
+ await env.KV.put(`session:${token}`, JSON.stringify({
501
+ createdAt: Date.now(),
502
+ expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1e3
503
+ }), { expirationTtl: 7 * 24 * 60 * 60 });
504
+ const headers = {
505
+ "Content-Type": "application/json",
506
+ "Set-Cookie": createSessionCookie(token, origin),
507
+ "X-Session-Token": token
508
+ };
509
+ return new Response(JSON.stringify({
510
+ success: true,
511
+ message: "Login successful"
512
+ }), {
513
+ status: 200,
514
+ headers
515
+ });
516
+ } else {
517
+ await recordFailedAttempt(env, clientIP);
518
+ return createErrorResponse("Invalid credentials", 401);
519
+ }
520
+ } catch (e) {
521
+ return createErrorResponse("Invalid request body", 400);
522
+ }
523
+ }
524
+ async function handleLogout(request, env) {
525
+ if (request.method !== "POST") {
526
+ return createErrorResponse("Method not allowed", 405);
527
+ }
528
+ const origin = request.headers.get("Origin") || new URL(request.url).origin;
529
+ const cookieHeader = request.headers.get("Cookie");
530
+ const sessionToken = cookieHeader?.split(";").find((c) => c.trim().startsWith("session="))?.split("=")[1];
531
+ if (sessionToken) {
532
+ await env.KV.delete(`session:${sessionToken}`);
533
+ }
534
+ const hostname = new URL(origin).hostname;
535
+ const isSecure = origin.startsWith("https://");
536
+ const SameSite = isSecure ? "Strict" : "Lax";
537
+ const logoutCookie = `session=; HttpOnly; SameSite=${SameSite}; Path=/; Max-Age=0; Domain=${hostname}${isSecure ? "; Secure" : ""}`;
538
+ return new Response(JSON.stringify({ success: true, message: "Logged out" }), {
539
+ status: 200,
540
+ headers: {
541
+ "Content-Type": "application/json",
542
+ "Set-Cookie": logoutCookie
543
+ }
544
+ });
545
+ }
546
+ const contentCache = /* @__PURE__ */ new Map();
547
+ const CACHE_TTL = 5 * 60 * 1e3;
548
+ function getCachedOrFetch(key, fetchFn) {
549
+ const cached = contentCache.get(key);
550
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
551
+ return Promise.resolve(cached.data);
552
+ }
553
+ return fetchFn().then((data) => {
554
+ contentCache.set(key, { data, timestamp: Date.now() });
555
+ return data;
556
+ });
557
+ }
558
+ function parseFrontmatter(content) {
559
+ const lines = content.split("\n");
560
+ const metadata = {};
561
+ let contentStart = 0;
562
+ if (lines[0]?.trim() === "---") {
563
+ for (let i = 1; i < lines.length; i++) {
564
+ if (lines[i]?.trim() === "---") {
565
+ contentStart = i + 1;
566
+ break;
567
+ }
568
+ const colonIdx = lines[i].indexOf(":");
569
+ if (colonIdx > 0) {
570
+ const key = lines[i].slice(0, colonIdx).trim();
571
+ let value = lines[i].slice(colonIdx + 1).trim();
572
+ if (value.startsWith("[") && value.endsWith("]")) {
573
+ value = value.slice(1, -1);
574
+ metadata[key] = value.split(",").map((v) => v.trim());
575
+ } else {
576
+ metadata[key] = value;
577
+ }
578
+ }
579
+ }
580
+ }
581
+ return {
582
+ metadata,
583
+ content: lines.slice(contentStart).join("\n").trim()
584
+ };
585
+ }
586
+ function checkContentBucket(env) {
587
+ if (!env?.CONTENT_BUCKET) {
588
+ return createErrorResponse("Content bucket not configured", 500);
589
+ }
590
+ return null;
591
+ }
592
+ async function fetchContentItem(bucket, type, slug) {
593
+ const mdObj = await bucket.get(`${type}/${slug}.md`);
594
+ const jsonObj = await bucket.get(`${type}/${slug}.json`);
595
+ if (!mdObj && !jsonObj) throw new Error(`${type.slice(0, -1)} not found`);
596
+ let metadata = {};
597
+ if (jsonObj) {
598
+ metadata = await jsonObj.json();
599
+ }
600
+ let content = "";
601
+ if (mdObj) {
602
+ const text = await mdObj.text();
603
+ const parsed = parseFrontmatter(text);
604
+ content = parsed.content;
605
+ metadata = { ...parsed.metadata, ...metadata };
606
+ }
607
+ return { ...metadata, slug, content };
608
+ }
609
+ async function fetchContentList(bucket, type, latest) {
610
+ const list = await bucket.list({ prefix: `${type}/` });
611
+ const items = [];
612
+ for (const item of list.objects) {
613
+ if (item.key.endsWith(".json")) {
614
+ const obj = await bucket.get(item.key);
615
+ if (obj) {
616
+ const metadata = await obj.json();
617
+ const slug = item.key.replace(`${type}/`, "").replace(".json", "");
618
+ items.push({ ...metadata, slug });
619
+ }
620
+ }
621
+ }
622
+ const sorted = items.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
623
+ return latest ? sorted.slice(0, latest) : sorted;
624
+ }
625
+ async function searchContent(bucket, query) {
626
+ const q = query.toLowerCase();
627
+ const results = [];
628
+ const [blogsList, storiesList] = await Promise.all([
629
+ bucket.list({ prefix: "blogs/" }),
630
+ bucket.list({ prefix: "stories/" })
631
+ ]);
632
+ for (const item of [...blogsList.objects, ...storiesList.objects]) {
633
+ if (item.key.endsWith(".md")) {
634
+ const obj = await bucket.get(item.key);
635
+ if (obj) {
636
+ const text = await obj.text();
637
+ const { metadata } = parseFrontmatter(text);
638
+ const matchTitle = metadata.title?.toLowerCase().includes(q);
639
+ const matchDesc = metadata.description?.toLowerCase().includes(q);
640
+ const matchTags = metadata.tags?.some((t) => t.toLowerCase().includes(q));
641
+ if (matchTitle || matchDesc || matchTags) {
642
+ results.push(metadata);
643
+ }
644
+ }
645
+ }
646
+ }
647
+ return results;
648
+ }
649
+ async function handleBlogs(env, slug, latest) {
650
+ const bucketCheck = checkContentBucket(env);
651
+ if (bucketCheck) return bucketCheck;
652
+ try {
653
+ const cacheKey = slug ? `blog-${slug}` : `blogs-list-${latest || "all"}`;
654
+ const result = await getCachedOrFetch(cacheKey, async () => {
655
+ if (slug) {
656
+ return await fetchContentItem(env.CONTENT_BUCKET, "blogs", slug);
657
+ }
658
+ return await fetchContentList(env.CONTENT_BUCKET, "blogs", latest);
659
+ });
660
+ return createJSONResponse(result);
661
+ } catch (error) {
662
+ console.error("Error serving blogs:", error);
663
+ return createErrorResponse("Blog not found", 404);
664
+ }
665
+ }
666
+ async function handleStories(env, slug, latest) {
667
+ const bucketCheck = checkContentBucket(env);
668
+ if (bucketCheck) return bucketCheck;
669
+ try {
670
+ const cacheKey = slug ? `story-${slug}` : `stories-list-${latest || "all"}`;
671
+ const result = await getCachedOrFetch(cacheKey, async () => {
672
+ if (slug) {
673
+ return await fetchContentItem(env.CONTENT_BUCKET, "stories", slug);
674
+ }
675
+ return await fetchContentList(env.CONTENT_BUCKET, "stories", latest);
676
+ });
677
+ return createJSONResponse(result);
678
+ } catch (error) {
679
+ console.error("Error serving stories:", error);
680
+ return createErrorResponse("Story not found", 404);
681
+ }
682
+ }
683
+ async function handleSearch(env, query) {
684
+ const bucketCheck = checkContentBucket(env);
685
+ if (bucketCheck) return bucketCheck;
686
+ if (!query) {
687
+ return createErrorResponse("Search query required", 400);
688
+ }
689
+ try {
690
+ const searchResults = await getCachedOrFetch(`search-${query}`, async () => {
691
+ return await searchContent(env.CONTENT_BUCKET, query);
692
+ });
693
+ return createJSONResponse(searchResults);
694
+ } catch (error) {
695
+ console.error("Error serving search:", error);
696
+ return createErrorResponse("Search failed", 500);
697
+ }
698
+ }
699
+ const PLACEHOLDER_LOGO = `<svg width="600" height="320" viewBox="0 0 600 320" xmlns="http://www.w3.org/2000/svg">
700
+ <style>
701
+ .text-main {
702
+ fill: light-dark(#4A5568, #E2E8F0);
703
+ font-family: 'Segoe UI', 'Arial Black', sans-serif;
704
+ font-weight: 900;
705
+ font-size: 46px;
706
+ letter-spacing: -0.01em;
707
+ text-anchor: middle;
708
+ }
709
+ </style>
710
+
711
+ <!-- Big circle placeholder -->
712
+ <circle cx="300" cy="160" r="100" fill="none" stroke="light-dark(#A0AEC0, #718096)" stroke-width="2" stroke-dasharray="8 4" opacity="0.5" />
713
+
714
+ <!-- Plus sign in circle -->
715
+ <line x1="300" y1="100" x2="300" y2="220" stroke="light-dark(#A0AEC0, #718096)" stroke-width="2" opacity="0.3" />
716
+ <line x1="240" y1="160" x2="360" y2="160" stroke="light-dark(#A0AEC0, #718096)" stroke-width="2" opacity="0.3" />
717
+
718
+ <text x="300" y="290" class="text-main">YOUR LOGO</text>
719
+ </svg>`;
720
+ async function handleLogo(env) {
721
+ try {
722
+ if (env?.CONTENT_BUCKET) {
723
+ const logo = await env.CONTENT_BUCKET.get("logo.svg");
724
+ if (logo) {
725
+ const headers = new Headers();
726
+ logo.writeHttpMetadata(headers);
727
+ headers.set("Content-Type", "image/svg+xml");
728
+ headers.set("Cache-Control", "public, max-age=3600");
729
+ headers.set("Access-Control-Allow-Origin", "*");
730
+ return new Response(logo.body, { headers });
731
+ }
732
+ }
733
+ return new Response(PLACEHOLDER_LOGO, {
734
+ headers: {
735
+ "Content-Type": "image/svg+xml",
736
+ "Cache-Control": "public, max-age=3600",
737
+ "Access-Control-Allow-Origin": "*"
738
+ }
739
+ });
740
+ } catch (error) {
741
+ console.error("Error serving logo:", error);
742
+ return new Response(PLACEHOLDER_LOGO, {
743
+ headers: {
744
+ "Content-Type": "image/svg+xml",
745
+ "Access-Control-Allow-Origin": "*"
746
+ }
747
+ });
748
+ }
749
+ }
750
+ const DEFAULT_STATIC_DETAILS = {
751
+ siteTitle: "My Personal Website",
752
+ copyright: "2026 My Personal Website",
753
+ linkedin: "https://linkedin.com/in/yourname",
754
+ github: "https://github.com/yourname",
755
+ email: "yourname@domain.com"
756
+ };
757
+ async function handleStaticDetails(env, method, body) {
758
+ try {
759
+ if (!env?.CONTENT_BUCKET) {
760
+ return new Response(JSON.stringify(DEFAULT_STATIC_DETAILS), {
761
+ headers: { "Content-Type": "application/json" }
762
+ });
763
+ }
764
+ if (!method || method === "GET") {
765
+ const staticDetails = await env.CONTENT_BUCKET.get("static-details.json");
766
+ if (staticDetails) {
767
+ const data = await staticDetails.json();
768
+ return new Response(JSON.stringify({ ...DEFAULT_STATIC_DETAILS, ...data }), {
769
+ headers: {
770
+ "Content-Type": "application/json",
771
+ "Access-Control-Allow-Origin": "*"
772
+ }
773
+ });
774
+ }
775
+ return new Response(JSON.stringify(DEFAULT_STATIC_DETAILS), {
776
+ headers: {
777
+ "Content-Type": "application/json",
778
+ "Access-Control-Allow-Origin": "*"
779
+ }
780
+ });
781
+ }
782
+ return new Response(JSON.stringify({ error: "Use content endpoint for PUT" }), {
783
+ status: 400,
784
+ headers: { "Content-Type": "application/json" }
785
+ });
786
+ } catch (error) {
787
+ console.error("Error serving static details:", error);
788
+ return new Response(JSON.stringify(DEFAULT_STATIC_DETAILS), {
789
+ headers: { "Content-Type": "application/json" }
790
+ });
791
+ }
792
+ }
793
+ class WebsiteAPI {
794
+ constructor() {
795
+ this.customHandlers = /* @__PURE__ */ new Map();
796
+ }
797
+ registerHandler(route, handler) {
798
+ this.customHandlers.set(route, handler);
799
+ }
800
+ addCORSHeaders(response) {
801
+ response.headers.set("Access-Control-Allow-Origin", "*");
802
+ response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
803
+ response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Session-Token");
804
+ return response;
805
+ }
806
+ addAdminCORSHeaders(response, origin) {
807
+ const allowOrigin = origin && (origin.includes("localhost") || origin.includes("127.0.0.1")) ? origin : "same-origin";
808
+ response.headers.set("Access-Control-Allow-Origin", allowOrigin);
809
+ response.headers.set("Access-Control-Allow-Credentials", "true");
810
+ response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
811
+ response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Session-Token");
812
+ return response;
813
+ }
814
+ handleCORS(origin) {
815
+ const allowOrigin = origin && (origin.includes("localhost") || origin.includes("127.0.0.1")) ? origin : "*";
816
+ return new Response(null, {
817
+ status: 200,
818
+ headers: {
819
+ "Access-Control-Allow-Origin": allowOrigin,
820
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
821
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Session-Token",
822
+ "Access-Control-Allow-Credentials": "true",
823
+ "Access-Control-Max-Age": "86400"
824
+ }
825
+ });
826
+ }
827
+ async fetch(request, env) {
828
+ const url = new URL(request.url);
829
+ const origin = request.headers.get("Origin") || url.origin;
830
+ if (request.method === "OPTIONS") {
831
+ return this.handleCORS(origin);
832
+ }
833
+ const pathname = url.pathname;
834
+ const route = pathname.replace(/^\/api\//, "").replace(/^\//, "").replace(/\/+$/, "");
835
+ if (this.customHandlers.has(route)) {
836
+ const handler = this.customHandlers.get(route);
837
+ return this.addCORSHeaders(await handler(request, env));
838
+ }
839
+ try {
840
+ if (route === "content" || route.startsWith("content/")) {
841
+ const subpath = route.replace(/^content\/?/, "");
842
+ return this.addAdminCORSHeaders(await handleContent(request, env, subpath), origin);
843
+ }
844
+ if (route === "auth" || route.startsWith("auth/")) {
845
+ const subpath = route.replace(/^auth\/?/, "");
846
+ return this.addAdminCORSHeaders(await handleAuth(request, env, subpath || "/"), origin);
847
+ }
848
+ switch (route) {
849
+ case "info":
850
+ return this.addCORSHeaders(await handleInfo());
851
+ case "home":
852
+ return this.addAdminCORSHeaders(await handleHome(env), origin);
853
+ case "cache-clear":
854
+ const cookieHeader = request.headers.get("Cookie");
855
+ const sessionToken = cookieHeader?.split(";").find((c) => c.trim().startsWith("session="))?.split("=")[1];
856
+ const session = sessionToken ? await env.KV.get(`session:${sessionToken}`, "json") : null;
857
+ if (!session || session.expiresAt < Date.now()) {
858
+ return this.addAdminCORSHeaders(createErrorResponse("Unauthorized", 401), origin);
859
+ }
860
+ clearContentCache$1();
861
+ return this.addAdminCORSHeaders(new Response(JSON.stringify({ success: true, message: "Cache cleared" }), { status: 200 }), origin);
862
+ case "aboutme":
863
+ return this.addAdminCORSHeaders(await handleAboutMe(env), origin);
864
+ case "logo":
865
+ return this.addAdminCORSHeaders(await handleLogo(env), origin);
866
+ case "static":
867
+ return this.addAdminCORSHeaders(await handleStaticDetails(env), origin);
868
+ case "blogs":
869
+ return this.addAdminCORSHeaders(await handleBlogs(env), origin);
870
+ case "blogs/latest":
871
+ const latestCount = url.searchParams.get("count");
872
+ return this.addAdminCORSHeaders(await handleBlogs(env, void 0, latestCount ? parseInt(latestCount) : 5), origin);
873
+ default:
874
+ if (route.startsWith("images/")) {
875
+ return this.addAdminCORSHeaders(await handleContent(request, env, route), origin);
876
+ }
877
+ if (route.startsWith("blogs/")) {
878
+ const slug = route.replace("blogs/", "");
879
+ return this.addAdminCORSHeaders(await handleBlogs(env, slug), origin);
880
+ }
881
+ if (route.startsWith("stories")) {
882
+ if (route === "stories") {
883
+ return this.addAdminCORSHeaders(await handleStories(env), origin);
884
+ }
885
+ if (route === "stories/latest") {
886
+ const latestCount2 = url.searchParams.get("count");
887
+ return this.addAdminCORSHeaders(await handleStories(env, void 0, latestCount2 ? parseInt(latestCount2) : 5), origin);
888
+ }
889
+ const slug = route.replace("stories/", "");
890
+ return this.addAdminCORSHeaders(await handleStories(env, slug), origin);
891
+ }
892
+ if (route === "search") {
893
+ const query = url.searchParams.get("q");
894
+ return this.addAdminCORSHeaders(await handleSearch(env, query || void 0), origin);
895
+ }
896
+ return this.addAdminCORSHeaders(createErrorResponse("Route not found", 404), origin);
897
+ }
898
+ } catch (error) {
899
+ console.error("API Error:", error);
900
+ return this.addCORSHeaders(createErrorResponse("Internal server error", 500));
901
+ }
902
+ }
903
+ }
904
+ export {
905
+ AUTH_KV as A,
906
+ BASE_DELAY_MS as B,
907
+ MAX_ATTEMPTS as M,
908
+ RATE_LIMIT_KV as R,
909
+ WebsiteAPI as W,
910
+ clearRateLimit as a,
911
+ getAuthStore as b,
912
+ checkRateLimit as c,
913
+ getClientIP as d,
914
+ hashPassword as e,
915
+ generateSalt as g,
916
+ handleAuth as h,
917
+ recordFailedAttempt as r,
918
+ setupAuth as s,
919
+ verifyCredentials as v
920
+ };