Implement DB interface for Piece CRUD

This commit is contained in:
2024-08-13 23:17:48 +02:00
parent 1bd323bdd6
commit ac091b1dc9
5 changed files with 171 additions and 11 deletions

View File

@@ -1,8 +1,8 @@
import { Schema as S } from "@effect/schema";
import { Database as SqliteDatabase } from "bun:sqlite";
import { SessionId, UserId } from "common";
import { AccessLog, SessionData, User } from "common/db";
import { Context, Effect, Layer, pipe } from "effect";
import { PieceId, SessionId, UserId } from "common";
import { AccessLog, Piece, SessionData, User } from "common/db";
import { Context, Effect, Layer, pipe, Option as O } from "effect";
import { NoSuchElementException } from "effect/Cause";
import { ulid } from "ulid";
@@ -16,6 +16,11 @@ export function generateSessionId(byteLength: number = 12): SessionId {
export interface DatabaseInterface {
readonly createAccessLog: (accessLog: AccessLog) => Effect.Effect<void>;
readonly createPiece: (name: string, composer: O.Option<string>, lyricist: O.Option<string>, arranger: O.Option<string>, user: UserId) => Effect.Effect<PieceId>;
readonly getPieces: Effect.Effect<readonly Piece[]>;
readonly updatePiece: (pieceId: PieceId, name: string, composer: O.Option<string>, lyricist: O.Option<string>, arranger: O.Option<string>, user: UserId) => Effect.Effect<void, NoSuchElementException>;
readonly deletePiece: (pieceId: PieceId) => Effect.Effect<void, NoSuchElementException>;
readonly getUserByUsername: (username: string) => Effect.Effect<User, NoSuchElementException>;
readonly getUserById: (userId: UserId) => Effect.Effect<User, NoSuchElementException>;
@@ -96,6 +101,21 @@ export const DatabaseLive = (filePath: string = "db.sqlite3") => Layer.effect(Da
[timestamp: string, requestId: string, method: string, pathname: string, query: string, ip: string | null]
>("INSERT INTO AccessLog (timestamp, requestId, method, pathname, query, ip) VALUES (?, ?, ?, ?, ?, ?)");
const createPiece = db.prepare<
never,
[pieceId: PieceId, name: string, composer: string | null, lyricist: string | null, arranger: string | null, user: UserId]
>("INSERT INTO Piece (pieceId, name, composer, lyricist, arranger, createdBy, createdAt, modifiedBy, modifiedAt) VALUES (?1, ?2, ?3, ?4, ?5, ?6, datetime(), ?6, datetime())");
const getPieces = db.prepare<
typeof Piece.Encoded,
[]
>("SELECT pieceId, name, composer, lyricist, arranger, createdBy, createdAt, modifiedBy, modifiedAt FROM Piece ORDER BY name, composer, arranger");
const updatePiece = db.prepare<
never,
[pieceId: PieceId, name: string, composer: string | null, lyricist: string | null, arranger: string | null, user: UserId]
>("UPDATE Piece SET name = ?2, composer = ?3, lyricist = ?4, arranger = ?5, modifiedBy = ?6, modifiedAt = datetime() WHERE pieceId = ?1");
const pieceExists = db.prepare<object, [pieceId: PieceId]>("SELECT 1 FROM Piece WHERE PieceId = ?");
const deletePiece = db.prepare<never, [pieceId: PieceId]>("DELETE FROM Piece WHERE pieceId = ?");
const getUserByUsername = db.prepare<
{ userId: string, username: string, password: string, admin: number },
[username: string]
@@ -119,6 +139,29 @@ export const DatabaseLive = (filePath: string = "db.sqlite3") => Layer.effect(Da
createAccessLog.run(timestamp, requestId, method, pathname, query, ip);
}),
createPiece: (name: string, composer: O.Option<string>, lyricist: O.Option<string>, arranger: O.Option<string>, user: UserId) => Effect.sync(() => {
const pieceId = PieceId.make(ulid());
createPiece.run(pieceId, name, O.getOrNull(composer), O.getOrNull(lyricist), O.getOrNull(arranger), user);
return pieceId;
}),
getPieces: Effect.sync(() => {
const res = getPieces.all();
const decoder = S.decodeSync(Piece);
return Object.freeze(res.map((encoded) => decoder(encoded)));
}),
updatePiece: (pieceId: PieceId, name: string, composer: O.Option<string>, lyricist: O.Option<string>, arranger: O.Option<string>, user: UserId) => Effect.suspend(() => {
updatePiece.run(pieceId, name, O.getOrNull(composer), O.getOrNull(lyricist), O.getOrNull(arranger), user);
const matched = pieceExists.get(pieceId) !== null;
return matched ? Effect.void : Effect.fail(new NoSuchElementException());
}),
deletePiece: (pieceId: PieceId) => Effect.suspend(() => {
const matched = deletePiece.run(pieceId).changes > 0;
return matched ? Effect.void : Effect.fail(new NoSuchElementException());
}),
getUserByUsername: (username) => Effect.suspend(() => {
const res = getUserByUsername.get(username);
if (res === null) return Effect.fail(new NoSuchElementException());