Merge branch 'tasks-bork' into tasks

This commit is contained in:
Daniel Flanagan 2024-01-21 20:16:05 -06:00
commit 26d05e3cd4
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
6 changed files with 88 additions and 105 deletions

5
db.ts
View file

@ -2,7 +2,7 @@ import {
createPentagon, createPentagon,
TableDefinition, TableDefinition,
} from 'https://deno.land/x/pentagon@v0.1.5/mod.ts' } from 'https://deno.land/x/pentagon@v0.1.5/mod.ts'
import { TaskModel, TodoModel, UserModel } from '@homeman/models.ts' import { TodoModel, UserModel } from '@homeman/models.ts'
export const kv = await Deno.openKv('homeman.db') export const kv = await Deno.openKv('homeman.db')
@ -19,9 +19,6 @@ export const schema: Record<string, TableDefinition> = {
assignee: ['users', UserModel, 'assigneeUserId', 'id'], assignee: ['users', UserModel, 'assigneeUserId', 'id'],
}, },
}, },
tasks: {
schema: TaskModel,
},
} }
export const db = createPentagon(kv, schema) export const db = createPentagon(kv, schema)

View file

@ -1,16 +1,9 @@
import { import { DailyPhase, DailyPhaseModel, Task, toPhase } from '@homeman/models.ts'
DailyPhase,
DailyPhaseModel,
DoneTask,
Task,
toPhase,
} from '@homeman/models.ts'
import { useSignal } from '@preact/signals' import { useSignal } from '@preact/signals'
import { excitement } from '@homeman/common.ts' import { excitement } from '@homeman/common.ts'
export interface Props { export interface Props {
tasks: Task[] tasks: Task[]
done: DoneTask[]
} }
interface TaskWithIndex extends Task { interface TaskWithIndex extends Task {
@ -62,38 +55,37 @@ export function Routine(
<main class='flex flex-col overflow-x-scroll'> <main class='flex flex-col overflow-x-scroll'>
<ul> <ul>
{taskGroups[currentPhase.value].map(( {taskGroups[currentPhase.value].map((
{ emoji, id, doneAt, index }: TaskWithIndex, task: TaskWithIndex,
) => ( ) => {
const { emoji, description, doneAt, index } = task
return (
<li <li
role='button' role='button'
class={`text-lg cursor-pointer p-4 hover:bg-gray-500/20 ${ class={`text-lg cursor-pointer p-4 hover:bg-gray-500/20 ${
doneAt ? 'line-through opacity-30 hover:bg-gray-500' : '' doneAt ? 'line-through opacity-30 hover:bg-gray-500' : ''
}`} }`}
onClick={() => { onClick={async () => {
tasks.value[index].doneAt = tasks.value[index].doneAt tasks.value[index].doneAt = tasks.value[index].doneAt
? null ? null
: new Date() : new Date()
tasks.value = [...tasks.value] tasks.value = [...tasks.value]
if (tasks.value[index].doneAt) { if (tasks.value[index].doneAt) {
fetch('/api/tasks', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ id: id }),
})
excitement() excitement()
} else {
fetch('/api/tasks', {
method: 'DELETE',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ id: id }),
})
} }
await (await fetch('/api/tasks', {
method: 'PUT',
body: JSON.stringify({
...task,
done: !!tasks.value[index].doneAt,
}),
})).json()
}} }}
> >
{emoji ? `${emoji.trim()} ` : ''} {emoji ? `${emoji.trim()} ` : ''}
{id.trim()} {description.trim()}
</li> </li>
))} )
})}
</ul> </ul>
</main> </main>
</> </>

13
main.ts
View file

@ -10,5 +10,18 @@ import '$std/dotenv/load.ts'
import { start } from '$fresh/server.ts' import { start } from '$fresh/server.ts'
import manifest from './fresh.gen.ts' import manifest from './fresh.gen.ts'
import config from './fresh.config.ts' import config from './fresh.config.ts'
import { kv } from '@homeman/db.ts'
import { Task } from '@homeman/models.ts'
Deno.cron('Reset daily tasks', '0 4 * * *', async () => {
console.log('4am daily tasks reset')
const tasks = await Array.fromAsync(kv.list<Task>({ prefix: ['task'] }))
tasks.forEach(
async (t) => {
const nv = { ...t.value, doneAt: null }
await kv.set(t.key, nv)
},
)
})
await start(manifest, config) await start(manifest, config)

View file

@ -1,5 +1,5 @@
import { DailyPhase, TaskModel } from '@homeman/models.ts' import { DailyPhase, TaskModel } from '@homeman/models.ts'
import { db } from '@homeman/db.ts' import { kv } from '@homeman/db.ts'
import { ulid } from 'https://deno.land/x/ulid@v0.3.0/mod.ts' import { ulid } from 'https://deno.land/x/ulid@v0.3.0/mod.ts'
const hardcoded: Record<DailyPhase, [string, string][]> = { const hardcoded: Record<DailyPhase, [string, string][]> = {
@ -41,6 +41,7 @@ const hardcoded: Record<DailyPhase, [string, string][]> = {
['👨‍⚖️', 'Sleep!'], ['👨‍⚖️', 'Sleep!'],
], ],
} }
console.log({ kv })
Object.entries(hardcoded).forEach(async ([tphase, phaseTasks]) => { Object.entries(hardcoded).forEach(async ([tphase, phaseTasks]) => {
const phase = tphase as DailyPhase const phase = tphase as DailyPhase
for (const [emoji, description] of phaseTasks) { for (const [emoji, description] of phaseTasks) {
@ -52,9 +53,10 @@ Object.entries(hardcoded).forEach(async ([tphase, phaseTasks]) => {
doneAt: null, doneAt: null,
}) })
console.log(task) console.log(task)
const result = await db.tasks.create({ try {
data: task, await kv.set(['task', task.id], task)
}) } catch (e) {
console.log('after create:', result) console.error('error creating:', e)
}
} }
}) })

