Code for my personal website

chore: remove kommentar integration for now

-370
-10
astro.config.mjs
··· 23 23 context: 'client', 24 24 access: 'public', 25 25 optional: false 26 - }), 27 - KOMMENTAR_API_KEY: envField.string({ 28 - context: 'server', 29 - access: 'secret', 30 - optional: false 31 - }), 32 - KOMMENTAR_API_SECRET: envField.string({ 33 - context: 'server', 34 - access: 'secret', 35 - optional: false 36 26 }) 37 27 } 38 28 },
-60
src/pages/api/kommentar/comments/[host].ts
··· 1 - import { type APIRoute } from 'astro'; 2 - import { KOMMENTAR_API_KEY, KOMMENTAR_API_SECRET } from 'astro:env/server'; 3 - 4 - export const prerender = false; 5 - 6 - export const GET: APIRoute = async ({ request, params }) => { 7 - if (request.headers.get('Content-Type') === 'application/json') { 8 - const hostId = params.host; 9 - 10 - const response = await fetch( 11 - `https://api.kommentar.dev/api/hosts/${hostId}/comments`, 12 - { 13 - headers: { 14 - 'Content-Type': 'application/json', 15 - 'x-api-key': KOMMENTAR_API_KEY, 16 - 'x-api-secret': KOMMENTAR_API_SECRET 17 - } 18 - } 19 - ); 20 - 21 - const comments = await response.json(); 22 - 23 - return new Response(JSON.stringify(comments), { 24 - status: 200, 25 - headers: { 26 - 'Content-Type': 'application/json' 27 - } 28 - }); 29 - } 30 - 31 - return new Response(null, { status: 400 }); 32 - }; 33 - 34 - export const POST: APIRoute = async ({ params, request, cookies }) => { 35 - const hostId = params.host; 36 - const body = await request.json(); 37 - 38 - const response = await fetch( 39 - `https://api.kommentar.dev/api/hosts/${hostId}/comments`, 40 - { 41 - method: 'POST', 42 - headers: { 43 - 'Content-Type': 'application/json', 44 - 'x-api-key': KOMMENTAR_API_KEY, 45 - 'x-api-secret': KOMMENTAR_API_SECRET, 46 - Cookie: `sessionId=${cookies.get('sessionId')!.value}` 47 - }, 48 - body: JSON.stringify(body) 49 - } 50 - ); 51 - 52 - const comments = await response.json(); 53 - 54 - return new Response(JSON.stringify(comments), { 55 - status: 200, 56 - headers: { 57 - 'Content-Type': 'application/json' 58 - } 59 - }); 60 - };
-150
src/pages/blog/[...slug].astro
··· 7 7 import { ghost } from '@/lib/ghost'; 8 8 import type { PostOrPage } from '@tryghost/content-api'; 9 9 import Socials from '@/components/Socials.astro'; 10 - import Comment from '@/components/Comment.astro'; 11 - import { getComments, postComment } from '@/lib/kommentar'; 12 10 import { v7 as uuidV7 } from 'uuid'; 13 11 14 12 export const prerender = false; ··· 28 26 } 29 27 30 28 type Props = PostOrPage; 31 - 32 - const errors = { 33 - displayName: '', 34 - realName: '', 35 - comment: '' 36 - }; 37 - 38 - const formValues = { 39 - displayName: '', 40 - realName: '', 41 - comment: '' 42 - }; 43 - 44 - let commentSubmitted = false; 45 - 46 - if (Astro.request.method === 'POST') { 47 - try { 48 - const data = await Astro.request.formData(); 49 - 50 - const displayName = String(data.get('displayName')); 51 - const realName = String(data.get('realName')); 52 - const comment = String(data.get('comment')); 53 - 54 - formValues.displayName = displayName; 55 - formValues.realName = realName; 56 - formValues.comment = comment; 57 - 58 - // Display name validation: required, minimum 3 characters 59 - if ( 60 - !displayName || 61 - typeof displayName !== 'string' || 62 - displayName.trim().length < 3 63 - ) { 64 - errors.displayName += 65 - 'Please enter a valid display name. At least 3 characters.'; 66 - } 67 - 68 - // Real name validation: optional, but if provided, minimum 5 characters 69 - if ( 70 - realName && 71 - realName !== '' && 72 - (typeof realName !== 'string' || realName.trim().length < 5) 73 - ) { 74 - errors.realName += 75 - 'Real name must be a string with at least 5 characters.'; 76 - } 77 - 78 - // Comment validation: required, between 10 and 255 characters 79 - if ( 80 - !comment || 81 - typeof comment !== 'string' || 82 - comment.trim().length < 10 || 83 - comment.trim().length > 255 84 - ) { 85 - errors.comment += 'Comment must have between 10 and 255 characters.'; 86 - } 87 - 88 - const hasErrors = Object.values(errors).some((msg) => msg); 89 - 90 - if (!hasErrors && Astro.cookies.has('userSessionId')) { 91 - await postComment({ 92 - baseUrl: String(Astro.url.origin), 93 - comment: { 94 - hostId: String(`blog-${slug}`), 95 - content: String(comment.trim()), 96 - commenter: { 97 - displayName: String(displayName.trim()), 98 - realName: String((realName || '').trim()) 99 - } 100 - }, 101 - sessionId: Astro.cookies.get('userSessionId')!.value 102 - }); 103 - 104 - formValues.displayName = ''; 105 - formValues.realName = ''; 106 - formValues.comment = ''; 107 - 108 - commentSubmitted = true; 109 - } 110 - } catch (error) { 111 - if (error instanceof Error) { 112 - console.error(error.message); 113 - } 114 - } 115 - } 116 - 117 - const comments = await getComments({ 118 - baseUrl: String(Astro.url.origin), 119 - hostId: `blog-${slug}` 120 - }); 121 29 --- 122 30 123 31 <Layout title={String(blogPost.title)} description={String(blogPost.excerpt)}> ··· 144 52 </article> 145 53 <div class='animate'> 146 54 <Socials /> 147 - </div> 148 - <div class='animate w-full h-auto flex flex-col gap-3 mt-6'> 149 - <span class='text-xl font-bold'>Comments</span> 150 - 151 - { 152 - commentSubmitted && ( 153 - <div class='border border-lime-600 text-lime-600 px-4 py-3 rounded'> 154 - Comment posted successfully! 155 - </div> 156 - ) 157 - } 158 - 159 - <form class='w-full flex flex-col gap-2' method='POST'> 160 - <span>Post a comment</span> 161 - <div class='w-full flex flex-col sm:flex-row justify-between gap-2'> 162 - <label class='w-full text-xs flex flex-col gap-2'> 163 - Display Name * 164 - <input 165 - type='text' 166 - name='displayName' 167 - value={formValues.displayName} 168 - required 169 - /> 170 - { 171 - errors.displayName && ( 172 - <span class='text-red-500'>{errors.displayName}</span> 173 - ) 174 - } 175 - </label> 176 - <label class='w-full text-xs flex flex-col gap-2'> 177 - Real Name (overrides Display Name) 178 - <input type='text' name='realName' value={formValues.realName} /> 179 - { 180 - errors.realName && ( 181 - <span class='text-red-500'>{errors.realName}</span> 182 - ) 183 - } 184 - </label> 185 - </div> 186 - <label class='text-xs flex flex-col gap-2'> 187 - Comment * 188 - <textarea name='comment' value={formValues.comment} required 189 - ></textarea> 190 - {errors.comment && <span class='text-red-500'>{errors.comment}</span>} 191 - </label> 192 - <button 193 - type='submit' 194 - class='flex flex-nowrap py-2 px-4 pr-10 rounded-lg border border-black/15 dark:border-white/20 195 - hover:bg-lime-500/30 dark:hover:bg-lime-300/5 196 - hover:text-black dark:hover:text-white 197 - hover:border-lime-700 dark:hover:border-lime-300 198 - hover:shadow-lg hover:shadow-lime-600/30 dark:hover:shadow-lime-300/10 199 - transition-all duration-300 ease-in-out 200 - font-bold' 201 - >Submit</button 202 - > 203 - </form> 204 - {comments.map((comment) => <Comment comment={comment} />)} 205 55 </div> 206 56 </Container> 207 57 </Layout>
-150
src/pages/projects/[...slug].astro
··· 7 7 import { ghost } from '@/lib/ghost'; 8 8 import type { PostOrPage } from '@tryghost/content-api'; 9 9 import Socials from '@/components/Socials.astro'; 10 - import Comment from '@/components/Comment.astro'; 11 - import { getComments, postComment } from '@/lib/kommentar'; 12 10 import { v7 as uuidV7 } from 'uuid'; 13 11 14 12 export const prerender = false; ··· 31 29 } 32 30 33 31 type Props = PostOrPage; 34 - 35 - const errors = { 36 - displayName: '', 37 - realName: '', 38 - comment: '' 39 - }; 40 - 41 - const formValues = { 42 - displayName: '', 43 - realName: '', 44 - comment: '' 45 - }; 46 - 47 - let commentSubmitted = false; 48 - 49 - if (Astro.request.method === 'POST') { 50 - try { 51 - const data = await Astro.request.formData(); 52 - 53 - const displayName = String(data.get('displayName')); 54 - const realName = String(data.get('realName')); 55 - const comment = String(data.get('comment')); 56 - 57 - formValues.displayName = displayName; 58 - formValues.realName = realName; 59 - formValues.comment = comment; 60 - 61 - // Display name validation: required, minimum 3 characters 62 - if ( 63 - !displayName || 64 - typeof displayName !== 'string' || 65 - displayName.trim().length < 3 66 - ) { 67 - errors.displayName += 68 - 'Please enter a valid display name. At least 3 characters.'; 69 - } 70 - 71 - // Real name validation: optional, but if provided, minimum 5 characters 72 - if ( 73 - realName && 74 - realName !== '' && 75 - (typeof realName !== 'string' || realName.trim().length < 5) 76 - ) { 77 - errors.realName += 78 - 'Real name must be a string with at least 5 characters.'; 79 - } 80 - 81 - // Comment validation: required, between 10 and 255 characters 82 - if ( 83 - !comment || 84 - typeof comment !== 'string' || 85 - comment.trim().length < 10 || 86 - comment.trim().length > 255 87 - ) { 88 - errors.comment += 'Comment must have between 10 and 255 characters.'; 89 - } 90 - 91 - const hasErrors = Object.values(errors).some((msg) => msg); 92 - 93 - if (!hasErrors && Astro.cookies.has('userSessionId')) { 94 - await postComment({ 95 - baseUrl: String(Astro.url.origin), 96 - comment: { 97 - hostId: String(`projects-${slug}`), 98 - content: String(comment.trim()), 99 - commenter: { 100 - displayName: String(displayName.trim()), 101 - realName: String((realName || '').trim()) 102 - } 103 - }, 104 - sessionId: Astro.cookies.get('userSessionId')!.value 105 - }); 106 - 107 - formValues.displayName = ''; 108 - formValues.realName = ''; 109 - formValues.comment = ''; 110 - 111 - commentSubmitted = true; 112 - } 113 - } catch (error) { 114 - if (error instanceof Error) { 115 - console.error(error.message); 116 - } 117 - } 118 - } 119 - 120 - const comments = await getComments({ 121 - baseUrl: String(Astro.url.origin), 122 - hostId: `projects-${slug}` 123 - }); 124 32 --- 125 33 126 34 <Layout ··· 150 58 </article> 151 59 <div class='animate'> 152 60 <Socials /> 153 - </div> 154 - <div class='animate w-full h-auto flex flex-col gap-3 mt-6'> 155 - <span class='text-xl font-bold'>Comments</span> 156 - 157 - { 158 - commentSubmitted && ( 159 - <div class='border border-lime-600 text-lime-600 px-4 py-3 rounded'> 160 - Comment posted successfully! 161 - </div> 162 - ) 163 - } 164 - 165 - <form class='w-full flex flex-col gap-2' method='POST'> 166 - <span>Post a comment</span> 167 - <div class='w-full flex flex-col sm:flex-row justify-between gap-2'> 168 - <label class='w-full text-xs flex flex-col gap-2'> 169 - Display Name * 170 - <input 171 - type='text' 172 - name='displayName' 173 - value={formValues.displayName} 174 - required 175 - /> 176 - { 177 - errors.displayName && ( 178 - <span class='text-red-500'>{errors.displayName}</span> 179 - ) 180 - } 181 - </label> 182 - <label class='w-full text-xs flex flex-col gap-2'> 183 - Real Name (overrides Display Name) 184 - <input type='text' name='realName' value={formValues.realName} /> 185 - { 186 - errors.realName && ( 187 - <span class='text-red-500'>{errors.realName}</span> 188 - ) 189 - } 190 - </label> 191 - </div> 192 - <label class='text-xs flex flex-col gap-2'> 193 - Comment * 194 - <textarea name='comment' value={formValues.comment} required 195 - ></textarea> 196 - {errors.comment && <span class='text-red-500'>{errors.comment}</span>} 197 - </label> 198 - <button 199 - type='submit' 200 - class='flex flex-nowrap py-2 px-4 pr-10 rounded-lg border border-black/15 dark:border-white/20 201 - hover:bg-lime-500/30 dark:hover:bg-lime-300/5 202 - hover:text-black dark:hover:text-white 203 - hover:border-lime-700 dark:hover:border-lime-300 204 - hover:shadow-lg hover:shadow-lime-600/30 dark:hover:shadow-lime-300/10 205 - transition-all duration-300 ease-in-out 206 - font-bold' 207 - >Submit</button 208 - > 209 - </form> 210 - {comments.map((comment) => <Comment comment={comment} />)} 211 61 </div> 212 62 </Container> 213 63 </Layout>