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" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,32 +45,9 @@ export const guestbook = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sanitize reply here
|
// sanitize reply here
|
||||||
|
|
||||||
const update = await db.update(Guestbook).set({ reply }).where(eq(Guestbook.id, id)).returning();
|
const update = await db.update(Guestbook).set({ reply }).where(eq(Guestbook.id, id)).returning();
|
||||||
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 {
|
||||||
@ -124,22 +96,4 @@ button, .button {
|
|||||||
border-color: var(--bg-color);
|
border-color: var(--bg-color);
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
---
|
|
@ -30,7 +30,7 @@ export function ficsLoader(loader: Loader) {
|
|||||||
...valueWithoutDigest.data,
|
...valueWithoutDigest.data,
|
||||||
...chapters.length > 1 && { chapters: chapters },
|
...chapters.length > 1 && { chapters: chapters },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (chapters.length === 1) {
|
if (chapters.length === 1) {
|
||||||
// i've committed unspeakable atrocities here
|
// i've committed unspeakable atrocities here
|
||||||
const search = import.meta.glob<MarkdownInstance<any>>(`../content/fics/**/*.md`, { eager: true });
|
const search = import.meta.glob<MarkdownInstance<any>>(`../content/fics/**/*.md`, { eager: true });
|
||||||
|
Loading…
x
Reference in New Issue
Block a user