Better db functions
This commit is contained in:
parent
f592524be1
commit
967919f9e7
|
@ -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;
|
||||
|
|
95
db/mod.ts
95
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<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];
|
||||
}
|
||||
|
|
|
@ -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() || []);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
4
types.ts
4
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 {
|
||||
|
|
Loading…
Reference in a new issue