diff --git a/package.json b/package.json
index 30d3e6e..fb326f9 100644
--- a/package.json
+++ b/package.json
@@ -3,8 +3,8 @@
"module": "src/main.ts",
"type": "module",
"scripts": {
- "dev": "bun run --hot src/main.ts",
- "start": "bun run src/main.ts",
+ "dev": "bun run --hot src/main.tsx",
+ "start": "bun run src/main.tsx",
"db:push": "bunx drizzle-kit push:sqlite",
"db:studio": "bunx drizzle-kit studio",
"db:seed": "bun run src/model/store/seed.ts"
diff --git a/src/context/index.ts b/src/context/index.ts
index 8e2ab59..216dfb6 100644
--- a/src/context/index.ts
+++ b/src/context/index.ts
@@ -19,7 +19,14 @@ export const ctx = new Elysia({
})
)
.decorate("db", db)
- .decorate("config", config);
+ .decorate("config", config)
+ .decorate(
+ "html",
+ (html: string) =>
+ new Response(html, {
+ headers: { "Content-Type": "text/html; charset=utf8" },
+ })
+ );
// .onStart(({ log }) => log.info("Server starting"))
// .onStop(({ log }) => log.info("Server stopping"))
// .onRequest(({ log, request }) => {
diff --git a/src/controllers/todos.tsx b/src/controllers/todos.tsx
index 189eeb6..ed0f9ac 100644
--- a/src/controllers/todos.tsx
+++ b/src/controllers/todos.tsx
@@ -1,9 +1,10 @@
-import Elysia from "elysia";
+import Elysia, { t } from "elysia";
import { ctx } from "../context";
-import { insertTodoSchema } from "../model/todo";
-import { TodoItem } from "../views/todoItem";
+import { insertTodoSchema, todos } from "../model/todo";
+import { TodoItem, TodoForm, TodoList } from "../views/todos";
import Html from "@kitajs/html";
-import { db as works } from "../model/store";
+import { db } from "../model/store";
+import { eq } from "drizzle-orm";
export const todosController = new Elysia({
name: "@app/todos",
@@ -13,16 +14,80 @@ export const todosController = new Elysia({
.model({
todo: insertTodoSchema,
})
- .get("/", async ({ log, db }) => {
- console.log("db", db === works);
- log.info("get todos");
- const todos = await works.query.todos.findMany();
-
- return (
-
- {todos.map((todo) => (
-
- ))}
-
- );
- });
+ .get("/", async () => {
+ const data = await db.select().from(todos).limit(10);
+ return ;
+ })
+ .post(
+ "/toggle/:id",
+ async ({ params }) => {
+ const [oldTodo] = await db
+ .select()
+ .from(todos)
+ .where(eq(todos.id, params.id));
+
+ if (!oldTodo) {
+ throw new Error("Todo not found");
+ }
+
+ const [newTodo] = await db
+ .update(todos)
+ .set({ completed: !oldTodo.completed })
+ .where(eq(todos.id, params.id))
+ .returning();
+
+ if (!newTodo) {
+ throw new Error("Todo not found");
+ }
+
+ return ;
+ },
+ {
+ params: t.Object({
+ id: t.Numeric(),
+ }),
+ }
+ )
+ .delete(
+ "/:id",
+ async ({ params }) => {
+ await db.delete(todos).where(eq(todos.id, params.id));
+ },
+ {
+ params: t.Object({
+ id: t.Numeric(),
+ }),
+ }
+ )
+ .post(
+ "",
+ async ({ body }) => {
+ const content = {
+ beth: "Learn the BETH stack",
+ vim: "Learn vim",
+ like: "Like the video",
+ sub: "Subscribe to Ethan",
+ };
+
+ const [newTodo] = await db
+ .insert(todos)
+ .values({ content: content[body.content] })
+ .returning();
+
+ if (!newTodo) {
+ throw new Error("Todo not found");
+ }
+
+ return ;
+ },
+ {
+ body: t.Object({
+ content: t.Union([
+ t.Literal("beth"),
+ t.Literal("vim"),
+ t.Literal("like"),
+ t.Literal("sub"),
+ ]),
+ }),
+ }
+ );
diff --git a/src/htmx.d.ts b/src/htmx.d.ts
index 8668f8b..52105a5 100644
--- a/src/htmx.d.ts
+++ b/src/htmx.d.ts
@@ -1,15 +1,30 @@
type RoutesByType<
Schema extends Record,
Type extends "get" | "post" | "put" | "delete" | "patch"
-> = RemoveSlash<
- keyof {
- [key in keyof Schema as Schema[key] extends { [key in Type]: unknown }
- ? key
- : never]: true;
- }
+> = RouterPattern<
+ RemoveSlash<
+ keyof {
+ [key in keyof Schema as Schema[key] extends { [key in Type]: unknown }
+ ? key
+ : never]: true;
+ }
+ >
>;
-type RemoveSlash = S extends `${infer T}/` ? T : S;
+type RemoveSlash = S extends `${infer T}/`
+ ? T extends ""
+ ? S
+ : T
+ : S;
+
+type RouterPattern =
+ T extends `${infer Start}:${infer Param}/${infer Rest}`
+ ? `${Start}${string}/${RouterPattern}`
+ : T extends `${infer Start}:${infer Param}`
+ ? `${Start}${string}`
+ : T extends `${infer Start}*`
+ ? `${Start}${string}`
+ : T;
declare namespace JSX {
type Schema = import("./main").App["meta"]["schema"];
diff --git a/src/main.ts b/src/main.ts
deleted file mode 100644
index c9b11a2..0000000
--- a/src/main.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Elysia } from "elysia";
-import { swagger } from "@elysiajs/swagger";
-import { staticPlugin } from "@elysiajs/static";
-import { todosController } from "./controllers/todos";
-
-const app = new Elysia({
- name: "@app/main",
-})
- // .use(swagger())
- .use(staticPlugin())
- .use(todosController)
- .listen(3000);
-
-export type App = typeof app;
-
-console.log(`app is listening on ${app.server?.hostname}:${app.server?.port}`);
-
-app
- .handle(new Request("http://localhost:3000/todos"))
- .then((res) => res.text())
- .then(console.log);
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..308c88e
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,32 @@
+import { Elysia } from "elysia";
+import { swagger } from "@elysiajs/swagger";
+import { staticPlugin } from "@elysiajs/static";
+import { todosController } from "./controllers/todos";
+import { BaseHtml } from "./views/base";
+import Html from "@kitajs/html";
+
+const app = new Elysia({
+ name: "@app/main",
+})
+ // .use(swagger())
+ // .use(staticPlugin())
+ .use(todosController)
+ .get("/", ({ html }) =>
+ html(
+
+
+
+ )
+ )
+ .listen(3000);
+
+export type App = typeof app;
+
+console.log(
+ `app is listening on http://${app.server?.hostname}:${app.server?.port}`
+);
diff --git a/src/views/base.tsx b/src/views/base.tsx
new file mode 100644
index 0000000..6c1549f
--- /dev/null
+++ b/src/views/base.tsx
@@ -0,0 +1,18 @@
+import Html from "@kitajs/html";
+
+export const BaseHtml = ({ children }: Html.PropsWithChildren) => `
+
+
+
+
+
+
+ THE BETH STACK
+
+
+
+
+
+
+${children}
+`;
diff --git a/src/views/todoItem.tsx b/src/views/todoItem.tsx
deleted file mode 100644
index 344b3d8..0000000
--- a/src/views/todoItem.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import type { Todo } from "../model/todo";
-import Html from "@kitajs/html";
-
-export const TodoItem = (todo: Todo) => {
- return (
-
-
- {todo.content}
-
- );
-};
diff --git a/src/views/todos.tsx b/src/views/todos.tsx
new file mode 100644
index 0000000..ec83cba
--- /dev/null
+++ b/src/views/todos.tsx
@@ -0,0 +1,58 @@
+import type { Todo } from "../model/todo";
+import Html from "@kitajs/html";
+
+export function TodoItem({ content, completed, id }: Todo) {
+ return (
+
+ );
+}
+
+export function TodoList({ todos }: { todos: Todo[] }) {
+ return (
+
+ {todos.map((todo) => (
+
+ ))}
+
+
+ );
+}
+
+export function TodoForm() {
+ return (
+
+ );
+}
diff --git a/tsconfig.json b/tsconfig.json
index 57de93e..fd1fe61 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -9,6 +9,7 @@
"noEmit": true,
"composite": true,
"strict": true,
+ "noUncheckedIndexedAccess": true,
"downlevelIteration": true,
"skipLibCheck": true,
"jsx": "react",