Skip to content

Commit

Permalink
Solve [WsExceptionsHandler] undefined (#594)
Browse files Browse the repository at this point in the history
  • Loading branch information
sorenjohanson authored Dec 3, 2024
1 parent 939f0f7 commit 1068b5a
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 51 deletions.
37 changes: 25 additions & 12 deletions teammapper-backend/src/map/controllers/maps.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
NotFoundException,
Param,
Post,
Logger
} from '@nestjs/common'
import { MapsService } from '../services/maps.service'
import {
Expand All @@ -19,6 +20,7 @@ import { EntityNotFoundError } from 'typeorm'

@Controller('api/maps')
export default class MapsController {
private readonly logger = new Logger(MapsController.name)
constructor(private mapsService: MapsService) {}

@Get(':id')
Expand Down Expand Up @@ -51,21 +53,28 @@ export default class MapsController {
@Post()
async create(
@Body() body: IMmpClientMapCreateRequest
): Promise<IMmpClientPrivateMap> {
): Promise<IMmpClientPrivateMap | undefined> {
const newMap = await this.mapsService.createEmptyMap(body.rootNode)
return {
map: await this.mapsService.exportMapToClient(newMap.id),
adminId: newMap.adminId,
modificationSecret: newMap.modificationSecret,
const exportedMap = await this.mapsService.exportMapToClient(newMap.id)

if (exportedMap) {
return {
map: exportedMap,
adminId: newMap.adminId,
modificationSecret: newMap.modificationSecret,
}
}
}

@Post(':id/duplicate')
async duplicate(
@Param('id') mapId: string,
): Promise<IMmpClientPrivateMap> {
): Promise<IMmpClientPrivateMap | undefined> {
const oldMap = await this.mapsService.findMap(mapId).catch((e: Error) => {
if (e.name === 'MalformedUUIDError') throw new NotFoundException()
if (e.name === 'MalformedUUIDError') {
this.logger.warn(`:id/duplicate(): Wrong/no UUID provided for findMap() with mapId ${mapId}`)
return;
}
})

if (!oldMap) throw new NotFoundException()
Expand All @@ -75,11 +84,15 @@ export default class MapsController {
const oldNodes = await this.mapsService.findNodes(oldMap.id)

await this.mapsService.addNodes(newMap.id, oldNodes)

return {
map: await this.mapsService.exportMapToClient(newMap.id),
adminId: newMap.adminId,
modificationSecret: newMap.modificationSecret

const exportedMap = await this.mapsService.exportMapToClient(newMap.id);

if (exportedMap) {
return {
map: exportedMap,
adminId: newMap.adminId,
modificationSecret: newMap.modificationSecret
}
}
}
}
12 changes: 8 additions & 4 deletions teammapper-backend/src/map/controllers/maps.gateway.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Inject, UseGuards } from '@nestjs/common'
import { Inject, UseGuards, Logger } from '@nestjs/common'
import { CACHE_MANAGER } from '@nestjs/cache-manager'
import { Cache } from 'cache-manager'
import { randomBytes } from 'crypto'
Expand Down Expand Up @@ -37,6 +37,8 @@ export class MapsGateway implements OnGatewayDisconnect {
@WebSocketServer()
server: Server

private readonly logger = new Logger(MapsService.name)

constructor(
private mapsService: MapsService,
@Inject(CACHE_MANAGER) private cacheManager: Cache,
Expand All @@ -57,10 +59,12 @@ export class MapsGateway implements OnGatewayDisconnect {
async onJoin(
@ConnectedSocket() client: Socket,
@MessageBody() request: IMmpClientJoinRequest
): Promise<IMmpClientMap> {
): Promise<IMmpClientMap | undefined> {
const map = await this.mapsService.findMap(request.mapId)
if (!map)
return Promise.reject()
if (!map) {
this.logger.warn(`onJoin(): Could not find map ${request.mapId} when client ${client.id} tried to join`);
return;
}

client.join(request.mapId)
this.cacheManager.set(client.id, request.mapId, 10000)
Expand Down
12 changes: 5 additions & 7 deletions teammapper-backend/src/map/services/maps.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Logger } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { MmpMap } from '../entities/mmpMap.entity'
import { MmpNode } from '../entities/mmpNode.entity'
import { EntityNotFoundError, Repository } from 'typeorm'
import { Repository } from 'typeorm'
import { ConfigModule } from '@nestjs/config'
import AppModule from '../../app.module'
import {
Expand Down Expand Up @@ -164,12 +164,10 @@ describe('MapsController', () => {
})

describe('exportMapToClient', () => {
it('throws error when no map is available', async () => {
expect(
mapsService.exportMapToClient(
'78a2ae85-1815-46da-a2bc-a41de6bdd5ab'
)
).rejects.toThrow(EntityNotFoundError)
it('returns undefined when no map is available', async () => {
expect(await mapsService.exportMapToClient(
'78a2ae85-1815-46da-a2bc-a41de6bdd5ab'
)).toEqual(undefined)
})
})

Expand Down
86 changes: 58 additions & 28 deletions teammapper-backend/src/map/services/maps.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { EntityNotFoundError, Repository } from 'typeorm'
import { Repository } from 'typeorm'
import { MmpMap } from '../entities/mmpMap.entity'
import { MmpNode } from '../entities/mmpNode.entity'
import {
Expand Down Expand Up @@ -42,32 +42,50 @@ export class MapsService {

async updateLastAccessed(uuid: string, lastAccessed = new Date()) {
const map = await this.findMap(uuid)
if (!map) return Promise.reject(new EntityNotFoundError("MmpMap", uuid))
if (!map) {
this.logger.warn(`updateLastAccessed(): Map was not found`)
return;
}

this.mapsRepository.update(uuid, { lastAccessed })
}

async exportMapToClient(uuid: string): Promise<IMmpClientMap> {
async exportMapToClient(uuid: string): Promise<IMmpClientMap | undefined> {
const map = await this.findMap(uuid)
if (!map) return Promise.reject(new EntityNotFoundError("MmpMap", uuid))
if (!map) {
this.logger.warn(`exportMapToClient(): Map was not found`)
return;
}

const nodes = await this.findNodes(map?.id)
const days = configService.deleteAfterDays()

return mapMmpMapToClient(
map,
nodes,
await this.getDeletedAt(map, days),
days
)
const deletedAt = await this.getDeletedAt(map, days);

if (deletedAt) {
return mapMmpMapToClient(
map,
nodes,
deletedAt,
days
)
}
}

async addNode(mapId: string, node: MmpNode): Promise<MmpNode> {
async addNode(mapId: string, node: MmpNode): Promise<MmpNode | undefined> {
// detached nodes are not allowed to have a parent
if (node.detached && node.nodeParentId) return Promise.reject()
if (node.detached && node.nodeParentId) {
this.logger.warn(`addNode(): Detached node ${node.id} is not allowed to have a parent.`);
return;
}
// root nodes are not allowed to have a parent
if (node.root && node.nodeParentId) return Promise.reject()
if (!mapId || !node) return Promise.reject()
if (node.root && node.nodeParentId) {
this.logger.warn(`addNode(): Root node ${node.id} is not allowed to have a parent.`);
return;
}
if (!mapId || !node) {
this.logger.warn(`addNode(): Required arguments mapId or node not supplied`);
return;
}

const existingNode = await this.nodesRepository.findOne({
where: { id: node.id, nodeMapId: mapId },
Expand All @@ -82,35 +100,41 @@ export class MapsService {
try {
return this.nodesRepository.save(newNode)
} catch(error) {
this.logger.error(`${error.constructor.name} - Failed to add node ${newNode.id}: ${error}`)
this.logger.warn(`${error.constructor.name} addNode(): Failed to add node ${newNode.id}: ${error}`)
return Promise.reject(error)
}
}

async addNodesFromClient(
mapId: string,
clientNodes: IMmpClientNode[]
): Promise<MmpNode[]> {
): Promise<MmpNode[] | []> {
const mmpNodes = clientNodes.map(x => mapClientNodeToMmpNode(x, mapId))
return await this.addNodes(mapId, mmpNodes)
}

async addNodes(
mapId: string,
nodes: Partial<MmpNode>[]
): Promise<MmpNode[]> {
if (!mapId || nodes.length === 0) return Promise.reject()
): Promise<MmpNode[] | []> {
if (!mapId || nodes.length === 0) {
this.logger.warn(`Required arguments mapId or nodes not supplied to addNodes()`);
return [];
}

const reducer = async (previousPromise: Promise<MmpNode[]>, node: MmpNode): Promise<MmpNode[]> => {
const accCreatedNodes = await previousPromise;
if (await this.validatesNodeParentForNode(mapId, node)) {
try {
const newNode = await this.addNode(mapId, node);
return accCreatedNodes.concat([newNode]);
if (newNode) {
return accCreatedNodes.concat([newNode]);
}
} catch (error) {
this.logger.warn(`Failed to add node ${node.id} to map ${mapId}: ${error}`);
return accCreatedNodes;
}

return accCreatedNodes;
}

this.logger.warn(`Parent with id ${node.nodeParentId} does not exist for node ${node.id} and map ${mapId}`);
Expand Down Expand Up @@ -140,12 +164,15 @@ export class MapsService {
async updateNode(
mapId: string,
clientNode: IMmpClientNode
): Promise<MmpNode> {
): Promise<MmpNode | undefined> {
const existingNode = await this.nodesRepository.findOne({
where: { nodeMapId: mapId, id: clientNode.id },
})

if (!existingNode) return Promise.reject()
if (!existingNode) {
this.logger.warn(`updateNode(): Existing node on server for given client node ${clientNode.id} has not been found.`);
return;
}

try {
return this.nodesRepository.save({
Expand All @@ -154,7 +181,7 @@ export class MapsService {
lastModified: new Date(),
})
} catch(error) {
this.logger.error(`${error.constructor.name} - Failed to update node ${existingNode.id}: ${error}`)
this.logger.warn(`${error.constructor.name} updateNode(): Failed to update node ${existingNode.id}: ${error}`)
return Promise.reject(error)
}
}
Expand Down Expand Up @@ -186,7 +213,7 @@ export class MapsService {
try {
await this.nodesRepository.save(newRootNode)
} catch(error) {
this.logger.error(`${error.constructor.name} - Failed to create root node ${newRootNode.id}: ${error}`)
this.logger.warn(`${error.constructor.name} createEmptyMap(): Failed to create root node ${newRootNode.id}: ${error}`)
return Promise.reject(error)
}
}
Expand Down Expand Up @@ -226,7 +253,7 @@ export class MapsService {
try {
await this.nodesRepository.save(serverNode);
} catch(error) {
this.logger.error(`${error.constructor.name} - Failed to update node ${serverNode.id} during diff update: ${error}`)
this.logger.warn(`${error.constructor.name} diffUpdatedCallback(): Failed to update node ${serverNode.id}: ${error}`)
return Promise.reject(error)
}
}
Expand Down Expand Up @@ -274,8 +301,11 @@ export class MapsService {
return await this.mapsRepository.findOne({ where: { id: mapId } })
}

async getDeletedAt(map: MmpMap, afterDays: number): Promise<Date> {
if (!map) return Promise.reject()
async getDeletedAt(map: MmpMap, afterDays: number): Promise<Date | undefined> {
if (!map) {
this.logger.warn(`Required argument map was not supplied to getDeletedAt()`);
return;
}

// get newest node of this map:
const newestNodeQuery = this.nodesRepository
Expand Down

0 comments on commit 1068b5a

Please sign in to comment.