@leadertechie/personal-site-kit 0.1.0-alpha.2 → 0.1.0-alpha.20

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 (169) hide show
  1. package/README.md +94 -17
  2. package/dist/api/content-utils.d.ts +27 -0
  3. package/dist/api/content-utils.d.ts.map +1 -0
  4. package/dist/api/handlers/about-me.d.ts.map +1 -1
  5. package/dist/api/handlers/auth-handler.d.ts +2 -0
  6. package/dist/api/handlers/auth-handler.d.ts.map +1 -0
  7. package/dist/api/handlers/auth.d.ts +23 -0
  8. package/dist/api/handlers/auth.d.ts.map +1 -0
  9. package/dist/api/handlers/content-api.d.ts +0 -1
  10. package/dist/api/handlers/content-api.d.ts.map +1 -1
  11. package/dist/api/handlers/content.d.ts.map +1 -1
  12. package/dist/api/handlers/home.d.ts.map +1 -1
  13. package/dist/api/handlers/static-details.d.ts +1 -1
  14. package/dist/api/handlers/static-details.d.ts.map +1 -1
  15. package/dist/api/index.d.ts +2 -0
  16. package/dist/api/index.d.ts.map +1 -1
  17. package/dist/api/website-api.d.ts +1 -1
  18. package/dist/api/website-api.d.ts.map +1 -1
  19. package/dist/api.js +17 -2
  20. package/dist/assets/logo-placeholder.svg +21 -0
  21. package/dist/chunks/index-C1krnvU3.js +211 -0
  22. package/dist/chunks/index-DrnbjP2Q.js +2715 -0
  23. package/dist/chunks/site-store-CGV9c2DI.js +89 -0
  24. package/dist/chunks/{template-gGTkeOcA.js → template-DVy2k_na.js} +128 -90
  25. package/dist/chunks/website-api-CFRUPu0X.js +958 -0
  26. package/dist/index.js +42 -14
  27. package/dist/prerender/data-fetcher.d.ts +19 -0
  28. package/dist/prerender/data-fetcher.d.ts.map +1 -0
  29. package/dist/prerender/page-content.d.ts.map +1 -1
  30. package/dist/prerender/page-generators/about.d.ts +16 -0
  31. package/dist/prerender/page-generators/about.d.ts.map +1 -0
  32. package/dist/prerender/page-generators/base.d.ts +25 -0
  33. package/dist/prerender/page-generators/base.d.ts.map +1 -0
  34. package/dist/prerender/page-generators/blog-detail.d.ts +15 -0
  35. package/dist/prerender/page-generators/blog-detail.d.ts.map +1 -0
  36. package/dist/prerender/page-generators/blogs-list.d.ts +17 -0
  37. package/dist/prerender/page-generators/blogs-list.d.ts.map +1 -0
  38. package/dist/prerender/page-generators/home.d.ts +19 -0
  39. package/dist/prerender/page-generators/home.d.ts.map +1 -0
  40. package/dist/prerender/page-generators/index.d.ts +9 -0
  41. package/dist/prerender/page-generators/index.d.ts.map +1 -0
  42. package/dist/prerender/page-generators/not-found.d.ts +14 -0
  43. package/dist/prerender/page-generators/not-found.d.ts.map +1 -0
  44. package/dist/prerender/page-generators/stories-list.d.ts +17 -0
  45. package/dist/prerender/page-generators/stories-list.d.ts.map +1 -0
  46. package/dist/prerender/page-generators/story-detail.d.ts +15 -0
  47. package/dist/prerender/page-generators/story-detail.d.ts.map +1 -0
  48. package/dist/prerender/template.d.ts +3 -1
  49. package/dist/prerender/template.d.ts.map +1 -1
  50. package/dist/prerender/website-prerender.d.ts +6 -0
  51. package/dist/prerender/website-prerender.d.ts.map +1 -1
  52. package/dist/prerender.js +291 -145
  53. package/dist/shared/config/index.d.ts +1 -0
  54. package/dist/shared/config/index.d.ts.map +1 -1
  55. package/dist/shared/core/site-store.d.ts +1 -0
  56. package/dist/shared/core/site-store.d.ts.map +1 -1
  57. package/dist/shared/core/theme-toggle.d.ts.map +1 -1
  58. package/dist/shared/page-content.d.ts.map +1 -1
  59. package/dist/shared/router.d.ts +9 -3
  60. package/dist/shared/router.d.ts.map +1 -1
  61. package/dist/shared/website-ui.d.ts +23 -0
  62. package/dist/shared/website-ui.d.ts.map +1 -1
  63. package/dist/shared.js +6 -4
  64. package/dist/ui/about-me/index.d.ts +2 -10
  65. package/dist/ui/about-me/index.d.ts.map +1 -1
  66. package/dist/ui/about-me/styles.d.ts.map +1 -1
  67. package/dist/ui/admin/api.d.ts +16 -0
  68. package/dist/ui/admin/api.d.ts.map +1 -0
  69. package/dist/ui/admin/components/AboutMeSection.d.ts +7 -0
  70. package/dist/ui/admin/components/AboutMeSection.d.ts.map +1 -0
  71. package/dist/ui/admin/components/AdminSection.d.ts +13 -0
  72. package/dist/ui/admin/components/AdminSection.d.ts.map +1 -0
  73. package/dist/ui/admin/components/BlogsSection.d.ts +7 -0
  74. package/dist/ui/admin/components/BlogsSection.d.ts.map +1 -0
  75. package/dist/ui/admin/components/HomeSection.d.ts +7 -0
  76. package/dist/ui/admin/components/HomeSection.d.ts.map +1 -0
  77. package/dist/ui/admin/components/ImagesSection.d.ts +7 -0
  78. package/dist/ui/admin/components/ImagesSection.d.ts.map +1 -0
  79. package/dist/ui/admin/components/LoginForm.d.ts +9 -0
  80. package/dist/ui/admin/components/LoginForm.d.ts.map +1 -0
  81. package/dist/ui/admin/components/LogoSection.d.ts +7 -0
  82. package/dist/ui/admin/components/LogoSection.d.ts.map +1 -0
  83. package/dist/ui/admin/components/ProfileSection.d.ts +7 -0
  84. package/dist/ui/admin/components/ProfileSection.d.ts.map +1 -0
  85. package/dist/ui/admin/components/StaticSection.d.ts +9 -0
  86. package/dist/ui/admin/components/StaticSection.d.ts.map +1 -0
  87. package/dist/ui/admin/components/StoriesSection.d.ts +7 -0
  88. package/dist/ui/admin/components/StoriesSection.d.ts.map +1 -0
  89. package/dist/ui/admin/components/index.d.ts +11 -0
  90. package/dist/ui/admin/components/index.d.ts.map +1 -0
  91. package/dist/ui/admin/index.d.ts +27 -26
  92. package/dist/ui/admin/index.d.ts.map +1 -1
  93. package/dist/ui/admin/styles.d.ts.map +1 -1
  94. package/dist/ui/admin/types.d.ts +24 -0
  95. package/dist/ui/admin/types.d.ts.map +1 -0
  96. package/dist/ui/banner/index.d.ts.map +1 -1
  97. package/dist/ui/banner/styles.d.ts.map +1 -1
  98. package/dist/ui/blog-viewer/__tests__/blogviewer.test.d.ts +2 -0
  99. package/dist/ui/blog-viewer/__tests__/blogviewer.test.d.ts.map +1 -0
  100. package/dist/ui/blog-viewer/index.d.ts +25 -0
  101. package/dist/ui/blog-viewer/index.d.ts.map +1 -0
  102. package/dist/ui/blog-viewer/styles.d.ts +2 -0
  103. package/dist/ui/blog-viewer/styles.d.ts.map +1 -0
  104. package/dist/ui/footer/index.d.ts.map +1 -1
  105. package/dist/ui/footer/styles.d.ts.map +1 -1
  106. package/dist/ui/index.d.ts +2 -0
  107. package/dist/ui/index.d.ts.map +1 -1
  108. package/dist/ui/story-viewer/__tests__/storyviewer.test.d.ts +2 -0
  109. package/dist/ui/story-viewer/__tests__/storyviewer.test.d.ts.map +1 -0
  110. package/dist/ui/story-viewer/index.d.ts +25 -0
  111. package/dist/ui/story-viewer/index.d.ts.map +1 -0
  112. package/dist/ui/story-viewer/styles.d.ts +2 -0
  113. package/dist/ui/story-viewer/styles.d.ts.map +1 -0
  114. package/dist/ui.js +15 -3
  115. package/package.json +37 -13
  116. package/public/assets/logo-placeholder.svg +21 -0
  117. package/dist/chunks/index-BqixlS-2.js +0 -1157
  118. package/dist/chunks/website-api-CVsi-OLc.js +0 -596
  119. package/dist/ui/about-me/renderer.d.ts +0 -5
  120. package/dist/ui/about-me/renderer.d.ts.map +0 -1
  121. package/src/api/__tests__/info.test.ts +0 -44
  122. package/src/api/__tests__/utils.test.ts +0 -78
  123. package/src/api/handlers/about-me.ts +0 -99
  124. package/src/api/handlers/content-api.ts +0 -268
  125. package/src/api/handlers/content.ts +0 -72
  126. package/src/api/handlers/home.ts +0 -79
  127. package/src/api/handlers/info.ts +0 -12
  128. package/src/api/handlers/logo.ts +0 -55
  129. package/src/api/handlers/static-details.ts +0 -48
  130. package/src/api/index.ts +0 -7
  131. package/src/api/utils.ts +0 -16
  132. package/src/api/website-api.ts +0 -124
  133. package/src/index.ts +0 -4
  134. package/src/prerender/__tests__/page-content.test.ts +0 -54
  135. package/src/prerender/__tests__/template.test.ts +0 -54
  136. package/src/prerender/index.ts +0 -7
  137. package/src/prerender/page-content.ts +0 -263
  138. package/src/prerender/prerender.ts +0 -25
  139. package/src/prerender/template.ts +0 -65
  140. package/src/prerender/website-prerender.ts +0 -152
  141. package/src/shared/config/api.ts +0 -16
  142. package/src/shared/config/index.ts +0 -41
  143. package/src/shared/config/types.ts +0 -16
  144. package/src/shared/core/__tests__/theme-toggle.test.ts +0 -204
  145. package/src/shared/core/site-store.ts +0 -38
  146. package/src/shared/core/theme-toggle.ts +0 -118
  147. package/src/shared/index.ts +0 -17
  148. package/src/shared/interfaces/ifooter-link.ts +0 -4
  149. package/src/shared/interfaces/iroute.ts +0 -4
  150. package/src/shared/models/theme-variables.css +0 -25
  151. package/src/shared/page-content.ts +0 -210
  152. package/src/shared/router.ts +0 -241
  153. package/src/shared/runtime.ts +0 -11
  154. package/src/shared/template.ts +0 -35
  155. package/src/shared/website-ui.ts +0 -92
  156. package/src/styles/markdown.css +0 -129
  157. package/src/ui/about-me/api.ts +0 -12
  158. package/src/ui/about-me/index.ts +0 -155
  159. package/src/ui/about-me/renderer.ts +0 -7
  160. package/src/ui/about-me/styles.ts +0 -10
  161. package/src/ui/admin/index.ts +0 -492
  162. package/src/ui/admin/styles.ts +0 -317
  163. package/src/ui/banner/index.ts +0 -38
  164. package/src/ui/banner/styles.ts +0 -10
  165. package/src/ui/footer/index.ts +0 -37
  166. package/src/ui/footer/styles.ts +0 -9
  167. package/src/ui/index.ts +0 -4
  168. /package/{src/shared → dist}/styles/markdown.css +0 -0
  169. /package/{src → dist}/styles/theme.css +0 -0
