Compare commits

...

3 Commits

Author SHA1 Message Date
3d30f55e72 switch back from drizzle to astro db for Now 2025-12-01 12:45:33 -05:00
bd2c535cee minor fixes 2025-11-25 17:26:36 -05:00
ef688a21b7 fix schemas and update deps 2025-11-25 13:52:04 -05:00
21 changed files with 986 additions and 1107 deletions

View File

@ -1,27 +1,27 @@
// @ts-check
import { defineConfig, envField } from 'astro/config';
import { modifiedTime } from './src/utils/lastModified.mjs';
import db from "@astrojs/db";
import mdx from "@astrojs/mdx";
import node from "@astrojs/node";
import devOnlyRoutes from '@fujocoded/astro-dev-only';
// https://astro.build/config
export default defineConfig({
site: "https://haetae.32-b.it",
site: "http://haetae.32-b.it",
markdown: {
remarkPlugins: [modifiedTime],
smartypants: false,
// smartypants: false,
},
integrations: [
mdx(),
db(),
devOnlyRoutes({
// dryRun: true,
routePatterns: ["/guestbook/admin"]
}),
],
adapter: node({
mode: "standalone",
}),
adapter: node({ mode: "standalone" }),
env: {
schema: {
ASTRO_DB_REMOTE_URL: envField.string({ context: "server", access: "secret" }),

19
db/config.ts Normal file
View File

@ -0,0 +1,19 @@
import { column, defineDb, defineTable, NOW } from 'astro:db';
const Guestbook = defineTable({
columns: {
id: column.number({ primaryKey: true }),
username: column.text(),
website: column.text({ optional: true }),
message: column.text(),
published: column.date({ default: NOW }),
updated: column.date({ optional: true }),
reply: column.text({ optional: true }),
}
});
export default defineDb({
tables: {
Guestbook,
},
});

View File

@ -1,4 +1,4 @@
import { sql } from "drizzle-orm";
import { sql, type InferSelectModel } from "drizzle-orm";
import { int, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const guestbookTable = sqliteTable("guestbook_table", {
@ -10,3 +10,5 @@ export const guestbookTable = sqliteTable("guestbook_table", {
updated: text().$onUpdate(() => sql`(CURRENT_TIMESTAMP)`),
reply: text(),
});
export type GuestbookEntry = typeof guestbookTable.$inferSelect;

View File

@ -1,9 +0,0 @@
import { db } from "db";
import { guestbookTable } from "./schema";
export default async function seed() {
await db.insert(guestbookTable).values([
{ id: 1, username: "test user", message: "this is a message!" },
{ id: 2, username: "heylo", website: "https://world.org", message: "hiii!!" },
]);
}

View File

@ -1,13 +0,0 @@
import { loadEnv } from "vite";
import { defineConfig } from 'drizzle-kit';
const { ASTRO_DB_REMOTE_URL } = loadEnv(process.env.NODE_ENV!, process.cwd(), "");
export default defineConfig({
out: "./db/migrations",
schema: "./db/schema.ts",
dialect: "turso",
dbCredentials: {
url: ASTRO_DB_REMOTE_URL,
},
});

Binary file not shown.

View File

@ -9,20 +9,18 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^4.3.6",
"@astrojs/node": "9.4.4",
"@astrojs/rss": "4.0.12",
"@astrojs/db": "^0.18.3",
"@astrojs/mdx": "^4.3.12",
"@astrojs/node": "9.5.1",
"@astrojs/rss": "4.0.14",
"@fujocoded/astro-dev-only": "0.0.4",
"@libsql/client": "^0.15.15",
"astro": "5.13.10",
"astro": "5.16.0",
"astro-breadcrumbs": "^3.3.1",
"dayjs": "^1.11.18",
"drizzle-orm": "^0.44.5",
"isomorphic-dompurify": "^2.28.0",
"marked": "^16.3.0"
"dayjs": "^1.11.19",
"isomorphic-dompurify": "^2.33.0",
"marked": "^16.4.2"
},
"devDependencies": {
"@types/node": "^22.18.6",
"drizzle-kit": "^0.31.4"
"@types/node": "^22.19.1"
}
}

1808
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
nodeLinker: hoisted
onlyBuiltDependencies:
- esbuild
- sharp
nodeLinker: hoisted
overrides:
esbuild@<=0.24.2: '>=0.25.0'

View File

@ -1,8 +1,8 @@
export default {
host: {
host: "137.184.37.162",
host: "15.235.122.159",
username: "haetae",
remoteProjectPath: "/var/www/fujohost/haetae/",
remoteProjectPath: "/var/www/",
remoteDbPath: "/var/www/fujohost/haetae/guestbook.db",
dbDriver: "astro:db",
privateKeyPath: "/home/ayowaddup/.ssh/shippy.id_ed25519"

View File

@ -1,5 +1,5 @@
import { defineAction } from "astro:actions";
import { z } from "astro:content";
import { z } from "astro:schema";
import DOMPurify from "isomorphic-dompurify";
export const contact = {

View File

@ -1,8 +1,7 @@
import { ActionError, defineAction } from "astro:actions";
import { z } from "astro:content";
import { db } from "db";
import { guestbookTable } from "db/schema";
import { eq } from "drizzle-orm";
import { z } from "astro:schema";
import { db, eq, Guestbook } from "astro:db";
// import { guestbookTable } from "db/schema";
import DOMPurify from "isomorphic-dompurify";
export const guestbook = {
@ -26,7 +25,7 @@ export const guestbook = {
const sanitized = DOMPurify.sanitize(addLine);
try {
const entry = await db.insert(guestbookTable).values({
const entry = await db.insert(Guestbook).values({
username,
website,
message: sanitized,
@ -50,7 +49,7 @@ export const guestbook = {
throw new ActionError({ code: "UNAUTHORIZED" });
}
const entry = await db.select().from(guestbookTable).where(eq(guestbookTable.id, id));
const entry = await db.select().from(Guestbook).where(eq(Guestbook.id, id));
if (!entry) {
throw new ActionError({
code: "NOT_FOUND",
@ -62,10 +61,10 @@ export const guestbook = {
const sanitized = DOMPurify.sanitize(addLine);
try {
const update = await db.update(guestbookTable).set({
const update = await db.update(Guestbook).set({
reply: sanitized,
updated: new Date().toDateString(),
}).where(eq(guestbookTable.id, id)).returning();
updated: new Date(),
}).where(eq(Guestbook.id, id)).returning();
return update[0];
} catch (e) {
@ -83,7 +82,7 @@ export const guestbook = {
throw new ActionError({ code: "UNAUTHORIZED" });
}
const entry = await db.select().from(guestbookTable).where(eq(guestbookTable.id, id));
const entry = await db.select().from(Guestbook).where(eq(Guestbook.id, id));
if (!entry) {
throw new ActionError({
code: "NOT_FOUND",
@ -92,7 +91,7 @@ export const guestbook = {
}
try {
const entry = await db.delete(guestbookTable).where(eq(guestbookTable.id, id)).returning();
const entry = await db.delete(Guestbook).where(eq(Guestbook.id, id)).returning();
return entry[0];
} catch (e) {

View File

@ -1,9 +1,13 @@
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html, body {
height: 100vh;
body {
width: 100vw;
min-height: 100vh;
@supports (min-height: 100svh) {
min-height: 100svh;
}
}
body { line-height: calc(1em + 0.5rem); }
@ -15,5 +19,5 @@ img, picture, video, canvas, svg {
input, button, textarea, select { font: inherit; }
p, h1, h2, h3, h4, h5, h6 { overflow-wrap: break-word; }
p { text-wrap: pretty; }
h1, h2, h3, h4, h5, h6 { text-wrap: balance; }
p { text-wrap: pretty; }

View File

@ -1,11 +1,14 @@
---
import { desc } from "drizzle-orm";
import { db } from "db";
import { guestbookTable } from "db/schema";
import type { GuestbookEntry } from "db/schema";
import formatDate from "@/utils/formatDate";
import speech from "$/speech.png";
import pikachu from "$/images/portrait-0025.png";
const entries = await db.select().from(guestbookTable).orderBy(desc(guestbookTable.published));
interface Props {
entries: GuestbookEntry[];
}
const { entries } = Astro.props;
---
<section id="entries">
{entries.map(entry => (
@ -29,7 +32,8 @@ const entries = await db.select().from(guestbookTable).orderBy(desc(guestbookTab
</div>
</article>
{entry.reply && <article class="reply" id={`reply-${entry.username}-${entry.id}`}>
{entry.reply && (
<article class="reply" id={`reply-${entry.username}-${entry.id}`}>
<img src={pikachu.src} width="80" height="80" alt="a portrait of pikachu" />
<div class="entry">
<header>
@ -43,7 +47,8 @@ const entries = await db.select().from(guestbookTable).orderBy(desc(guestbookTab
<Fragment set:html={entry.reply} />
</div>
</div>
</article>}
</article>
)}
</>
))}
@ -53,9 +58,16 @@ const entries = await db.select().from(guestbookTable).orderBy(desc(guestbookTab
<p>There's nothing here! Want to be the first to comment?</p>
</article>
)}
<slot name="pagination" />
</section>
<style>
<style define:vars={{ border: `url(${speech.src})` }}>
:root {
--speech-bg-color: #f8f8f8;
--speech-fg-color: #404040;
}
#entries {
margin: 2rem 0 3rem;
display: flex;

View File

@ -3,6 +3,7 @@ import { getCollection } from "astro:content";
import UpdateCard from "./UpdateCard.astro";
const updates = await getCollection("updates");
const data = updates.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
---
<section id="updates">
<header>
@ -18,9 +19,12 @@ const updates = await getCollection("updates");
<span class="segment">Date</span>
</div>
</h2>
<!--
<UpdateCard title={data[0].data.title} date={data[0].data.date}>
{data[0].body}
</UpdateCard> -->
<UpdateCard title="redo gallery and guestbook" date="2024-05-10">
{updates.length}
<p>redid <a href="/gallery">gallery page</a> to make adding images easier and added alpinejs to <a href="/guestbook">guestbook</a> for fetching entries live</p>
</UpdateCard>
@ -29,14 +33,14 @@ const updates = await getCollection("updates");
</UpdateCard>
<footer id="updates-pagination">
<a id="previous" aria-label="go to previous post" href="javascript:void(0)">
<button id="previous" aria-label="go to previous post">
<div class="arrow" />
<div class="title">prev</div>
</a>
<a id="next" aria-label="go to next post" href="javascript:void(0)">
</button>
<button id="next" aria-label="go to next post">
<div class="title">next</div>
<div class="arrow" />
</a>
</button>
</footer>
</div>
</div>
@ -146,12 +150,16 @@ const updates = await getCollection("updates");
}
/* BASE STYLING FOR ARROW BUTTONS */
a {
button {
display: flex;
flex-flow: row wrap;
align-items: stretch;
color: var(--normal-color);
text-decoration: none;
background: unset;
box-shadow: none;
border: none;
font-family: ;
.title {
display: grid;
@ -194,3 +202,8 @@ const updates = await getCollection("updates");
}
}
</style>
<!-- <script>
const prev = document.getElementById("previous");
const next = document.getElementById("next");
</script> -->

View File

@ -53,19 +53,20 @@ const links = [
{chapters.length > 1 && (
<nav id="chapter-pagination" slot="pagination">
<div id="chapter-index">
<form id="chapter-index">
<label for="chapter-select">Chapters:</label>
<select name="chapter-select" id="chapter-select">
{chapters.map(chapter => (
<option
value={`/fics/${chapter.id}`}
selected={chapter.id.split("/")[1] === chapterId ? "selected" : undefined}
selected={chapter.id.split("/")[1] === chapterId ? true : undefined}
>
{chapter.data.title}
</option>
))}
</select>
</div>
<button>Go to chapter</button>
</form>
{previous && <a id="previous" href={`/fics/${previous.id}`}>{previous.data.title}</a>}
{next && <a id="next" href={`/fics/${next.id}`}>{next.data.title}</a>}
</nav>
@ -104,7 +105,7 @@ const links = [
#chapter-index {
grid-area: 1 / 1 / 1 / -1;
width: min-content;
/* width: min-content; */
justify-self: center;
}
@ -130,10 +131,10 @@ const links = [
</style>
<script>
const form = document.forms.namedItem("chapter-index");
const select: HTMLSelectElement | null = document.querySelector("#chapter-select");
select?.addEventListener("change", (e) => {
if (e.target instanceof HTMLSelectElement) {
window.location.href = e.target.value;
}
form?.addEventListener("submit", (e) => {
e.preventDefault();
window.location.href = select?.value!;
});
</script>

View File

@ -24,7 +24,7 @@ const { Content } = await render(fic);
const parser = marked.use({ gfm: true, breaks: true });
const summary = await parser.parse(fic.data.summary);
const lastMod = fic.rendered && (fic.rendered.metadata!.frontmatter as any)['lastModified'];
const lastModified = fic.rendered && (fic.rendered.metadata!.frontmatter as any)['lastModified'];
const notes = fic.rendered && await parser.parse((fic.rendered.metadata!.frontmatter as any)["notes"]);
---
<Layout title={fic.data.title}>
@ -78,7 +78,7 @@ const notes = fic.rendered && await parser.parse((fic.rendered.metadata!.frontma
{fic.body && (
<section id="oneshot">
<ChapterContent lastModified={lastMod} notes={notes}>
<ChapterContent lastModified={lastModified} notes={notes}>
<Content />
</ChapterContent>
</section>

View File

@ -0,0 +1,33 @@
---
import type { GetStaticPaths } from "astro";
import { Font } from "astro:assets";
import { desc } from "drizzle-orm";
import { db } from "db";
import { guestbookTable } from "db/schema";
import Layout from "@/layouts/Layout.astro";
import Entries from "~/Entries.astro";
export const getStaticPaths = (async ({ paginate }) => {
const data = await db.select().from(guestbookTable).orderBy(desc(guestbookTable.published));
return paginate(data, { pageSize: 1 });
}) satisfies GetStaticPaths;
const { page } = Astro.props;
---
<Layout>
<Fragment slot="head">
<Font cssVariable="--mono" preload />
<Font cssVariable="--mlss" preload />
</Fragment>
<Entries entries={page.data} server:defer>
<nav slot="pagination">
{page.currentPage}
{page.url.first ? <a href={page.url.first}>First</a> : null}
{page.url.prev ? <a href={page.url.prev}>Previous</a> : null}
{page.url.next ? <a href={page.url.next}>Next</a> : null}
{page.url.last ? <a href={page.url.last}>Last</a> : null}
</nav>
</Entries>
</Layout>

View File

@ -1,13 +1,17 @@
---
import { actions, isInputError } from "astro:actions";
import { Font } from "astro:assets";
import { desc } from "drizzle-orm";
import { db } from "db";
import { guestbookTable } from "db/schema";
import Layout from "@/layouts/Layout.astro";
import speech from "$/speech.png";
import Entries from "~/Entries.astro";
import Dialog from "~/Dialog.astro";
const result = Astro.getActionResult(actions.guestbook.addEntry);
const entries = await db.select().from(guestbookTable).orderBy(desc(guestbookTable.published));
entries.slice(0, 10);
const inputErrors = isInputError(result?.error) ? result.error.fields : {};
---
<Layout title="haetae, guestbook">
@ -42,7 +46,7 @@ const inputErrors = isInputError(result?.error) ? result.error.fields : {};
</form>
</section>
<Entries server:defer>
<Entries entries={entries} server:defer>
<p slot="fallback">Loading...</p>
</Entries>
@ -52,12 +56,7 @@ const inputErrors = isInputError(result?.error) ? result.error.fields : {};
</main>
</Layout>
<style define:vars={{ border: `url(${speech.src})` }}>
:root {
--speech-bg-color: #f8f8f8;
--speech-fg-color: #404040;
}
<style>
main {
max-width: clamp(75ch, 80ch, 100%);
margin: 1rem auto;

View File

@ -1,15 +1,9 @@
import { statSync } from "fs";
import { execSync } from "child_process";
export function modifiedTime() {
return function (_tree, file) {
const path = file.history[0];
const result = statSync(path);
file.data.astro.frontmatter.lastModified = result.mtime;
// try {
// const result = statSync(path);
// file.data.astro.frontmatter.lastModified = result.mtime;
// } catch (error) {
// return;
// }
return function (_tree, { data, history }) {
const path = history[0];
const result = execSync(`git log -1 --pretty="format:%cI" "${path}"`);
data.astro.frontmatter.lastModified = result.toString();
}
}