diff --git a/.astro/types.d.ts b/.astro/types.d.ts index 4c922f7..4edf25a 100644 --- a/.astro/types.d.ts +++ b/.astro/types.d.ts @@ -623,5 +623,5 @@ declare module 'astro:content' { type AnyEntryMap = ContentEntryMap & DataEntryMap; - type ContentConfig = typeof import("../src/content/config"); + export type ContentConfig = typeof import("../src/content/config.js"); } diff --git a/astro.config.mjs b/astro.config.mjs index 5e7112d..2bedf3a 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -13,40 +13,65 @@ import metaTags from "astro-meta-tags"; // https://astro.build/config export default defineConfig({ // ... - integrations: [expressiveCode({ - theme: ["dracula-soft"] - }), mdx(), react(), metaTags()], + integrations: [ + expressiveCode({ + theme: ["dracula-soft"], + }), + mdx(), + react(), + metaTags(), + ], markdown: { remarkPlugins: [], - rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, { - behavior: "wrap" - }], [rehypeToc, { - customizeTOC: toc => { - if (toc.children[0].children?.length > 0) { - return toc; - } - return false; - }, - customizeTOCItem: (toc, heading) => { - const headingContent = heading.children?.[0]; - if (headingContent.children.length > 1) { - toc.children[0].children = headingContent.children; - } - return toc; - } - }], [rehypeCustomEmoji, { - emojis: { - bobaparty: "/emojis/bobaparty.png", - bobatwt: "/emojis/bobatwt.png", - bobaeyes: "/emojis/bobaeyes.png" - }, - className: "custom-emoji" - }], rehypeAddAltText] + rehypePlugins: [ + rehypeSlug, + [ + rehypeAutolinkHeadings, + { + behavior: "wrap", + }, + ], + [ + rehypeToc, + { + customizeTOC: (toc) => { + if (toc.children[0].children?.length > 0) { + return toc; + } + return false; + }, + customizeTOCItem: (toc, heading) => { + const headingContent = heading.children?.[0]; + if (headingContent.children.length > 1) { + toc.children[0].children = headingContent.children; + } + return toc; + }, + }, + ], + [ + rehypeCustomEmoji, + { + emojis: { + bobaparty: "/emojis/bobaparty.png", + bobatwt: "/emojis/bobatwt.png", + bobaeyes: "/emojis/bobaeyes.png", + }, + className: "custom-emoji", + }, + ], + rehypeAddAltText, + ], }, redirects: { - "/subscribe": "/support-me" + "/subscribe": "/support-me", }, - output: "hybrid", + output: "server", adapter: vercel(), - site: "https://www.essentialrandomness.com" -}); \ No newline at end of file + site: "https://www.essentialrandomness.com", + vite: { + optimizeDeps: { + exclude: ["oslo"], + }, + }, +}); diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..5b3e6b2 --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "drizzle-kit"; +export default defineConfig({ + schema: "./src/db/schema.ts", + out: "./src/db/drizzle", + driver: "turso", + dbCredentials: { + url: process.env.TURSO_DB_LOGIN!, + authToken: process.env.TURSO_DB_AUTH, + }, +}); diff --git a/package.json b/package.json index 97b4742..9648c7a 100644 --- a/package.json +++ b/package.json @@ -13,16 +13,21 @@ "@astrojs/mdx": "2.0.6", "@astrojs/react": "3.0.9", "@astrojs/rss": "^4.0.3", - "@astrojs/vercel": "^7.0.1", + "@astrojs/vercel": "^7.1.1", + "@libsql/client": "^0.4.3", + "@lucia-auth/adapter-drizzle": "^1.0.0", "@paypal/paypal-js": "^7.0.0", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", - "astro": "4.2.2", + "astro": "^4.3.2", "astro-expressive-code": "^0.32.2", "astro-icon": "^0.8.1", "astro-meta-tags": "^0.2.1", "clsx": "^2.0.0", + "drizzle-orm": "^0.29.3", + "lucia": "^3.0.1", "marked": "^7.0.4", + "oslo": "^1.0.4", "react": "^18.0.0", "react-dom": "^18.0.0", "react-icons": "npm:@lorenzobloedow/react-icons", @@ -37,6 +42,7 @@ "unist-util-select": "^5.1.0" }, "devDependencies": { + "drizzle-kit": "^0.20.14", "hast-util-from-html-isomorphic": "^2.0.0", "postcss-nesting": "^12.0.1", "prettier": "^3.0.1", diff --git a/src/auth/createUser.ts b/src/auth/createUser.ts new file mode 100644 index 0000000..f555b49 --- /dev/null +++ b/src/auth/createUser.ts @@ -0,0 +1,15 @@ +// pages/api/signup.ts +import { generateId } from "lucia"; +import { Argon2id } from "oslo/password"; +import { db } from "../db"; +import { users } from "../db/schema"; + +const userId = generateId(15); +const hashedPassword = await new Argon2id().hash("blorbos"); + +export const createUser = async () => + await db.insert(users).values({ + id: userId, + username: "bobatan", + hashed_password: hashedPassword, + }); diff --git a/src/auth/index.ts b/src/auth/index.ts new file mode 100644 index 0000000..aa84458 --- /dev/null +++ b/src/auth/index.ts @@ -0,0 +1,30 @@ +import { DrizzleSQLiteAdapter } from "@lucia-auth/adapter-drizzle"; +import { db } from "../db"; +import { sessions, users } from "../db/schema"; +import { Lucia } from "lucia"; + +declare module "lucia" { + interface Register { + Lucia: typeof Lucia; + DatabaseUserAttributes: DatabaseUserAttributes; + } +} + +interface DatabaseUserAttributes { + username: string; +} + +const adapter = new DrizzleSQLiteAdapter(db, sessions, users); +export const auth = new Lucia(adapter, { + sessionCookie: { + attributes: { + secure: import.meta.env.PROD, + }, + }, + getUserAttributes: (attributes) => { + return { + // attributes has the type of DatabaseUserAttributes + username: attributes.username, + }; + }, +}); diff --git a/src/db/index.ts b/src/db/index.ts new file mode 100644 index 0000000..9ec76aa --- /dev/null +++ b/src/db/index.ts @@ -0,0 +1,9 @@ +import { drizzle } from "drizzle-orm/libsql"; +import { createClient } from "@libsql/client"; + +const client = createClient({ + url: import.meta.env.TURSO_DB_LOGIN!, + authToken: import.meta.env.TURSO_DB_AUTH, +}); + +export const db = drizzle(client); diff --git a/src/db/schema.ts b/src/db/schema.ts new file mode 100644 index 0000000..630a96f --- /dev/null +++ b/src/db/schema.ts @@ -0,0 +1,15 @@ +import { text, integer, sqliteTable, int } from "drizzle-orm/sqlite-core"; + +export const users = sqliteTable("users", { + id: text("id").primaryKey().notNull(), + username: text("username").notNull().unique(), + hashed_password: text("hashed_password"), +}); + +export const sessions = sqliteTable("sessions", { + id: text("id").notNull().primaryKey(), + userId: text("user_id") + .notNull() + .references(() => users.id), + expiresAt: integer("expires_at").notNull(), +}); diff --git a/src/env.d.ts b/src/env.d.ts index acef35f..df02170 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -1,2 +1,9 @@ /// /// + +declare namespace App { + interface Locals { + session: import("lucia").Session | null; + user: import("lucia").User | null; + } +} diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 48027c5..2b69339 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -13,6 +13,8 @@ const { description = "Ms Boba's experimental website. Open for business while construction is ongoing.", preview = "/og-image.png", } = Astro.props; + +const user = Astro.locals.user; --- @@ -54,7 +56,20 @@ const { src="https://plausible.io/js/script.js"> -