User CRUD on backend and refactors
This commit is contained in:
@@ -6,32 +6,20 @@ import { implement } from "./api";
|
||||
import * as Authentication from "./services/Authentication";
|
||||
import * as Database from "./services/Database";
|
||||
|
||||
const READ_ACCESS = HashSet.make(Role.Editor, Role.Viewer);
|
||||
const WRITE_ACCESS = HashSet.make(Role.Editor);
|
||||
const READ_ACCESS = HashSet.make(Role.Admin, Role.Editor, Role.Viewer);
|
||||
const WRITE_ACCESS = HashSet.make(Role.Admin, Role.Editor);
|
||||
const ADMIN_ACCESS = HashSet.make(Role.Admin);
|
||||
|
||||
const requireAuthenticated = pipe(
|
||||
Authentication.Authentication,
|
||||
Effect.flatMap(({ me }) => me),
|
||||
);
|
||||
|
||||
const requireReadAccess = pipe(
|
||||
const requireOneOf = (roles: HashSet.HashSet<Role>) => pipe(
|
||||
Authentication.Authentication,
|
||||
Effect.flatMap(({ me }) => me),
|
||||
Effect.flatMap((user) => pipe(
|
||||
HashSet.intersection(user.roles, READ_ACCESS),
|
||||
HashSet.size,
|
||||
Number.greaterThan(0),
|
||||
)
|
||||
? Effect.succeed(user)
|
||||
: Effect.fail(Unauthorized.make())
|
||||
),
|
||||
);
|
||||
|
||||
const requireWriteAccess = pipe(
|
||||
Authentication.Authentication,
|
||||
Effect.flatMap(({ me }) => me),
|
||||
Effect.flatMap((user) => pipe(
|
||||
HashSet.intersection(user.roles, WRITE_ACCESS),
|
||||
HashSet.intersection(user.roles, roles),
|
||||
HashSet.size,
|
||||
Number.greaterThan(0),
|
||||
)
|
||||
@@ -52,19 +40,93 @@ export const handle = implement(api, {
|
||||
Authentication.Authentication,
|
||||
Effect.flatMap(({ logout }) => logout),
|
||||
),
|
||||
|
||||
// --- User CRUD ---
|
||||
|
||||
getUser: (userId) => Effect.gen(function* () {
|
||||
yield* requireAuthenticated;
|
||||
|
||||
const { getUser } = yield* Authentication.Authentication;
|
||||
const user = yield* getUser(userId);
|
||||
const user = yield* pipe(
|
||||
Authentication.getUser(userId),
|
||||
Effect.catchTag("NoSuchElementException", () => Effect.fail(NotFound.make())),
|
||||
);
|
||||
|
||||
return user;
|
||||
}),
|
||||
queryUsers: ({ displayName, role, offset, limit }) => Effect.gen(function* () {
|
||||
yield* requireAuthenticated;
|
||||
const db = yield* Database.Database;
|
||||
|
||||
let query = db
|
||||
.selectFrom("User")
|
||||
.leftJoin("UserRole", "User.userId", "UserRole.role")
|
||||
.select("User.userId")
|
||||
.groupBy("User.userId")
|
||||
.orderBy("displayName")
|
||||
.offset(offset)
|
||||
.limit(limit);
|
||||
|
||||
query = Option.match(displayName, {
|
||||
onNone: () => query,
|
||||
onSome: (displayName) => query.where("User.displayName", "like", "%" + displayName + "%"),
|
||||
});
|
||||
|
||||
query = Option.match(role, {
|
||||
onNone: () => query,
|
||||
onSome: (role) => query.where("UserRole.role", "=", role),
|
||||
});
|
||||
|
||||
const res = yield* query.$call(Database.execute);
|
||||
return res.map(({ userId }) => userId);
|
||||
}),
|
||||
updateUser: ({ userId, ...user }) => Effect.gen(function* () {
|
||||
yield* requireOneOf(ADMIN_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
const res = yield* pipe(
|
||||
Authentication.getUser(userId),
|
||||
Effect.mapError(() => NotFound.make()),
|
||||
);
|
||||
|
||||
yield* db
|
||||
.deleteFrom("UserRole")
|
||||
.where("userId", "=", userId)
|
||||
.$call(Database.execute);
|
||||
|
||||
const roles = yield* pipe(
|
||||
user.roles,
|
||||
Effect.forEach((role) => db
|
||||
.insertInto("UserRole")
|
||||
.values({ userId, role })
|
||||
.returning("role")
|
||||
.$call(Database.executeTakeFirstOrDie)
|
||||
.pipe(Effect.map(({ role }) => role)),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
userId: res.userId,
|
||||
displayName: res.displayName,
|
||||
avatarUrl: res.avatarUrl,
|
||||
roles: HashSet.fromIterable(roles),
|
||||
};
|
||||
}),
|
||||
deleteUser: (userId) => Effect.gen(function* () {
|
||||
yield* requireOneOf(ADMIN_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
yield* db
|
||||
.deleteFrom("User")
|
||||
.where("userId", "=", userId)
|
||||
.returning("userId")
|
||||
.$call(Database.executeTakeFirst)
|
||||
.pipe(Effect.mapError(() => NotFound.make()));
|
||||
}),
|
||||
|
||||
// --- Piece CRUD ---
|
||||
|
||||
createPiece: (piece) => Effect.gen(function* () {
|
||||
const { userId } = yield* requireWriteAccess;
|
||||
const { userId } = yield* requireOneOf(WRITE_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
const res = yield* db
|
||||
@@ -99,7 +161,7 @@ export const handle = implement(api, {
|
||||
};
|
||||
}),
|
||||
getPiece: (pieceId) => Effect.gen(function* () {
|
||||
yield* requireReadAccess;
|
||||
yield* requireOneOf(READ_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
const piece = yield* db
|
||||
@@ -149,13 +211,15 @@ export const handle = implement(api, {
|
||||
};
|
||||
}),
|
||||
queryPieces: ({ name, author, offset, limit }) => Effect.gen(function* () {
|
||||
yield* requireReadAccess;
|
||||
yield* requireOneOf(READ_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
let query = db
|
||||
.selectFrom("Piece")
|
||||
.select("pieceId")
|
||||
.orderBy(["name", "composer", "arranger"])
|
||||
.orderBy("name")
|
||||
.orderBy("composer")
|
||||
.orderBy("arranger")
|
||||
.offset(offset)
|
||||
.limit(limit);
|
||||
|
||||
@@ -177,7 +241,7 @@ export const handle = implement(api, {
|
||||
return res.map(({ pieceId }) => pieceId);
|
||||
}),
|
||||
updatePiece: ({ pieceId, ...piece }) => Effect.gen(function* () {
|
||||
const { userId } = yield* requireWriteAccess;
|
||||
const { userId } = yield* requireOneOf(WRITE_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
const res = yield* db
|
||||
@@ -235,7 +299,7 @@ export const handle = implement(api, {
|
||||
};
|
||||
}),
|
||||
deletePiece: (pieceId) => Effect.gen(function* () {
|
||||
yield* requireWriteAccess;
|
||||
yield* requireOneOf(WRITE_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
yield* db
|
||||
@@ -249,7 +313,7 @@ export const handle = implement(api, {
|
||||
// --- Attachment CRUD ---
|
||||
|
||||
createAttachment: (attachment) => Effect.gen(function* () {
|
||||
const { userId } = yield* requireWriteAccess;
|
||||
const { userId } = yield* requireOneOf(WRITE_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
const sha256 = Sha256.make(new Uint8Array(Bun.SHA256.byteLength));
|
||||
@@ -292,7 +356,7 @@ export const handle = implement(api, {
|
||||
};
|
||||
}),
|
||||
getAttachment: (attachmentId) => Effect.gen(function* () {
|
||||
yield* requireReadAccess;
|
||||
yield* requireOneOf(READ_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
const res = yield* db
|
||||
@@ -306,7 +370,7 @@ export const handle = implement(api, {
|
||||
return res;
|
||||
}),
|
||||
updateAttachment: ({ attachmentId, ...attachment }) => Effect.gen(function* () {
|
||||
const { userId } = yield* requireWriteAccess;
|
||||
const { userId } = yield* requireOneOf(WRITE_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
const res = yield* db
|
||||
@@ -337,7 +401,7 @@ export const handle = implement(api, {
|
||||
};
|
||||
}),
|
||||
deleteAttachment: (attachmentId) => Effect.gen(function* () {
|
||||
yield* requireWriteAccess;
|
||||
yield* requireOneOf(WRITE_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
yield* db
|
||||
@@ -351,7 +415,7 @@ export const handle = implement(api, {
|
||||
// --- Repertoire CRUD ---
|
||||
|
||||
createRepertoire: (repertoire) => Effect.gen(function* () {
|
||||
const { userId } = yield* requireWriteAccess;
|
||||
const { userId } = yield* requireOneOf(WRITE_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
const repertoireId = RepertoireId.make(Bun.randomUUIDv7());
|
||||
@@ -393,7 +457,7 @@ export const handle = implement(api, {
|
||||
};
|
||||
}),
|
||||
getRepertoire: (repertoireId) => Effect.gen(function* () {
|
||||
yield* requireReadAccess;
|
||||
yield* requireOneOf(READ_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
const repertoire = yield* db
|
||||
@@ -424,8 +488,8 @@ export const handle = implement(api, {
|
||||
),
|
||||
};
|
||||
}),
|
||||
queryRepertoire: ({ name, offset, limit }) => Effect.gen(function* () {
|
||||
yield* requireReadAccess;
|
||||
queryRepertoires: ({ name, offset, limit }) => Effect.gen(function* () {
|
||||
yield* requireOneOf(READ_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
let query = db
|
||||
@@ -444,7 +508,7 @@ export const handle = implement(api, {
|
||||
return res.map(({ repertoireId }) => repertoireId);
|
||||
}),
|
||||
updateRepertoire: ({ repertoireId, ...repertoire }) => Effect.gen(function* () {
|
||||
const { userId } = yield* requireWriteAccess;
|
||||
const { userId } = yield* requireOneOf(WRITE_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
const res = yield* db
|
||||
@@ -490,7 +554,7 @@ export const handle = implement(api, {
|
||||
};
|
||||
}),
|
||||
deleteRepertoire: (repertoireId) => Effect.gen(function* () {
|
||||
yield* requireWriteAccess;
|
||||
yield* requireOneOf(WRITE_ACCESS);
|
||||
const db = yield* Database.Database;
|
||||
|
||||
yield* db
|
||||
|
||||
Reference in New Issue
Block a user