who needs allat. maybe i want to live simply
This commit is contained in:
		
							parent
							
								
									db51f003bb
								
							
						
					
					
						commit
						776fc1b38e
					
				| @ -17,12 +17,6 @@ export default defineConfig({ | |||||||
|   adapter: node({ |   adapter: node({ | ||||||
|     mode: "standalone", |     mode: "standalone", | ||||||
|   }), |   }), | ||||||
|   session: { |  | ||||||
|     driver: "localstorage", |  | ||||||
|     options: { |  | ||||||
|       base: "app:", |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   experimental: { |   experimental: { | ||||||
|     fonts: [ |     fonts: [ | ||||||
|       { |       { | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								db/config.ts
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								db/config.ts
									
									
									
									
									
								
							| @ -12,19 +12,7 @@ const Guestbook = defineTable({ | |||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const Comment = defineTable({ |  | ||||||
|   columns: { |  | ||||||
|     id: column.number({ primaryKey: true }), |  | ||||||
|     postId: column.text(), |  | ||||||
|     replyId: column.number(), |  | ||||||
|     username: column.text(), |  | ||||||
|     website: column.text({ optional: true }), |  | ||||||
|     comment: column.text({ multiline: true }), |  | ||||||
|     published: column.date({ default: NOW }), |  | ||||||
|   }, |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| // https://astro.build/db/config
 | // https://astro.build/db/config
 | ||||||
| export default defineDb({ | export default defineDb({ | ||||||
|   tables: { Guestbook, Comment }, |   tables: { Guestbook }, | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -1,21 +0,0 @@ | |||||||
| import { defineAction } from "astro:actions"; |  | ||||||
| import { z } from "astro:content"; |  | ||||||
| import sanitize from "sanitize-html"; |  | ||||||
| 
 |  | ||||||
| export const comments = { |  | ||||||
|   addComment: defineAction({ |  | ||||||
|     accept: "form", |  | ||||||
|     input: z.object({ |  | ||||||
|       postId: z.string(), |  | ||||||
|       replyId: z.number().optional(), |  | ||||||
|       name: z.string(), |  | ||||||
|       website: z.string().url().optional(), |  | ||||||
|       comment: z.string(), |  | ||||||
|     }), |  | ||||||
|     handler: async (input) => { |  | ||||||
|       // sanitize but allow line breaks
 |  | ||||||
|       sanitize(input.comment); |  | ||||||
|       // post to comment server
 |  | ||||||
|     }, |  | ||||||
|   }), |  | ||||||
| }; |  | ||||||
| @ -1,7 +1,6 @@ | |||||||
| import { ActionError, defineAction } from "astro:actions"; | import { ActionError, defineAction } from "astro:actions"; | ||||||
| import { z } from "astro:content"; | import { z } from "astro:content"; | ||||||
| import { db, eq, Guestbook } from "astro:db"; | import { db, eq, Guestbook } from "astro:db"; | ||||||
| import bcrypt from "bcryptjs"; |  | ||||||
| import sanitize from "sanitize-html"; | import sanitize from "sanitize-html"; | ||||||
| 
 | 
 | ||||||
| export const guestbook = { | export const guestbook = { | ||||||
| @ -14,6 +13,8 @@ export const guestbook = { | |||||||
|     }), |     }), | ||||||
|     handler: async ({ username, website, message }) => { |     handler: async ({ username, website, message }) => { | ||||||
|       // figure out how to add line breaks and THEN sanitize message
 |       // figure out how to add line breaks and THEN sanitize message
 | ||||||
|  |       const addLine = message.replaceAll("/n", "<br/>"); | ||||||
|  |       sanitize(addLine); | ||||||
| 
 | 
 | ||||||
|       const entry = await db.insert(Guestbook).values({ |       const entry = await db.insert(Guestbook).values({ | ||||||
|         username, |         username, | ||||||
| @ -31,7 +32,7 @@ export const guestbook = { | |||||||
|       reply: z.string(), |       reply: z.string(), | ||||||
|     }), |     }), | ||||||
|     handler: async ({ id, reply }, context) => { |     handler: async ({ id, reply }, context) => { | ||||||
|       if (!context.session?.get("pwd")) { |       if (context.url.hostname !== "127.0.0.1" || "localhost") { | ||||||
|         throw new ActionError({ code: "UNAUTHORIZED" }); |         throw new ActionError({ code: "UNAUTHORIZED" }); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
| @ -49,27 +50,4 @@ export const guestbook = { | |||||||
|       return update[0]; |       return update[0]; | ||||||
|     }, |     }, | ||||||
|   }), |   }), | ||||||
|   login: defineAction({ |  | ||||||
|     accept: "form", |  | ||||||
|     input: z.object({ |  | ||||||
|       password: z.string(), |  | ||||||
|     }), |  | ||||||
|     handler: async ({ password }, context) => { |  | ||||||
|       // find env var here
 |  | ||||||
|       if (password !== "super secret password") { |  | ||||||
|         throw new ActionError({ code: "UNAUTHORIZED" }); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       const hash = await bcrypt.hash(password, 10); |  | ||||||
|       context.session?.set("pwd", hash); |  | ||||||
|       return { code: 200, message: "set the thing" }; |  | ||||||
|     } |  | ||||||
|   }), |  | ||||||
|   logout: defineAction({ |  | ||||||
|     accept: "form", |  | ||||||
|     handler: async (_input, context) => { |  | ||||||
|       context.session?.destroy(); |  | ||||||
|       return { code: 200, message: "set the thing" }; |  | ||||||
|     } |  | ||||||
|   }), |  | ||||||
| }; | }; | ||||||
| @ -1,9 +1,7 @@ | |||||||
| import { comments } from "./comment"; |  | ||||||
| import { contact } from "./contact"; | import { contact } from "./contact"; | ||||||
| import { guestbook } from "./guestbook"; | import { guestbook } from "./guestbook"; | ||||||
| 
 | 
 | ||||||
| export const server = { | export const server = { | ||||||
|   comments, |  | ||||||
|   contact, |   contact, | ||||||
|   guestbook, |   guestbook, | ||||||
| }; | }; | ||||||
| @ -12,34 +12,6 @@ | |||||||
|   --ko-font: GulimChe, DotumChe, Gulim, Dotum, system-ui-ko, system-ui-ja, system-ui-zh-cn, system-ui-zh-tw, system-ui-zh-hk, monospace, sans-serif; |   --ko-font: GulimChe, DotumChe, Gulim, Dotum, system-ui-ko, system-ui-ja, system-ui-zh-cn, system-ui-zh-tw, system-ui-zh-hk, monospace, sans-serif; | ||||||
|   --dotum-11-font: var(--dotumche-11), var(--dotum-11), var(--ko-font); |   --dotum-11-font: var(--dotumche-11), var(--dotum-11), var(--ko-font); | ||||||
|   --dotum-12-font: var(--dotumche-12), var(--dotum-12), var(--ko-font); |   --dotum-12-font: var(--dotumche-12), var(--dotum-12), var(--ko-font); | ||||||
| 
 |  | ||||||
|   @media screen and (prefers-color-scheme: light) { |  | ||||||
|     --bg-color: #e6f2ef; |  | ||||||
|     --fg-color: #151640; |  | ||||||
|     --accent-color: #f783b0; |  | ||||||
|     --secondary-color: #3f6d9e; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   @media screen and (prefers-color-scheme: dark) { |  | ||||||
|     --bg-color: #555568; |  | ||||||
|     --fg-color: #f3eded; |  | ||||||
|     --accent-color: #eeb9c7; |  | ||||||
|     --secondary-color: #b9eedc; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .light { |  | ||||||
|   --bg-color: #e6f2ef; |  | ||||||
|   --fg-color: #151640; |  | ||||||
|   --accent-color: #f783b0; |  | ||||||
|   --secondary-color: #3f6d9e; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .dark { |  | ||||||
|   --bg-color: #555568; |  | ||||||
|   --fg-color: #f3eded; |  | ||||||
|   --accent-color: #eeb9c7; |  | ||||||
|   --secondary-color: #b9eedc; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| body { | body { | ||||||
| @ -125,21 +97,3 @@ button, .button { | |||||||
|     box-shadow: 0 0 0 var(--fg-color); |     box-shadow: 0 0 0 var(--fg-color); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| .dark { |  | ||||||
|   button, .button { |  | ||||||
|     background-color: var(--accent-color); |  | ||||||
|     color: var(--bg-color); |  | ||||||
| 
 |  | ||||||
|     &:hover { |  | ||||||
|       background-color: hsl(from var(--accent-color) h s calc(l - 10)); |  | ||||||
|       color: var(--bg-color); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     &:active, &:focus { |  | ||||||
|       color: var(--bg-color); |  | ||||||
|       background-color: var(--secondary-color); |  | ||||||
|       border-color: var(--bg-color); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
							
								
								
									
										50
									
								
								src/assets/styles/themes.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/assets/styles/themes.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | |||||||
|  | :root { | ||||||
|  |   @media screen and (prefers-color-scheme: light) { | ||||||
|  |     --bg-color: #e6f2ef; | ||||||
|  |     --fg-color: #151640; | ||||||
|  |     --accent-color: #f783b0; | ||||||
|  |     --secondary-color: #3f6d9e; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @media screen and (prefers-color-scheme: dark) { | ||||||
|  |     --bg-color: #555568; | ||||||
|  |     --fg-color: #f3eded; | ||||||
|  |     --accent-color: #eeb9c7; | ||||||
|  |     --secondary-color: #b9eedc; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .light { | ||||||
|  |   --bg-color: #e6f2ef; | ||||||
|  |   --fg-color: #151640; | ||||||
|  |   --accent-color: #f783b0; | ||||||
|  |   --secondary-color: #3f6d9e; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .dark { | ||||||
|  |   --bg-color: #555568; | ||||||
|  |   --fg-color: #f3eded; | ||||||
|  |   --accent-color: #eeb9c7; | ||||||
|  |   --secondary-color: #b9eedc; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* custom theme styling */ | ||||||
|  | 
 | ||||||
|  | /* dark-specific styles */ | ||||||
|  | .dark { | ||||||
|  |   button, .button { | ||||||
|  |     background-color: var(--accent-color); | ||||||
|  |     color: var(--bg-color); | ||||||
|  | 
 | ||||||
|  |     &:hover { | ||||||
|  |       background-color: hsl(from var(--accent-color) h s calc(l - 10)); | ||||||
|  |       color: var(--bg-color); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     &:active, &:focus { | ||||||
|  |       color: var(--bg-color); | ||||||
|  |       background-color: var(--secondary-color); | ||||||
|  |       border-color: var(--bg-color); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								src/env.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								src/env.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +0,0 @@ | |||||||
| interface Window { |  | ||||||
|   Alpine: import("alpinejs").Alpine; |  | ||||||
| } |  | ||||||
| @ -1,5 +1,6 @@ | |||||||
| --- | --- | ||||||
| import "$/styles/base.css"; | import "$/styles/base.css"; | ||||||
|  | import "$/styles/themes.css"; | ||||||
| import { Font } from "astro:assets"; | import { Font } from "astro:assets"; | ||||||
| 
 | 
 | ||||||
| interface Props { title?: string; } | interface Props { title?: string; } | ||||||
| @ -11,7 +12,6 @@ const { title = "haetae" }: Props = Astro.props; | |||||||
| 	<head> | 	<head> | ||||||
| 		<meta charset="UTF-8" /> | 		<meta charset="UTF-8" /> | ||||||
| 		<meta name="viewport" content="width=device-width" /> | 		<meta name="viewport" content="width=device-width" /> | ||||||
| 		<meta name="generator" content={Astro.generator} /> |  | ||||||
| 		<meta name="pinterest" content="nopin nohover" /> | 		<meta name="pinterest" content="nopin nohover" /> | ||||||
| 		<meta name="robots" content="noai, noimageai" /> | 		<meta name="robots" content="noai, noimageai" /> | ||||||
| 		<Font cssVariable="--sq" preload /> | 		<Font cssVariable="--sq" preload /> | ||||||
|  | |||||||
| @ -3,62 +3,71 @@ export const prerender = false; | |||||||
| 
 | 
 | ||||||
| import { actions } from "astro:actions"; | import { actions } from "astro:actions"; | ||||||
| import { db, desc, Guestbook } from "astro:db"; | import { db, desc, Guestbook } from "astro:db"; | ||||||
|  | import Layout from "@/layouts/Layout.astro"; | ||||||
|  | import formatDate from "@/utils/formatDate"; | ||||||
| 
 | 
 | ||||||
| const pwd = await Astro.session?.get("pwd"); | if (!import.meta.env.DEV) { | ||||||
| 
 |   console.error("you shouldn't be here..."); | ||||||
| if (!pwd) { |   return Astro.redirect("/guestbook"); | ||||||
|   const fromUrl = Astro.url.pathname + Astro.url.search; |  | ||||||
|   return Astro.redirect(`/guestbook/login?redirect=${fromUrl}`); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const entries = await db.select().from(Guestbook).orderBy(desc(Guestbook.published)); | const entries = await db.select().from(Guestbook).orderBy(desc(Guestbook.published)); | ||||||
| --- | --- | ||||||
| <h1>entries</h1> | <Layout title="guestbook admin"> | ||||||
|  |   <h1>entries</h1> | ||||||
| 
 | 
 | ||||||
| <section> |   <section> | ||||||
|   <table> |     <table> | ||||||
|     <thead> |       <thead> | ||||||
|       <tr> |         <tr> | ||||||
|         <th>username</th> |           <th>username</th> | ||||||
|         <th>website</th> |           <th>website</th> | ||||||
|         <th>message</th> |           <th>message</th> | ||||||
|         <th>published</th> |           <th>published</th> | ||||||
|         <th>reply</th> |           <th>reply</th> | ||||||
|         <th>edit</th> |           <th>edit</th> | ||||||
|       </tr> |  | ||||||
|     </thead> |  | ||||||
|     <tbody> |  | ||||||
|       {entries.map(entry => ( |  | ||||||
|         <tr id={`${entry.id}`}> |  | ||||||
|           <td>{entry.username}</td> |  | ||||||
|           <td>{entry.website}</td> |  | ||||||
|           <td>{entry.message}</td> |  | ||||||
|           <td>{entry.published}</td> |  | ||||||
|           <td>{entry.reply}</td> |  | ||||||
|           <td><button class="edit">edit</button></td> |  | ||||||
|         </tr> |         </tr> | ||||||
|       ))} |       </thead> | ||||||
|     </tbody> |       <tbody> | ||||||
|   </table> |         {entries.map(entry => ( | ||||||
|  |           <tr id={`${entry.id}`}> | ||||||
|  |             <td>{entry.username}</td> | ||||||
|  |             <td>{entry.website}</td> | ||||||
|  |             <td>{entry.message}</td> | ||||||
|  |             <td>{formatDate(entry.published, false, 'MMMM D, YYYY')}</td> | ||||||
|  |             <td>{entry.reply}</td> | ||||||
|  |             <td><button class="edit">edit</button></td> | ||||||
|  |           </tr> | ||||||
|  |         ))} | ||||||
|  |       </tbody> | ||||||
|  |     </table> | ||||||
| 
 | 
 | ||||||
|   <dialog id="edit-entry"> |     <dialog id="edit-entry"> | ||||||
|     <form id="edit-entry-form" action={actions.guestbook.reply} method="post"> |       <form id="edit-entry-form" action={actions.guestbook.reply} method="post"> | ||||||
|       <input type="hidden" name="id" value="" /> |         <input type="hidden" name="id" id="entryId" value="" /> | ||||||
|       <p id="entry-username"></p> |         <p id="entry-username"></p> | ||||||
|       <p id="entry-website"></p> |         <p id="entry-website"></p> | ||||||
|       <div id="entry-message"></div> |         <div id="entry-message"></div> | ||||||
|       <p id="entry-published"></p> |         <p id="entry-published"></p> | ||||||
|       <label for="reply">Reply</label> |         <label for="reply">Reply</label> | ||||||
|       <textarea name="reply" id="reply"></textarea> |         <textarea name="reply" id="reply"></textarea> | ||||||
| 
 | 
 | ||||||
|       <button type="submit">Reply</button> |         <button type="submit">Reply</button> | ||||||
|     </form> |       </form> | ||||||
|   </dialog> |     </dialog> | ||||||
| </section> |   </section> | ||||||
|  | </Layout> | ||||||
| 
 | 
 | ||||||
| <form action={actions.guestbook.logout} method="post"> | <style> | ||||||
|   <button type="submit">logout</button> |   table { | ||||||
| </form> |     border-collapse: collapse; | ||||||
|  | 
 | ||||||
|  |     td { | ||||||
|  |       max-width: 30ch; | ||||||
|  |       text-overflow: ellipsis; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </style> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
|   const modal = document.getElementById("edit-entry") as HTMLDialogElement; |   const modal = document.getElementById("edit-entry") as HTMLDialogElement; | ||||||
| @ -70,7 +79,7 @@ const entries = await db.select().from(Guestbook).orderBy(desc(Guestbook.publish | |||||||
|       const data = row.cells; |       const data = row.cells; | ||||||
|       const fields = document.querySelectorAll("[id^='entry-']"); |       const fields = document.querySelectorAll("[id^='entry-']"); | ||||||
| 
 | 
 | ||||||
|       (document.getElementById("id") as HTMLInputElement).value = id; |       (document.getElementById("entryId") as HTMLInputElement).value = id; | ||||||
|       fields.forEach((field, i) => { |       fields.forEach((field, i) => { | ||||||
|         field.innerHTML = data.item(i)!.innerText; |         field.innerHTML = data.item(i)!.innerText; | ||||||
|       }); |       }); | ||||||
|  | |||||||
| @ -1,16 +0,0 @@ | |||||||
| --- |  | ||||||
| export const prerender = false; |  | ||||||
| 
 |  | ||||||
| import bcrypt from "bcryptjs"; |  | ||||||
| 
 |  | ||||||
| if (Astro.request.method === "POST") { |  | ||||||
|   const form = await Astro.request.formData(); |  | ||||||
|   const pwd = form.get("password"); |  | ||||||
| 
 |  | ||||||
|   if (pwd) { |  | ||||||
|     const hashed = bcrypt.hashSync(pwd.toString(), 10); |  | ||||||
|     Astro.session?.set("pwd", hashed); |  | ||||||
|     return Astro.redirect("/guestbook/admin"); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| --- |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user