Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solve [WsExceptionsHandler] undefined #594

Merged
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]);
JannikStreek marked this conversation as resolved.
Show resolved Hide resolved
}
} 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
Loading