View file

@ -1,64 +1,40 @@
import { Handlers } from '$fresh/server.ts' import { Handlers } from '$fresh/server.ts'
import { Task, TaskModel } from '@homeman/models.ts' import { Task, TaskModel } from '@homeman/models.ts'
import { db, kv } from '@homeman/db.ts' import { kv } from '@homeman/db.ts'
import { ulid } from 'https://deno.land/x/ulid@v0.3.0/mod.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' import { z } from 'https://deno.land/x/zod@v3.21.4/mod.ts'
const TaskPayload = TaskModel.partial({ id: true }) const TaskPayload = TaskModel.omit({ doneAt: true }).partial({
id: true,
}).extend({
done: z.boolean().optional(),
})
type TaskPayload = z.infer<typeof TaskPayload> type TaskPayload = z.infer<typeof TaskPayload>
async function createOrUpdate(task: TaskPayload) { async function createOrUpdate(task: TaskPayload) {
if (!task.id || task.id === '') { const newTask: Task = TaskModel.parse({
const newTask: Task = {
...task, ...task,
id: ulid(), doneAt: task.done ? new Date() : null,
})
if (!newTask.id || newTask.id === '') {
newTask.id = ulid()
} }
const result = await db.tasks.create({ data: newTask }) const result = await kv.set(['task', newTask.id], newTask)
await kv.set(['last_task_updated'], newTask.id) await kv.set(['last_task_updated'], newTask.id)
return result 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<Task | null> = { export const handler: Handlers<Task | null> = {
async POST(req, _ctx) { async POST(req, _ctx) {
if (req.headers.get('content-type')?.includes('json')) {
const result = await createOrUpdate( const result = await createOrUpdate(
TaskPayload.parse(await req.json()), TaskPayload.parse(await req.json()),
) )
return new Response(JSON.stringify(result)) return new Response(JSON.stringify(result))
} else { },
const form = await req.formData() async PUT(req, _ctx) {
const id = form.get('id')?.toString() || undefined const t = TaskPayload.parse(await req.json())
const result = await createOrUpdate(t)
const doneAt = form.get('doneAt') return new Response(JSON.stringify(result))
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)
}
}, },
async DELETE(req, _ctx) { async DELETE(req, _ctx) {
// task: form or query params or json // task: form or query params or json
@ -70,7 +46,7 @@ export const handler: Handlers<Task | null> = {
} }
console.log('delete task data:', data) console.log('delete task data:', data)
const taskData = TaskModel.pick({ id: true }).parse(data) const taskData = TaskModel.pick({ id: true }).parse(data)
const result = await db.tasks.delete({ where: taskData }) const result = await kv.delete(['task', taskData.id])
await kv.set(['last_task_updated'], taskData.id) await kv.set(['last_task_updated'], taskData.id)
return new Response(JSON.stringify(result)) return new Response(JSON.stringify(result))
}, },
@ -98,9 +74,7 @@ export const handler: Handlers<Task | null> = {
continue continue
} }
const task = await db.tasks.findFirst({ const task = (await kv.get(['task', entry.value])).value
where: { id: entry.value },
})
const chunk = `data: ${ const chunk = `data: ${
JSON.stringify({ JSON.stringify({
id: entry.value, id: entry.value,
@ -136,10 +110,12 @@ export const handler: Handlers<Task | null> = {
const taskData = TaskModel.pick({ id: true }).safeParse(data) const taskData = TaskModel.pick({ id: true }).safeParse(data)
if (taskData.success) { if (taskData.success) {
return new Response( return new Response(
JSON.stringify(await db.tasks.findFirst({ where: taskData.data })), JSON.stringify((await kv.get(['task', taskData.data.id])).value),
) )
} else { } else {
return new Response(JSON.stringify(await db.tasks.findMany({}))) return new Response(
JSON.stringify(Array.fromAsync(kv.list({ prefix: ['task'] }))),
)
} }
}, },
} }

View file

@ -1,7 +1,7 @@
import { Handlers, PageProps } from '$fresh/server.ts' import { Handlers, PageProps } from '$fresh/server.ts'
import { Task } from '@homeman/models.ts' import { Task } from '@homeman/models.ts'
import { Routine } from '@homeman/islands/Routine.tsx' import { Routine } from '@homeman/islands/Routine.tsx'
import { db } from '@homeman/db.ts' import { kv } from '@homeman/db.ts'
// import { db, kv, Todo, UserWithTodos } from '@homeman/models.ts' // import { db, kv, Todo, UserWithTodos } from '@homeman/models.ts'
interface Data { interface Data {
@ -10,7 +10,10 @@ interface Data {
export const handler: Handlers = { export const handler: Handlers = {
async GET(_req, ctx) { async GET(_req, ctx) {
const tasks = await db.tasks.findMany({}) const tasks = (await Array.fromAsync(kv.list({ prefix: ['task'] }))).map(
(r) => r.value
)
console.log({ tasks })
return ctx.render({ tasks }) return ctx.render({ tasks })
}, },
} }