ls-deno/db/mod.ts

128 lines
3.2 KiB
TypeScript

import {
Pool,
PoolClient,
PostgresError,
} from "https://deno.land/x/postgres@v0.16.1/mod.ts";
import {
type QueryArguments,
type QueryArrayResult,
type QueryObjectResult,
} from "https://deno.land/x/postgres@v0.16.1/query/query.ts?s=QueryArguments";
import { config } from "@/config.ts";
import {
type Identifiable,
type Note,
type Timestamped,
type User,
} from "@/types.ts";
export { PostgresError };
export { type QueryObjectResult };
const pool = new Pool(config.postgres.url, 3, true);
async function dbOp<T>(op: (connection: PoolClient) => Promise<T>) {
let result = null;
let exception = null;
try {
const connection = await pool.connect();
try {
result = await op(connection);
} catch (err) {
console.error("Error querying database:", { ...err });
exception = err;
} finally {
connection.release();
}
} catch (err) {
exception = err;
console.error("Error connecting to database:", err);
}
if (exception != null) throw exception;
return result;
}
export async function queryObject<T>(
sql: string,
args?: QueryArguments,
): Promise<QueryObjectResult<T> | null> {
return await dbOp(async (connection) =>
await connection.queryObject<T>({
camelcase: true,
text: sql.trim(),
args,
})
);
}
export async function queryArray<T extends []>(
sql: string,
args?: QueryArguments,
): Promise<QueryArrayResult<T> | null> {
return await dbOp(async (connection) =>
await connection.queryArray<T>({
text: sql.trim(),
args,
})
);
}
export async function listNotes() {
return await queryObject<Note>(
"select * from note order by created_at desc",
);
}
export async function getNote(id: string | { id: string }) {
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],
);
}
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 createUser(
{ username, passwordDigest }: Ungenerated<User>,
) {
return await queryObject<{ user_id: string }>(
`
with new_user as (
insert into "user" (username, password_digest, status)
values ($username, $passwordDigest, 'unverified')
returning id as user_id
), new_team as (
insert into "team" (display_name)
values ($teamName)
returning id as team_id
)
insert into "team_user" (user_id, team_id, status)
values (
(select user_id from new_user),
(select team_id from new_team),
'owner'
) returning user_id
`,
{ username, passwordDigest, teamName: `${username}'s First Team` },
);
}
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],
);
}