Better db functions

This commit is contained in:
Daniel Flanagan 2022-10-08 00:24:03 -05:00
parent f592524be1
commit 967919f9e7
Signed by: lytedev
GPG Key ID: 5B2020A0F9921EF4
6 changed files with 99 additions and 47 deletions

View File

@ -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;

View File

@ -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<T extends []>(
);
}
export async function listNotes() {
return await queryObject<Note>(
"select * from note order by created_at desc",
export async function listNotes(): Promise<Note[] | null> {
return someRows(
await queryObject<Note>(
"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<Note | null> {
const idVal = typeof id == "object" ? id.id : id;
console.debug("getNote id =", JSON.stringify(idVal));
return await queryObject<Note>(
"select * from note where id = $1",
[idVal],
return singleRow(
await queryObject<Note>(
"select * from note where id = $1",
[idVal],
),
);
}
type Ungenerated<T> = Omit<T, keyof Identifiable | keyof Timestamped>;
export async function createNote({ content }: Ungenerated<Note>) {
return await queryObject<Note>(
"insert into note (content) values ($1) returning *",
[content],
export async function createNote(
{ content }: Ungenerated<Note>,
): Promise<Note | null> {
return singleRow(
await queryObject<Note>(
"insert into note (content) values ($1) returning *",
[content],
),
);
}
export async function createUser(
{ username, passwordDigest }: Ungenerated<User>,
) {
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<User>,
): Promise<User | null> {
if (!id && !username) throw "getUser called without id or username";
const column = id ? "id" : "username";
return singleRow(
await queryObject<User>(
`select * from "user" where "${column}" = $1`,
[id || username],
),
);
}
export async function getUser({ id, username }: Partial<User>) {
if (!id && !username) throw "getUser called without id or username";
const column = id ? "id" : "username";
return await queryObject<User>(
`select * from "user" where "${column}" = $1`,
[id || username],
export async function getTeam(
{ id }: Partial<Team>,
): Promise<Team | null> {
return singleRow(
await queryObject<Team>(
`select * from "team" where "id" = $1`,
[id],
),
);
}
function someRows<T>(result: { rows: T[] } | null): T[] | null {
console.debug(result);
if (!result || result.rows.length < 1) return null;
else return result.rows;
}
function singleRow<T>(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];
}

View File

@ -5,9 +5,7 @@ import { type Note } from "@/types.ts";
export const handler: Handlers<Note[]> = {
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() || []);
},
};

View File

@ -6,9 +6,8 @@ import { type Note } from "@/types.ts";
export const handler: Handlers<Note> = {
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);
},
};

View File

@ -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<NoteID> = {
export const handler: Handlers<Note> = {
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<NoteID>) {
export default function NotesPage({ data: { id } }: PageProps<Note>) {
return (
<Page>
<h1>You created a note!</h1>
<a href="/note">Back to notes</a>
<a href={`/note/${noteId}`}>View your note</a>
<a href={`/note/${id}`}>View your note</a>
</Page>
);
}

View File

@ -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 {