Better db functions
This commit is contained in:
parent
f592524be1
commit
967919f9e7
|
@ -152,12 +152,17 @@ try {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await createNote({ content: "Hello, notes!" });
|
console.debug(
|
||||||
await createUser({
|
await Promise.all([
|
||||||
username: "lytedev",
|
createNote({ content: "Hello, notes!" }),
|
||||||
passwordDigest:
|
createUser({
|
||||||
"$2a$10$9fyDAOz6H4a393KHyjbvIe1WFxbhCJhq/CZmlXcEg4d1bE9Ey25WW",
|
username: "lytedev",
|
||||||
});
|
passwordDigest:
|
||||||
|
"$2a$10$9fyDAOz6H4a393KHyjbvIe1WFxbhCJhq/CZmlXcEg4d1bE9Ey25WW",
|
||||||
|
status: "superadmin",
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("Failed to run seed database:", { ...err });
|
console.log("Failed to run seed database:", { ...err });
|
||||||
throw err;
|
throw err;
|
||||||
|
|
95
db/mod.ts
95
db/mod.ts
|
@ -13,6 +13,7 @@ import { config } from "@/config.ts";
|
||||||
import {
|
import {
|
||||||
type Identifiable,
|
type Identifiable,
|
||||||
type Note,
|
type Note,
|
||||||
|
type Team,
|
||||||
type Timestamped,
|
type Timestamped,
|
||||||
type User,
|
type User,
|
||||||
} from "@/types.ts";
|
} from "@/types.ts";
|
||||||
|
@ -68,35 +69,46 @@ export async function queryArray<T extends []>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listNotes() {
|
export async function listNotes(): Promise<Note[] | null> {
|
||||||
return await queryObject<Note>(
|
return someRows(
|
||||||
"select * from note order by created_at desc",
|
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;
|
const idVal = typeof id == "object" ? id.id : id;
|
||||||
console.debug("getNote id =", JSON.stringify(idVal));
|
console.debug("getNote id =", JSON.stringify(idVal));
|
||||||
return await queryObject<Note>(
|
return singleRow(
|
||||||
"select * from note where id = $1",
|
await queryObject<Note>(
|
||||||
[idVal],
|
"select * from note where id = $1",
|
||||||
|
[idVal],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ungenerated<T> = Omit<T, keyof Identifiable | keyof Timestamped>;
|
type Ungenerated<T> = Omit<T, keyof Identifiable | keyof Timestamped>;
|
||||||
|
|
||||||
export async function createNote({ content }: Ungenerated<Note>) {
|
export async function createNote(
|
||||||
return await queryObject<Note>(
|
{ content }: Ungenerated<Note>,
|
||||||
"insert into note (content) values ($1) returning *",
|
): Promise<Note | null> {
|
||||||
[content],
|
return singleRow(
|
||||||
|
await queryObject<Note>(
|
||||||
|
"insert into note (content) values ($1) returning *",
|
||||||
|
[content],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createUser(
|
export async function createUser(
|
||||||
{ username, passwordDigest }: Ungenerated<User>,
|
{ username, passwordDigest }: Ungenerated<User>,
|
||||||
) {
|
): Promise<[User | null, Team | null] | null> {
|
||||||
return await queryObject<{ user_id: string }>(
|
const result = singleRow(
|
||||||
`
|
await queryObject<{ teamId: string; userId: string }>(
|
||||||
|
`
|
||||||
with new_user as (
|
with new_user as (
|
||||||
insert into "user" (username, password_digest, status)
|
insert into "user" (username, password_digest, status)
|
||||||
values ($username, $passwordDigest, 'unverified')
|
values ($username, $passwordDigest, 'unverified')
|
||||||
|
@ -111,17 +123,56 @@ export async function createUser(
|
||||||
(select user_id from new_user),
|
(select user_id from new_user),
|
||||||
(select team_id from new_team),
|
(select team_id from new_team),
|
||||||
'owner'
|
'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>) {
|
export async function getTeam(
|
||||||
if (!id && !username) throw "getUser called without id or username";
|
{ id }: Partial<Team>,
|
||||||
const column = id ? "id" : "username";
|
): Promise<Team | null> {
|
||||||
return await queryObject<User>(
|
return singleRow(
|
||||||
`select * from "user" where "${column}" = $1`,
|
await queryObject<Team>(
|
||||||
[id || username],
|
`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[]> = {
|
export const handler: Handlers<Note[]> = {
|
||||||
async GET(_request, context) {
|
async GET(_request, context) {
|
||||||
const result = await listNotes();
|
return await context.render(await listNotes() || []);
|
||||||
if (!result) throw "unable to fetch from database";
|
|
||||||
return await context.render(result.rows);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,8 @@ import { type Note } from "@/types.ts";
|
||||||
export const handler: Handlers<Note> = {
|
export const handler: Handlers<Note> = {
|
||||||
async GET(request, context) {
|
async GET(request, context) {
|
||||||
console.debug({ request, context });
|
console.debug({ request, context });
|
||||||
const result = await getNote(context.params.id);
|
const note = await getNote(context.params.id);
|
||||||
if (!result) throw "unable to fetch from database";
|
if (!note) throw "unable to fetch from database";
|
||||||
const [note] = result.rows as Note[];
|
|
||||||
return await context.render(note);
|
return await context.render(note);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,29 +1,24 @@
|
||||||
import { Handlers, PageProps } from "$fresh/server.ts";
|
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 { Page } from "@/components/Page.tsx";
|
||||||
|
import { type Note } from "@/types.ts";
|
||||||
|
|
||||||
type NoteID = string;
|
export const handler: Handlers<Note> = {
|
||||||
|
|
||||||
export const handler: Handlers<NoteID> = {
|
|
||||||
async POST(request, context) {
|
async POST(request, context) {
|
||||||
const content = (await request.formData()).get("content");
|
const content = (await request.formData()).get("content");
|
||||||
if (!content) throw "no content provided";
|
if (!content) throw "no content provided";
|
||||||
const result = await queryObject<{ id: string }>(
|
const note = await createNote({ content: content.toString() });
|
||||||
"insert into note (content) values ($1) returning id",
|
if (!note) throw "no note created";
|
||||||
[content],
|
return await context.render(note);
|
||||||
);
|
|
||||||
if (!result) throw "insert failed";
|
|
||||||
const { rows: [{ id }] } = result;
|
|
||||||
return await context.render(id);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function NotesPage({ data: noteId }: PageProps<NoteID>) {
|
export default function NotesPage({ data: { id } }: PageProps<Note>) {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<h1>You created a note!</h1>
|
<h1>You created a note!</h1>
|
||||||
<a href="/note">Back to notes</a>
|
<a href="/note">Back to notes</a>
|
||||||
<a href={`/note/${noteId}`}>View your note</a>
|
<a href={`/note/${id}`}>View your note</a>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
4
types.ts
4
types.ts
|
@ -16,6 +16,10 @@ export interface Note extends Identifiable, Timestamped {
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Team extends Identifiable, Timestamped {
|
||||||
|
displayName: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type UserStatus = "unverified" | "verified" | "superadmin";
|
export type UserStatus = "unverified" | "verified" | "superadmin";
|
||||||
|
|
||||||
export interface User extends Identifiable, Timestamped {
|
export interface User extends Identifiable, Timestamped {
|
||||||
|
|
Loading…
Reference in a new issue