From 967919f9e71c00eee0028d2b426710371e589347 Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Sat, 8 Oct 2022 00:24:03 -0500 Subject: [PATCH] Better db functions --- db/migrations.ts | 17 +++++--- db/mod.ts | 95 ++++++++++++++++++++++++++++++++---------- routes/note.tsx | 4 +- routes/note/[id].tsx | 5 +-- routes/note/create.tsx | 21 ++++------ types.ts | 4 ++ 6 files changed, 99 insertions(+), 47 deletions(-) diff --git a/db/migrations.ts b/db/migrations.ts index 25914e2..73dee48 100644 --- a/db/migrations.ts +++ b/db/migrations.ts @@ -152,12 +152,17 @@ try { } try { - await createNote({ content: "Hello, notes!" }); - await createUser({ - username: "lytedev", - passwordDigest: - "$2a$10$9fyDAOz6H4a393KHyjbvIe1WFxbhCJhq/CZmlXcEg4d1bE9Ey25WW", - }); + console.debug( + await Promise.all([ + createNote({ content: "Hello, notes!" }), + createUser({ + username: "lytedev", + passwordDigest: + "$2a$10$9fyDAOz6H4a393KHyjbvIe1WFxbhCJhq/CZmlXcEg4d1bE9Ey25WW", + status: "superadmin", + }), + ]), + ); } catch (err) { console.log("Failed to run seed database:", { ...err }); throw err; diff --git a/db/mod.ts b/db/mod.ts index d6da14c..32d1438 100644 --- a/db/mod.ts +++ b/db/mod.ts @@ -13,6 +13,7 @@ import { config } from "@/config.ts"; import { type Identifiable, type Note, + type Team, type Timestamped, type User, } from "@/types.ts"; @@ -68,35 +69,46 @@ export async function queryArray( ); } -export async function listNotes() { - return await queryObject( - "select * from note order by created_at desc", +export async function listNotes(): Promise { + return someRows( + await queryObject( + "select * from note order by created_at desc", + ), ); } -export async function getNote(id: string | { id: string }) { +export async function getNote( + id: string | { id: string }, +): Promise { const idVal = typeof id == "object" ? id.id : id; console.debug("getNote id =", JSON.stringify(idVal)); - return await queryObject( - "select * from note where id = $1", - [idVal], + return singleRow( + await queryObject( + "select * from note where id = $1", + [idVal], + ), ); } type Ungenerated = Omit; -export async function createNote({ content }: Ungenerated) { - return await queryObject( - "insert into note (content) values ($1) returning *", - [content], +export async function createNote( + { content }: Ungenerated, +): Promise { + return singleRow( + await queryObject( + "insert into note (content) values ($1) returning *", + [content], + ), ); } export async function createUser( { username, passwordDigest }: Ungenerated, -) { - return await queryObject<{ user_id: string }>( - ` +): Promise<[User | null, Team | null] | null> { + const result = singleRow( + await queryObject<{ teamId: string; userId: string }>( + ` with new_user as ( insert into "user" (username, password_digest, status) values ($username, $passwordDigest, 'unverified') @@ -111,17 +123,56 @@ export async function createUser( (select user_id from new_user), (select team_id from new_team), 'owner' - ) returning user_id + ) returning user_id, team_id `, - { username, passwordDigest, teamName: `${username}'s First Team` }, + { username, passwordDigest, teamName: `${username}'s First Team` }, + ), + ); + if (!result) return null; + const { userId, teamId } = result; + return await Promise.all([ + getUser({ id: userId }), + getTeam({ id: teamId }), + ]); +} + +export async function getUser( + { id, username }: Partial, +): Promise { + if (!id && !username) throw "getUser called without id or username"; + const column = id ? "id" : "username"; + return singleRow( + await queryObject( + `select * from "user" where "${column}" = $1`, + [id || username], + ), ); } -export async function getUser({ id, username }: Partial) { - if (!id && !username) throw "getUser called without id or username"; - const column = id ? "id" : "username"; - return await queryObject( - `select * from "user" where "${column}" = $1`, - [id || username], +export async function getTeam( + { id }: Partial, +): Promise { + return singleRow( + await queryObject( + `select * from "team" where "id" = $1`, + [id], + ), ); } + +function someRows(result: { rows: T[] } | null): T[] | null { + console.debug(result); + if (!result || result.rows.length < 1) return null; + else return result.rows; +} + +function singleRow(result: { rows: T[] } | null): T | null { + if (!result || result.rows.length < 1) return null; + else if (result.rows.length > 1) { + console.error( + "This singleRow result brought back more than 1 row:", + result, + ); + return null; + } else return result.rows[0]; +} diff --git a/routes/note.tsx b/routes/note.tsx index 294e0d3..8307c78 100644 --- a/routes/note.tsx +++ b/routes/note.tsx @@ -5,9 +5,7 @@ import { type Note } from "@/types.ts"; export const handler: Handlers = { async GET(_request, context) { - const result = await listNotes(); - if (!result) throw "unable to fetch from database"; - return await context.render(result.rows); + return await context.render(await listNotes() || []); }, }; diff --git a/routes/note/[id].tsx b/routes/note/[id].tsx index a941199..7784ca0 100644 --- a/routes/note/[id].tsx +++ b/routes/note/[id].tsx @@ -6,9 +6,8 @@ import { type Note } from "@/types.ts"; export const handler: Handlers = { async GET(request, context) { console.debug({ request, context }); - const result = await getNote(context.params.id); - if (!result) throw "unable to fetch from database"; - const [note] = result.rows as Note[]; + const note = await getNote(context.params.id); + if (!note) throw "unable to fetch from database"; return await context.render(note); }, }; diff --git a/routes/note/create.tsx b/routes/note/create.tsx index f6c48d1..66fc1da 100644 --- a/routes/note/create.tsx +++ b/routes/note/create.tsx @@ -1,29 +1,24 @@ import { Handlers, PageProps } from "$fresh/server.ts"; -import { queryObject } from "@/db/mod.ts"; +import { createNote } from "@/db/mod.ts"; import { Page } from "@/components/Page.tsx"; +import { type Note } from "@/types.ts"; -type NoteID = string; - -export const handler: Handlers = { +export const handler: Handlers = { async POST(request, context) { const content = (await request.formData()).get("content"); if (!content) throw "no content provided"; - const result = await queryObject<{ id: string }>( - "insert into note (content) values ($1) returning id", - [content], - ); - if (!result) throw "insert failed"; - const { rows: [{ id }] } = result; - return await context.render(id); + const note = await createNote({ content: content.toString() }); + if (!note) throw "no note created"; + return await context.render(note); }, }; -export default function NotesPage({ data: noteId }: PageProps) { +export default function NotesPage({ data: { id } }: PageProps) { return (

You created a note!

Back to notes - View your note + View your note
); } diff --git a/types.ts b/types.ts index 54298e5..121f1c4 100644 --- a/types.ts +++ b/types.ts @@ -16,6 +16,10 @@ export interface Note extends Identifiable, Timestamped { content: string; } +export interface Team extends Identifiable, Timestamped { + displayName: string; +} + export type UserStatus = "unverified" | "verified" | "superadmin"; export interface User extends Identifiable, Timestamped {