who needs allat. maybe i want to live simply

This commit is contained in:
haetae 2025-08-08 03:00:12 -04:00
parent db51f003bb
commit 776fc1b38e
12 changed files with 113 additions and 182 deletions

View File

@ -17,12 +17,6 @@ export default defineConfig({
adapter: node({
mode: "standalone",
}),
session: {
driver: "localstorage",
options: {
base: "app:",
},
},
experimental: {
fonts: [
{

View File

@ -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
export default defineDb({
tables: { Guestbook, Comment },
tables: { Guestbook },
});

View File

@ -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
},
}),
};

View File

@ -1,7 +1,6 @@
import { ActionError, defineAction } from "astro:actions";
import { z } from "astro:content";
import { db, eq, Guestbook } from "astro:db";
import bcrypt from "bcryptjs";
import sanitize from "sanitize-html";
export const guestbook = {
@ -14,6 +13,8 @@ export const guestbook = {
}),
handler: async ({ username, website, 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({
username,
@ -31,7 +32,7 @@ export const guestbook = {
reply: z.string(),
}),
handler: async ({ id, reply }, context) => {
if (!context.session?.get("pwd")) {
if (context.url.hostname !== "127.0.0.1" || "localhost") {
throw new ActionError({ code: "UNAUTHORIZED" });
}
@ -49,27 +50,4 @@ export const guestbook = {
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" };
}
}),
};

View File

@ -1,9 +1,7 @@
import { comments } from "./comment";
import { contact } from "./contact";
import { guestbook } from "./guestbook";
export const server = {
comments,
contact,
guestbook,
};

View File

@ -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;
--dotum-11-font: var(--dotumche-11), var(--dotum-11), 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 {
@ -125,21 +97,3 @@ button, .button {
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);
}
}
}

View 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
View File

@ -1,3 +0,0 @@
interface Window {
Alpine: import("alpinejs").Alpine;
}

View File

@ -1,5 +1,6 @@
---
import "$/styles/base.css";
import "$/styles/themes.css";
import { Font } from "astro:assets";
interface Props { title?: string; }
@ -11,7 +12,6 @@ const { title = "haetae" }: Props = Astro.props;
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<meta name="pinterest" content="nopin nohover" />
<meta name="robots" content="noai, noimageai" />
<Font cssVariable="--sq" preload />

View File

@ -3,62 +3,71 @@ export const prerender = false;
import { actions } from "astro:actions";
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 (!pwd) {
const fromUrl = Astro.url.pathname + Astro.url.search;
return Astro.redirect(`/guestbook/login?redirect=${fromUrl}`);
if (!import.meta.env.DEV) {
console.error("you shouldn't be here...");
return Astro.redirect("/guestbook");
}
const entries = await db.select().from(Guestbook).orderBy(desc(Guestbook.published));
---
<h1>entries</h1>
<Layout title="guestbook admin">
<h1>entries</h1>
<section>
<table>
<thead>
<tr>
<th>username</th>
<th>website</th>
<th>message</th>
<th>published</th>
<th>reply</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>
<section>
<table>
<thead>
<tr>
<th>username</th>
<th>website</th>
<th>message</th>
<th>published</th>
<th>reply</th>
<th>edit</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody>
{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">
<form id="edit-entry-form" action={actions.guestbook.reply} method="post">
<input type="hidden" name="id" value="" />
<p id="entry-username"></p>
<p id="entry-website"></p>
<div id="entry-message"></div>
<p id="entry-published"></p>
<label for="reply">Reply</label>
<textarea name="reply" id="reply"></textarea>
<dialog id="edit-entry">
<form id="edit-entry-form" action={actions.guestbook.reply} method="post">
<input type="hidden" name="id" id="entryId" value="" />
<p id="entry-username"></p>
<p id="entry-website"></p>
<div id="entry-message"></div>
<p id="entry-published"></p>
<label for="reply">Reply</label>
<textarea name="reply" id="reply"></textarea>
<button type="submit">Reply</button>
</form>
</dialog>
</section>
<button type="submit">Reply</button>
</form>
</dialog>
</section>
</Layout>
<form action={actions.guestbook.logout} method="post">
<button type="submit">logout</button>
</form>
<style>
table {
border-collapse: collapse;
td {
max-width: 30ch;
text-overflow: ellipsis;
}
}
</style>
<script>
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 fields = document.querySelectorAll("[id^='entry-']");
(document.getElementById("id") as HTMLInputElement).value = id;
(document.getElementById("entryId") as HTMLInputElement).value = id;
fields.forEach((field, i) => {
field.innerHTML = data.item(i)!.innerText;
});

View File

@ -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");
}
}
---