@leadertechie/personal-site-kit 0.1.0-alpha.6 → 0.1.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/handlers/about-me.d.ts.map +1 -1
- package/dist/api/handlers/auth-handler.d.ts.map +1 -1
- package/dist/api/website-api.d.ts.map +1 -1
- package/dist/api.js +2 -2
- package/dist/chunks/index-CGvOrVf8.js +213 -0
- package/dist/chunks/{index-C3wLSCKU.js → index-CYd_Pe2U.js} +1353 -482
- package/dist/chunks/{template-MawmknFQ.js → template-D1uGvdWZ.js} +10 -8
- package/dist/chunks/{website-api-DI3muo2s.js → website-api-FLejlWxJ.js} +78 -41
- package/dist/index.js +19 -9
- package/dist/prerender/data-fetcher.d.ts +19 -0
- package/dist/prerender/data-fetcher.d.ts.map +1 -0
- package/dist/prerender/page-content.d.ts.map +1 -1
- package/dist/prerender/page-generators/about.d.ts +16 -0
- package/dist/prerender/page-generators/about.d.ts.map +1 -0
- package/dist/prerender/page-generators/base.d.ts +26 -0
- package/dist/prerender/page-generators/base.d.ts.map +1 -0
- package/dist/prerender/page-generators/blog-detail.d.ts +15 -0
- package/dist/prerender/page-generators/blog-detail.d.ts.map +1 -0
- package/dist/prerender/page-generators/blogs-list.d.ts +17 -0
- package/dist/prerender/page-generators/blogs-list.d.ts.map +1 -0
- package/dist/prerender/page-generators/home.d.ts +19 -0
- package/dist/prerender/page-generators/home.d.ts.map +1 -0
- package/dist/prerender/page-generators/index.d.ts +9 -0
- package/dist/prerender/page-generators/index.d.ts.map +1 -0
- package/dist/prerender/page-generators/not-found.d.ts +14 -0
- package/dist/prerender/page-generators/not-found.d.ts.map +1 -0
- package/dist/prerender/page-generators/stories-list.d.ts +17 -0
- package/dist/prerender/page-generators/stories-list.d.ts.map +1 -0
- package/dist/prerender/page-generators/story-detail.d.ts +15 -0
- package/dist/prerender/page-generators/story-detail.d.ts.map +1 -0
- package/dist/prerender.js +109 -102
- package/dist/shared/config/index.d.ts.map +1 -1
- package/dist/shared.js +1 -1
- package/dist/ui/about-me/index.d.ts +2 -10
- package/dist/ui/about-me/index.d.ts.map +1 -1
- package/dist/ui/admin/api.d.ts +16 -0
- package/dist/ui/admin/api.d.ts.map +1 -0
- package/dist/ui/admin/components/AboutMeSection.d.ts +7 -0
- package/dist/ui/admin/components/AboutMeSection.d.ts.map +1 -0
- package/dist/ui/admin/components/AdminSection.d.ts +13 -0
- package/dist/ui/admin/components/AdminSection.d.ts.map +1 -0
- package/dist/ui/admin/components/BlogsSection.d.ts +7 -0
- package/dist/ui/admin/components/BlogsSection.d.ts.map +1 -0
- package/dist/ui/admin/components/HomeSection.d.ts +7 -0
- package/dist/ui/admin/components/HomeSection.d.ts.map +1 -0
- package/dist/ui/admin/components/ImagesSection.d.ts +7 -0
- package/dist/ui/admin/components/ImagesSection.d.ts.map +1 -0
- package/dist/ui/admin/components/LoginForm.d.ts +9 -0
- package/dist/ui/admin/components/LoginForm.d.ts.map +1 -0
- package/dist/ui/admin/components/LogoSection.d.ts +7 -0
- package/dist/ui/admin/components/LogoSection.d.ts.map +1 -0
- package/dist/ui/admin/components/ProfileSection.d.ts +7 -0
- package/dist/ui/admin/components/ProfileSection.d.ts.map +1 -0
- package/dist/ui/admin/components/StaticSection.d.ts +13 -0
- package/dist/ui/admin/components/StaticSection.d.ts.map +1 -0
- package/dist/ui/admin/components/StoriesSection.d.ts +7 -0
- package/dist/ui/admin/components/StoriesSection.d.ts.map +1 -0
- package/dist/ui/admin/components/index.d.ts +11 -0
- package/dist/ui/admin/components/index.d.ts.map +1 -0
- package/dist/ui/admin/index.d.ts +10 -26
- package/dist/ui/admin/index.d.ts.map +1 -1
- package/dist/ui/admin/types.d.ts +24 -0
- package/dist/ui/admin/types.d.ts.map +1 -0
- package/dist/ui/blog-viewer/index.d.ts.map +1 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/story-viewer/index.d.ts.map +1 -1
- package/dist/ui.js +14 -4
- package/package.json +1 -1
- package/src/api/handlers/about-me.ts +19 -9
- package/src/api/handlers/auth-handler.ts +41 -18
- package/src/api/handlers/content.ts +1 -1
- package/src/api/handlers/home.ts +2 -2
- package/src/api/website-api.ts +25 -13
- package/src/prerender/__tests__/page-content.test.ts +1 -11
- package/src/prerender/data-fetcher.ts +93 -0
- package/src/prerender/page-content.ts +109 -106
- package/src/prerender/page-generators/about.ts +38 -0
- package/src/prerender/page-generators/base.ts +77 -0
- package/src/prerender/page-generators/blog-detail.ts +35 -0
- package/src/prerender/page-generators/blogs-list.ts +43 -0
- package/src/prerender/page-generators/home.ts +54 -0
- package/src/prerender/page-generators/index.ts +8 -0
- package/src/prerender/page-generators/not-found.ts +36 -0
- package/src/prerender/page-generators/stories-list.ts +43 -0
- package/src/prerender/page-generators/story-detail.ts +35 -0
- package/src/shared/config/index.ts +4 -2
- package/src/shared/page-content.ts +1 -1
- package/src/shared/router.ts +5 -5
- package/src/ui/about-me/index.ts +23 -57
- package/src/ui/admin/api.ts +93 -0
- package/src/ui/admin/components/AboutMeSection.ts +47 -0
- package/src/ui/admin/components/AdminSection.ts +134 -0
- package/src/ui/admin/components/BlogsSection.ts +62 -0
- package/src/ui/admin/components/HomeSection.ts +47 -0
- package/src/ui/admin/components/ImagesSection.ts +54 -0
- package/src/ui/admin/components/LoginForm.ts +116 -0
- package/src/ui/admin/components/LogoSection.ts +51 -0
- package/src/ui/admin/components/ProfileSection.ts +47 -0
- package/src/ui/admin/components/StaticSection.ts +67 -0
- package/src/ui/admin/components/StoriesSection.ts +62 -0
- package/src/ui/admin/components/index.ts +10 -0
- package/src/ui/admin/index.ts +192 -434
- package/src/ui/admin/types.ts +26 -0
- package/src/ui/blog-viewer/index.ts +4 -1
- package/src/ui/index.ts +7 -0
- package/src/ui/story-viewer/index.ts +4 -1
- package/dist/ui/about-me/renderer.d.ts +0 -5
- package/dist/ui/about-me/renderer.d.ts.map +0 -1
- package/src/ui/about-me/renderer.ts +0 -7
|
@@ -66,24 +66,34 @@ export async function handleAboutMe(env?: any): Promise<Response> {
|
|
|
66
66
|
}
|
|
67
67
|
console.log('handleAboutMe: r2 created, fetching data');
|
|
68
68
|
|
|
69
|
-
const [profileObj,
|
|
69
|
+
const [profileObj, rendered] = await Promise.all([
|
|
70
70
|
r2.getObject('profile.json'),
|
|
71
|
-
r2.
|
|
71
|
+
r2.getRendered('about-me.md')
|
|
72
72
|
]);
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
if (!rendered) {
|
|
75
|
+
return new Response(JSON.stringify({ error: 'About-me content not found. Please run seed.' }), {
|
|
76
|
+
status: 404,
|
|
77
|
+
headers: { 'Content-Type': 'application/json' },
|
|
78
|
+
});
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
let profile: Profile = {
|
|
82
|
+
name: 'Your Name',
|
|
83
|
+
title: 'Professional',
|
|
84
|
+
experience: 'Experienced',
|
|
85
|
+
profileImageUrl: ''
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (profileObj) {
|
|
89
|
+
profile = await profileObj.json() as Profile;
|
|
90
|
+
}
|
|
81
91
|
console.log('handleAboutMe: profile loaded:', profile.name);
|
|
82
92
|
|
|
83
93
|
const responseData: AboutMeApiResponse = {
|
|
84
94
|
profile,
|
|
85
|
-
contentNodes:
|
|
86
|
-
processedMarkdown:
|
|
95
|
+
contentNodes: [],
|
|
96
|
+
processedMarkdown: rendered.content
|
|
87
97
|
};
|
|
88
98
|
|
|
89
99
|
return new Response(JSON.stringify(responseData), {
|
|
@@ -10,18 +10,25 @@ import {
|
|
|
10
10
|
MAX_ATTEMPTS
|
|
11
11
|
} from './auth';
|
|
12
12
|
|
|
13
|
-
function createSessionCookie(token: string,
|
|
13
|
+
function createSessionCookie(token: string, origin: string): string {
|
|
14
14
|
const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toUTCString();
|
|
15
|
-
const
|
|
16
|
-
|
|
15
|
+
const isSecure = origin.startsWith('https://');
|
|
16
|
+
const hostname = new URL(origin).hostname;
|
|
17
|
+
const SameSite = isSecure ? 'Strict' : 'Lax';
|
|
18
|
+
|
|
19
|
+
let cookie = `session=${token}; HttpOnly; SameSite=${SameSite}; Path=/; Expires=${expires}`;
|
|
20
|
+
if (isSecure) {
|
|
21
|
+
cookie += '; Secure';
|
|
22
|
+
}
|
|
23
|
+
cookie += `; Domain=${hostname}`;
|
|
24
|
+
return cookie;
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
export async function handleAuth(request: Request, env: any, subpath: string): Promise<Response> {
|
|
20
28
|
const method = request.method;
|
|
21
29
|
const clientIP = getClientIP(request);
|
|
22
30
|
const path = subpath.replace(/^\//, '').split('/')[0];
|
|
23
|
-
const
|
|
24
|
-
const isSecure = url.protocol === 'https:';
|
|
31
|
+
const origin = request.headers.get('Origin') || new URL(request.url).origin;
|
|
25
32
|
|
|
26
33
|
// Check rate limit for login attempts
|
|
27
34
|
const rateCheck = await checkRateLimit(env, clientIP);
|
|
@@ -40,11 +47,11 @@ export async function handleAuth(request: Request, env: any, subpath: string): P
|
|
|
40
47
|
|
|
41
48
|
switch (path) {
|
|
42
49
|
case 'setup':
|
|
43
|
-
return handleSetup(request, env, clientIP,
|
|
50
|
+
return handleSetup(request, env, clientIP, origin);
|
|
44
51
|
case 'status':
|
|
45
52
|
return handleStatus(env);
|
|
46
53
|
case 'login':
|
|
47
|
-
return handleLogin(request, env, clientIP,
|
|
54
|
+
return handleLogin(request, env, clientIP, origin);
|
|
48
55
|
case 'logout':
|
|
49
56
|
return handleLogout(request, env);
|
|
50
57
|
default:
|
|
@@ -52,19 +59,22 @@ export async function handleAuth(request: Request, env: any, subpath: string): P
|
|
|
52
59
|
}
|
|
53
60
|
}
|
|
54
61
|
|
|
55
|
-
async function handleSetup(request: Request, env: any, clientIP: string,
|
|
62
|
+
async function handleSetup(request: Request, env: any, clientIP: string, origin: string): Promise<Response> {
|
|
63
|
+
console.log('handleSetup: starting setup, env.KV exists:', !!env.KV);
|
|
56
64
|
if (request.method !== 'POST') {
|
|
57
65
|
return createErrorResponse('Method not allowed', 405);
|
|
58
66
|
}
|
|
59
67
|
|
|
60
|
-
const existing = await getAuthStore(env);
|
|
61
|
-
if (existing) {
|
|
62
|
-
return createErrorResponse('Admin already configured. Use /auth/login to login.', 400);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
68
|
try {
|
|
69
|
+
const existing = await getAuthStore(env);
|
|
70
|
+
console.log('handleSetup: existing store check:', !!existing);
|
|
71
|
+
if (existing) {
|
|
72
|
+
return createErrorResponse('Admin already configured. Use /auth/login to login.', 400);
|
|
73
|
+
}
|
|
74
|
+
|
|
66
75
|
const body = await request.json();
|
|
67
76
|
const { username, password } = body;
|
|
77
|
+
console.log('handleSetup: body parsed, username:', username);
|
|
68
78
|
|
|
69
79
|
if (!username || !password) {
|
|
70
80
|
return createErrorResponse('Username and password required', 400);
|
|
@@ -74,18 +84,23 @@ async function handleSetup(request: Request, env: any, clientIP: string, isSecur
|
|
|
74
84
|
return createErrorResponse('Username must be 3+ chars, password must be 8+ chars', 400);
|
|
75
85
|
}
|
|
76
86
|
|
|
87
|
+
console.log('handleSetup: calling setupAuth');
|
|
77
88
|
await setupAuth(env, username, password);
|
|
89
|
+
console.log('handleSetup: setupAuth successful');
|
|
78
90
|
await clearRateLimit(env, clientIP);
|
|
79
91
|
|
|
80
92
|
const token = crypto.randomUUID();
|
|
93
|
+
console.log('handleSetup: session token generated');
|
|
81
94
|
await env.KV.put(`session:${token}`, JSON.stringify({
|
|
82
95
|
createdAt: Date.now(),
|
|
83
96
|
expiresAt: Date.now() + (7 * 24 * 60 * 60 * 1000)
|
|
84
97
|
}), { expirationTtl: 7 * 24 * 60 * 60 });
|
|
98
|
+
console.log('handleSetup: session stored in KV');
|
|
85
99
|
|
|
86
100
|
const headers: Record<string, string> = {
|
|
87
101
|
'Content-Type': 'application/json',
|
|
88
|
-
'Set-Cookie': createSessionCookie(token,
|
|
102
|
+
'Set-Cookie': createSessionCookie(token, origin),
|
|
103
|
+
'X-Session-Token': token
|
|
89
104
|
};
|
|
90
105
|
|
|
91
106
|
return new Response(JSON.stringify({
|
|
@@ -96,7 +111,8 @@ async function handleSetup(request: Request, env: any, clientIP: string, isSecur
|
|
|
96
111
|
headers
|
|
97
112
|
});
|
|
98
113
|
} catch (e) {
|
|
99
|
-
|
|
114
|
+
console.error('handleSetup: error occurred:', e);
|
|
115
|
+
return createErrorResponse('Internal server error: ' + (e as Error).message, 500);
|
|
100
116
|
}
|
|
101
117
|
}
|
|
102
118
|
|
|
@@ -109,7 +125,7 @@ async function handleStatus(env: any): Promise<Response> {
|
|
|
109
125
|
});
|
|
110
126
|
}
|
|
111
127
|
|
|
112
|
-
async function handleLogin(request: Request, env: any, clientIP: string,
|
|
128
|
+
async function handleLogin(request: Request, env: any, clientIP: string, origin: string): Promise<Response> {
|
|
113
129
|
if (request.method !== 'POST') {
|
|
114
130
|
return createErrorResponse('Method not allowed', 405);
|
|
115
131
|
}
|
|
@@ -138,7 +154,8 @@ async function handleLogin(request: Request, env: any, clientIP: string, isSecur
|
|
|
138
154
|
|
|
139
155
|
const headers: Record<string, string> = {
|
|
140
156
|
'Content-Type': 'application/json',
|
|
141
|
-
'Set-Cookie': createSessionCookie(token,
|
|
157
|
+
'Set-Cookie': createSessionCookie(token, origin),
|
|
158
|
+
'X-Session-Token': token
|
|
142
159
|
};
|
|
143
160
|
|
|
144
161
|
return new Response(JSON.stringify({
|
|
@@ -162,6 +179,7 @@ async function handleLogout(request: Request, env: any): Promise<Response> {
|
|
|
162
179
|
return createErrorResponse('Method not allowed', 405);
|
|
163
180
|
}
|
|
164
181
|
|
|
182
|
+
const origin = request.headers.get('Origin') || new URL(request.url).origin;
|
|
165
183
|
const cookieHeader = request.headers.get('Cookie');
|
|
166
184
|
const sessionToken = cookieHeader?.split(';')
|
|
167
185
|
.find(c => c.trim().startsWith('session='))
|
|
@@ -171,11 +189,16 @@ async function handleLogout(request: Request, env: any): Promise<Response> {
|
|
|
171
189
|
await env.KV.delete(`session:${sessionToken}`);
|
|
172
190
|
}
|
|
173
191
|
|
|
192
|
+
const hostname = new URL(origin).hostname;
|
|
193
|
+
const isSecure = origin.startsWith('https://');
|
|
194
|
+
const SameSite = isSecure ? 'Strict' : 'Lax';
|
|
195
|
+
const logoutCookie = `session=; HttpOnly; SameSite=${SameSite}; Path=/; Max-Age=0; Domain=${hostname}${isSecure ? '; Secure' : ''}`;
|
|
196
|
+
|
|
174
197
|
return new Response(JSON.stringify({ success: true, message: 'Logged out' }), {
|
|
175
198
|
status: 200,
|
|
176
199
|
headers: {
|
|
177
200
|
'Content-Type': 'application/json',
|
|
178
|
-
'Set-Cookie':
|
|
201
|
+
'Set-Cookie': logoutCookie
|
|
179
202
|
}
|
|
180
203
|
});
|
|
181
204
|
}
|
|
@@ -48,7 +48,7 @@ export async function handleContent(request: Request, env: any, subpath: string)
|
|
|
48
48
|
return createErrorResponse('Admin not configured. Use POST /auth/setup to configure.', 401);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const sessionToken = getSessionToken(request);
|
|
51
|
+
const sessionToken = getSessionToken(request) || request.headers.get('X-Session-Token');
|
|
52
52
|
let isAuthenticated = false;
|
|
53
53
|
|
|
54
54
|
if (sessionToken) {
|
package/src/api/handlers/home.ts
CHANGED
|
@@ -46,8 +46,8 @@ export async function handleHome(env?: any): Promise<Response> {
|
|
|
46
46
|
|
|
47
47
|
const r2 = getLoader(env);
|
|
48
48
|
const [astResult, renderedResult] = await Promise.all([
|
|
49
|
-
r2.getWithAST('home.md'),
|
|
50
|
-
r2.getRendered('home.md')
|
|
49
|
+
r2.getWithAST('pages/home.md'),
|
|
50
|
+
r2.getRendered('pages/home.md')
|
|
51
51
|
]);
|
|
52
52
|
|
|
53
53
|
if (!astResult || !renderedResult) {
|
package/src/api/website-api.ts
CHANGED
|
@@ -21,25 +21,32 @@ export class WebsiteAPI {
|
|
|
21
21
|
private addCORSHeaders(response: Response): Response {
|
|
22
22
|
response.headers.set('Access-Control-Allow-Origin', '*' );
|
|
23
23
|
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS' );
|
|
24
|
-
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
24
|
+
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Session-Token');
|
|
25
25
|
return response;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
private addAdminCORSHeaders(response: Response): Response {
|
|
29
|
-
|
|
28
|
+
private addAdminCORSHeaders(response: Response, origin: string): Response {
|
|
29
|
+
const allowOrigin = origin && (origin.includes('localhost') || origin.includes('127.0.0.1'))
|
|
30
|
+
? origin
|
|
31
|
+
: 'same-origin';
|
|
32
|
+
response.headers.set('Access-Control-Allow-Origin', allowOrigin);
|
|
30
33
|
response.headers.set('Access-Control-Allow-Credentials', 'true');
|
|
31
34
|
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
32
|
-
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
35
|
+
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Session-Token');
|
|
33
36
|
return response;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
private handleCORS(): Response {
|
|
39
|
+
private handleCORS(origin: string): Response {
|
|
40
|
+
const allowOrigin = origin && (origin.includes('localhost') || origin.includes('127.0.0.1'))
|
|
41
|
+
? origin
|
|
42
|
+
: '*';
|
|
37
43
|
return new Response(null, {
|
|
38
44
|
status: 200,
|
|
39
45
|
headers: {
|
|
40
|
-
'Access-Control-Allow-Origin':
|
|
46
|
+
'Access-Control-Allow-Origin': allowOrigin ,
|
|
41
47
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS' ,
|
|
42
|
-
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
48
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Session-Token',
|
|
49
|
+
'Access-Control-Allow-Credentials': 'true',
|
|
43
50
|
'Access-Control-Max-Age': '86400',
|
|
44
51
|
},
|
|
45
52
|
});
|
|
@@ -47,9 +54,10 @@ export class WebsiteAPI {
|
|
|
47
54
|
|
|
48
55
|
public async fetch(request: Request, env: any): Promise<Response> {
|
|
49
56
|
const url = new URL(request.url);
|
|
57
|
+
const origin = request.headers.get('Origin') || url.origin;
|
|
50
58
|
|
|
51
59
|
if (request.method === 'OPTIONS') {
|
|
52
|
-
return this.handleCORS();
|
|
60
|
+
return this.handleCORS(origin);
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
const pathname = url.pathname;
|
|
@@ -68,7 +76,13 @@ export class WebsiteAPI {
|
|
|
68
76
|
// Check for content route first (content/*)
|
|
69
77
|
if (route === 'content' || route.startsWith('content/')) {
|
|
70
78
|
const subpath = route.replace(/^content\/?/, '');
|
|
71
|
-
return this.addAdminCORSHeaders(await handleContent(request, env, subpath));
|
|
79
|
+
return this.addAdminCORSHeaders(await handleContent(request, env, subpath), origin);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check for auth route (auth/*)
|
|
83
|
+
if (route === 'auth' || route.startsWith('auth/')) {
|
|
84
|
+
const subpath = route.replace(/^auth\/?/, '');
|
|
85
|
+
return this.addAdminCORSHeaders(await handleAuth(request, env, subpath || '/'), origin);
|
|
72
86
|
}
|
|
73
87
|
|
|
74
88
|
switch (route) {
|
|
@@ -83,18 +97,16 @@ export class WebsiteAPI {
|
|
|
83
97
|
?.split('=')[1];
|
|
84
98
|
const session = sessionToken ? await env.KV.get(`session:${sessionToken}`, 'json') : null;
|
|
85
99
|
if (!session || session.expiresAt < Date.now()) {
|
|
86
|
-
return this.addAdminCORSHeaders(createErrorResponse('Unauthorized', 401));
|
|
100
|
+
return this.addAdminCORSHeaders(createErrorResponse('Unauthorized', 401), origin);
|
|
87
101
|
}
|
|
88
102
|
clearContentCache();
|
|
89
|
-
return this.addAdminCORSHeaders(new Response(JSON.stringify({ success: true, message: 'Cache cleared' }), { status: 200 }));
|
|
103
|
+
return this.addAdminCORSHeaders(new Response(JSON.stringify({ success: true, message: 'Cache cleared' }), { status: 200 }), origin);
|
|
90
104
|
case 'aboutme':
|
|
91
105
|
return this.addCORSHeaders(await handleAboutMe(env));
|
|
92
106
|
case 'logo':
|
|
93
107
|
return this.addCORSHeaders(await handleLogo(env));
|
|
94
108
|
case 'static':
|
|
95
109
|
return this.addCORSHeaders(await handleStaticDetails(env));
|
|
96
|
-
case 'auth':
|
|
97
|
-
return this.addAdminCORSHeaders(await handleAuth(request, env, '/auth'));
|
|
98
110
|
case 'blogs':
|
|
99
111
|
return this.addCORSHeaders(await handleBlogs(env));
|
|
100
112
|
case 'blogs/latest':
|
|
@@ -7,25 +7,15 @@ describe('generatePageContent', () => {
|
|
|
7
7
|
{ link: '/about-me', text: 'About' }
|
|
8
8
|
];
|
|
9
9
|
const mockFooterLinks = [
|
|
10
|
-
{ text: 'Link',
|
|
10
|
+
{ text: 'Link', link: '#' }
|
|
11
11
|
];
|
|
12
12
|
const mockEnv = {
|
|
13
|
-
CONTENT_BUCKET: {
|
|
14
|
-
get: vi.fn().mockResolvedValue({
|
|
15
|
-
json: () => Promise.resolve({ name: 'User', title: 'Professional', experience: 'some' })
|
|
16
|
-
}),
|
|
17
|
-
list: vi.fn().mockResolvedValue({ objects: [] })
|
|
18
|
-
},
|
|
19
13
|
apiUrl: 'https://api.example.com',
|
|
20
14
|
baseUrl: 'https://www.example.com'
|
|
21
15
|
};
|
|
22
16
|
|
|
23
17
|
beforeEach(() => {
|
|
24
18
|
vi.clearAllMocks();
|
|
25
|
-
global.fetch = vi.fn().mockResolvedValue({
|
|
26
|
-
ok: true,
|
|
27
|
-
json: () => Promise.resolve({ siteTitle: 'My Site', copyright: '2026' })
|
|
28
|
-
});
|
|
29
19
|
});
|
|
30
20
|
|
|
31
21
|
it('should generate home page content', async () => {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { R2ContentLoader } from "@leadertechie/r2tohtml";
|
|
2
|
+
|
|
3
|
+
export interface Profile {
|
|
4
|
+
name: string;
|
|
5
|
+
title: string;
|
|
6
|
+
experience: string;
|
|
7
|
+
profileImageUrl: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface BlogMeta {
|
|
11
|
+
slug: string;
|
|
12
|
+
title: string;
|
|
13
|
+
summary: string;
|
|
14
|
+
tags: string[];
|
|
15
|
+
date: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let loader: R2ContentLoader | null = null;
|
|
19
|
+
|
|
20
|
+
function getLoader(env: any): R2ContentLoader | null {
|
|
21
|
+
if (!loader) {
|
|
22
|
+
if (!env?.CONTENT_BUCKET) return null;
|
|
23
|
+
loader = new R2ContentLoader(
|
|
24
|
+
{ bucket: env.CONTENT_BUCKET, cacheTTL: 5 * 60 * 1000 },
|
|
25
|
+
{ md2html: { imagePathPrefix: "images/", styleOptions: { classPrefix: "md-", addHeadingIds: true } } }
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return loader;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function fetchProfile(env: any): Promise<Profile | null> {
|
|
32
|
+
try {
|
|
33
|
+
const r2 = getLoader(env);
|
|
34
|
+
if (!r2) return null;
|
|
35
|
+
const obj = await r2.getObject("profile.json");
|
|
36
|
+
if (!obj) return null;
|
|
37
|
+
return await obj.json() as Profile;
|
|
38
|
+
} catch { return null; }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function fetchAboutMe(env: any): Promise<string> {
|
|
42
|
+
try {
|
|
43
|
+
const r2 = getLoader(env);
|
|
44
|
+
if (!r2) return "";
|
|
45
|
+
const result = await r2.getRendered("about-me.md");
|
|
46
|
+
return result?.content || "";
|
|
47
|
+
} catch { return ""; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function fetchHome(env: any): Promise<string> {
|
|
51
|
+
try {
|
|
52
|
+
const r2 = getLoader(env);
|
|
53
|
+
if (!r2) return "";
|
|
54
|
+
const result = await r2.getRendered("pages/home.md");
|
|
55
|
+
return result?.content || "";
|
|
56
|
+
} catch { return ""; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function fetchLatestBlogSummaries(env: any, count: number = 3): Promise<BlogMeta[]> {
|
|
60
|
+
try {
|
|
61
|
+
const r2 = getLoader(env);
|
|
62
|
+
if (!r2) return [];
|
|
63
|
+
const list = await r2.list("blogs/");
|
|
64
|
+
const metas: BlogMeta[] = [];
|
|
65
|
+
for (const obj of list.objects) {
|
|
66
|
+
if (obj.key.endsWith(".json")) {
|
|
67
|
+
try {
|
|
68
|
+
const metaObj = await r2.getObject(obj.key);
|
|
69
|
+
if (metaObj) metas.push(await metaObj.json() as BlogMeta);
|
|
70
|
+
} catch {}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return metas.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, count);
|
|
74
|
+
} catch { return []; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function fetchLatestStorySummaries(env: any, count: number = 3): Promise<BlogMeta[]> {
|
|
78
|
+
try {
|
|
79
|
+
const r2 = getLoader(env);
|
|
80
|
+
if (!r2) return [];
|
|
81
|
+
const list = await r2.list("stories/");
|
|
82
|
+
const metas: BlogMeta[] = [];
|
|
83
|
+
for (const obj of list.objects) {
|
|
84
|
+
if (obj.key.endsWith(".json")) {
|
|
85
|
+
try {
|
|
86
|
+
const metaObj = await r2.getObject(obj.key);
|
|
87
|
+
if (metaObj) metas.push(await metaObj.json() as BlogMeta);
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return metas.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, count);
|
|
92
|
+
} catch { return []; }
|
|
93
|
+
}
|