Storage class is now OOP

This commit is contained in:
Dennis Postma 2025-01-01 21:34:23 +01:00
parent 04e081c31a
commit 5982422e04
15 changed files with 109 additions and 72 deletions

View File

@ -3,7 +3,7 @@ import * as path from 'path'
import { pathToFileURL } from 'url'
import Logger, { LoggerType } from '#application/logger'
import { getAppPath } from '#application/storage'
import Storage from '#application/storage'
import { Command } from '#application/types'
export class CommandRegistry {
@ -15,7 +15,7 @@ export class CommandRegistry {
}
public async loadCommands(): Promise<void> {
const directory = getAppPath('commands')
const directory = Storage.getAppPath('commands')
this.logger.info(`Loading commands from: ${directory}`)
try {
@ -32,7 +32,7 @@ export class CommandRegistry {
private async loadCommandFile(file: fs.Dirent): Promise<void> {
try {
const filePath = getAppPath('commands', file.name)
const filePath = Storage.getAppPath('commands', file.name)
const commandName = path.basename(file.name, path.extname(file.name))
const module = await import(pathToFileURL(filePath).href)

View File

@ -1,34 +1,71 @@
import fs from 'fs'
import path from 'path'
import config from './config'
import config from '#application/config'
export function getRootPath(folder: string, ...additionalSegments: string[]) {
return path.join(process.cwd(), folder, ...additionalSegments)
}
class Storage {
private readonly baseDir: string
private readonly rootDir: string
export function getAppPath(folder: string, ...additionalSegments: string[]) {
const baseDir = config.ENV === 'development' ? 'src' : 'dist'
return path.join(process.cwd(), baseDir, folder, ...additionalSegments)
}
constructor() {
this.rootDir = process.cwd()
this.baseDir = config.ENV === 'development' ? 'src' : 'dist'
}
export function getPublicPath(folder: string, ...additionalSegments: string[]) {
return path.join(process.cwd(), 'public', folder, ...additionalSegments)
}
/**
* Gets path relative to project root
*/
public getRootPath(folder: string, ...additionalSegments: string[]): string {
return path.join(this.rootDir, folder, ...additionalSegments)
}
export function doesPathExist(path: string) {
try {
fs.accessSync(path, fs.constants.F_OK)
return true
} catch (e) {
return false
/**
* Gets path relative to app directory (src/dist)
*/
public getAppPath(folder: string, ...additionalSegments: string[]): string {
return path.join(this.rootDir, this.baseDir, folder, ...additionalSegments)
}
/**
* Gets path relative to public directory
*/
public getPublicPath(folder: string, ...additionalSegments: string[]): string {
return path.join(this.rootDir, 'public', folder, ...additionalSegments)
}
/**
* Checks if a path exists
* @throws Error if path is empty or invalid
*/
public doesPathExist(pathToCheck: string): boolean {
if (!pathToCheck) {
throw new Error('Path cannot be empty')
}
try {
fs.accessSync(pathToCheck, fs.constants.F_OK)
return true
} catch (e) {
return false
}
}
/**
* Creates a directory and any necessary parent directories
* @throws Error if directory creation fails
*/
public createDir(dirPath: string): void {
if (!dirPath) {
throw new Error('Directory path cannot be empty')
}
try {
fs.mkdirSync(dirPath, { recursive: true })
} catch (error) {
const typedError = error as Error
throw new Error(`Failed to create directory: ${typedError.message}`)
}
}
}
export function createDir(path: string) {
try {
fs.mkdirSync(path, { recursive: true })
} catch (e) {
console.error(e)
}
}
export default new Storage()

View File

@ -1,11 +1,10 @@
import fs from 'fs'
import sharp from 'sharp'
import { Server } from 'socket.io'
import { BaseCommand } from '#application/base/baseCommand'
import { CharacterGender, CharacterRace } from '#application/enums'
import { getPublicPath } from '#application/storage'
import Storage from '#application/storage'
import { UUID } from '#application/types'
import { Character } from '#entities/character'
import { CharacterHair } from '#entities/characterHair'
@ -44,7 +43,7 @@ export default class InitCommand extends BaseCommand {
}
private async importTiles(): Promise<void> {
for (const tile of fs.readdirSync(getPublicPath('tiles'))) {
for (const tile of fs.readdirSync(Storage.getPublicPath('tiles'))) {
const newTile = new Tile()
newTile.setId(tile.split('.')[0] as UUID).setName('New tile')
@ -53,18 +52,18 @@ export default class InitCommand extends BaseCommand {
}
private async importObjects(): Promise<void> {
for (const object of fs.readdirSync(getPublicPath('objects'))) {
for (const object of fs.readdirSync(Storage.getPublicPath('objects'))) {
const newMapObject = new MapObject()
newMapObject
.setId(object.split('.')[0] as UUID)
.setName('New object')
.setFrameWidth(
(await sharp(getPublicPath('objects', object))
(await sharp(Storage.getPublicPath('objects', object))
.metadata()
.then((metadata) => metadata.height)) ?? 0
)
.setFrameHeight(
(await sharp(getPublicPath('objects', object))
(await sharp(Storage.getPublicPath('objects', object))
.metadata()
.then((metadata) => metadata.width)) ?? 0
)

View File

@ -3,12 +3,12 @@ import fs from 'fs'
import sharp from 'sharp'
import { BaseCommand } from '#application/base/baseCommand'
import { getPublicPath } from '#application/storage'
import Storage from '#application/storage'
export default class TilesCommand extends BaseCommand {
public async execute(): Promise<void> {
// Get all tiles
const tilesDir = getPublicPath('tiles')
const tilesDir = Storage.getPublicPath('tiles')
const tiles = fs.readdirSync(tilesDir).filter((file) => file.endsWith('.png'))
// Create output directory if it doesn't exist
@ -18,14 +18,14 @@ export default class TilesCommand extends BaseCommand {
for (const tile of tiles) {
// Check if tile is already 66x34
const metadata = await sharp(getPublicPath('tiles', tile)).metadata()
const metadata = await sharp(Storage.getPublicPath('tiles', tile)).metadata()
if (metadata.width === 66 && metadata.height === 34) {
this.logger.info(`Tile ${tile} already processed`)
continue
}
const inputPath = getPublicPath('tiles', tile)
const tempPath = getPublicPath('tiles', `temp_${tile}`)
const inputPath = Storage.getPublicPath('tiles', tile)
const tempPath = Storage.getPublicPath('tiles', `temp_${tile}`)
try {
await sharp(inputPath)

View File

@ -4,7 +4,7 @@ import { Server } from 'socket.io'
import { gameLogger, gameMasterLogger } from '#application/logger'
import prisma from '#application/prisma'
import { getPublicPath } from '#application/storage'
import Storage from '#application/storage'
import { TSocket } from '#application/types'
import characterRepository from '#repositories/characterRepository'
@ -38,10 +38,10 @@ export default class ObjectRemoveEvent {
})
// get root path
const public_folder = getPublicPath('objects')
const public_folder = Storage.getPublicPath('objects')
// remove the tile from the disk
const finalFilePath = getPublicPath('objects', data.object + '.png')
const finalFilePath = Storage.getPublicPath('objects', data.object + '.png')
fs.unlink(finalFilePath, (err) => {
if (err) {
gameMasterLogger.error(`Error deleting object ${data.object}: ${err.message}`)

View File

@ -6,7 +6,7 @@ import { Server } from 'socket.io'
import { gameMasterLogger } from '#application/logger'
import prisma from '#application/prisma'
import { getPublicPath } from '#application/storage'
import Storage from '#application/storage'
import { TSocket } from '#application/types'
import characterRepository from '#repositories/characterRepository'
@ -32,7 +32,7 @@ export default class ObjectUploadEvent {
if (character.role !== 'gm') {
return callback(false)
}
const public_folder = getPublicPath('objects')
const public_folder = Storage.getPublicPath('objects')
// Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true })
@ -56,7 +56,7 @@ export default class ObjectUploadEvent {
const uuid = object.id
const filename = `${uuid}.png`
const finalFilePath = getPublicPath('objects', filename)
const finalFilePath = Storage.getPublicPath('objects', filename)
await writeFile(finalFilePath, objectData)
gameMasterLogger.info('gm:object:upload', `Object ${key} uploaded with id ${uuid}`)

View File

@ -3,7 +3,7 @@ import fs from 'fs/promises'
import { Server } from 'socket.io'
import prisma from '#application/prisma'
import { getPublicPath } from '#application/storage'
import Storage from '#application/storage'
import { TSocket } from '#application/types'
import characterRepository from '#repositories/characterRepository'
@ -26,7 +26,7 @@ export default class SpriteCreateEvent {
return callback(false)
}
const public_folder = getPublicPath('sprites')
const public_folder = Storage.getPublicPath('sprites')
// Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true })
@ -39,7 +39,7 @@ export default class SpriteCreateEvent {
const uuid = sprite.id
// Create folder with uuid
const sprite_folder = getPublicPath('sprites', uuid)
const sprite_folder = Storage.getPublicPath('sprites', uuid)
await fs.mkdir(sprite_folder, { recursive: true })
callback(true)

View File

@ -4,7 +4,7 @@ import { Server } from 'socket.io'
import { gameMasterLogger } from '#application/logger'
import prisma from '#application/prisma'
import { getPublicPath } from '#application/storage'
import Storage from '#application/storage'
import { TSocket } from '#application/types'
import CharacterRepository from '#repositories/characterRepository'
@ -19,7 +19,7 @@ export default class GMSpriteDeleteEvent {
private readonly io: Server,
private readonly socket: TSocket
) {
this.public_folder = getPublicPath('sprites')
this.public_folder = Storage.getPublicPath('sprites')
}
public listen(): void {
@ -45,7 +45,7 @@ export default class GMSpriteDeleteEvent {
}
private async deleteSpriteFolder(spriteId: string): Promise<void> {
const finalFilePath = getPublicPath('sprites', spriteId)
const finalFilePath = Storage.getPublicPath('sprites', spriteId)
if (fs.existsSync(finalFilePath)) {
await fs.promises.rmdir(finalFilePath, { recursive: true })

View File

@ -7,7 +7,7 @@ import type { Prisma, SpriteAction } from '@prisma/client'
import { gameMasterLogger } from '#application/logger'
import prisma from '#application/prisma'
import { getPublicPath } from '#application/storage'
import Storage from '#application/storage'
import { TSocket } from '#application/types'
import CharacterRepository from '#repositories/characterRepository'
@ -314,13 +314,13 @@ export default class SpriteUpdateEvent {
}
private async saveSpritesToDisk(id: string, actions: ProcessedSpriteAction[]): Promise<void> {
const publicFolder = getPublicPath('sprites', id)
const publicFolder = Storage.getPublicPath('sprites', id)
await mkdir(publicFolder, { recursive: true })
await Promise.all(
actions.map(async (action) => {
const spritesheet = await this.createSpritesheet(action.buffersWithDimensions)
await writeFile(getPublicPath('sprites', id, `${action.action}.png`), spritesheet)
await writeFile(Storage.getPublicPath('sprites', id, `${action.action}.png`), spritesheet)
})
)
}

View File

@ -4,7 +4,7 @@ import { Server } from 'socket.io'
import { gameMasterLogger } from '#application/logger'
import prisma from '#application/prisma'
import { getPublicPath } from '#application/storage'
import Storage from '#application/storage'
import { TSocket } from '#application/types'
import characterRepository from '#repositories/characterRepository'
@ -19,7 +19,7 @@ export default class GMTileDeleteEvent {
private readonly io: Server,
private readonly socket: TSocket
) {
this.public_folder = getPublicPath('tiles')
this.public_folder = Storage.getPublicPath('tiles')
}
public listen(): void {
@ -56,7 +56,7 @@ export default class GMTileDeleteEvent {
}
private async deleteTileFile(tileId: string): Promise<void> {
const finalFilePath = getPublicPath('tiles', `${tileId}.png`)
const finalFilePath = Storage.getPublicPath('tiles', `${tileId}.png`)
try {
await fs.unlink(finalFilePath)
} catch (error: any) {

View File

@ -5,7 +5,7 @@ import { Server } from 'socket.io'
import { gameMasterLogger } from '#application/logger'
import prisma from '#application/prisma'
import { getPublicPath } from '#application/storage'
import Storage from '#application/storage'
import { TSocket } from '#application/types'
import characterRepository from '#repositories/characterRepository'
@ -32,7 +32,7 @@ export default class TileUploadEvent {
return
}
const public_folder = getPublicPath('tiles')
const public_folder = Storage.getPublicPath('tiles')
// Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true })
@ -45,7 +45,7 @@ export default class TileUploadEvent {
})
const uuid = tile.id
const filename = `${uuid}.png`
const finalFilePath = getPublicPath('tiles', filename)
const finalFilePath = Storage.getPublicPath('tiles', filename)
await writeFile(finalFilePath, tileData)
})

View File

@ -4,7 +4,7 @@ import { Request, Response } from 'express'
import { BaseController } from '#application/base/baseController'
import Database from '#application/database'
import { getPublicPath } from '#application/storage'
import Storage from '#application/storage'
import { AssetData, UUID } from '#application/types'
import SpriteRepository from '#repositories/spriteRepository'
import TileRepository from '#repositories/tileRepository'
@ -98,7 +98,7 @@ export class AssetsController extends BaseController {
public async downloadAsset(req: Request, res: Response) {
const { type, spriteId, file } = req.params
const assetPath = type === 'sprites' && spriteId ? getPublicPath(type, spriteId, file) : getPublicPath(type, file)
const assetPath = type === 'sprites' && spriteId ? Storage.getPublicPath(type, spriteId, file) : Storage.getPublicPath(type, file)
if (!fs.existsSync(assetPath)) {
this.logger.error(`File not found: ${assetPath}`)

View File

@ -4,14 +4,15 @@ import { Request, Response } from 'express'
import sharp from 'sharp'
import { BaseController } from '#application/base/baseController'
import { getPublicPath } from '#application/storage'
import Storage from '#application/storage'
import { UUID } from '#application/types'
import CharacterHairRepository from '#repositories/characterHairRepository'
import CharacterRepository from '#repositories/characterRepository'
import CharacterTypeRepository from '#repositories/characterTypeRepository'
interface AvatarOptions {
characterTypeId: number
characterHairId?: number
characterTypeId: UUID
characterHairId?: UUID
}
export class AvatarController extends BaseController {
@ -57,7 +58,7 @@ export class AvatarController extends BaseController {
return this.sendError(res, 'Character type not found', 404)
}
const bodySpritePath = getPublicPath('sprites', characterType.sprite.id, 'idle_right_down.png')
const bodySpritePath = Storage.getPublicPath('sprites', characterType.sprite.id, 'idle_right_down.png')
if (!fs.existsSync(bodySpritePath)) {
return this.sendError(res, 'Body sprite file not found', 404)
}
@ -71,7 +72,7 @@ export class AvatarController extends BaseController {
if (options.characterHairId) {
const characterHair = await CharacterHairRepository.getById(options.characterHairId)
if (characterHair?.sprite?.id) {
const hairSpritePath = getPublicPath('sprites', characterHair.sprite.id, 'front.png')
const hairSpritePath = Storage.getPublicPath('sprites', characterHair.sprite.id, 'front.png')
if (fs.existsSync(hairSpritePath)) {
avatar = avatar.composite([{ input: hairSpritePath, gravity: 'north' }])
}

View File

@ -6,7 +6,7 @@ import { Server as SocketServer } from 'socket.io'
import config from '#application/config'
import Logger, { LoggerType } from '#application/logger'
import { getAppPath } from '#application/storage'
import Storage from '#application/storage'
import { TSocket } from '#application/types'
import SocketManager from '#managers/socketManager'
@ -56,9 +56,9 @@ class QueueManager {
const { jobName, params, socketId } = job.data
try {
const jobsDir = getAppPath('jobs')
const jobsDir = Storage.getAppPath('jobs')
const extension = config.ENV === 'development' ? '.ts' : '.js'
const jobPath = getAppPath('jobs', `${jobName}${extension}`)
const jobPath = Storage.getAppPath('jobs', `${jobName}${extension}`)
if (!fs.existsSync(jobPath)) {
this.logger.warn(`Job file not found: ${jobPath}`)

View File

@ -7,7 +7,7 @@ import { Server as SocketServer } from 'socket.io'
import config from '#application/config'
import Logger, { LoggerType } from '#application/logger'
import { getAppPath } from '#application/storage'
import Storage from '#application/storage'
import { TSocket } from '#application/types'
import { Authentication } from '#middleware/authentication'
@ -61,11 +61,11 @@ class SocketManager {
*/
private async loadEventHandlers(baseDir: string, subDir: string, socket: TSocket): Promise<void> {
try {
const fullDir = getAppPath(baseDir, subDir)
const fullDir = Storage.getAppPath(baseDir, subDir)
const files = await fs.promises.readdir(fullDir, { withFileTypes: true })
for (const file of files) {
const filePath = getAppPath(baseDir, subDir, file.name)
const filePath = Storage.getAppPath(baseDir, subDir, file.name)
if (file.isDirectory()) {
await this.loadEventHandlers(baseDir, `${subDir}/${file.name}`, socket)