Finished initial implementation of circle endpoints
This commit is contained in:
30
API Tests/BGApp/Circle/Create.yml
Normal file
30
API Tests/BGApp/Circle/Create.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
info:
|
||||
name: Create
|
||||
type: http
|
||||
seq: 1
|
||||
|
||||
http:
|
||||
method: POST
|
||||
url: "{{BASE_URL}}/{{SECTOR}}"
|
||||
body:
|
||||
type: json
|
||||
data: |-
|
||||
{
|
||||
"name": "{{Name}}",
|
||||
"isPublic": {{IsPublic}},
|
||||
"colour": "#FFFFFF"
|
||||
}
|
||||
auth: inherit
|
||||
|
||||
runtime:
|
||||
variables:
|
||||
- name: Name
|
||||
value: Test Private Circle
|
||||
- name: IsPublic
|
||||
value: "false"
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
20
API Tests/BGApp/Circle/Delete.yml
Normal file
20
API Tests/BGApp/Circle/Delete.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
info:
|
||||
name: Delete
|
||||
type: http
|
||||
seq: 4
|
||||
|
||||
http:
|
||||
method: DELETE
|
||||
url: "{{BASE_URL}}/{{SECTOR}}/{{CircleID}}"
|
||||
auth: inherit
|
||||
|
||||
runtime:
|
||||
variables:
|
||||
- name: CircleID
|
||||
value: 6Z4214
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
20
API Tests/BGApp/Circle/Get.yml
Normal file
20
API Tests/BGApp/Circle/Get.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
info:
|
||||
name: Get
|
||||
type: http
|
||||
seq: 2
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{BASE_URL}}/{{SECTOR}}/{{CircleID}}"
|
||||
auth: inherit
|
||||
|
||||
runtime:
|
||||
variables:
|
||||
- name: CircleID
|
||||
value: P17GOJ
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
24
API Tests/BGApp/Circle/Search.yml
Normal file
24
API Tests/BGApp/Circle/Search.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
info:
|
||||
name: Search
|
||||
type: http
|
||||
seq: 5
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{BASE_URL}}/{{SECTOR}}/search/{{Query}}/{{PageSize}}/{{Page}}"
|
||||
auth: inherit
|
||||
|
||||
runtime:
|
||||
variables:
|
||||
- name: Query
|
||||
value: test
|
||||
- name: PageSize
|
||||
value: "5"
|
||||
- name: Page
|
||||
value: "1"
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
28
API Tests/BGApp/Circle/Update.yml
Normal file
28
API Tests/BGApp/Circle/Update.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
info:
|
||||
name: Update
|
||||
type: http
|
||||
seq: 3
|
||||
|
||||
http:
|
||||
method: PATCH
|
||||
url: "{{BASE_URL}}/{{SECTOR}}/{{CircleID}}"
|
||||
body:
|
||||
type: json
|
||||
data: |-
|
||||
{
|
||||
"name":"{{Name}}"
|
||||
}
|
||||
auth: inherit
|
||||
|
||||
runtime:
|
||||
variables:
|
||||
- name: CircleID
|
||||
value: 6Z4214
|
||||
- name: Name
|
||||
value: Test update
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
10
API Tests/BGApp/Circle/folder.yml
Normal file
10
API Tests/BGApp/Circle/folder.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
info:
|
||||
name: Circle
|
||||
type: folder
|
||||
seq: 1
|
||||
|
||||
request:
|
||||
auth: inherit
|
||||
variables:
|
||||
- name: SECTOR
|
||||
value: circles
|
||||
@@ -17,9 +17,9 @@ http:
|
||||
runtime:
|
||||
variables:
|
||||
- name: GameID
|
||||
value: ""
|
||||
value: DM5GMY
|
||||
- name: Name
|
||||
value: ""
|
||||
value: Update test
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#file: noinspection SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection
|
||||
opencollection: 1.0.0
|
||||
|
||||
info:
|
||||
@@ -19,16 +18,6 @@ request:
|
||||
auth:
|
||||
type: bearer
|
||||
token: "{{BEARER_TOKEN}}"
|
||||
actions:
|
||||
- type: set-variable
|
||||
phase: after-response
|
||||
selector:
|
||||
expression: ${token}
|
||||
method: jsonq
|
||||
variable:
|
||||
name: Token
|
||||
scope: runtime
|
||||
disabled: true
|
||||
bundled: false
|
||||
extensions:
|
||||
bruno:
|
||||
|
||||
74
src/endpoints/circles.ts
Normal file
74
src/endpoints/circles.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { orm } from '../orm/orm';
|
||||
import { UnwrappedRequest } from '../utilities/guard';
|
||||
import { CreatedResponse, ErrorResponse, OkResponse, PagedResponse } from '../utilities/responseHelper';
|
||||
import { CreateCircleRequest, InviteToCircleRequest, UpdateCircleRequest } from '../utilities/requestModels';
|
||||
import { CircleId, PlayerId, UserId } from '../utilities/secureIds';
|
||||
|
||||
async function create(request: UnwrappedRequest<CreateCircleRequest>): Promise<Response> {
|
||||
try {
|
||||
return new CreatedResponse(await orm.circles.create(request.body, request.claims));
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async function invite(request: UnwrappedRequest<InviteToCircleRequest>): Promise<Response> {
|
||||
try {
|
||||
let relatedEntityId: UserId | PlayerId | string | undefined;
|
||||
if (request.body.userId) {
|
||||
relatedEntityId = UserId.fromHash(request.body.userId);
|
||||
} else if (request.body.playerId) {
|
||||
relatedEntityId = PlayerId.fromHash(request.body.playerId);
|
||||
} else {
|
||||
relatedEntityId = request.body.email;
|
||||
}
|
||||
return new CreatedResponse(
|
||||
await orm.circles.invite(CircleId.fromHash(request.params.id), relatedEntityId, request.claims),
|
||||
);
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async function get(request: UnwrappedRequest): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(await orm.circles.get(CircleId.fromHash(request.params.id)));
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async function update(request: UnwrappedRequest<UpdateCircleRequest>): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(
|
||||
await orm.circles.update(CircleId.fromHash(request.params.id), request.body),
|
||||
);
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async function drop(request: UnwrappedRequest): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(await orm.circles.drop(CircleId.fromHash(request.params.id)));
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async function query(request: UnwrappedRequest): Promise<Response> {
|
||||
try {
|
||||
return new PagedResponse(request, await orm.circles.query(request.params.query));
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
create,
|
||||
get,
|
||||
update,
|
||||
drop,
|
||||
query,
|
||||
invite,
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import collections from './routes/collections';
|
||||
import { buildRoute } from './utilities/routeBuilder';
|
||||
import { MatchId } from './utilities/secureIds';
|
||||
import matches from './routes/matches';
|
||||
import circles from './routes/circles';
|
||||
|
||||
const server = Bun.serve({
|
||||
routes: buildRoute({
|
||||
@@ -19,6 +20,7 @@ const server = Bun.serve({
|
||||
invites,
|
||||
collections,
|
||||
matches,
|
||||
circles,
|
||||
},
|
||||
test: {
|
||||
GET: () => {
|
||||
|
||||
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),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { GamesOrm } from './games';
|
||||
import { InvitesOrm } from './invites';
|
||||
import { CollectionsOrm } from './collections';
|
||||
import { MatchOrm } from './matches';
|
||||
import { CircleOrm } from './circles';
|
||||
|
||||
class Orm {
|
||||
readonly claims: ClaimsOrm = new ClaimsOrm();
|
||||
@@ -14,6 +15,7 @@ class Orm {
|
||||
readonly invites: InvitesOrm = new InvitesOrm();
|
||||
readonly collections: CollectionsOrm = new CollectionsOrm();
|
||||
readonly matches: MatchOrm = new MatchOrm();
|
||||
readonly circles: CircleOrm = new CircleOrm();
|
||||
}
|
||||
|
||||
export const orm = new Orm();
|
||||
|
||||
@@ -89,6 +89,69 @@ export class UsersOrm {
|
||||
);
|
||||
}
|
||||
|
||||
async getByPlayer(id: PlayerId, claims?: Claims): Promise<User> {
|
||||
|
||||
const dbResult: any = first(
|
||||
await sql`SELECT *
|
||||
FROM users
|
||||
WHERE player_id = ${id.raw}
|
||||
AND is_active = true
|
||||
LIMIT 1`,
|
||||
);
|
||||
|
||||
if (!dbResult) {
|
||||
throw new NotFoundError('No matching user exists');
|
||||
}
|
||||
|
||||
if (
|
||||
claims &&
|
||||
!(
|
||||
claims.test(Claims.ADMIN, Claims.USERS.OTHER.READ) ||
|
||||
(claims.test(Claims.USERS.SELF.READ) && dbResult.id === claims?.userId.raw)
|
||||
)
|
||||
) {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
|
||||
return new User(
|
||||
UserId.fromID(dbResult.id),
|
||||
PlayerId.fromID(dbResult.player_id),
|
||||
dbResult.email,
|
||||
dbResult.is_admin,
|
||||
);
|
||||
}
|
||||
|
||||
async getByEmail(email:string, claims?: Claims): Promise<User> {
|
||||
const dbResult: any = first(
|
||||
await sql`SELECT *
|
||||
FROM users
|
||||
WHERE email = ${email}
|
||||
AND is_active = true
|
||||
LIMIT 1`,
|
||||
);
|
||||
|
||||
if (!dbResult) {
|
||||
throw new NotFoundError('No matching user exists');
|
||||
}
|
||||
|
||||
if (
|
||||
claims &&
|
||||
!(
|
||||
claims.test(Claims.ADMIN, Claims.USERS.OTHER.READ) ||
|
||||
(claims.test(Claims.USERS.SELF.READ) && dbResult.id === claims?.userId.raw)
|
||||
)
|
||||
) {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
|
||||
return new User(
|
||||
UserId.fromID(dbResult.id),
|
||||
PlayerId.fromID(dbResult.player_id),
|
||||
dbResult.email,
|
||||
dbResult.is_admin,
|
||||
);
|
||||
}
|
||||
|
||||
async update(id: UserId, patch: UpdateUserRequest, claims?: Claims): Promise<User> {
|
||||
if (
|
||||
claims &&
|
||||
|
||||
30
src/routes/circles.ts
Normal file
30
src/routes/circles.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { guard } from '../utilities/guard';
|
||||
import { Claims } from '../orm/claims';
|
||||
import circles from '../endpoints/circles';
|
||||
|
||||
export default {
|
||||
'POST': guard(circles.create, [Claims.ADMIN, Claims.CIRCLES.PUBLIC.CREATE, Claims.CIRCLES.PRIVATE.CREATE]),
|
||||
':id': {
|
||||
GET: guard(circles.get, [
|
||||
Claims.ADMIN,
|
||||
Claims.CIRCLES.PUBLIC.READ,
|
||||
Claims.CIRCLES.PRIVATE.READ,
|
||||
Claims.CIRCLES.PRIVATE.READ_IF_MEMBER,
|
||||
]),
|
||||
PATCH: guard(circles.update, [Claims.ADMIN, Claims.CIRCLES.OWNED.UPDATE]),
|
||||
DELETE: guard(circles.drop, [Claims.ADMIN, Claims.CIRCLES.OWNED.DELETE]),
|
||||
invite: {
|
||||
POST: guard(circles.invite, [
|
||||
Claims.ADMIN,
|
||||
Claims.CIRCLES.PUBLIC.USERS.INVITE,
|
||||
Claims.CIRCLES.OWNED.USERS.INVITE,
|
||||
]),
|
||||
},
|
||||
},
|
||||
'search': {
|
||||
':query': {
|
||||
variants: [':pageSize/:page', ':page'],
|
||||
GET: guard(circles.query, [Claims.ADMIN, Claims.CIRCLES.PUBLIC.READ]),
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -36,7 +36,8 @@ export class ClaimDefinition {
|
||||
ADD: 'CIRCLES_PUBLIC_COMMENTS_ADD',
|
||||
},
|
||||
USERS: {
|
||||
INVITE: 'CIRCLES_OWNED_USER_INVITE',
|
||||
INVITE: 'CIRCLES_PUBLIC_USER_INVITE',
|
||||
LIST: 'CIRCLES_PUBLIC_USER_LIST',
|
||||
},
|
||||
},
|
||||
PRIVATE: {
|
||||
|
||||
@@ -54,3 +54,19 @@ export interface CreateMatchRequest {
|
||||
gameId: string;
|
||||
participants: { playerId: string; standing: number }[];
|
||||
}
|
||||
export interface CreateCircleRequest {
|
||||
name: string;
|
||||
isPublic: boolean;
|
||||
imagePath?: string;
|
||||
colour: string;
|
||||
}
|
||||
export interface UpdateCircleRequest {
|
||||
name: string;
|
||||
imagePath?: string;
|
||||
colour: string;
|
||||
}
|
||||
export interface InviteToCircleRequest {
|
||||
email?:string;
|
||||
userId?:string;
|
||||
playerId?:string;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BadRequestError, NotFoundError, UnauthorizedError } from './errors';
|
||||
import { clamp, isObject } from 'lodash';
|
||||
import { clamp, isArray, isObject } from 'lodash';
|
||||
import { UnwrappedRequest } from './guard';
|
||||
|
||||
export class ErrorResponse extends Response {
|
||||
@@ -52,7 +52,7 @@ export class OkResponse extends Response {
|
||||
constructor(body?: any) {
|
||||
if (body) {
|
||||
return Response.json(
|
||||
isObject(body)
|
||||
isObject(body) && !isArray(body)
|
||||
? {
|
||||
...body,
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ export function buildRoute(
|
||||
const endpointDefinition = {
|
||||
POST: route.POST,
|
||||
GET: route.GET,
|
||||
PATCH: route.PATCH,
|
||||
PUT: route.PUT,
|
||||
DELETE: route.DELETE,
|
||||
};
|
||||
|
||||
@@ -66,83 +66,104 @@ class SecureId {
|
||||
export class UserId extends SecureId {
|
||||
protected static override hashPrefix: string = 'UserId';
|
||||
|
||||
// This method exists to force type errors when using an incorrect ID class.
|
||||
#uniqueMethodUser(){}
|
||||
|
||||
public static fromHash(hash: string): UserId {
|
||||
return super.fromHash(hash, UserId);
|
||||
return super.fromHash(hash, UserId) as UserId;
|
||||
}
|
||||
|
||||
public static fromID(id: string): UserId {
|
||||
return super.fromID(id, UserId);
|
||||
return super.fromID(id, UserId) as UserId;
|
||||
}
|
||||
}
|
||||
|
||||
export class PlayerId extends SecureId {
|
||||
protected static override hashPrefix: string = 'PlayerId';
|
||||
|
||||
// This method exists to force type errors when using an incorrect ID class.
|
||||
#uniqueMethodPlayer(){}
|
||||
|
||||
public static fromHash(hash: string): PlayerId {
|
||||
return super.fromHash(hash, PlayerId);
|
||||
return super.fromHash(hash, PlayerId) as PlayerId;
|
||||
}
|
||||
|
||||
public static fromID(id: string): PlayerId {
|
||||
return super.fromID(id, PlayerId);
|
||||
return super.fromID(id, PlayerId) as PlayerId;
|
||||
}
|
||||
}
|
||||
|
||||
export class InviteId extends SecureId {
|
||||
protected static override hashPrefix: string = 'InviteId';
|
||||
|
||||
// This method exists to force type errors when using an incorrect ID class.
|
||||
#uniqueMethodInvite(){}
|
||||
|
||||
public static fromHash(hash: string): InviteId {
|
||||
return super.fromHash(hash, InviteId);
|
||||
return super.fromHash(hash, InviteId) as InviteId;
|
||||
}
|
||||
|
||||
public static fromID(id: string): InviteId {
|
||||
return super.fromID(id, InviteId);
|
||||
return super.fromID(id, InviteId) as InviteId;
|
||||
}
|
||||
}
|
||||
|
||||
export class GameId extends SecureId {
|
||||
protected static override hashPrefix: string = 'GameId';
|
||||
|
||||
// This method exists to force type errors when using an incorrect ID class.
|
||||
#uniqueMethodGame(){}
|
||||
|
||||
public static fromHash(hash: string): GameId {
|
||||
return super.fromHash(hash, GameId);
|
||||
return super.fromHash(hash, GameId) as GameId;
|
||||
}
|
||||
|
||||
public static fromID(id: string): GameId {
|
||||
return super.fromID(id, GameId);
|
||||
return super.fromID(id, GameId) as GameId;
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionId extends SecureId {
|
||||
protected static override hashPrefix: string = 'CollectionId';
|
||||
|
||||
// This method exists to force type errors when using an incorrect ID class.
|
||||
#uniqueMethodCollection(){}
|
||||
|
||||
public static fromHash(hash: string): CollectionId {
|
||||
return super.fromHash(hash, CollectionId);
|
||||
return super.fromHash(hash, CollectionId) as CollectionId;
|
||||
}
|
||||
|
||||
public static fromID(id: string): CollectionId {
|
||||
return super.fromID(id, CollectionId);
|
||||
return super.fromID(id, CollectionId) as CollectionId;
|
||||
}
|
||||
}
|
||||
|
||||
export class MatchId extends SecureId {
|
||||
protected static override hashPrefix: string = 'MatchId';
|
||||
|
||||
// This method exists to force type errors when using an incorrect ID class.
|
||||
#uniqueMethodMatch(){}
|
||||
|
||||
public static fromHash(hash: string): MatchId {
|
||||
return super.fromHash(hash, MatchId);
|
||||
return super.fromHash(hash, MatchId) as MatchId;
|
||||
}
|
||||
|
||||
public static fromID(id: string): MatchId {
|
||||
return super.fromID(id, MatchId);
|
||||
return super.fromID(id, MatchId) as MatchId;
|
||||
}
|
||||
}
|
||||
|
||||
export class CircleId extends SecureId {
|
||||
protected static override hashPrefix: string = 'CircleId';
|
||||
|
||||
// This method exists to force type errors when using an incorrect ID class.
|
||||
#uniqueMethodCircle(){}
|
||||
|
||||
public static fromHash(hash: string): CircleId {
|
||||
return super.fromHash(hash, CircleId);
|
||||
return super.fromHash(hash, CircleId) as CircleId;
|
||||
}
|
||||
|
||||
public static fromID(id: string): CircleId {
|
||||
return super.fromID(id, CircleId);
|
||||
return super.fromID(id, CircleId) as CircleId;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user