Minor tidy up and refactor to improve consistency.
This commit is contained in:
@@ -33,9 +33,9 @@ async function login(request: UnwrappedRequest<LoginRequest>): Promise<Response>
|
|||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: true,
|
secure: true,
|
||||||
maxAge: tokenLifeSpanInDays * 24 * 60 * 60,
|
maxAge: tokenLifeSpanInDays * 24 * 60 * 60,
|
||||||
path: '/api/auth/token'
|
path: '/api/auth/token',
|
||||||
});
|
});
|
||||||
return new OkResponse();
|
return new OkResponse({ token });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return new ErrorResponse(error as Error);
|
return new ErrorResponse(error as Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
import { orm } from '../orm/orm';
|
import { orm } from '../orm/orm';
|
||||||
import { UnwrappedRequest } from '../utilities/guard';
|
import { UnwrappedRequest } from '../utilities/guard';
|
||||||
import { CreatedResponse, ErrorResponse, OkResponse, PagedResponse } from '../utilities/responseHelper';
|
import { CreatedResponse, ErrorResponse, OkResponse, PagedResponse } from '../utilities/responseHelper';
|
||||||
import {
|
import { GameToCollectionRequest, CreateCollectionRequest, UpdateCollectionRequest } from '../utilities/requestModels';
|
||||||
GameToCollectionRequest,
|
|
||||||
CreateCollectionRequest,
|
|
||||||
UpdateCollectionRequest,
|
|
||||||
} from '../utilities/requestModels';
|
|
||||||
import { CollectionId, GameId } from '../utilities/secureIds';
|
import { CollectionId, GameId } from '../utilities/secureIds';
|
||||||
|
|
||||||
async function create(request: UnwrappedRequest<CreateCollectionRequest>): Promise<Response> {
|
async function create(request: UnwrappedRequest<CreateCollectionRequest>): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const newPlayer = await orm.collections.create(request.body, request.claims);
|
return new CreatedResponse(await orm.collections.create(request.body, request.claims));
|
||||||
return new CreatedResponse(newPlayer);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return new ErrorResponse(error as Error);
|
return new ErrorResponse(error as Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
import { orm } from '../orm/orm';
|
import { orm } from '../orm/orm';
|
||||||
import { UnwrappedRequest } from '../utilities/guard';
|
import { UnwrappedRequest } from '../utilities/guard';
|
||||||
import { CreatedResponse, ErrorResponse, OkResponse, PagedResponse } from '../utilities/responseHelper';
|
import { CreatedResponse, ErrorResponse, OkResponse, PagedResponse } from '../utilities/responseHelper';
|
||||||
import {
|
import { CreateGameRequest, UpdateGameRequest } from '../utilities/requestModels';
|
||||||
CreateGameRequest,
|
|
||||||
UpdateGameRequest,
|
|
||||||
} from '../utilities/requestModels';
|
|
||||||
import { GameId } from '../utilities/secureIds';
|
import { GameId } from '../utilities/secureIds';
|
||||||
|
|
||||||
async function create(request: UnwrappedRequest<CreateGameRequest>): Promise<Response> {
|
async function create(request: UnwrappedRequest<CreateGameRequest>): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const newUser = await orm.games.create(
|
return new CreatedResponse(
|
||||||
|
await orm.games.create(
|
||||||
{
|
{
|
||||||
name: request.body.name,
|
name: request.body.name,
|
||||||
bggId: request.body.bggId,
|
bggId: request.body.bggId,
|
||||||
imagePath: request.body.imagePath,
|
imagePath: request.body.imagePath,
|
||||||
},
|
},
|
||||||
request.claims,
|
request.claims,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return new CreatedResponse(newUser);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return new ErrorResponse(error as Error);
|
return new ErrorResponse(error as Error);
|
||||||
}
|
}
|
||||||
@@ -33,13 +31,7 @@ async function get(request: UnwrappedRequest): Promise<Response> {
|
|||||||
|
|
||||||
async function update(request: UnwrappedRequest<UpdateGameRequest>): Promise<Response> {
|
async function update(request: UnwrappedRequest<UpdateGameRequest>): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
return new OkResponse(
|
return new OkResponse(await orm.games.update(GameId.fromHash(request.params.id), request.body, request.claims));
|
||||||
await orm.games.update(
|
|
||||||
GameId.fromHash(request.params.id),
|
|
||||||
request.body,
|
|
||||||
request.claims,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return new ErrorResponse(error as Error);
|
return new ErrorResponse(error as Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
import { orm } from '../orm/orm';
|
import { orm } from '../orm/orm';
|
||||||
import { UnwrappedRequest } from '../utilities/guard';
|
import { UnwrappedRequest } from '../utilities/guard';
|
||||||
import { CreatedResponse, ErrorResponse } from '../utilities/responseHelper';
|
import { CreatedResponse, ErrorResponse } from '../utilities/responseHelper';
|
||||||
import {
|
import { AcceptInviteRequest, InviteUserRequest } from '../utilities/requestModels';
|
||||||
AcceptInviteRequest,
|
|
||||||
InviteUserRequest,
|
|
||||||
} from '../utilities/requestModels';
|
|
||||||
import { PlayerId, UserId } from '../utilities/secureIds';
|
import { PlayerId, UserId } from '../utilities/secureIds';
|
||||||
|
|
||||||
async function create(request: UnwrappedRequest<InviteUserRequest>): Promise<Response> {
|
async function create(request: UnwrappedRequest<InviteUserRequest>): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const newUser = await orm.invites.create(
|
return new CreatedResponse(
|
||||||
{
|
await orm.invites.create({
|
||||||
...request.body,
|
...request.body,
|
||||||
playerId: PlayerId.fromHash(request.body.playerId),
|
playerId: PlayerId.fromHash(request.body.playerId),
|
||||||
invitedByUserId: request.claims.userId as UserId,
|
invitedByUserId: request.claims.userId as UserId,
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
return new CreatedResponse(newUser);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return new ErrorResponse(error as Error);
|
return new ErrorResponse(error as Error);
|
||||||
}
|
}
|
||||||
@@ -24,8 +20,7 @@ async function create(request: UnwrappedRequest<InviteUserRequest>): Promise<Res
|
|||||||
|
|
||||||
async function accept(request: UnwrappedRequest<AcceptInviteRequest>): Promise<Response> {
|
async function accept(request: UnwrappedRequest<AcceptInviteRequest>): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const newUser = await orm.invites.accept(request.body);
|
return new CreatedResponse(await orm.invites.accept(request.body));
|
||||||
return new CreatedResponse(newUser);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return new ErrorResponse(error as Error);
|
return new ErrorResponse(error as Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,15 @@ import { MatchParticipant } from '../orm/matches';
|
|||||||
|
|
||||||
async function create(request: UnwrappedRequest<CreateMatchRequest>): Promise<Response> {
|
async function create(request: UnwrappedRequest<CreateMatchRequest>): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const newUser = await orm.matches.create({
|
return new CreatedResponse(
|
||||||
|
await orm.matches.create({
|
||||||
gameId: GameId.fromHash(request.body.gameId),
|
gameId: GameId.fromHash(request.body.gameId),
|
||||||
ownerId: request.claims.userId as UserId,
|
ownerId: request.claims.userId as UserId,
|
||||||
participants: request.body.participants.map(
|
participants: request.body.participants.map(
|
||||||
(x) => new MatchParticipant(PlayerId.fromHash(x.playerId), x.standing),
|
(x) => new MatchParticipant(PlayerId.fromHash(x.playerId), x.standing),
|
||||||
),
|
),
|
||||||
});
|
}),
|
||||||
return new CreatedResponse(newUser);
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return new ErrorResponse(error as Error);
|
return new ErrorResponse(error as Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import { PlayerId } from '../utilities/secureIds';
|
|||||||
|
|
||||||
async function create(request: UnwrappedRequest<CreatePlayerRequest>): Promise<Response> {
|
async function create(request: UnwrappedRequest<CreatePlayerRequest>): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const newPlayer = await orm.players.create(request.body);
|
return new CreatedResponse(await orm.players.create(request.body));
|
||||||
return new CreatedResponse(newPlayer);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return new ErrorResponse(error as Error);
|
return new ErrorResponse(error as Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,12 @@ import { PlayerId, UserId } from '../utilities/secureIds';
|
|||||||
|
|
||||||
async function create(request: UnwrappedRequest<CreateUserRequest>): Promise<Response> {
|
async function create(request: UnwrappedRequest<CreateUserRequest>): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
const newUser = await orm.users.create(
|
return new CreatedResponse(
|
||||||
{
|
await orm.users.create({
|
||||||
...request.body,
|
...request.body,
|
||||||
playerId: PlayerId.fromHash(request.body.playerId),
|
playerId: PlayerId.fromHash(request.body.playerId),
|
||||||
}
|
}),
|
||||||
);
|
);
|
||||||
return new CreatedResponse(newUser);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return new ErrorResponse(error as Error);
|
return new ErrorResponse(error as Error);
|
||||||
}
|
}
|
||||||
@@ -28,9 +27,7 @@ async function get(request: UnwrappedRequest): Promise<Response> {
|
|||||||
|
|
||||||
async function update(request: UnwrappedRequest<UpdateUserRequest>): Promise<Response> {
|
async function update(request: UnwrappedRequest<UpdateUserRequest>): Promise<Response> {
|
||||||
try {
|
try {
|
||||||
return new OkResponse(
|
return new OkResponse(await orm.users.update(UserId.fromHash(request.params.id), request.body, request.claims));
|
||||||
await orm.users.update(UserId.fromHash(request.params.id), request.body, request.claims),
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return new ErrorResponse(error as Error);
|
return new ErrorResponse(error as Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,20 +51,20 @@ export class CircleOrm {
|
|||||||
${model.name},
|
${model.name},
|
||||||
${model.isPublic},
|
${model.isPublic},
|
||||||
${model.colour})`;
|
${model.colour})`;
|
||||||
const newCircleId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
const newRecordId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
||||||
|
|
||||||
return await this.get(CircleId.fromID(newCircleId));
|
return await this.get(CircleId.fromID(newRecordId));
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(id: CircleId, claims?: Claims): Promise<Circle> {
|
async get(id: CircleId, claims?: Claims): Promise<Circle> {
|
||||||
const circleResult: any = first(
|
const record: any = first(
|
||||||
await sql`SELECT *
|
await sql`SELECT *
|
||||||
FROM circles
|
FROM circles
|
||||||
WHERE id = ${id.raw}
|
WHERE id = ${id.raw}
|
||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!circleResult) {
|
if (!record) {
|
||||||
throw new NotFoundError('No matching game exists');
|
throw new NotFoundError('No matching game exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,8 +72,8 @@ export class CircleOrm {
|
|||||||
if (
|
if (
|
||||||
claims &&
|
claims &&
|
||||||
!claims.test(Claims.ADMIN) &&
|
!claims.test(Claims.ADMIN) &&
|
||||||
!(claims.test(Claims.CIRCLES.PUBLIC.READ) && circleResult.is_public) &&
|
!(claims.test(Claims.CIRCLES.PUBLIC.READ) && record.is_public) &&
|
||||||
!(claims.test(Claims.CIRCLES.OWNED.READ) && circleResult.owning_user_id === claims.userId.raw) &&
|
!(claims.test(Claims.CIRCLES.OWNED.READ) && record.owning_user_id === claims.userId.raw) &&
|
||||||
!(
|
!(
|
||||||
claims.test(Claims.CIRCLES.PRIVATE.READ_IF_MEMBER) &&
|
claims.test(Claims.CIRCLES.PRIVATE.READ_IF_MEMBER) &&
|
||||||
(user = await orm.users.get(claims.userId)) &&
|
(user = await orm.users.get(claims.userId)) &&
|
||||||
@@ -86,25 +86,25 @@ export class CircleOrm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Circle({
|
return new Circle({
|
||||||
id: CircleId.fromID(circleResult.id),
|
id: CircleId.fromID(record.id),
|
||||||
owningUserId: UserId.fromID(circleResult.owning_user_id),
|
owningUserId: UserId.fromID(record.owning_user_id),
|
||||||
name: circleResult.name,
|
name: record.name,
|
||||||
isPublic: circleResult.is_public,
|
isPublic: record.is_public,
|
||||||
imagePath: circleResult.image_path,
|
imagePath: record.image_path,
|
||||||
colour: circleResult.colour,
|
colour: record.colour,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: CircleId, patch: UpdateCircleRequest): Promise<Circle> {
|
async update(id: CircleId, patch: UpdateCircleRequest): Promise<Circle> {
|
||||||
const recordToUpdate = await this.get(id);
|
const circle = await this.get(id);
|
||||||
recordToUpdate.name = patch.name ?? recordToUpdate.name;
|
circle.name = patch.name ?? circle.name;
|
||||||
recordToUpdate.colour = patch.colour ?? recordToUpdate.colour;
|
circle.colour = patch.colour ?? circle.colour;
|
||||||
recordToUpdate.imagePath = patch.imagePath ?? recordToUpdate.imagePath;
|
circle.imagePath = patch.imagePath ?? circle.imagePath;
|
||||||
|
|
||||||
await sql`UPDATE circles
|
await sql`UPDATE circles
|
||||||
SET name=${recordToUpdate.name},
|
SET name=${circle.name},
|
||||||
colour=${recordToUpdate.colour},
|
colour=${circle.colour},
|
||||||
image_path=${recordToUpdate.imagePath}
|
image_path=${circle.imagePath}
|
||||||
WHERE id = ${id.raw}`;
|
WHERE id = ${id.raw}`;
|
||||||
|
|
||||||
return await this.get(id);
|
return await this.get(id);
|
||||||
@@ -131,7 +131,11 @@ export class CircleOrm {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async invite(circleId: CircleId, relatedRecord: PlayerId | UserId | string | undefined, claims: Claims): Promise<void> {
|
async invite(
|
||||||
|
circleId: CircleId,
|
||||||
|
relatedRecord: PlayerId | UserId | string | undefined,
|
||||||
|
claims: Claims,
|
||||||
|
): Promise<void> {
|
||||||
if (relatedRecord === undefined) {
|
if (relatedRecord === undefined) {
|
||||||
throw new BadRequestError();
|
throw new BadRequestError();
|
||||||
}
|
}
|
||||||
@@ -164,18 +168,18 @@ export class CircleOrm {
|
|||||||
|
|
||||||
query: (query: string) => Promise<Circle[]> = memo<(query: string) => Promise<Circle[]>, Circle[]>(this.#query);
|
query: (query: string) => Promise<Circle[]> = memo<(query: string) => Promise<Circle[]>, Circle[]>(this.#query);
|
||||||
async #query(query: string): Promise<Circle[]> {
|
async #query(query: string): Promise<Circle[]> {
|
||||||
const dbResult: any = await sql` SELECT
|
const queryResult: any = await sql` SELECT
|
||||||
id, name, owning_user_id, is_public
|
id, name, owning_user_id, is_public
|
||||||
FROM (SELECT *, SIMILARITY(${query}, name) as similarity FROM circles WHERE is_public=true)
|
FROM (SELECT *, SIMILARITY(${query}, name) as similarity FROM circles WHERE is_public=true)
|
||||||
WHERE similarity > 0
|
WHERE similarity > 0
|
||||||
ORDER BY similarity
|
ORDER BY similarity
|
||||||
LIMIT 5;`;
|
LIMIT 5;`;
|
||||||
|
|
||||||
if (!dbResult) {
|
if (!queryResult) {
|
||||||
throw new NotFoundError('No matching circles exists');
|
throw new NotFoundError('No matching circles exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
return dbResult.map(
|
return queryResult.map(
|
||||||
(x: { id: string; name: string; owning_user_id: string; is_public: boolean }) =>
|
(x: { id: string; name: string; owning_user_id: string; is_public: boolean }) =>
|
||||||
new Circle({
|
new Circle({
|
||||||
id: CircleId.fromID(x.id),
|
id: CircleId.fromID(x.id),
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class Claims extends ClaimDefinition {
|
|||||||
constructor(raw?: { userId?: string | UserId; claims?: string[] }) {
|
constructor(raw?: { userId?: string | UserId; claims?: string[] }) {
|
||||||
super();
|
super();
|
||||||
if (raw?.userId instanceof UserId) {
|
if (raw?.userId instanceof UserId) {
|
||||||
this.userId = raw.userId
|
this.userId = raw.userId;
|
||||||
} else {
|
} else {
|
||||||
this.userId = UserId.fromHash(raw?.userId ?? '');
|
this.userId = UserId.fromHash(raw?.userId ?? '');
|
||||||
}
|
}
|
||||||
@@ -30,20 +30,20 @@ export class Claims extends ClaimDefinition {
|
|||||||
|
|
||||||
export class ClaimsOrm {
|
export class ClaimsOrm {
|
||||||
async getByUserId(userId: UserId): Promise<Claims> {
|
async getByUserId(userId: UserId): Promise<Claims> {
|
||||||
const dbResults: any[] = await sql`SELECT c.name
|
const records: any[] = await sql`SELECT c.name
|
||||||
from user_claims as uc
|
from user_claims as uc
|
||||||
JOIN claims as c on uc.claim_id = c.id
|
JOIN claims as c on uc.claim_id = c.id
|
||||||
where uc.user_id = ${userId.raw};`;
|
where uc.user_id = ${userId.raw};`;
|
||||||
return new Claims({
|
return new Claims({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
claims: dbResults.map((x) => x.name),
|
claims: records.map((x) => x.name),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDefaultClaims(): Promise<number[]> {
|
async getDefaultClaims(): Promise<number[]> {
|
||||||
const dbResults: any[] = await sql`SELECT id
|
const records: any[] = await sql`SELECT id
|
||||||
FROM claims
|
FROM claims
|
||||||
WHERE is_default = true;`;
|
WHERE is_default = true;`;
|
||||||
return dbResults.map((x) => x.id);
|
return records.map((x) => x.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ export class CollectionsOrm {
|
|||||||
async create(model: { name: string }, claims: Claims): Promise<Collection> {
|
async create(model: { name: string }, claims: Claims): Promise<Collection> {
|
||||||
await sql`INSERT INTO collections (name, user_id)
|
await sql`INSERT INTO collections (name, user_id)
|
||||||
VALUES (${model.name}, ${claims?.userId?.raw})`;
|
VALUES (${model.name}, ${claims?.userId?.raw})`;
|
||||||
const newPCollectionId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
const newRecordId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
||||||
|
|
||||||
return await this.get(CollectionId.fromID(newPCollectionId));
|
return await this.get(CollectionId.fromID(newRecordId));
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(id: CollectionId, claims?: Claims): Promise<Collection> {
|
async get(id: CollectionId, claims?: Claims): Promise<Collection> {
|
||||||
const dbResult: any = await sql`SELECT
|
const records: any = await sql`SELECT
|
||||||
c.id AS collection_id,
|
c.id AS collection_id,
|
||||||
c.name AS collection_name,
|
c.name AS collection_name,
|
||||||
c.user_id AS user_id,
|
c.user_id AS user_id,
|
||||||
@@ -45,21 +45,21 @@ export class CollectionsOrm {
|
|||||||
!(
|
!(
|
||||||
claims.test(Claims.ADMIN, Claims.COLLECTIONS.UNOWNED.READ) ||
|
claims.test(Claims.ADMIN, Claims.COLLECTIONS.UNOWNED.READ) ||
|
||||||
(Claims.test(claims, Claims.COLLECTIONS.OWNED.READ) &&
|
(Claims.test(claims, Claims.COLLECTIONS.OWNED.READ) &&
|
||||||
dbResult?.[0]?.user_id === claims?.userId?.raw)
|
records?.[0]?.user_id === claims?.userId?.raw)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dbResult?.length) {
|
if (!records?.length) {
|
||||||
throw new NotFoundError('No matching player exists');
|
throw new NotFoundError('No matching player exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Collection({
|
return new Collection({
|
||||||
id: CollectionId.fromID(dbResult[0].collection_id),
|
id: CollectionId.fromID(records[0].collection_id),
|
||||||
name: dbResult[0].collection_name,
|
name: records[0].collection_name,
|
||||||
ownerId: UserId.fromID(dbResult[0].user_id),
|
ownerId: UserId.fromID(records[0].user_id),
|
||||||
games: dbResult
|
games: records
|
||||||
.filter((x: { game_id: string; game_name: string }) => x.game_id)
|
.filter((x: { game_id: string; game_name: string }) => x.game_id)
|
||||||
.map(
|
.map(
|
||||||
(x: { game_id: string; game_name: string }) =>
|
(x: { game_id: string; game_name: string }) =>
|
||||||
|
|||||||
@@ -24,44 +24,44 @@ export class GamesOrm {
|
|||||||
async create(model: CreateGameRequest, claims?: Claims): Promise<Game> {
|
async create(model: CreateGameRequest, claims?: Claims): Promise<Game> {
|
||||||
await sql`INSERT INTO games (name, image_path, bgg_id)
|
await sql`INSERT INTO games (name, image_path, bgg_id)
|
||||||
VALUES (${model.name}, ${claims?.test(Claims.GAMES.MANAGE_IMAGES) ? model.imagePath : null}, ${model.bggId})`;
|
VALUES (${model.name}, ${claims?.test(Claims.GAMES.MANAGE_IMAGES) ? model.imagePath : null}, ${model.bggId})`;
|
||||||
const newGameId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
const newRecordId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
||||||
|
|
||||||
return await this.get(GameId.fromID(newGameId));
|
return await this.get(GameId.fromID(newRecordId));
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(id: GameId): Promise<Game> {
|
async get(id: GameId): Promise<Game> {
|
||||||
const dbResult: any = first(
|
const record: any = first(
|
||||||
await sql`SELECT *
|
await sql`SELECT *
|
||||||
FROM games
|
FROM games
|
||||||
WHERE id = ${id.raw}
|
WHERE id = ${id.raw}
|
||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!dbResult) {
|
if (!record) {
|
||||||
throw new NotFoundError('No matching game exists');
|
throw new NotFoundError('No matching game exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Game({
|
return new Game({
|
||||||
id: GameId.fromID(dbResult.id),
|
id: GameId.fromID(record.id),
|
||||||
name: dbResult.name,
|
name: record.name,
|
||||||
bggId: dbResult.bgg_id,
|
bggId: record.bgg_id,
|
||||||
imagePath: dbResult.image_path,
|
imagePath: record.image_path,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: GameId, patch: UpdateGameRequest, claims?: Claims): Promise<Game> {
|
async update(id: GameId, patch: UpdateGameRequest, claims?: Claims): Promise<Game> {
|
||||||
const gameToUpdate = await this.get(id);
|
const game = await this.get(id);
|
||||||
gameToUpdate.name = patch.name ?? gameToUpdate.name;
|
game.name = patch.name ?? game.name;
|
||||||
gameToUpdate.bggId = patch.bggId ?? gameToUpdate.bggId;
|
game.bggId = patch.bggId ?? game.bggId;
|
||||||
|
|
||||||
if (claims?.test(Claims.GAMES.MANAGE_IMAGES)) {
|
if (claims?.test(Claims.GAMES.MANAGE_IMAGES)) {
|
||||||
gameToUpdate.imagePath = patch.imagePath ?? gameToUpdate.imagePath;
|
game.imagePath = patch.imagePath ?? game.imagePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sql`UPDATE games
|
await sql`UPDATE games
|
||||||
SET name=${gameToUpdate.name},
|
SET name=${game.name},
|
||||||
bgg_id=${gameToUpdate.bggId},
|
bgg_id=${game.bggId},
|
||||||
image_path=${gameToUpdate.imagePath}
|
image_path=${game.imagePath}
|
||||||
WHERE id = ${id.raw}`;
|
WHERE id = ${id.raw}`;
|
||||||
|
|
||||||
return await this.get(id);
|
return await this.get(id);
|
||||||
@@ -90,18 +90,18 @@ export class GamesOrm {
|
|||||||
|
|
||||||
query: (query: string) => Promise<Game[]> = memo<(query: string) => Promise<Game[]>, Game[]>(this.#query);
|
query: (query: string) => Promise<Game[]> = memo<(query: string) => Promise<Game[]>, Game[]>(this.#query);
|
||||||
async #query(query: string): Promise<Game[]> {
|
async #query(query: string): Promise<Game[]> {
|
||||||
const dbResult: any = await sql` SELECT
|
const records: any = await sql` SELECT
|
||||||
id, name
|
id, name
|
||||||
FROM (SELECT *, SIMILARITY(${query}, name) as similarity FROM games)
|
FROM (SELECT *, SIMILARITY(${query}, name) as similarity FROM games)
|
||||||
WHERE similarity > 0
|
WHERE similarity > 0
|
||||||
ORDER BY similarity
|
ORDER BY similarity
|
||||||
LIMIT 5;`;
|
LIMIT 5;`;
|
||||||
|
|
||||||
if (!dbResult) {
|
if (!records) {
|
||||||
throw new NotFoundError('No matching game exists');
|
throw new NotFoundError('No matching game exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
return dbResult.map(
|
return records.map(
|
||||||
(x: { id: string; name: string }) =>
|
(x: { id: string; name: string }) =>
|
||||||
new Game({
|
new Game({
|
||||||
id: GameId.fromID(x.id),
|
id: GameId.fromID(x.id),
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class InvitesOrm {
|
|||||||
throw new UnauthorizedError('Invite allowance reached.');
|
throw new UnauthorizedError('Invite allowance reached.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const inviteExists = (
|
const doesInviteExist = (
|
||||||
first(
|
first(
|
||||||
await sql`SELECT COUNT(*) > 0 AS exists
|
await sql`SELECT COUNT(*) > 0 AS exists
|
||||||
FROM user_invites
|
FROM user_invites
|
||||||
@@ -45,12 +45,12 @@ export class InvitesOrm {
|
|||||||
exists: boolean;
|
exists: boolean;
|
||||||
}
|
}
|
||||||
)?.exists;
|
)?.exists;
|
||||||
if (inviteExists) {
|
if (doesInviteExist) {
|
||||||
throw new BadRequestError('Player has already been invited.');
|
throw new BadRequestError('Player has already been invited.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const playerHasUser = (
|
const isPlayerAssociatedWithUser = (
|
||||||
first(
|
first(
|
||||||
await sql`SELECT COUNT(*) > 0 AS exists
|
await sql`SELECT COUNT(*) > 0 AS exists
|
||||||
FROM users
|
FROM users
|
||||||
@@ -60,7 +60,7 @@ export class InvitesOrm {
|
|||||||
exists: boolean;
|
exists: boolean;
|
||||||
}
|
}
|
||||||
)?.exists;
|
)?.exists;
|
||||||
if (playerHasUser) {
|
if (isPlayerAssociatedWithUser) {
|
||||||
throw new BadRequestError('User has already been invited.');
|
throw new BadRequestError('User has already been invited.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ export class InvitesOrm {
|
|||||||
const invitationCode = createRandomString(6);
|
const invitationCode = createRandomString(6);
|
||||||
await sql`INSERT INTO user_invites (invite_code, email, player_id, invited_by_user_id)
|
await sql`INSERT INTO user_invites (invite_code, email, player_id, invited_by_user_id)
|
||||||
VALUES (${invitationCode}, ${email}, ${playerId.raw}, ${invitedByUserId.raw})`;
|
VALUES (${invitationCode}, ${email}, ${playerId.raw}, ${invitedByUserId.raw})`;
|
||||||
const newInviteId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
const newRecordId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
||||||
|
|
||||||
const resend = new Resend(process.env.RESEND_KEY);
|
const resend = new Resend(process.env.RESEND_KEY);
|
||||||
const resendResponse = await resend.emails.send({
|
const resendResponse = await resend.emails.send({
|
||||||
@@ -83,51 +83,51 @@ export class InvitesOrm {
|
|||||||
throw new InternalServerError();
|
throw new InternalServerError();
|
||||||
}
|
}
|
||||||
|
|
||||||
await sql`UPDATE user_invites SET was_email_sent = true WHERE id=${newInviteId}`;
|
await sql`UPDATE user_invites SET was_email_sent = true WHERE id=${newRecordId}`;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async accept({ inviteCode, password }: { inviteCode: string; password: string }): Promise<User> {
|
async accept({ inviteCode, password }: { inviteCode: string; password: string }): Promise<User> {
|
||||||
const invite: {
|
const record: {
|
||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
player_id: string;
|
player_id: string;
|
||||||
accepted: boolean;
|
accepted: boolean;
|
||||||
} = first(await sql`SELECT * FROM user_invites WHERE invite_code=${inviteCode} LIMIT 1`);
|
} = first(await sql`SELECT * FROM user_invites WHERE invite_code=${inviteCode} LIMIT 1`);
|
||||||
|
|
||||||
if (!invite) {
|
if (!record) {
|
||||||
throw new NotFoundError('Invalid invite code');
|
throw new NotFoundError('Invalid invite code');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invite.accepted) {
|
if (record.accepted) {
|
||||||
throw new UnauthorizedError('Invite already accepted');
|
throw new UnauthorizedError('Invite already accepted');
|
||||||
}
|
}
|
||||||
|
|
||||||
const playerHasUser = (
|
const isPlayerAssociatedWithUser = (
|
||||||
first(
|
first(
|
||||||
await sql`SELECT COUNT(*) > 0 AS exists
|
await sql`SELECT COUNT(*) > 0 AS exists
|
||||||
FROM users
|
FROM users
|
||||||
WHERE player_id = ${invite.player_id}
|
WHERE player_id = ${record.player_id}
|
||||||
OR email = ${invite.email}`,
|
OR email = ${record.email}`,
|
||||||
) as {
|
) as {
|
||||||
exists: boolean;
|
exists: boolean;
|
||||||
}
|
}
|
||||||
)?.exists;
|
)?.exists;
|
||||||
if (playerHasUser) {
|
if (isPlayerAssociatedWithUser) {
|
||||||
throw new BadRequestError('User has already been invited.');
|
throw new BadRequestError('User has already been invited.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdUser = await orm.users.create({
|
const user = await orm.users.create({
|
||||||
email: invite.email,
|
email: record.email,
|
||||||
playerId: PlayerId.fromID(invite.player_id),
|
playerId: PlayerId.fromID(record.player_id),
|
||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
|
|
||||||
await sql`UPDATE user_invites
|
await sql`UPDATE user_invites
|
||||||
SET accepted = true
|
SET accepted = true
|
||||||
WHERE id = ${invite.id}`;
|
WHERE id = ${record.id}`;
|
||||||
|
|
||||||
return createdUser;
|
return user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,10 +176,11 @@ export class MatchOrm {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async removePlayer(matchId: MatchId, participantId: UserId): Promise<void>;
|
async removePlayer(matchId: MatchId, participantId: UserId | PlayerId): Promise<void> {
|
||||||
async removePlayer(matchId: MatchId, participantId: PlayerId): Promise<void> {
|
let playerId: PlayerId;
|
||||||
let playerId: PlayerId = participantId;
|
if (participantId instanceof PlayerId) {
|
||||||
if (participantId instanceof UserId) {
|
playerId = participantId;
|
||||||
|
} else {
|
||||||
playerId = (await orm.users.get(participantId))?.playerId;
|
playerId = (await orm.users.get(participantId))?.playerId;
|
||||||
if (!playerId) {
|
if (!playerId) {
|
||||||
throw new BadRequestError('User is not a participant');
|
throw new BadRequestError('User is not a participant');
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ export class PlayersOrm {
|
|||||||
async create(model: { name: string }): Promise<Player> {
|
async create(model: { name: string }): Promise<Player> {
|
||||||
await sql`INSERT INTO players (name)
|
await sql`INSERT INTO players (name)
|
||||||
VALUES (${model.name})`;
|
VALUES (${model.name})`;
|
||||||
const newPlayerId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
const newRecordId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
||||||
|
|
||||||
return await this.get(PlayerId.fromID(newPlayerId));
|
return await this.get(PlayerId.fromID(newRecordId));
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(id: PlayerId, claims?: Claims): Promise<Player> {
|
async get(id: PlayerId, claims?: Claims): Promise<Player> {
|
||||||
@@ -47,23 +47,23 @@ export class PlayersOrm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbResult: any = first(
|
const record: any = first(
|
||||||
await sql`SELECT *
|
await sql`SELECT *
|
||||||
FROM players
|
FROM players
|
||||||
WHERE id = ${id.raw}
|
WHERE id = ${id.raw}
|
||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!dbResult) {
|
if (!record) {
|
||||||
throw new NotFoundError('No matching player exists');
|
throw new NotFoundError('No matching player exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Player({
|
return new Player({
|
||||||
id: PlayerId.fromID(dbResult.id),
|
id: PlayerId.fromID(record.id),
|
||||||
name: dbResult.name,
|
name: record.name,
|
||||||
elo: parseInt(dbResult.elo),
|
elo: parseInt(record.elo),
|
||||||
isRatingLocked: dbResult.is_rating_locked,
|
isRatingLocked: record.is_rating_locked,
|
||||||
canBeMultiple: dbResult.can_be_multiple,
|
canBeMultiple: record.can_be_multiple,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,15 +119,15 @@ export class PlayersOrm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const playerToUpdate = await this.get(id);
|
const player = await this.get(id);
|
||||||
playerToUpdate.name = patch.name ?? playerToUpdate.name;
|
player.name = patch.name ?? player.name;
|
||||||
playerToUpdate.isRatingLocked = patch.isRatingLocked ?? playerToUpdate.isRatingLocked;
|
player.isRatingLocked = patch.isRatingLocked ?? player.isRatingLocked;
|
||||||
playerToUpdate.canBeMultiple = patch.canBeMultiple ?? playerToUpdate.canBeMultiple;
|
player.canBeMultiple = patch.canBeMultiple ?? player.canBeMultiple;
|
||||||
|
|
||||||
await sql`UPDATE players
|
await sql`UPDATE players
|
||||||
SET name=${playerToUpdate.name},
|
SET name=${player.name},
|
||||||
is_rating_locked=${playerToUpdate.isRatingLocked},
|
is_rating_locked=${player.isRatingLocked},
|
||||||
can_be_multiple=${playerToUpdate.canBeMultiple}
|
can_be_multiple=${player.canBeMultiple}
|
||||||
WHERE id = ${id.raw}`;
|
WHERE id = ${id.raw}`;
|
||||||
|
|
||||||
return await this.get(id);
|
return await this.get(id);
|
||||||
|
|||||||
@@ -47,15 +47,15 @@ export class UsersOrm {
|
|||||||
const passwordHash = await argon2.hash(password);
|
const passwordHash = await argon2.hash(password);
|
||||||
await sql`INSERT INTO users (email, pass_hash, player_id)
|
await sql`INSERT INTO users (email, pass_hash, player_id)
|
||||||
VALUES (${email}, ${passwordHash}, ${playerId.raw})`;
|
VALUES (${email}, ${passwordHash}, ${playerId.raw})`;
|
||||||
const newUserId: UserId = UserId.fromID((first(await sql`SELECT lastval();`) as any)?.lastval as string);
|
const newRecordId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
||||||
await sql.transaction(async (tx) => {
|
await sql.transaction(async (tx) => {
|
||||||
for (let i in defaultClaims) {
|
for (let i in defaultClaims) {
|
||||||
await tx`INSERT INTO user_claims (user_id, claim_id)
|
await tx`INSERT INTO user_claims (user_id, claim_id)
|
||||||
VALUES (${newUserId.raw}, ${defaultClaims[i]})`;
|
VALUES (${newRecordId}, ${defaultClaims[i]})`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return await this.get(newUserId);
|
return await this.get(UserId.fromID(newRecordId));
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(id: UserId, claims?: Claims): Promise<User> {
|
async get(id: UserId, claims?: Claims): Promise<User> {
|
||||||
@@ -69,7 +69,7 @@ export class UsersOrm {
|
|||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbResult: any = first(
|
const record: any = first(
|
||||||
await sql`SELECT *
|
await sql`SELECT *
|
||||||
FROM users
|
FROM users
|
||||||
WHERE id = ${id.raw}
|
WHERE id = ${id.raw}
|
||||||
@@ -77,21 +77,20 @@ export class UsersOrm {
|
|||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!dbResult) {
|
if (!record) {
|
||||||
throw new NotFoundError('No matching user exists');
|
throw new NotFoundError('No matching user exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
return new User(
|
return new User(
|
||||||
UserId.fromID(dbResult.id),
|
UserId.fromID(record.id),
|
||||||
PlayerId.fromID(dbResult.player_id),
|
PlayerId.fromID(record.player_id),
|
||||||
dbResult.email,
|
record.email,
|
||||||
dbResult.is_admin,
|
record.is_admin,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getByPlayer(id: PlayerId, claims?: Claims): Promise<User> {
|
async getByPlayer(id: PlayerId, claims?: Claims): Promise<User> {
|
||||||
|
const record: any = first(
|
||||||
const dbResult: any = first(
|
|
||||||
await sql`SELECT *
|
await sql`SELECT *
|
||||||
FROM users
|
FROM users
|
||||||
WHERE player_id = ${id.raw}
|
WHERE player_id = ${id.raw}
|
||||||
@@ -99,7 +98,7 @@ export class UsersOrm {
|
|||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!dbResult) {
|
if (!record) {
|
||||||
throw new NotFoundError('No matching user exists');
|
throw new NotFoundError('No matching user exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,22 +106,22 @@ export class UsersOrm {
|
|||||||
claims &&
|
claims &&
|
||||||
!(
|
!(
|
||||||
claims.test(Claims.ADMIN, Claims.USERS.OTHER.READ) ||
|
claims.test(Claims.ADMIN, Claims.USERS.OTHER.READ) ||
|
||||||
(claims.test(Claims.USERS.SELF.READ) && dbResult.id === claims?.userId.raw)
|
(claims.test(Claims.USERS.SELF.READ) && record.id === claims?.userId.raw)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new User(
|
return new User(
|
||||||
UserId.fromID(dbResult.id),
|
UserId.fromID(record.id),
|
||||||
PlayerId.fromID(dbResult.player_id),
|
PlayerId.fromID(record.player_id),
|
||||||
dbResult.email,
|
record.email,
|
||||||
dbResult.is_admin,
|
record.is_admin,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getByEmail(email: string, claims?: Claims): Promise<User> {
|
async getByEmail(email: string, claims?: Claims): Promise<User> {
|
||||||
const dbResult: any = first(
|
const record: any = first(
|
||||||
await sql`SELECT *
|
await sql`SELECT *
|
||||||
FROM users
|
FROM users
|
||||||
WHERE email = ${email}
|
WHERE email = ${email}
|
||||||
@@ -130,7 +129,7 @@ export class UsersOrm {
|
|||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!dbResult) {
|
if (!record) {
|
||||||
throw new NotFoundError('No matching user exists');
|
throw new NotFoundError('No matching user exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,17 +137,17 @@ export class UsersOrm {
|
|||||||
claims &&
|
claims &&
|
||||||
!(
|
!(
|
||||||
claims.test(Claims.ADMIN, Claims.USERS.OTHER.READ) ||
|
claims.test(Claims.ADMIN, Claims.USERS.OTHER.READ) ||
|
||||||
(claims.test(Claims.USERS.SELF.READ) && dbResult.id === claims?.userId.raw)
|
(claims.test(Claims.USERS.SELF.READ) && record.id === claims?.userId.raw)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new User(
|
return new User(
|
||||||
UserId.fromID(dbResult.id),
|
UserId.fromID(record.id),
|
||||||
PlayerId.fromID(dbResult.player_id),
|
PlayerId.fromID(record.player_id),
|
||||||
dbResult.email,
|
record.email,
|
||||||
dbResult.is_admin,
|
record.is_admin,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,15 +162,15 @@ export class UsersOrm {
|
|||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
const userToUpdate = await this.get(id);
|
const user = await this.get(id);
|
||||||
if (!claims || claims.test(Claims.ADMIN)) {
|
if (!claims || claims.test(Claims.ADMIN)) {
|
||||||
userToUpdate.isActive = patch.isActive ?? userToUpdate.isActive;
|
user.isActive = patch.isActive ?? user.isActive;
|
||||||
userToUpdate.isAdmin = patch.isAdmin ?? userToUpdate.isAdmin;
|
user.isAdmin = patch.isAdmin ?? user.isAdmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sql`UPDATE users
|
await sql`UPDATE users
|
||||||
SET is_active=${userToUpdate.isActive},
|
SET is_active=${user.isActive},
|
||||||
is_admin=${userToUpdate.isAdmin}
|
is_admin=${user.isAdmin}
|
||||||
WHERE id = ${id.raw}`;
|
WHERE id = ${id.raw}`;
|
||||||
|
|
||||||
return await this.get(id);
|
return await this.get(id);
|
||||||
@@ -203,35 +202,35 @@ export class UsersOrm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async verifyCredentials(email: string, password: string): Promise<{ userId: UserId; refreshCount: string } | null> {
|
async verifyCredentials(email: string, password: string): Promise<{ userId: UserId; refreshCount: string } | null> {
|
||||||
const dbResult: any = first(
|
const record: any = first(
|
||||||
await sql`SELECT *
|
await sql`SELECT *
|
||||||
FROM users
|
FROM users
|
||||||
WHERE email = ${email}
|
WHERE email = ${email}
|
||||||
AND is_active = true
|
AND is_active = true
|
||||||
limit 1`,
|
limit 1`,
|
||||||
);
|
);
|
||||||
if (!dbResult) {
|
if (!record) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await argon2.verify(dbResult.pass_hash, password))) {
|
if (!(await argon2.verify(record.pass_hash, password))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
userId: UserId.fromID(dbResult.id),
|
userId: UserId.fromID(record.id),
|
||||||
refreshCount: dbResult.refresh_count,
|
refreshCount: record.refresh_count,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyRefreshCount(id: UserId, refreshCount: string): Promise<boolean> {
|
async verifyRefreshCount(id: UserId, refreshCount: string): Promise<boolean> {
|
||||||
const dbResult: any = first(
|
const record: any = first(
|
||||||
await sql`SELECT *
|
await sql`SELECT *
|
||||||
FROM users
|
FROM users
|
||||||
WHERE id = ${id.raw}
|
WHERE id = ${id.raw}
|
||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
);
|
);
|
||||||
return dbResult.refresh_count === refreshCount;
|
return record.refresh_count === refreshCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
async changePassword(id: UserId, oldPassword: string | null, newPassword: string, claims?: Claims): Promise<void> {
|
async changePassword(id: UserId, oldPassword: string | null, newPassword: string, claims?: Claims): Promise<void> {
|
||||||
@@ -244,14 +243,14 @@ export class UsersOrm {
|
|||||||
throw new BadRequestError('Password is required');
|
throw new BadRequestError('Password is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbUser: any = first(
|
const record: any = first(
|
||||||
await sql`SELECT *
|
await sql`SELECT *
|
||||||
FROM users
|
FROM users
|
||||||
WHERE id = ${id.raw}
|
WHERE id = ${id.raw}
|
||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isAdmin && !(await argon2.verify(dbUser.pass_hash, oldPassword as string))) {
|
if (!isAdmin && !(await argon2.verify(record.pass_hash, oldPassword as string))) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default {
|
|||||||
},
|
},
|
||||||
changePassword: {
|
changePassword: {
|
||||||
':id': {
|
':id': {
|
||||||
PATCH: guard(auth.changePassword, [Claims.ADMIN, Claims.USERS.SELF.UPDATE]),
|
PATCH: guard(auth.changePassword, Claims.ADMIN, Claims.USERS.SELF.UPDATE),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,28 +3,30 @@ import { Claims } from '../orm/claims';
|
|||||||
import circles from '../endpoints/circles';
|
import circles from '../endpoints/circles';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'POST': guard(circles.create, [Claims.ADMIN, Claims.CIRCLES.PUBLIC.CREATE, Claims.CIRCLES.PRIVATE.CREATE]),
|
'POST': guard(circles.create, Claims.ADMIN, Claims.CIRCLES.PUBLIC.CREATE, Claims.CIRCLES.PRIVATE.CREATE),
|
||||||
':id': {
|
':id': {
|
||||||
GET: guard(circles.get, [
|
GET: guard(
|
||||||
|
circles.get,
|
||||||
Claims.ADMIN,
|
Claims.ADMIN,
|
||||||
Claims.CIRCLES.PUBLIC.READ,
|
Claims.CIRCLES.PUBLIC.READ,
|
||||||
Claims.CIRCLES.PRIVATE.READ,
|
Claims.CIRCLES.PRIVATE.READ,
|
||||||
Claims.CIRCLES.PRIVATE.READ_IF_MEMBER,
|
Claims.CIRCLES.PRIVATE.READ_IF_MEMBER,
|
||||||
]),
|
),
|
||||||
PATCH: guard(circles.update, [Claims.ADMIN, Claims.CIRCLES.OWNED.UPDATE]),
|
PATCH: guard(circles.update, Claims.ADMIN, Claims.CIRCLES.OWNED.UPDATE),
|
||||||
DELETE: guard(circles.drop, [Claims.ADMIN, Claims.CIRCLES.OWNED.DELETE]),
|
DELETE: guard(circles.drop, Claims.ADMIN, Claims.CIRCLES.OWNED.DELETE),
|
||||||
invite: {
|
invite: {
|
||||||
POST: guard(circles.invite, [
|
POST: guard(
|
||||||
|
circles.invite,
|
||||||
Claims.ADMIN,
|
Claims.ADMIN,
|
||||||
Claims.CIRCLES.PUBLIC.USERS.INVITE,
|
Claims.CIRCLES.PUBLIC.USERS.INVITE,
|
||||||
Claims.CIRCLES.OWNED.USERS.INVITE,
|
Claims.CIRCLES.OWNED.USERS.INVITE,
|
||||||
]),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'search': {
|
'search': {
|
||||||
':query': {
|
':query': {
|
||||||
variants: [':pageSize/:page', ':page'],
|
variants: [':pageSize/:page', ':page'],
|
||||||
GET: guard(circles.query, [Claims.ADMIN, Claims.CIRCLES.PUBLIC.READ]),
|
GET: guard(circles.query, Claims.ADMIN, Claims.CIRCLES.PUBLIC.READ),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,26 +3,20 @@ import { Claims } from '../orm/claims';
|
|||||||
import collections from '../endpoints/collections';
|
import collections from '../endpoints/collections';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'POST': guard(collections.create, [Claims.ADMIN, Claims.COLLECTIONS.CREATE]),
|
'POST': guard(collections.create, Claims.ADMIN, Claims.COLLECTIONS.CREATE),
|
||||||
':id': {
|
':id': {
|
||||||
GET: guard(collections.get, [Claims.ADMIN, Claims.COLLECTIONS.UNOWNED.READ, Claims.COLLECTIONS.OWNED.READ]),
|
GET: guard(collections.get, Claims.ADMIN, Claims.COLLECTIONS.UNOWNED.READ, Claims.COLLECTIONS.OWNED.READ),
|
||||||
PATCH: guard(collections.update, [Claims.ADMIN, Claims.PLAYERS.OTHER.UPDATE, Claims.PLAYERS.SELF.UPDATE]),
|
PATCH: guard(collections.update, Claims.ADMIN, Claims.PLAYERS.OTHER.UPDATE, Claims.PLAYERS.SELF.UPDATE),
|
||||||
DELETE: guard(collections.drop, [Claims.ADMIN, Claims.PLAYERS.OTHER.DELETE, Claims.PLAYERS.SELF.DELETE]),
|
DELETE: guard(collections.drop, Claims.ADMIN, Claims.PLAYERS.OTHER.DELETE, Claims.PLAYERS.SELF.DELETE),
|
||||||
add: {
|
add: {
|
||||||
POST: guard(collections.addGame, [
|
POST: guard(collections.addGame, Claims.ADMIN, Claims.COLLECTIONS.OWNED.GAME.ADD),
|
||||||
Claims.ADMIN,
|
|
||||||
Claims.COLLECTIONS.OWNED.GAME.ADD,
|
|
||||||
]),
|
|
||||||
},
|
},
|
||||||
remove: {
|
remove: {
|
||||||
POST: guard(collections.removeGame, [
|
POST: guard(collections.removeGame, Claims.ADMIN, Claims.COLLECTIONS.OWNED.GAME.REMOVE),
|
||||||
Claims.ADMIN,
|
|
||||||
Claims.COLLECTIONS.OWNED.GAME.REMOVE,
|
|
||||||
]),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'list': {
|
'list': {
|
||||||
variants: [':pageSize/:page', ':page'],
|
variants: [':pageSize/:page', ':page'],
|
||||||
GET: guard(collections.list, [Claims.ADMIN, Claims.COLLECTIONS.OWNED.LIST]),
|
GET: guard(collections.list, Claims.ADMIN, Claims.COLLECTIONS.OWNED.LIST),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ import { Claims } from '../orm/claims';
|
|||||||
import games from '../endpoints/games';
|
import games from '../endpoints/games';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'POST': guard(games.create, [Claims.ADMIN, Claims.GAMES.CREATE]),
|
'POST': guard(games.create, Claims.ADMIN, Claims.GAMES.CREATE),
|
||||||
':id': {
|
':id': {
|
||||||
GET: guard(games.get, [Claims.ADMIN, Claims.GAMES.READ]),
|
GET: guard(games.get, Claims.ADMIN, Claims.GAMES.READ),
|
||||||
PATCH: guard(games.update, [Claims.ADMIN, Claims.GAMES.UPDATE]),
|
PATCH: guard(games.update, Claims.ADMIN, Claims.GAMES.UPDATE),
|
||||||
DELETE: guard(games.drop, [Claims.ADMIN, Claims.GAMES.DELETE]),
|
DELETE: guard(games.drop, Claims.ADMIN, Claims.GAMES.DELETE),
|
||||||
},
|
},
|
||||||
'search': {
|
'search': {
|
||||||
':query': {
|
':query': {
|
||||||
variants: [':pageSize/:page', ':page'],
|
variants: [':pageSize/:page', ':page'],
|
||||||
GET: guard(games.query, [Claims.ADMIN, Claims.GAMES.READ]),
|
GET: guard(games.query, Claims.ADMIN, Claims.GAMES.READ),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Claims } from '../orm/claims';
|
|||||||
import invite from '../endpoints/invites';
|
import invite from '../endpoints/invites';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
POST: guard(invite.create, [Claims.ADMIN, Claims.USERS.INVITE]),
|
POST: guard(invite.create, Claims.ADMIN, Claims.USERS.INVITE),
|
||||||
accept: {
|
accept: {
|
||||||
POST: unwrapMethod(invite.accept),
|
POST: unwrapMethod(invite.accept),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import matches from '../endpoints/matches';
|
|||||||
import { Claims } from '../orm/claims';
|
import { Claims } from '../orm/claims';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'POST': guard(matches.create, [Claims.ADMIN, Claims.MATCHES.CREATE]),
|
'POST': guard(matches.create, Claims.ADMIN, Claims.MATCHES.CREATE),
|
||||||
':id': {
|
':id': {
|
||||||
GET: guard(matches.get, [Claims.ADMIN, Claims.MATCHES.OWNED.READ, Claims.MATCHES.PARTICIPANT.READ]),
|
GET: guard(matches.get, Claims.ADMIN, Claims.MATCHES.OWNED.READ, Claims.MATCHES.PARTICIPANT.READ),
|
||||||
DELETE: guard(matches.drop, [Claims.ADMIN, Claims.MATCHES.OWNED.DELETE, Claims.USERS.SELF.UPDATE]),
|
DELETE: guard(matches.drop, Claims.ADMIN, Claims.MATCHES.OWNED.DELETE, Claims.USERS.SELF.UPDATE),
|
||||||
leave: {
|
leave: {
|
||||||
POST: guard(matches.leave, [Claims.MATCHES.PARTICIPANT.LEAVE]),
|
POST: guard(matches.leave, Claims.MATCHES.PARTICIPANT.LEAVE),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import { Claims } from '../orm/claims';
|
|||||||
import player from '../endpoints/players';
|
import player from '../endpoints/players';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'POST': guard(player.create, [Claims.ADMIN, Claims.PLAYERS.CREATE]),
|
'POST': guard(player.create, Claims.ADMIN, Claims.PLAYERS.CREATE),
|
||||||
':id': {
|
':id': {
|
||||||
GET: guard(player.get, [Claims.ADMIN, Claims.PLAYERS.OTHER.READ, Claims.PLAYERS.SELF.READ]),
|
GET: guard(player.get, Claims.ADMIN, Claims.PLAYERS.OTHER.READ, Claims.PLAYERS.SELF.READ),
|
||||||
PATCH: guard(player.update, [Claims.ADMIN, Claims.PLAYERS.OTHER.UPDATE, Claims.PLAYERS.SELF.UPDATE]),
|
PATCH: guard(player.update, Claims.ADMIN, Claims.PLAYERS.OTHER.UPDATE, Claims.PLAYERS.SELF.UPDATE),
|
||||||
DELETE: guard(player.drop, [Claims.ADMIN, Claims.PLAYERS.OTHER.DELETE, Claims.PLAYERS.SELF.DELETE]),
|
DELETE: guard(player.drop, Claims.ADMIN, Claims.PLAYERS.OTHER.DELETE, Claims.PLAYERS.SELF.DELETE),
|
||||||
},
|
},
|
||||||
'list': {
|
'list': {
|
||||||
variants: [':pageSize/:page', ':page'],
|
variants: [':pageSize/:page', ':page'],
|
||||||
GET: guard(player.list, [Claims.ADMIN, Claims.PLAYERS.OTHER.READ]),
|
GET: guard(player.list, Claims.ADMIN, Claims.PLAYERS.OTHER.READ),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import user from '../endpoints/users';
|
|||||||
import { Claims } from '../orm/claims';
|
import { Claims } from '../orm/claims';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'POST': guard(user.create, [Claims.ADMIN, Claims.USERS.CREATE]),
|
'POST': guard(user.create, Claims.ADMIN, Claims.USERS.CREATE),
|
||||||
':id': {
|
':id': {
|
||||||
GET: guard(user.get, [Claims.ADMIN, Claims.USERS.OTHER.READ, Claims.USERS.SELF.READ]),
|
GET: guard(user.get, Claims.ADMIN, Claims.USERS.OTHER.READ, Claims.USERS.SELF.READ),
|
||||||
PATCH: guard(user.update, [Claims.ADMIN, Claims.USERS.OTHER.UPDATE, Claims.USERS.SELF.UPDATE]),
|
PATCH: guard(user.update, Claims.ADMIN, Claims.USERS.OTHER.UPDATE, Claims.USERS.SELF.UPDATE),
|
||||||
DELETE: guard(user.drop, [Claims.ADMIN, Claims.USERS.OTHER.UPDATE, Claims.USERS.SELF.UPDATE]),
|
DELETE: guard(user.drop, Claims.ADMIN, Claims.USERS.OTHER.UPDATE, Claims.USERS.SELF.UPDATE),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import { Claims } from '../orm/claims';
|
|||||||
export function guardRedirect(
|
export function guardRedirect(
|
||||||
method: (request: UnwrappedRequest<any>) => Promise<Response> | Response,
|
method: (request: UnwrappedRequest<any>) => Promise<Response> | Response,
|
||||||
redirectMethod: Function,
|
redirectMethod: Function,
|
||||||
guardedClaims: string[],
|
...guardedClaims: string[]
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
return guard(method, guardedClaims);
|
return guard(method, ...guardedClaims);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return redirectMethod();
|
return redirectMethod();
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ export function guardRedirect(
|
|||||||
|
|
||||||
export function guard(
|
export function guard(
|
||||||
method: (request: UnwrappedRequest<any>) => Promise<Response> | Response,
|
method: (request: UnwrappedRequest<any>) => Promise<Response> | Response,
|
||||||
guardedClaims: string[],
|
...guardedClaims: string[]
|
||||||
): (r: Request) => Promise<Response> {
|
): (r: Request) => Promise<Response> {
|
||||||
return async (request: Request): Promise<Response> => {
|
return async (request: Request): Promise<Response> => {
|
||||||
const authHeader: string | null =
|
const authHeader: string | null =
|
||||||
@@ -26,10 +26,7 @@ export function guard(
|
|||||||
const userClaims: Claims = new Claims(
|
const userClaims: Claims = new Claims(
|
||||||
jwt.verify(authHeader as string, process.env.JWT_SECRET_KEY as string) as any,
|
jwt.verify(authHeader as string, process.env.JWT_SECRET_KEY as string) as any,
|
||||||
);
|
);
|
||||||
if (
|
if (!userClaims.userId.raw || !userClaims.claims.some((x: string): boolean => guardedClaims.includes(x))) {
|
||||||
!userClaims.userId.raw ||
|
|
||||||
!userClaims.claims.some((x: string): boolean => guardedClaims.includes(x))
|
|
||||||
) {
|
|
||||||
return new UnauthorizedResponse('Unauthorized');
|
return new UnauthorizedResponse('Unauthorized');
|
||||||
}
|
}
|
||||||
return method(await unwrap(request, userClaims));
|
return method(await unwrap(request, userClaims));
|
||||||
|
|||||||
Reference in New Issue
Block a user