@@ -1,596 +0,0 @@
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() {
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, astResult] = await Promise.all([
56
- r2.getObject("profile.json"),
57
- r2.getWithAST("about-me.md")
58
- ]);
59
- console.log("handleAboutMe: profileObj =", !!profileObj, "astResult =", !!astResult);
60
- if (!profileObj || !astResult) {
61
- throw new Error("Content not found in R2");
62
- }
63
- const profile = await profileObj.json();
64
- console.log("handleAboutMe: profile loaded:", profile.name);
65
- const responseData = {
66
- profile,
67
- contentNodes: astResult.contentNodes,
68
- processedMarkdown: ""
69
- };
70
- return new Response(JSON.stringify(responseData), {
71
- headers: { "Content-Type": "application/json" }
72
- });
73
- } catch (error) {
74
- console.error("Error serving aboutme content:", error);
75
- return new Response(JSON.stringify({ error: "Content not available", message: String(error) }), {
76
- status: 500,
77
- headers: { "Content-Type": "application/json" }
78
- });
79
- }
80
- }
81
- let loader = null;
82
- function getLoader(env) {
83
- if (!loader && env?.CONTENT_BUCKET) {
84
- loader = new R2ContentLoader(
85
- {
86
- bucket: env.CONTENT_BUCKET,
87
- cacheTTL: 5 * 60 * 1e3
88
- },
89
- {
90
- md2html: {
91
- imagePathPrefix: "images/",
92
- styleOptions: {
93
- classPrefix: "md-",
94
- addHeadingIds: true
95
- }
96
- }
97
- }
98
- );
99
- }
100
- return loader;
101
- }
102
- async function handleHome(env) {
103
- try {
104
- if (!env?.CONTENT_BUCKET) {
105
- return new Response(JSON.stringify({ error: "Content bucket not configured" }), {
106
- status: 500,
107
- headers: { "Content-Type": "application/json" }
108
- });
109
- }
110
- const r2 = getLoader(env);
111
- const [astResult, renderedResult] = await Promise.all([
112
- r2.getWithAST("home.md"),
113
- r2.getRendered("home.md")
114
- ]);
115
- if (!astResult || !renderedResult) {
116
- return new Response(JSON.stringify({
117
- contentNodes: [],
118
- processedMarkdown: "",
119
- content: ""
120
- }), {
121
- headers: { "Content-Type": "application/json" }
122
- });
123
- }
124
- const responseData = {
125
- contentNodes: astResult.contentNodes,
126
- processedMarkdown: "",
127
- content: renderedResult.content
128
- };
129
- return new Response(JSON.stringify(responseData), {
130
- headers: { "Content-Type": "application/json" }
131
- });
132
- } catch (error) {
133
- console.error("Error serving home content:", error);
134
- return new Response(JSON.stringify({ error: "Content not available" }), {
135
- status: 500,
136
- headers: { "Content-Type": "application/json" }
137
- });
138
- }
139
- }
140
- async function handleInfo() {
141
- return createJSONResponse({
142
- name: "TechieLeader",
143
- version: "1.0.0",
144
- description: "TechieLeader API",
145
- endpoints: [
146
- { path: "/info", method: "GET", description: "Get API information" }
147
- ]
148
- });
149
- }
150
- async function handleContent(request, env, subpath) {
151
- const bucket = env.CONTENT_BUCKET;
152
- if (!bucket) {
153
- return createErrorResponse("Content bucket not configured", 500);
154
- }
155
- const method = request.method;
156
- if (method === "GET" && (!subpath || subpath === "/")) {
157
- try {
158
- const list = await bucket.list();
159
- return createJSONResponse(list.objects.map((o) => ({
160
- key: o.key,
161
- size: o.size,
162
- uploaded: o.uploaded,
163
- httpEtag: o.httpEtag
164
- })));
165
- } catch (e) {
166
- return createErrorResponse("Failed to list content: " + e.message, 500);
167
- }
168
- }
169
- if (method === "GET" && subpath) {
170
- try {
171
- const object = await bucket.get(subpath);
172
- if (!object) {
173
- return createErrorResponse("Content not found", 404);
174
- }
175
- const headers = new Headers();
176
- object.writeHttpMetadata(headers);
177
- headers.set("etag", object.httpEtag);
178
- return new Response(object.body, { headers });
179
- } catch (e) {
180
- return createErrorResponse("Failed to get content: " + e.message, 500);
181
- }
182
- }
183
- const authHeader = request.headers.get("Authorization");
184
- const apiKey = env.ADMIN_API_KEY;
185
- if (apiKey && authHeader !== `Bearer ${apiKey}`) {
186
- return createErrorResponse("Unauthorized", 401);
187
- }
188
- if (method === "PUT" && subpath) {
189
- try {
190
- await bucket.put(subpath, request.body);
191
- return createJSONResponse({ success: true, key: subpath });
192
- } catch (e) {
193
- return createErrorResponse("Failed to upload content: " + e.message, 500);
194
- }
195
- }
196
- if (method === "DELETE" && subpath) {
197
- try {
198
- await bucket.delete(subpath);
199
- return createJSONResponse({ success: true, key: subpath });
200
- } catch (e) {
201
- return createErrorResponse("Failed to delete content: " + e.message, 500);
202
- }
203
- }
204
- return createErrorResponse("Method not allowed", 405);
205
- }
206
- const contentCache = /* @__PURE__ */ new Map();
207
- const CACHE_TTL = 5 * 60 * 1e3;
208
- async function getCachedOrFetch(key, fetchFn) {
209
- const cached = contentCache.get(key);
210
- if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
211
- return cached.data;
212
- }
213
- const data = await fetchFn();
214
- contentCache.set(key, { data, timestamp: Date.now() });
215
- return data;
216
- }
217
- function parseFrontmatter(content) {
218
- const lines = content.split("\n");
219
- const metadata = {};
220
- let contentStart = 0;
221
- if (lines[0]?.trim() === "---") {
222
- for (let i = 1; i < lines.length; i++) {
223
- if (lines[i]?.trim() === "---") {
224
- contentStart = i + 1;
225
- break;
226
- }
227
- const colonIdx = lines[i].indexOf(":");
228
- if (colonIdx > 0) {
229
- const key = lines[i].slice(0, colonIdx).trim();
230
- let value = lines[i].slice(colonIdx + 1).trim();
231
- if (value.startsWith("[") && value.endsWith("]")) {
232
- value = value.slice(1, -1);
233
- metadata[key] = value.split(",").map((v) => v.trim());
234
- } else {
235
- metadata[key] = value;
236
- }
237
- }
238
- }
239
- }
240
- return {
241
- metadata,
242
- content: lines.slice(contentStart).join("\n").trim()
243
- };
244
- }
245
- async function handleBlogs(env, slug, latest) {
246
- try {
247
- if (!env?.CONTENT_BUCKET) {
248
- return new Response(JSON.stringify({ error: "Content bucket not configured" }), {
249
- status: 500,
250
- headers: { "Content-Type": "application/json" }
251
- });
252
- }
253
- const cacheKey = slug ? `blog-${slug}` : `blogs-list-${latest || "all"}`;
254
- const result = await getCachedOrFetch(cacheKey, async () => {
255
- if (slug) {
256
- const mdObj = await env.CONTENT_BUCKET.get(`blogs/${slug}.md`);
257
- const jsonObj = await env.CONTENT_BUCKET.get(`blogs/${slug}.json`);
258
- if (!mdObj && !jsonObj) throw new Error("Blog not found");
259
- let metadata = {};
260
- if (jsonObj) {
261
- metadata = await jsonObj.json();
262
- }
263
- let content = "";
264
- if (mdObj) {
265
- const text = await mdObj.text();
266
- const parsed = parseFrontmatter(text);
267
- content = parsed.content;
268
- metadata = { ...parsed.metadata, ...metadata };
269
- }
270
- return { ...metadata, slug, content };
271
- }
272
- const list = await env.CONTENT_BUCKET.list({ prefix: "blogs/" });
273
- const blogs = [];
274
- for (const item of list.objects) {
275
- if (item.key.endsWith(".json")) {
276
- const obj = await env.CONTENT_BUCKET.get(item.key);
277
- if (obj) {
278
- const metadata = await obj.json();
279
- const slug2 = item.key.replace("blogs/", "").replace(".json", "");
280
- blogs.push({ ...metadata, slug: slug2 });
281
- }
282
- }
283
- }
284
- const sorted = blogs.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
285
- return latest ? sorted.slice(0, latest) : sorted;
286
- });
287
- return new Response(JSON.stringify(result), {
288
- headers: { "Content-Type": "application/json" }
289
- });
290
- } catch (error) {
291
- console.error("Error serving blogs:", error);
292
- return new Response(JSON.stringify({ error: "Blog not found" }), {
293
- status: 404,
294
- headers: { "Content-Type": "application/json" }
295
- });
296
- }
297
- }
298
- async function handleStories(env, slug, latest) {
299
- try {
300
- if (!env?.CONTENT_BUCKET) {
301
- return new Response(JSON.stringify({ error: "Content bucket not configured" }), {
302
- status: 500,
303
- headers: { "Content-Type": "application/json" }
304
- });
305
- }
306
- const cacheKey = slug ? `story-${slug}` : `stories-list-${latest || "all"}`;
307
- const result = await getCachedOrFetch(cacheKey, async () => {
308
- if (slug) {
309
- const mdObj = await env.CONTENT_BUCKET.get(`stories/${slug}.md`);
310
- const jsonObj = await env.CONTENT_BUCKET.get(`stories/${slug}.json`);
311
- if (!mdObj && !jsonObj) throw new Error("Story not found");
312
- let metadata = {};
313
- if (jsonObj) {
314
- metadata = await jsonObj.json();
315
- }
316
- let content = "";
317
- if (mdObj) {
318
- const text = await mdObj.text();
319
- const parsed = parseFrontmatter(text);
320
- content = parsed.content;
321
- metadata = { ...parsed.metadata, ...metadata };
322
- }
323
- return { ...metadata, slug, content };
324
- }
325
- const list = await env.CONTENT_BUCKET.list({ prefix: "stories/" });
326
- const stories = [];
327
- for (const item of list.objects) {
328
- if (item.key.endsWith(".json")) {
329
- const obj = await env.CONTENT_BUCKET.get(item.key);
330
- if (obj) {
331
- const metadata = await obj.json();
332
- const slug2 = item.key.replace("stories/", "").replace(".json", "");
333
- stories.push({ ...metadata, slug: slug2 });
334
- }
335
- }
336
- }
337
- const sorted = stories.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
338
- return latest ? sorted.slice(0, latest) : sorted;
339
- });
340
- return new Response(JSON.stringify(result), {
341
- headers: { "Content-Type": "application/json" }
342
- });
343
- } catch (error) {
344
- console.error("Error serving stories:", error);
345
- return new Response(JSON.stringify({ error: "Story not found" }), {
346
- status: 404,
347
- headers: { "Content-Type": "application/json" }
348
- });
349
- }
350
- }
351
- async function handleSearch(env, query) {
352
- try {
353
- if (!env?.CONTENT_BUCKET) {
354
- return new Response(JSON.stringify({ error: "Content bucket not configured" }), {
355
- status: 500,
356
- headers: { "Content-Type": "application/json" }
357
- });
358
- }
359
- if (!query) {
360
- return new Response(JSON.stringify({ error: "Search query required" }), {
361
- status: 400,
362
- headers: { "Content-Type": "application/json" }
363
- });
364
- }
365
- const searchResults = await getCachedOrFetch(`search-${query}`, async () => {
366
- const q = query.toLowerCase();
367
- const results = [];
368
- const [blogsList, storiesList] = await Promise.all([
369
- env.CONTENT_BUCKET.list({ prefix: "blogs/" }),
370
- env.CONTENT_BUCKET.list({ prefix: "stories/" })
371
- ]);
372
- for (const item of [...blogsList.objects, ...storiesList.objects]) {
373
- if (item.key.endsWith(".md")) {
374
- const obj = await env.CONTENT_BUCKET.get(item.key);
375
- if (obj) {
376
- const text = await obj.text();
377
- const { metadata } = parseFrontmatter(text);
378
- const matchTitle = metadata.title?.toLowerCase().includes(q);
379
- const matchDesc = metadata.description?.toLowerCase().includes(q);
380
- const matchTags = metadata.tags?.some((t) => t.toLowerCase().includes(q));
381
- if (matchTitle || matchDesc || matchTags) {
382
- results.push(metadata);
383
- }
384
- }
385
- }
386
- }
387
- return results;
388
- });
389
- return new Response(JSON.stringify(searchResults), {
390
- headers: { "Content-Type": "application/json" }
391
- });
392
- } catch (error) {
393
- console.error("Error serving search:", error);
394
- return new Response(JSON.stringify({ error: "Search failed" }), {
395
- status: 500,
396
- headers: { "Content-Type": "application/json" }
397
- });
398
- }
399
- }
400
- const PLACEHOLDER_LOGO = `<svg width="600" height="320" viewBox="0 0 600 320" xmlns="http://www.w3.org/2000/svg">
401
- <style>
402
- .text-main {
403
- fill: light-dark(#4A5568, #E2E8F0);
404
- font-family: 'Segoe UI', 'Arial Black', sans-serif;
405
- font-weight: 900;
406
- font-size: 46px;
407
- letter-spacing: -0.01em;
408
- text-anchor: middle;
409
- }
410
- </style>
411
-
412
- <!-- Big circle placeholder -->
413
- <circle cx="300" cy="160" r="100" fill="none" stroke="light-dark(#A0AEC0, #718096)" stroke-width="2" stroke-dasharray="8 4" opacity="0.5" />
414
-
415
- <!-- Plus sign in circle -->
416
- <line x1="300" y1="100" x2="300" y2="220" stroke="light-dark(#A0AEC0, #718096)" stroke-width="2" opacity="0.3" />
417
- <line x1="240" y1="160" x2="360" y2="160" stroke="light-dark(#A0AEC0, #718096)" stroke-width="2" opacity="0.3" />
418
-
419
- <text x="300" y="290" class="text-main">YOUR LOGO</text>
420
- </svg>`;
421
- async function handleLogo(env) {
422
- try {
423
- if (env?.CONTENT_BUCKET) {
424
- const logo = await env.CONTENT_BUCKET.get("logo.svg");
425
- if (logo) {
426
- const headers = new Headers();
427
- logo.writeHttpMetadata(headers);
428
- headers.set("Content-Type", "image/svg+xml");
429
- headers.set("Cache-Control", "public, max-age=3600");
430
- headers.set("Access-Control-Allow-Origin", "*");
431
- return new Response(logo.body, { headers });
432
- }
433
- }
434
- return new Response(PLACEHOLDER_LOGO, {
435
- headers: {
436
- "Content-Type": "image/svg+xml",
437
- "Cache-Control": "public, max-age=3600",
438
- "Access-Control-Allow-Origin": "*"
439
- }
440
- });
441
- } catch (error) {
442
- console.error("Error serving logo:", error);
443
- return new Response(PLACEHOLDER_LOGO, {
444
- headers: {
445
- "Content-Type": "image/svg+xml",
446
- "Access-Control-Allow-Origin": "*"
447
- }
448
- });
449
- }
450
- }
451
- const DEFAULT_STATIC_DETAILS = {
452
- siteTitle: "My Personal Website",
453
- copyright: "2026 My Personal Website",
454
- linkedin: "https://linkedin.com/in/yourname",
455
- github: "https://github.com/yourname",
456
- email: "yourname@domain.com"
457
- };
458
- async function handleStaticDetails(env, method, body) {
459
- try {
460
- if (!env?.CONTENT_BUCKET) {
461
- return new Response(JSON.stringify(DEFAULT_STATIC_DETAILS), {
462
- headers: { "Content-Type": "application/json" }
463
- });
464
- }
465
- if (!method || method === "GET") {
466
- const staticDetails = await env.CONTENT_BUCKET.get("staticdetails.json");
467
- if (staticDetails) {
468
- const data = await staticDetails.json();
469
- return new Response(JSON.stringify({ ...DEFAULT_STATIC_DETAILS, ...data }), {
470
- headers: {
471
- "Content-Type": "application/json",
472
- "Access-Control-Allow-Origin": "*"
473
- }
474
- });
475
- }
476
- return new Response(JSON.stringify(DEFAULT_STATIC_DETAILS), {
477
- headers: {
478
- "Content-Type": "application/json",
479
- "Access-Control-Allow-Origin": "*"
480
- }
481
- });
482
- }
483
- return new Response(JSON.stringify({ error: "Use content endpoint for PUT" }), {
484
- status: 400,
485
- headers: { "Content-Type": "application/json" }
486
- });
487
- } catch (error) {
488
- console.error("Error serving static details:", error);
489
- return new Response(JSON.stringify(DEFAULT_STATIC_DETAILS), {
490
- headers: { "Content-Type": "application/json" }
491
- });
492
- }
493
- }
494
- class WebsiteAPI {
495
- constructor() {
496
- this.customHandlers = /* @__PURE__ */ new Map();
497
- }
498
- registerHandler(route, handler) {
499
- this.customHandlers.set(route, handler);
500
- }
501
- addCORSHeaders(response) {
502
- response.headers.set("Access-Control-Allow-Origin", "*");
503
- response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
504
- response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
505
- return response;
506
- }
507
- handleCORS() {
508
- return new Response(null, {
509
- status: 200,
510
- headers: {
511
- "Access-Control-Allow-Origin": "*",
512
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
513
- "Access-Control-Allow-Headers": "Content-Type, Authorization",
514
- "Access-Control-Max-Age": "86400"
515
- }
516
- });
517
- }
518
- requireAuth(request, env) {
519
- const authHeader = request.headers.get("Authorization");
520
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
521
- return createErrorResponse("Unauthorized", 401);
522
- }
523
- const token = authHeader.slice(7);
524
- if (token !== env?.ADMIN_API_KEY) {
525
- return createErrorResponse("Unauthorized", 401);
526
- }
527
- return null;
528
- }
529
- async fetch(request, env) {
530
- const url = new URL(request.url);
531
- if (request.method === "OPTIONS") {
532
- return this.handleCORS();
533
- }
534
- const pathname = url.pathname;
535
- const route = pathname.replace(/^\/api\//, "").replace(/^\//, "").replace(/\/+$/, "");
536
- if (this.customHandlers.has(route)) {
537
- const handler = this.customHandlers.get(route);
538
- return this.addCORSHeaders(await handler(request, env));
539
- }
540
- try {
541
- if (route === "content" || route.startsWith("content/")) {
542
- const subpath = route.replace(/^content\/?/, "");
543
- return this.addCORSHeaders(await handleContent(request, env, subpath));
544
- }
545
- switch (route) {
546
- case "info":
547
- return this.addCORSHeaders(await handleInfo());
548
- case "home":
549
- return this.addCORSHeaders(await handleHome(env));
550
- case "cache-clear":
551
- const authError = this.requireAuth(request, env);
552
- if (authError) return this.addCORSHeaders(authError);
553
- clearContentCache();
554
- return this.addCORSHeaders(new Response(JSON.stringify({ success: true, message: "Cache cleared" }), { status: 200 }));
555
- case "aboutme":
556
- return this.addCORSHeaders(await handleAboutMe(env));
557
- case "logo":
558
- return this.addCORSHeaders(await handleLogo(env));
559
- case "static":
560
- return this.addCORSHeaders(await handleStaticDetails(env));
561
- case "blogs":
562
- return this.addCORSHeaders(await handleBlogs(env));
563
- case "blogs/latest":
564
- const latestCount = url.searchParams.get("count");
565
- return this.addCORSHeaders(await handleBlogs(env, void 0, latestCount ? parseInt(latestCount) : 5));
566
- default:
567
- if (route.startsWith("blogs/")) {
568
- const slug = route.replace("blogs/", "");
569
- return this.addCORSHeaders(await handleBlogs(env, slug));
570
- }
571
- if (route.startsWith("stories")) {
572
- if (route === "stories") {
573
- return this.addCORSHeaders(await handleStories(env));
574
- }
575
- if (route === "stories/latest") {
576
- const latestCount2 = url.searchParams.get("count");
577
- return this.addCORSHeaders(await handleStories(env, void 0, latestCount2 ? parseInt(latestCount2) : 5));
578
- }
579
- const slug = route.replace("stories/", "");
580
- return this.addCORSHeaders(await handleStories(env, slug));
581
- }
582
- if (route === "search") {
583
- const query = url.searchParams.get("q");
584
- return this.addCORSHeaders(await handleSearch(env, query || void 0));
585
- }
586
- return this.addCORSHeaders(createErrorResponse("Route not found", 404));
587
- }
588
- } catch (error) {
589
- console.error("API Error:", error);
590
- return this.addCORSHeaders(createErrorResponse("Internal server error", 500));
591
- }
592
- }
593
- }
594
- export {
595
- WebsiteAPI as W
596
- };
@@ -1,5 +0,0 @@
1
- import { ContentNode } from '@leadertechie/md2html';
2
- export declare class AboutMeRenderer {
3
- renderContent(nodes: ContentNode[]): unknown;
4
- }
5
- //# sourceMappingURL=renderer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/ui/about-me/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,qBAAa,eAAe;IAC1B,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO;CAG7C"}
@@ -1,44 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { handleInfo } from '../handlers/info';
4
-
5
- describe('Info Handler', () => {
6
- it('should return API information', async () => {
7
- const response = await handleInfo();
8
-
9
- expect(response.status).toBe(200);
10
- expect(response.headers.get('Content-Type')).toBe('application/json');
11
-
12
- const body = await response.text();
13
- const data = JSON.parse(body);
14
-
15
- expect(data.name).toBe('TechieLeader');
16
- expect(data.version).toBe('1.0.0');
17
- expect(data.description).toBe('TechieLeader API');
18
- expect(data.endpoints).toBeInstanceOf(Array);
19
- expect(data.endpoints.length).toBeGreaterThan(0);
20
- });
21
-
22
- it('should return valid endpoints array', async () => {
23
- const response = await handleInfo();
24
- const body = await response.text();
25
- const data = JSON.parse(body);
26
-
27
- expect(data.endpoints).toContainEqual({
28
- path: '/info',
29
- method: 'GET',
30
- description: 'Get API information'
31
- });
32
- });
33
-
34
- it('should have correct response structure', async () => {
35
- const response = await handleInfo();
36
- const body = await response.text();
37
- const data = JSON.parse(body);
38
-
39
- expect(data).toHaveProperty('name');
40
- expect(data).toHaveProperty('version');
41
- expect(data).toHaveProperty('description');
42
- expect(data).toHaveProperty('endpoints');
43
- });
44
- });