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 ( +
    +

    {content}

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