1
0
forked from noxious/server

Almost finalised refactoring

This commit is contained in:
2025-01-03 14:35:02 +01:00
parent fecdf222d7
commit a40b71140a
35 changed files with 257 additions and 410 deletions

View File

@ -1,9 +1,9 @@
import { BaseEvent } from '#application/base/baseEvent'
import CharacterHairRepository from '#repositories/characterHairRepository'
import characterRepository from '#repositories/characterRepository'
import { UUID } from '#application/types'
interface IPayload {
id: number
id: UUID
}
export default class characterHairDeleteEvent extends BaseEvent {
@ -12,20 +12,13 @@ export default class characterHairDeleteEvent extends BaseEvent {
}
private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> {
const character = await characterRepository.getById(this.socket.characterId as number)
if (!character) return callback(false)
if (character.role !== 'gm') {
return callback(false)
}
try {
const characterHair = await CharacterHairRepository.getById(data.id)
if (characterHair) {
await characterHair.delete()
}
if (!(await this.isCharacterGM())) return
callback(true)
const characterHair = await CharacterHairRepository.getById(data.id)
await (await CharacterHairRepository.getById(data.id))?.delete()
return callback(true)
} catch (error) {
this.logger.error(`Error deleting character type ${data.id}: ${error instanceof Error ? error.message : String(error)}`)
callback(false)

View File

@ -1,7 +1,6 @@
import { BaseEvent } from '#application/base/baseEvent'
import { CharacterHair } from '#entities/characterHair'
import characterHairRepository from '#repositories/characterHairRepository'
import characterRepository from '#repositories/characterRepository'
interface IPayload {}
@ -11,19 +10,14 @@ export default class characterHairListEvent extends BaseEvent {
}
private async handleEvent(data: IPayload, callback: (response: CharacterHair[]) => void): Promise<void> {
const character = await characterRepository.getById(this.socket.characterId as number)
if (!character) {
this.logger.error('gm:characterHair:list error', 'Character not found')
try {
if (!(await this.isCharacterGM())) return
const items = await characterHairRepository.getAll()
return callback(items)
} catch (error) {
this.logger.error('gm:characterHair:list error', error)
return callback([])
}
if (character.role !== 'gm') {
this.logger.info(`User ${character.id} tried to list character hair but is not a game master.`)
return callback([])
}
// get all objects
const items = await characterHairRepository.getAll()
callback(items)
}
}

View File

@ -6,7 +6,7 @@ import characterRepository from '#repositories/characterRepository'
import SpriteRepository from '#repositories/spriteRepository'
type Payload = {
id: number
id: UUID
name: string
gender: CharacterGender
isSelectable: boolean
@ -19,21 +19,17 @@ export default class CharacterHairUpdateEvent extends BaseEvent {
}
private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise<void> {
const character = await characterRepository.getById(this.socket.characterId as number)
if (!character) return callback(false)
if (character.role !== 'gm') {
return callback(false)
}
try {
if (!(await this.isCharacterGM())) return
const sprite = await SpriteRepository.getById(data.spriteId)
const characterHair = await CharacterHairRepository.getById(data.id)
if (characterHair) {
await characterHair.setName(data.name).setGender(data.gender).setIsSelectable(data.isSelectable).setSprite(sprite!).update()
if (!characterHair) {
return callback(false)
}
await characterHair.setName(data.name).setGender(data.gender).setIsSelectable(data.isSelectable).setSprite(sprite!).update()
return callback(true)
} catch (error) {
this.logger.error(`Error updating character hair: ${error instanceof Error ? error.message : String(error)}`)

View File

@ -1,41 +1,22 @@
import { CharacterGender, CharacterRace } from '@prisma/client'
import { Server } from 'socket.io'
import prisma from '#application/prisma'
import { TSocket } from '#application/types'
import characterRepository from '#repositories/characterRepository'
export default class CharacterTypeCreateEvent {
constructor(
private readonly io: Server,
private readonly socket: TSocket
) {}
import { BaseEvent } from '#application/base/baseEvent'
import { CharacterType } from '#entities/characterType'
export default class CharacterTypeCreateEvent extends BaseEvent {
public listen(): void {
this.socket.on('gm:characterType:create', this.handleEvent.bind(this))
}
private async handleEvent(data: undefined, callback: (response: boolean, characterType?: any) => void): Promise<void> {
try {
const character = await characterRepository.getById(this.socket.characterId as number)
if (!character) return callback(false)
if (!(await this.isCharacterGM())) return
if (character.role !== 'gm') {
return callback(false)
}
const newCharacterType = new CharacterType()
await newCharacterType.setName('New character type').save()
const newCharacterType = await prisma.characterType.create({
data: {
name: 'New character type',
gender: CharacterGender.MALE,
race: CharacterRace.HUMAN
}
})
callback(true, newCharacterType)
return callback(true, newCharacterType)
} catch (error) {
console.error('Error creating character type:', error)
callback(false)
return callback(false)
}
}
}

View File

@ -1,41 +1,28 @@
import { Server } from 'socket.io'
import { gameMasterLogger } from '#application/logger'
import { TSocket } from '#application/types'
import characterRepository from '#repositories/characterRepository'
import { UUID } from '#application/types'
import CharacterTypeRepository from '#repositories/characterTypeRepository'
import { BaseEvent } from '#application/base/baseEvent'
interface IPayload {
id: number
id: UUID
}
export default class CharacterTypeDeleteEvent {
constructor(
private readonly io: Server,
private readonly socket: TSocket
) {}
export default class CharacterTypeDeleteEvent extends BaseEvent {
public listen(): void {
this.socket.on('gm:characterType:remove', this.handleEvent.bind(this))
}
private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> {
const character = await characterRepository.getById(this.socket.characterId!)
if (!character) return callback(false)
if (character.role !== 'gm') {
return callback(false)
}
try {
if (!(await this.isCharacterGM())) return
const characterType = await CharacterTypeRepository.getById(data.id)
if (!characterType) return callback(false)
await characterType.delete()
callback(true)
return callback(true)
} catch (error) {
gameMasterLogger.error(`Error deleting character type ${data.id}: ${error instanceof Error ? error.message : String(error)}`)
callback(false)
this.logger.error(`Error deleting character type ${data.id}: ${error instanceof Error ? error.message : String(error)}`)
return callback(false)
}
}
}

View File

@ -1,37 +1,23 @@
import { CharacterType } from '@prisma/client'
import { Server } from 'socket.io'
import { gameMasterLogger } from '#application/logger'
import { TSocket } from '#application/types'
import characterRepository from '#repositories/characterRepository'
import CharacterTypeRepository from '#repositories/characterTypeRepository'
import { BaseEvent } from '#application/base/baseEvent'
import { CharacterType } from '#entities/characterType'
interface IPayload {}
export default class CharacterTypeListEvent {
constructor(
private readonly io: Server,
private readonly socket: TSocket
) {}
export default class CharacterTypeListEvent extends BaseEvent {
public listen(): void {
this.socket.on('gm:characterType:list', this.handleEvent.bind(this))
}
private async handleEvent(data: IPayload, callback: (response: CharacterType[]) => void): Promise<void> {
const character = await characterRepository.getById(this.socket.characterId as number)
if (!character) {
gameMasterLogger.error('gm:characterType:list error', 'Character not found')
try {
if (!(await this.isCharacterGM())) return
const items = await CharacterTypeRepository.getAll()
return callback(items)
} catch (error) {
this.logger.error('gm:characterType:list error', error)
return callback([])
}
if (character.role !== 'gm') {
gameMasterLogger.info(`User ${character.id} tried to list character types but is not a game master.`)
return callback([])
}
// get all objects
const items = await CharacterTypeRepository.getAll()
callback(items)
}
}

View File

@ -1,14 +1,15 @@
import ObjectRepository from '#repositories/mapObjectRepository'
import { BaseEvent } from '#application/base/baseEvent'
import { MapObject } from '#entities/mapObject'
interface IPayload {}
export default class ObjectListEvent extends BaseEvent{
export default class MapObjectListEvent extends BaseEvent {
public listen(): void {
this.socket.on('gm:object:list', this.handleEvent.bind(this))
this.socket.on('gm:mapObject:list', this.handleEvent.bind(this))
}
private async handleEvent(data: IPayload, callback: (response: Object[]) => void): Promise<void> {
private async handleEvent(data: IPayload, callback: (response: MapObject[]) => void): Promise<void> {
if (!(await this.isCharacterGM())) return
// get all objects

View File

@ -0,0 +1,38 @@
import fs from 'fs'
import Storage from '#application/storage'
import { BaseEvent } from '#application/base/baseEvent'
import MapObjectRepository from '#repositories/mapObjectRepository'
import { UUID } from '#application/types'
interface IPayload {
mapObjectId: UUID
}
export default class MapObjectRemoveEvent extends BaseEvent {
public listen(): void {
this.socket.on('gm:mapObject:remove', this.handleEvent.bind(this))
}
private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> {
if (!(await this.isCharacterGM())) return
try {
// remove the tile from the disk
const finalFilePath = Storage.getPublicPath('map_objects', data.mapObjectId + '.png')
fs.unlink(finalFilePath, async (err) => {
if (err) {
this.logger.error(`Error deleting object ${data.mapObjectId}: ${err.message}`)
callback(false)
return
}
await (await MapObjectRepository.getById(data.mapObjectId))?.delete()
return callback(true)
})
} catch (error) {
this.logger.error(`Error deleting object ${data.mapObjectId}: ${error instanceof Error ? error.message : String(error)}`)
return callback(false)
}
}
}

View File

@ -0,0 +1,46 @@
import { UUID } from '#application/types'
import { BaseEvent } from '#application/base/baseEvent'
import MapObjectRepository from '#repositories/mapObjectRepository'
type Payload = {
id: UUID
name: string
tags: string[]
originX: number
originY: number
isAnimated: boolean
frameRate: number
frameWidth: number
frameHeight: number
}
export default class MapObjectUpdateEvent extends BaseEvent {
public listen(): void {
this.socket.on('gm:mapObject:update', this.handleEvent.bind(this))
}
private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise<void> {
try {
if (!(await this.isCharacterGM())) return
const mapObject = await MapObjectRepository.getById(data.id)
if (!mapObject) return callback(false)
await mapObject
.setName(data.name)
.setTags(data.tags)
.setOriginX(data.originX)
.setOriginY(data.originY)
.setIsAnimated(data.isAnimated)
.setFrameRate(data.frameRate)
.setFrameWidth(data.frameWidth)
.setFrameHeight(data.frameHeight)
.update()
return callback(true)
} catch (error) {
console.error(error)
return callback(false)
}
}
}

View File

@ -0,0 +1,53 @@
import fs from 'fs/promises'
import { writeFile } from 'node:fs/promises'
import sharp from 'sharp'
import Storage from '#application/storage'
import { BaseEvent } from '#application/base/baseEvent'
import { MapObject } from '#entities/mapObject'
interface IObjectData {
[key: string]: Buffer
}
export default class MapObjectUploadEvent extends BaseEvent {
public listen(): void {
this.socket.on('gm:mapObject:upload', this.handleEvent.bind(this))
}
private async handleEvent(data: IObjectData, callback: (response: boolean) => void): Promise<void> {
try {
if (!(await this.isCharacterGM())) return
const public_folder = Storage.getPublicPath('map_objects')
// Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true })
const uploadPromises = Object.entries(data).map(async ([key, objectData]) => {
// Get image dimensions
const metadata = await sharp(objectData).metadata()
const width = metadata.width || 0
const height = metadata.height || 0
// Create new map object and save it to database
const mapObject = new MapObject()
await mapObject.setName(key).setTags([]).setOriginX(0).setOriginY(0).setFrameWidth(width).setFrameHeight(height).save()
// Save image to disk
const uuid = mapObject.getId()
const filename = `${uuid}.png`
const finalFilePath = Storage.getPublicPath('map_objects', filename)
await writeFile(finalFilePath, objectData)
this.logger.info('gm:mapObject:upload', `Object ${key} uploaded with id ${uuid}`)
})
await Promise.all(uploadPromises)
return callback(true)
} catch (error: any) {
this.logger.error('gm:mapObject:upload error', error.message)
return callback(false)
}
}
}

View File

@ -1,59 +0,0 @@
import fs from 'fs'
import { Server } from 'socket.io'
import { gameLogger, gameMasterLogger } from '#application/logger'
import prisma from '#application/prisma'
import Storage from '#application/storage'
import { TSocket } from '#application/types'
import characterRepository from '#repositories/characterRepository'
interface IPayload {
object: string
}
export default class ObjectRemoveEvent {
constructor(
private readonly io: Server,
private readonly socket: TSocket
) {}
public listen(): void {
this.socket.on('gm:object:remove', this.handleEvent.bind(this))
}
private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> {
const character = await characterRepository.getById(this.socket.characterId as number)
if (!character) return callback(false)
if (character.role !== 'gm') {
return callback(false)
}
try {
await prisma.object.delete({
where: {
id: data.object
}
})
// get root path
const public_folder = Storage.getPublicPath('objects')
// remove the tile from the disk
const finalFilePath = Storage.getPublicPath('objects', data.object + '.png')
fs.unlink(finalFilePath, (err) => {
if (err) {
gameMasterLogger.error(`Error deleting object ${data.object}: ${err.message}`)
callback(false)
return
}
callback(true)
})
} catch (error) {
gameLogger.error(`Error deleting object ${data.object}: ${error instanceof Error ? error.message : String(error)}`)
callback(false)
}
}
}

View File

@ -1,59 +0,0 @@
import { Server } from 'socket.io'
import prisma from '#application/prisma'
import { TSocket } from '#application/types'
import characterRepository from '#repositories/characterRepository'
type Payload = {
id: string
name: string
tags: string[]
originX: number
originY: number
isAnimated: boolean
frameRate: number
frameWidth: number
frameHeight: number
}
export default class ObjectUpdateEvent {
constructor(
private readonly io: Server,
private readonly socket: TSocket
) {}
public listen(): void {
this.socket.on('gm:object:update', this.handleEvent.bind(this))
}
private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise<void> {
const character = await characterRepository.getById(this.socket.characterId as number)
if (!character) return callback(false)
if (character.role !== 'gm') {
return callback(false)
}
try {
const object = await prisma.object.update({
where: {
id: data.id
},
data: {
name: data.name,
tags: data.tags,
originX: data.originX,
originY: data.originY,
isAnimated: data.isAnimated,
frameRate: data.frameRate,
frameWidth: data.frameWidth,
frameHeight: data.frameHeight
}
})
callback(true)
} catch (error) {
console.error(error)
callback(false)
}
}
}

View File

@ -1,73 +0,0 @@
import fs from 'fs/promises'
import { writeFile } from 'node:fs/promises'
import sharp from 'sharp'
import { Server } from 'socket.io'
import { gameMasterLogger } from '#application/logger'
import prisma from '#application/prisma'
import Storage from '#application/storage'
import { TSocket } from '#application/types'
import characterRepository from '#repositories/characterRepository'
interface IObjectData {
[key: string]: Buffer
}
export default class ObjectUploadEvent {
constructor(
private readonly io: Server,
private readonly socket: TSocket
) {}
public listen(): void {
this.socket.on('gm:object:upload', this.handleEvent.bind(this))
}
private async handleEvent(data: IObjectData, callback: (response: boolean) => void): Promise<void> {
try {
const character = await characterRepository.getById(this.socket.characterId as number)
if (!character) return callback(false)
if (character.role !== 'gm') {
return callback(false)
}
const public_folder = Storage.getPublicPath('objects')
// Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true })
const uploadPromises = Object.entries(data).map(async ([key, objectData]) => {
// Get image dimensions
const metadata = await sharp(objectData).metadata()
const width = metadata.width || 0
const height = metadata.height || 0
const object = await prisma.object.create({
data: {
name: key,
tags: [],
originX: 0,
originY: 0,
frameWidth: width,
frameHeight: height
}
})
const uuid = object.id
const filename = `${uuid}.png`
const finalFilePath = Storage.getPublicPath('objects', filename)
await writeFile(finalFilePath, objectData)
gameMasterLogger.info('gm:object:upload', `Object ${key} uploaded with id ${uuid}`)
})
await Promise.all(uploadPromises)
callback(true)
} catch (error: any) {
gameMasterLogger.error('gm:object:upload error', error.message)
callback(false)
}
}
}