diff --git a/.gitignore b/.gitignore index ba59dd2..de2362c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ _fresh/ # npm dependencies node_modules/ + +*.db +*.db-wal +*.db-shm diff --git a/components/Button.tsx b/components/Button.tsx index 9ee4a48..7b1df7e 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -1,12 +1,14 @@ import { JSX } from 'preact' import { IS_BROWSER } from '$fresh/runtime.ts' -export function Button(props: JSX.HTMLAttributes) { +export function Button( + { disabled, className, ...props }: JSX.HTMLAttributes, +) { return ( + ) return ( -
- {avatarUrl != null - ? ( - - ) - : null} +
+ {name} -
    +
      {assignedTodos.length < 1 - ? ( -
    • - All clear! 🎉 -
    • - ) + ? todoItem({ description: 'All clear! 🎉', classList: 'text-center' }) : assignedTodos.map(todoItem)}
diff --git a/crud.ts b/crud.ts new file mode 100644 index 0000000..3022cab --- /dev/null +++ b/crud.ts @@ -0,0 +1,47 @@ +import { Handlers } from '$fresh/server.ts' +import { ulid } from 'https://deno.land/x/ulid@v0.3.0/mod.ts' +import { type AnyZodObject } from 'https://deno.land/x/zod@v3.21.4/mod.ts' +import { db } from '@homeman/models.ts' + +export function crudHandler< + SchemaName extends keyof typeof db, + Schema = db[SchemaName + CreateSchema extends AnyZodObject, + Creator extends (s: CreateSchema) => Promise, +>( + schema: SchemaName, + createModel: CreateSchema, + creator: Creator, +): Handlers { + return { + async POST(req, _ctx) { + const data = createModel.parse(await req.json()) + const newModel: typeof db[SchemaName] = await creator( + data as CreateSchema, + ) + const result = await db[schema].create({ data: newModel }) + return new Response(JSON.stringify(result)) + }, + async PUT(req, _ctx) { + const user = UserModel.parse(await req.json()) + const result = await db.users.update({ data: user }) + return new Response(JSON.stringify(result)) + }, + async DELETE(req, _ctx) { + const userData = UserModel.pick({ id: true }).parse(await req.json()) + const result = await db.users.delete({ where: userData }) + return new Response(JSON.stringify(result)) + }, + async GET(req, _ctx) { + const data = await req.json().catch(() => {}) + const userData = UserModel.pick({ id: true }).safeParse(data) + if (userData.success) { + return new Response( + JSON.stringify(await db.users.findFirst({ where: userData.data })), + ) + } else { + return new Response(JSON.stringify(await db.users.findMany({}))) + } + }, + } +} diff --git a/flake.nix b/flake.nix index e3c62d2..a1a2c15 100644 --- a/flake.nix +++ b/flake.nix @@ -14,6 +14,7 @@ deno-dev = pkgs.mkShell { buildInputs = with pkgs; [ deno + xh ]; }; diff --git a/fresh.gen.ts b/fresh.gen.ts index d2b991f..b66bf8c 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -6,6 +6,8 @@ import * as $_404 from './routes/_404.tsx' import * as $_app from './routes/_app.tsx' import * as $admin from './routes/admin.tsx' import * as $api_joke from './routes/api/joke.ts' +import * as $api_todo from './routes/api/todo.ts' +import * as $api_user from './routes/api/user.ts' import * as $greet_name_ from './routes/greet/[name].tsx' import * as $index from './routes/index.tsx' import * as $Admin from './islands/Admin.tsx' @@ -19,6 +21,8 @@ const manifest = { './routes/_app.tsx': $_app, './routes/admin.tsx': $admin, './routes/api/joke.ts': $api_joke, + './routes/api/todo.ts': $api_todo, + './routes/api/user.ts': $api_user, './routes/greet/[name].tsx': $greet_name_, './routes/index.tsx': $index, }, diff --git a/islands/Admin.tsx b/islands/Admin.tsx index 436a34e..834015b 100644 --- a/islands/Admin.tsx +++ b/islands/Admin.tsx @@ -1,6 +1,8 @@ import { createRef } from 'preact' import { Todo, User } from '@homeman/models.ts' import { Button } from '@homeman/components/Button.tsx' +import { Input } from '@homeman/components/Input.tsx' +import { Label } from '@homeman/components/Label.tsx' export interface Props { users: User[] @@ -15,7 +17,42 @@ export function Admin({ users, todos }: Props) { } return (
- sup + +
+

Add user

+ +
+
+ + + +
+ + +
+
+

Users ({users.length}) @@ -37,9 +74,11 @@ export function Admin({ users, todos }: Props) { {name} - {avatarUrl == null ? 'None' : } + {avatarUrl == null + ? 'None' + : } - + #{color} diff --git a/models.ts b/models.ts index 07e3c70..5a880bb 100644 --- a/models.ts +++ b/models.ts @@ -2,7 +2,7 @@ import { z } from 'https://deno.land/x/zod@v3.21.4/mod.ts' import { createPentagon } from 'https://deno.land/x/pentagon@v0.1.5/mod.ts' // import { ulid } from 'https://deno.land/x/ulid@v0.3.0/mod.ts' -const kv = await Deno.openKv() +const kv = await Deno.openKv('homeman.db') // const todos = kv.list({ prefix: ['todos'] }) // const deleteme = [] @@ -22,6 +22,7 @@ const User = z.object({ avatarUrl: z.string().nullable(), color: z.string().nullable(), }) +export const UserModel = User export type User = z.infer export type UserWithTodos = User & { assignedTodos: Todo[] @@ -37,6 +38,7 @@ const Todo = z.object({ doneAt: z.date().nullable(), assigneeUserId: z.string().ulid().nullable(), }) +export const TodoModel = Todo export type Todo = z.infer export const db = createPentagon(kv, { diff --git a/routes/api/todo.ts b/routes/api/todo.ts new file mode 100644 index 0000000..a9dfda9 --- /dev/null +++ b/routes/api/todo.ts @@ -0,0 +1,35 @@ +import { Handlers } from '$fresh/server.ts' +import { db, Todo, TodoModel } from '@homeman/models.ts' +import { ulid } from 'https://deno.land/x/ulid@v0.3.0/mod.ts' + +const TodoCreate = TodoModel.omit({ id: true, createdAt: true }) + +export const handler: Handlers = { + async POST(req, _ctx) { + const todo = TodoCreate.parse(await req.json()) + const newTodo: Todo = { ...todo, id: ulid(), createdAt: new Date() } + const result = await db.todos.create({ data: newTodo }) + return new Response(JSON.stringify(result)) + }, + async PUT(req, _ctx) { + const todo = TodoModel.parse(await req.json()) + const result = await db.todos.update({ data: todo }) + return new Response(JSON.stringify(result)) + }, + async DELETE(req, _ctx) { + const todoData = TodoModel.pick({ id: true }).parse(await req.json()) + const result = await db.todos.delete({ where: todoData }) + return new Response(JSON.stringify(result)) + }, + async GET(req, _ctx) { + const data = await req.json().catch(() => {}) + const todoData = TodoModel.pick({ id: true }).safeParse(data) + if (todoData.success) { + return new Response( + JSON.stringify(await db.todos.findFirst({ where: todoData.data })), + ) + } else { + return new Response(JSON.stringify(await db.todos.findMany({}))) + } + }, +} diff --git a/routes/api/user.ts b/routes/api/user.ts new file mode 100644 index 0000000..0deddef --- /dev/null +++ b/routes/api/user.ts @@ -0,0 +1,37 @@ +import { Handlers } from '$fresh/server.ts' +import { db, User, UserModel } from '@homeman/models.ts' +import { ulid } from 'https://deno.land/x/ulid@v0.3.0/mod.ts' + +const UserCreate = UserModel.omit({ id: true, createdAt: true }) + +export const handler: Handlers = { + async POST(req, _ctx) { + const formData = await req.formData() + console.log('user form data POSTed', formData) + const user = UserCreate.parse(formData) + const newUser: User = { ...user, id: ulid(), createdAt: new Date() } + const result = await db.users.create({ data: newUser }) + return new Response(JSON.stringify(result)) + }, + async PUT(req, _ctx) { + const user = UserModel.parse(await req.json()) + const result = await db.users.update({ data: user }) + return new Response(JSON.stringify(result)) + }, + async DELETE(req, _ctx) { + const userData = UserModel.pick({ id: true }).parse(await req.json()) + const result = await db.users.delete({ where: userData }) + return new Response(JSON.stringify(result)) + }, + async GET(req, _ctx) { + const data = await req.json().catch(() => {}) + const userData = UserModel.pick({ id: true }).safeParse(data) + if (userData.success) { + return new Response( + JSON.stringify(await db.users.findFirst({ where: userData.data })), + ) + } else { + return new Response(JSON.stringify(await db.users.findMany({}))) + } + }, +} diff --git a/routes/index.tsx b/routes/index.tsx index 9124628..452c851 100644 --- a/routes/index.tsx +++ b/routes/index.tsx @@ -37,7 +37,7 @@ export default function Home(

Todos

-
    +
    • diff --git a/static/styles.css b/static/styles.css index 68cb033..730c0f4 100644 --- a/static/styles.css +++ b/static/styles.css @@ -2,5 +2,9 @@ @tailwind components; @tailwind utilities; +dialog { + @apply bg-stone-100 text-stone-950 dark:bg-stone-950 dark:text-stone-100; +} + body { }