diff --git a/.gitignore b/.gitignore index 38e2f67..7721393 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ node_modules/ *.db-shm /static/uploads + +/data diff --git a/db.ts b/db.ts index 2b02ada..58b2f3b 100644 --- a/db.ts +++ b/db.ts @@ -2,7 +2,7 @@ import { createPentagon, TableDefinition, } from 'https://deno.land/x/pentagon@v0.1.5/mod.ts' -import { DoneTaskModel, TodoModel, UserModel } from '@homeman/models.ts' +import { TaskModel, TodoModel, UserModel } from '@homeman/models.ts' export const kv = await Deno.openKv('homeman.db') @@ -19,8 +19,8 @@ export const schema: Record = { assignee: ['users', UserModel, 'assigneeUserId', 'id'], }, }, - doneTasks: { - schema: DoneTaskModel, + tasks: { + schema: TaskModel, }, } diff --git a/models.ts b/models.ts index 2a87408..34edf0b 100644 --- a/models.ts +++ b/models.ts @@ -58,16 +58,10 @@ export function toPhase(dt?: Date | null): z.infer { const Task = z.object({ id: z.string(), + description: z.string(), emoji: z.string().nullable(), doneAt: z.date().nullable(), phase: DailyPhase, }) export const TaskModel = Task export type Task = z.infer - -const DoneTask = Task.pick({ - id: true, - doneAt: true, -}) -export const DoneTaskModel = DoneTask -export type DoneTask = z.infer diff --git a/playground.ts b/playground.ts new file mode 100644 index 0000000..0d386a1 --- /dev/null +++ b/playground.ts @@ -0,0 +1,60 @@ +import { DailyPhase, TaskModel } from '@homeman/models.ts' +import { db } from '@homeman/db.ts' +import { ulid } from 'https://deno.land/x/ulid@v0.3.0/mod.ts' + +const hardcoded: Record = { + 'Morning': [ + ['🥣', 'Breakfast'], + ['🪥', 'Brush teeth'], + ['👕', 'Get dressed'], + ['🙏', 'Ask and thank Jesus'], + ['✝️', 'Bible story or devotional'], + ['📚', 'School: with Mrs. Emily or Mama'], + ['🎨', 'Create time: 🧶craft ✏️ draw 🧑🏼‍🎨 paint'], + ['🏗️', ' Build time: 🧱legos 🚂train tracks 🏎magna tiles'], + ['👯', 'Friend time: playdate or neighbor time'], + ['🚗', 'Outing: 📚library 🌳park 🥑groceries ☕ coffee shop'], + ], + 'Midday': [ + ['🥓', 'Lunch'], + ['🧹', 'Tidy time'], + ['🤫', 'Quiet time'], + ['🏃', 'BIG energy time: 🚲bike 🥁 drums 🤸 silly play'], + ['👯', 'Friend time: playdate or neighbor time'], + ['🚗', 'Outing: 📚library 🌳park 🥑groceries'], + ], + 'Evening': [ + ['🍽️', 'Dinner'], + ['🚗', 'Outing'], + ['👪', 'Family time: 🃏games 🕺dance party 🤸silly play 🏋️workout'], + ], + 'Bedtime': [ + ['🪥', 'Brush teeth'], + ['🛁', 'Take bath'], + ['👕', 'Put on pajamas'], + ['🙏', 'Ask and thank Jesus'], + ['✝️', 'Bible story or devotional'], + ['📕', 'Read a story'], + ['❤', 'Emotions check in: 😭 😡 😂 😟 😣 😀 ☹️ 😰 😁'], + ], + 'Night': [ + ['👨‍⚖️', 'Sleep!'], + ], +} +Object.entries(hardcoded).forEach(async ([tphase, phaseTasks]) => { + const phase = tphase as DailyPhase + for (const [emoji, description] of phaseTasks) { + const task = TaskModel.parse({ + phase, + emoji, + description, + id: ulid(), + doneAt: null, + }) + console.log(task) + const result = await db.tasks.create({ + data: task, + }) + console.log('after create:', result) + } +}) diff --git a/routes/api/tasks.ts b/routes/api/tasks.ts index a21e1fe..69fa4c7 100644 --- a/routes/api/tasks.ts +++ b/routes/api/tasks.ts @@ -1,81 +1,81 @@ import { Handlers } from '$fresh/server.ts' -import { DoneTask, DoneTaskModel } from '@homeman/models.ts' +import { Task, TaskModel } from '@homeman/models.ts' import { db, kv } from '@homeman/db.ts' +import { ulid } from 'https://deno.land/x/ulid@v0.3.0/mod.ts' import { z } from 'https://deno.land/x/zod@v3.21.4/mod.ts' -const DoneTaskPayload = DoneTaskModel.partial({ doneAt: true }) -type DoneTaskPayload = z.infer +const TaskPayload = TaskModel.partial({ id: true }) +type TaskPayload = z.infer -export const handler: Handlers = { +async function createOrUpdate(task: TaskPayload) { + if (!task.id || task.id === '') { + const newTask: Task = { + ...task, + id: ulid(), + } + const result = await db.tasks.create({ data: newTask }) + await kv.set(['last_task_updated'], newTask.id) + return result + } else { + const result = await db.tasks.update({ where: { id: task.id }, data: task }) + await kv.set(['last_task_updated'], task.id) + return result + } +} + +export const handler: Handlers = { async POST(req, _ctx) { - // a task is marked done - let id: string | undefined if (req.headers.get('content-type')?.includes('json')) { - id = DoneTaskPayload.parse(await req.json()).id - } else { - id = new URL(req.url).searchParams.get('id') || undefined - } - console.log('done task post:', id) - if (!id) { - return new Response( - JSON.stringify({ - message: "can't complete task with id empty string", - }), - { status: 400 }, + const result = await createOrUpdate( + TaskPayload.parse(await req.json()), ) + return new Response(JSON.stringify(result)) + } else { + const form = await req.formData() + const id = form.get('id')?.toString() || undefined + + const doneAt = form.get('doneAt') + console.log('task POST doneAt:', doneAt) + + const task = TaskPayload.parse({ + id: id, + emoji: form.get('emoji')?.toString() || null, + doneAt: form.get('doneAt')?.toString() || null, + description: form.get('description')?.toString(), + assigneeUserId: form.get('assigneeUserId')?.toString() || null, + }) + + if (!id) { + delete task.id + } + + if (!task.assigneeUserId) { + delete task.id + } + + await createOrUpdate(task) + + const url = new URL(req.url) + url.pathname = '/' + return Response.redirect(url, 303) } - - const newDoneTask: DoneTask = { - id, - doneAt: new Date(), - } - const res = await db.doneTasks.create({ - data: newDoneTask, - }) - console.log('done task create result:', res) - console.log(await db.doneTasks.findMany({})) - - await kv.set(['last_task_updated'], id) - - return new Response(JSON.stringify({ id })) }, async DELETE(req, _ctx) { - let id: string | undefined + // task: form or query params or json + let data if (req.headers.get('content-type')?.includes('json')) { - id = DoneTaskPayload.parse(await req.json()).id + data = await req.json() } else { - id = new URL(req.url).searchParams.get('id') || undefined + data = { id: new URL(req.url).searchParams.get('id') } } - console.log('done task delete:', id) - if (!id) { - return new Response( - JSON.stringify({ - message: "can't uncomplete task with id empty string", - }), - { status: 400 }, - ) - } - if (!id) { - return new Response( - JSON.stringify({ - message: "can't complete task with id empty string", - }), - { status: 400 }, - ) - } - - await db.doneTasks.deleteMany({ - where: { - id, - }, - }) - - await kv.set(['last_task_updated'], id) - - return new Response(JSON.stringify({ id })) + console.log('delete task data:', data) + const taskData = TaskModel.pick({ id: true }).parse(data) + const result = await db.tasks.delete({ where: taskData }) + await kv.set(['last_task_updated'], taskData.id) + return new Response(JSON.stringify(result)) }, async GET(req, ctx) { - // TODO: json or query params + // task: json or query params const accept = req.headers.get('accept') if (accept === 'text/event-stream') { console.log('Request for task event stream') @@ -98,7 +98,7 @@ export const handler: Handlers = { continue } - const task = await db.doneTask.findFirst({ + const task = await db.tasks.findFirst({ where: { id: entry.value }, }) const chunk = `data: ${ @@ -108,21 +108,21 @@ export const handler: Handlers = { value: task, }) }\n\n` - console.log('todo event chunk:', chunk) + console.log('task event chunk:', chunk) controller.enqueue(new TextEncoder().encode(chunk)) } if (entries.done) { return } } catch (e) { - console.error(`Error refreshing todo:`, e) + console.error(`Error refreshing task:`, e) } } }, cancel() { stream.cancel() console.log( - `Closed todo updates stream to ${JSON.stringify(ctx.remoteAddr)}`, + `Closed task updates stream to ${JSON.stringify(ctx.remoteAddr)}`, ) }, }) @@ -132,8 +132,14 @@ export const handler: Handlers = { }, }) } - return new Response( - JSON.stringify({ done: await db.doneTasks.findMany({}) }), - ) + const data = await req.json().catch(() => {}) + const taskData = TaskModel.pick({ id: true }).safeParse(data) + if (taskData.success) { + return new Response( + JSON.stringify(await db.tasks.findFirst({ where: taskData.data })), + ) + } else { + return new Response(JSON.stringify(await db.tasks.findMany({}))) + } }, } diff --git a/routes/routine.tsx b/routes/routine.tsx index 49ac3ef..be87cc5 100644 --- a/routes/routine.tsx +++ b/routes/routine.tsx @@ -1,72 +1,20 @@ import { Handlers, PageProps } from '$fresh/server.ts' -import { DailyPhase, DoneTask, Task } from '@homeman/models.ts' +import { Task } from '@homeman/models.ts' import { Routine } from '@homeman/islands/Routine.tsx' import { db } from '@homeman/db.ts' // import { db, kv, Todo, UserWithTodos } from '@homeman/models.ts' interface Data { tasks: Task[] - done: DoneTask[] } export const handler: Handlers = { async GET(_req, ctx) { - const tasks: Task[] = Array.from([]) - console.log(tasks) - - const hardcoded: Record = { - 'Morning': [ - ['🥣', 'Breakfast'], - ['🪥', 'Brush teeth'], - ['👕', 'Get dressed'], - ['🙏', 'Ask and thank Jesus'], - ['✝️', 'Bible story or devotional'], - ['📚', 'School: with Mrs. Emily or Mama'], - ['🎨', 'Create time: 🧶craft ✏️ draw 🧑🏼‍🎨 paint'], - ['🏗️', ' Build time: 🧱legos 🚂train tracks 🏎magna tiles'], - ['👯', 'Friend time: playdate or neighbor time'], - ['🚗', 'Outing: 📚library 🌳park 🥑groceries ☕ coffee shop'], - ], - 'Midday': [ - ['🥓', 'Lunch'], - ['🧹', 'Tidy time'], - ['🤫', 'Quiet time'], - ['🏃', 'BIG energy time: 🚲bike 🥁 drums 🤸 silly play'], - ['👯', 'Friend time: playdate or neighbor time'], - ['🚗', 'Outing: 📚library 🌳park 🥑groceries'], - ], - 'Evening': [ - ['🍽️', 'Dinner'], - ['🚗', 'Outing'], - ['👪', 'Family time: 🃏games 🕺dance party 🤸silly play 🏋️workout'], - ], - 'Bedtime': [ - ['🪥', 'Brush teeth'], - ['🛁', 'Take bath'], - ['👕', 'Put on pajamas'], - ['🙏', 'Ask and thank Jesus'], - ['✝️', 'Bible story or devotional'], - ['📕', 'Read a story'], - ['❤', 'Emotions check in: 😭 😡 😂 😟 😣 😀 ☹️ 😰 😁'], - ], - 'Night': [ - ['👨‍⚖️', 'Sleep!'], - ], - } - Object.entries(hardcoded).forEach(([tphase, phaseTasks]) => { - const phase = tphase as DailyPhase - for (const [emoji, id] of phaseTasks) { - tasks.push({ phase, emoji, id, doneAt: null }) - } - }) - - const done = await db.doneTasks.findMany({}) - console.log({ done }) - - return ctx.render({ tasks, done }) + const tasks = await db.tasks.findMany({}) + return ctx.render({ tasks }) }, } export default function Page(props: PageProps) { - return + return }