Finished initial implementation of circle endpoints
This commit is contained in:
188
src/orm/circles.ts
Normal file
188
src/orm/circles.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import { Claims } from './claims';
|
||||
import { sql } from 'bun';
|
||||
import { first } from 'lodash';
|
||||
import { BadRequestError, NotFoundError, UnauthorizedError } from '../utilities/errors';
|
||||
import { CreateCircleRequest, UpdateCircleRequest } from '../utilities/requestModels';
|
||||
import { memo } from '../utilities/helpers';
|
||||
import { CircleId, PlayerId, UserId } from '../utilities/secureIds';
|
||||
import { orm } from './orm';
|
||||
import { User } from './user';
|
||||
|
||||
export class Circle {
|
||||
id: CircleId;
|
||||
owningUserId: UserId;
|
||||
name: string;
|
||||
isPublic: boolean;
|
||||
imagePath?: string;
|
||||
colour?: string;
|
||||
|
||||
constructor({
|
||||
id,
|
||||
owningUserId,
|
||||
name,
|
||||
isPublic,
|
||||
imagePath,
|
||||
colour,
|
||||
}: {
|
||||
id: CircleId;
|
||||
owningUserId: UserId;
|
||||
name: string;
|
||||
isPublic: boolean;
|
||||
imagePath?: string;
|
||||
colour?: string;
|
||||
}) {
|
||||
this.id = id;
|
||||
this.owningUserId = owningUserId;
|
||||
this.name = name;
|
||||
this.isPublic = isPublic;
|
||||
this.imagePath = imagePath;
|
||||
this.colour = colour;
|
||||
}
|
||||
}
|
||||
|
||||
export class CircleOrm {
|
||||
async create(model: CreateCircleRequest, claims?: Claims): Promise<Circle> {
|
||||
if (model.isPublic && claims && !claims.test(Claims.ADMIN, Claims.CIRCLES.PUBLIC.CREATE)) {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
|
||||
await sql`INSERT INTO circles (owning_user_id, name, is_public, colour)
|
||||
VALUES (${claims?.userId.raw},
|
||||
${model.name},
|
||||
${model.isPublic},
|
||||
${model.colour})`;
|
||||
const newCircleId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
||||
|
||||
return await this.get(CircleId.fromID(newCircleId));
|
||||
}
|
||||
|
||||
async get(id: CircleId, claims?: Claims): Promise<Circle> {
|
||||
const circleResult: any = first(
|
||||
await sql`SELECT *
|
||||
FROM circles
|
||||
WHERE id = ${id.raw}
|
||||
LIMIT 1`,
|
||||
);
|
||||
|
||||
if (!circleResult) {
|
||||
throw new NotFoundError('No matching game exists');
|
||||
}
|
||||
|
||||
let user: User;
|
||||
if (
|
||||
claims &&
|
||||
!claims.test(Claims.ADMIN) &&
|
||||
!(claims.test(Claims.CIRCLES.PUBLIC.READ) && circleResult.is_public) &&
|
||||
!(claims.test(Claims.CIRCLES.OWNED.READ) && circleResult.owning_user_id === claims.userId.raw) &&
|
||||
!(
|
||||
claims.test(Claims.CIRCLES.PRIVATE.READ_IF_MEMBER) &&
|
||||
(user = await orm.users.get(claims.userId)) &&
|
||||
(await sql`SELECT * FROM player_circles WHERE circle_id = ${id.raw}`).some(
|
||||
(x: { player_id: string }) => x.player_id === user.playerId.raw,
|
||||
)
|
||||
)
|
||||
) {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
|
||||
return new Circle({
|
||||
id: CircleId.fromID(circleResult.id),
|
||||
owningUserId: UserId.fromID(circleResult.owning_user_id),
|
||||
name: circleResult.name,
|
||||
isPublic: circleResult.is_public,
|
||||
imagePath: circleResult.image_path,
|
||||
colour: circleResult.colour,
|
||||
});
|
||||
}
|
||||
|
||||
async update(id: CircleId, patch: UpdateCircleRequest): Promise<Circle> {
|
||||
const recordToUpdate = await this.get(id);
|
||||
recordToUpdate.name = patch.name ?? recordToUpdate.name;
|
||||
recordToUpdate.colour = patch.colour ?? recordToUpdate.colour;
|
||||
recordToUpdate.imagePath = patch.imagePath ?? recordToUpdate.imagePath;
|
||||
|
||||
await sql`UPDATE circles
|
||||
SET name=${recordToUpdate.name},
|
||||
colour=${recordToUpdate.colour},
|
||||
image_path=${recordToUpdate.imagePath}
|
||||
WHERE id = ${id.raw}`;
|
||||
|
||||
return await this.get(id);
|
||||
}
|
||||
|
||||
async drop(id: CircleId): Promise<void> {
|
||||
// Ensure record exists before attempting to delete
|
||||
await this.get(id);
|
||||
await sql.transaction(async (tx) => {
|
||||
await tx`DELETE
|
||||
FROM player_circles
|
||||
WHERE circle_id = ${id.raw}`;
|
||||
await tx`DELETE
|
||||
FROM circle_invites
|
||||
WHERE circle_id = ${id.raw}`;
|
||||
await tx`DELETE
|
||||
FROM circle_comments
|
||||
WHERE circle_id = ${id.raw}`;
|
||||
await tx`DELETE
|
||||
FROM circles
|
||||
WHERE id = ${id.raw}`;
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async invite(circleId: CircleId, relatedRecord: PlayerId | UserId | string | undefined, claims: Claims): Promise<void> {
|
||||
if(relatedRecord === undefined) {
|
||||
throw new BadRequestError();
|
||||
}
|
||||
|
||||
const circle = await this.get(circleId, claims);
|
||||
if (
|
||||
claims &&
|
||||
((circle.isPublic && !claims.test(Claims.CIRCLES.PUBLIC.USERS.INVITE)) ||
|
||||
(!circle.isPublic &&
|
||||
!(claims.test(Claims.CIRCLES.OWNED.USERS.INVITE) && circle.owningUserId === claims.userId)))
|
||||
) {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
|
||||
let invitedUserId: UserId | undefined;
|
||||
if (relatedRecord instanceof UserId) {
|
||||
invitedUserId = relatedRecord;
|
||||
} else if (relatedRecord instanceof PlayerId) {
|
||||
invitedUserId = (await orm.users.getByPlayer(relatedRecord))?.id;
|
||||
} else {
|
||||
invitedUserId = (await orm.users.getByEmail(relatedRecord))?.id;
|
||||
}
|
||||
|
||||
if (!invitedUserId) {
|
||||
throw new BadRequestError();
|
||||
}
|
||||
await sql`INSERT INTO circle_invites(invited_user_id, invited_by_user_id, circle_id)
|
||||
VALUES (${invitedUserId.raw}, ${claims.userId.raw}, ${circleId.raw})`;
|
||||
}
|
||||
|
||||
query: (query: string) => Promise<Circle[]> = memo<(query: string) => Promise<Circle[]>, Circle[]>(this.#query);
|
||||
async #query(query: string): Promise<Circle[]> {
|
||||
const dbResult: any = await sql` SELECT
|
||||
id, name, owning_user_id, is_public
|
||||
FROM (SELECT *, SIMILARITY(${query}, name) as similarity FROM circles WHERE is_public=true)
|
||||
WHERE similarity > 0
|
||||
ORDER BY similarity
|
||||
LIMIT 5;`;
|
||||
|
||||
if (!dbResult) {
|
||||
throw new NotFoundError('No matching circles exists');
|
||||
}
|
||||
|
||||
return dbResult.map(
|
||||
(x: { id: string; name: string; owning_user_id: string; is_public: boolean }) =>
|
||||
new Circle({
|
||||
id: CircleId.fromID(x.id),
|
||||
name: x.name,
|
||||
isPublic: x.is_public,
|
||||
owningUserId: UserId.fromID(x.owning_user_id),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user