1
0
forked from noxious/server

NPM update, removed CRUD functions from object repository, added prettier

This commit is contained in:
Dennis Postma 2024-07-22 01:36:05 +02:00
parent 792abdfaf6
commit 6131a8455a
51 changed files with 1450 additions and 1460 deletions

8
.prettierrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 300,
"trailingComma": "none"
}

27
package-lock.json generated
View File

@ -5,25 +5,26 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@prisma/client": "^5.13.0", "@prisma/client": "^5.17.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.19.2",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"prisma": "^5.13.0", "prisma": "^5.17.0",
"sharp": "^0.33.4", "sharp": "^0.33.4",
"socket.io": "^4.7.5", "socket.io": "^4.7.5",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.4.5", "typescript": "^5.5.3",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.12.11", "@types/node": "^20.14.11",
"nodemon": "^3.1.0" "nodemon": "^3.1.4",
"prettier": "^3.3.3"
} }
}, },
"node_modules/@cspotcode/source-map-support": { "node_modules/@cspotcode/source-map-support": {
@ -1886,6 +1887,22 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/prettier": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prisma": { "node_modules/prisma": {
"version": "5.17.0", "version": "5.17.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.17.0.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.17.0.tgz",

View File

@ -2,27 +2,29 @@
"scripts": { "scripts": {
"start": "npx prisma migrate deploy && node dist/server.js", "start": "npx prisma migrate deploy && node dist/server.js",
"dev": "nodemon --exec ts-node src/server.ts", "dev": "nodemon --exec ts-node src/server.ts",
"build": "tsc" "build": "tsc",
"format": "prettier --write src/"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^5.13.0", "@prisma/client": "^5.17.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.19.2",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"prisma": "^5.13.0", "prisma": "^5.17.0",
"sharp": "^0.33.4", "sharp": "^0.33.4",
"socket.io": "^4.7.5", "socket.io": "^4.7.5",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.4.5", "typescript": "^5.5.3",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.12.11", "@types/node": "^20.14.11",
"nodemon": "^3.1.0" "nodemon": "^3.1.4",
"prettier": "^3.3.3"
} }
} }

View File

@ -1,9 +1,9 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
type CommandInput = string[] type CommandInput = string[]
export default function (input: CommandInput, io: Server) { export default function (input: CommandInput, io: Server) {
const message: string = input.join(' ') ?? null; const message: string = input.join(' ') ?? null
if (!message) return console.log('message is required'); if (!message) return console.log('message is required')
io.emit('notification', {message: message}); io.emit('notification', { message: message })
}; }

View File

@ -1,8 +1,8 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import ZoneManager from "../managers/ZoneManager"; import ZoneManager from '../managers/ZoneManager'
type CommandInput = string[] type CommandInput = string[]
export default function (input: CommandInput, io: Server) { export default function (input: CommandInput, io: Server) {
console.log(ZoneManager.getLoadedZones()) console.log(ZoneManager.getLoadedZones())
}; }

View File

@ -1,23 +1,23 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../utilities/Types"; import { TSocket } from '../utilities/Types'
import ZoneManager from "../managers/ZoneManager"; import ZoneManager from '../managers/ZoneManager'
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('disconnect', (data: any) => { socket.on('disconnect', (data: any) => {
if (!socket.user) { if (!socket.user) {
console.log('User disconnected but had no user set'); console.log('User disconnected but had no user set')
return; return
} }
io.emit('user:disconnect', socket.user.id); io.emit('user:disconnect', socket.user.id)
if (!socket.character) { if (!socket.character) {
console.log('User disconnected but had no character set'); console.log('User disconnected but had no character set')
return; return
} }
ZoneManager.removeCharacterFromZone(socket.character.zoneId, socket.character); ZoneManager.removeCharacterFromZone(socket.character.zoneId, socket.character)
io.emit('character:disconnect', socket.character.id); io.emit('character:disconnect', socket.character.id)
}); })
} }

View File

@ -1,9 +1,9 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../utilities/Types"; import { TSocket } from '../utilities/Types'
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('login', () => { socket.on('login', () => {
// return user data // return user data
socket.emit('logged_in', {user: socket.user}); socket.emit('logged_in', { user: socket.user })
}); })
} }

View File

@ -1,7 +1,7 @@
import { Socket, Server } from "socket.io"; import { Socket, Server } from 'socket.io'
import {TSocket} from "../../utilities/Types"; import { TSocket } from '../../utilities/Types'
import CharacterRepository from "../../repositories/CharacterRepository"; import CharacterRepository from '../../repositories/CharacterRepository'
import {Character, User} from "@prisma/client"; import { Character, User } from '@prisma/client'
type SocketResponseT = { type SocketResponseT = {
character_id: number character_id: number
@ -9,12 +9,12 @@ type SocketResponseT = {
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('character:connect', async (data: SocketResponseT) => { socket.on('character:connect', async (data: SocketResponseT) => {
console.log('character:connect requested', data); console.log('character:connect requested', data)
try { try {
socket.character = await CharacterRepository.getByUserAndId(socket.user?.id as number, data.character_id) as Character; socket.character = (await CharacterRepository.getByUserAndId(socket.user?.id as number, data.character_id)) as Character
socket.emit('character:connect', socket.character) socket.emit('character:connect', socket.character)
} catch (error: any) { } catch (error: any) {
console.log('character:connect error', error); console.log('character:connect error', error)
} }
}); })
} }

View File

@ -1,37 +1,37 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../utilities/Types"; import { TSocket } from '../../utilities/Types'
import {Character} from "@prisma/client"; import { Character } from '@prisma/client'
import CharacterRepository from "../../repositories/CharacterRepository"; import CharacterRepository from '../../repositories/CharacterRepository'
import {ZCharacterCreate} from "../../utilities/ZodTypes"; import { ZCharacterCreate } from '../../utilities/ZodTypes'
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('character:create', async (data: any) => { socket.on('character:create', async (data: any) => {
// zod validate // zod validate
try { try {
data = ZCharacterCreate.parse(data); data = ZCharacterCreate.parse(data)
const user_id = socket.user?.id as number; const user_id = socket.user?.id as number
// Check if character name already exists // Check if character name already exists
const characterExists = await CharacterRepository.getByName(data.name); const characterExists = await CharacterRepository.getByName(data.name)
if (characterExists) { if (characterExists) {
return socket.emit('notification', {message: 'Character name already exists'}); return socket.emit('notification', { message: 'Character name already exists' })
} }
let characters: Character[] = await CharacterRepository.getByUserId(user_id) as Character[]; let characters: Character[] = (await CharacterRepository.getByUserId(user_id)) as Character[]
if (characters.length >= 4) { if (characters.length >= 4) {
return socket.emit('notification', {message: 'You can only have 4 characters'}); return socket.emit('notification', { message: 'You can only have 4 characters' })
} }
const character: Character = await CharacterRepository.create(user_id, data.name, 'player') as Character; const character: Character = (await CharacterRepository.create(user_id, data.name, 'player')) as Character
characters = [...characters, character]; characters = [...characters, character]
socket.emit('character:create:success'); socket.emit('character:create:success')
socket.emit('character:list', characters); socket.emit('character:list', characters)
} catch (error: any) { } catch (error: any) {
return socket.emit('notification', {message: error.errors[0]?.message ?? 'Invalid data'}); return socket.emit('notification', { message: error.errors[0]?.message ?? 'Invalid data' })
} }
}); })
} }

View File

@ -1,30 +1,30 @@
import {Server} from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../utilities/Types"; import { TSocket } from '../../utilities/Types'
import { Character, Zone } from '@prisma/client' import { Character, Zone } from '@prisma/client'
import CharacterRepository from "../../repositories/CharacterRepository"; import CharacterRepository from '../../repositories/CharacterRepository'
type TypePayload = { type TypePayload = {
character_id: number; character_id: number
} }
type TypeResponse = { type TypeResponse = {
zone: Zone; zone: Zone
characters: Character[]; characters: Character[]
} }
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('character:delete', async (data: TypePayload, callback: (response: TypeResponse) => void) => { socket.on('character:delete', async (data: TypePayload, callback: (response: TypeResponse) => void) => {
// zod validate // zod validate
try { try {
await CharacterRepository.deleteByUserIdAndId(socket.user?.id as number, data.character_id as number); await CharacterRepository.deleteByUserIdAndId(socket.user?.id as number, data.character_id as number)
const user_id = socket.user?.id as number; const user_id = socket.user?.id as number
const characters: Character[] = await CharacterRepository.getByUserId(user_id) as Character[]; const characters: Character[] = (await CharacterRepository.getByUserId(user_id)) as Character[]
socket.emit('character:list', characters); socket.emit('character:list', characters)
} catch (error: any) { } catch (error: any) {
console.log(error); console.log(error)
return socket.emit('notification', {message: 'Character delete failed. Please try again.'}); return socket.emit('notification', { message: 'Character delete failed. Please try again.' })
} }
}); })
} }

View File

@ -1,17 +1,17 @@
import { Socket, Server } from "socket.io"; import { Socket, Server } from 'socket.io'
import {TSocket} from "../../utilities/Types"; import { TSocket } from '../../utilities/Types'
import {Character} from "@prisma/client"; import { Character } from '@prisma/client'
import CharacterRepository from "../../repositories/CharacterRepository"; import CharacterRepository from '../../repositories/CharacterRepository'
export default function CharacterList(socket: TSocket, io: Server) { export default function CharacterList(socket: TSocket, io: Server) {
socket.on('character:list', async (data: any) => { socket.on('character:list', async (data: any) => {
try { try {
console.log('character:list requested'); console.log('character:list requested')
const user_id = socket.user?.id as number; const user_id = socket.user?.id as number
const characters: Character[] = await CharacterRepository.getByUserId(user_id) as Character[]; const characters: Character[] = (await CharacterRepository.getByUserId(user_id)) as Character[]
socket.emit('character:list', characters); socket.emit('character:list', characters)
} catch (error: any) { } catch (error: any) {
console.log('character:list error', error); console.log('character:list error', error)
} }
}); })
} }

View File

@ -1,33 +1,33 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../utilities/Types"; import { TSocket } from '../../utilities/Types'
import CharacterRepository from "../../repositories/CharacterRepository"; import CharacterRepository from '../../repositories/CharacterRepository'
import ZoneManager from "../../managers/ZoneManager"; import ZoneManager from '../../managers/ZoneManager'
type SocketResponseT = { type SocketResponseT = {
position_x: number, position_x: number
position_y: number, position_y: number
} }
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('character:move', async (data: SocketResponseT) => { socket.on('character:move', async (data: SocketResponseT) => {
try { try {
console.log('character:move requested', data); console.log('character:move requested', data)
if (!socket.character) { if (!socket.character) {
console.log('character:move error', 'Character not found'); console.log('character:move error', 'Character not found')
return; return
} }
socket.character.position_x = data.position_x; socket.character.position_x = data.position_x
socket.character.position_y = data.position_y; socket.character.position_y = data.position_y
await CharacterRepository.updatePosition(socket.character.id as number, data.position_x, data.position_y); await CharacterRepository.updatePosition(socket.character.id as number, data.position_x, data.position_y)
ZoneManager.updateCharacterInZone(socket.character.zoneId, socket.character); ZoneManager.updateCharacterInZone(socket.character.zoneId, socket.character)
console.log(socket.character); console.log(socket.character)
io.in(socket.character.zoneId.toString()).emit('character:moved', socket.character); io.in(socket.character.zoneId.toString()).emit('character:moved', socket.character)
} catch (error: any) { } catch (error: any) {
console.log('character:move error', error); console.log('character:move error', error)
} }
}); })
} }

View File

@ -1,8 +1,8 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../utilities/Types"; import { TSocket } from '../../utilities/Types'
import ZoneRepository from "../../repositories/ZoneRepository"; import ZoneRepository from '../../repositories/ZoneRepository'
import ZoneManager from "../../managers/ZoneManager"; import ZoneManager from '../../managers/ZoneManager'
import {Character, Zone} from "@prisma/client"; import { Character, Zone } from '@prisma/client'
/** /**
* Handle character zone leave event * Handle character zone leave event
@ -11,35 +11,35 @@ import {Character, Zone} from "@prisma/client";
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('character:zone:leave', async () => { socket.on('character:zone:leave', async () => {
console.log(`---Socket ${socket.character?.id} has leaved zone.`); console.log(`---Socket ${socket.character?.id} has leaved zone.`)
if (!socket.character) { if (!socket.character) {
console.log('Socket leaved zone but had no character set'); console.log('Socket leaved zone but had no character set')
return; return
} }
if (!socket.character.zoneId) { if (!socket.character.zoneId) {
console.log(`---Zone id not provided.`); console.log(`---Zone id not provided.`)
return; return
} }
const zone = await ZoneRepository.getById(socket.character.zoneId); const zone = await ZoneRepository.getById(socket.character.zoneId)
if (!zone) { if (!zone) {
console.log(`---Zone not found.`); console.log(`---Zone not found.`)
return; return
} }
socket.leave(zone.id.toString()); socket.leave(zone.id.toString())
socket.emit('character:zone:unload'); socket.emit('character:zone:unload')
// let other clients know of new character // let other clients know of new character
io.to(zone.id.toString()).emit('zone:character:leave', socket.character); io.to(zone.id.toString()).emit('zone:character:leave', socket.character)
// add character to zone manager // add character to zone manager
ZoneManager.removeCharacterFromZone(zone.id, socket.character as Character); ZoneManager.removeCharacterFromZone(zone.id, socket.character as Character)
}); })
} }
/** /**

View File

@ -1,16 +1,16 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../utilities/Types"; import { TSocket } from '../../utilities/Types'
import ZoneRepository from "../../repositories/ZoneRepository"; import ZoneRepository from '../../repositories/ZoneRepository'
import ZoneManager from "../../managers/ZoneManager"; import ZoneManager from '../../managers/ZoneManager'
import {Character, Zone} from "@prisma/client"; import { Character, Zone } from '@prisma/client'
interface IPayload { interface IPayload {
zoneId: number; zoneId: number
} }
interface IResponse { interface IResponse {
zone: Zone; zone: Zone
characters: Character[]; characters: Character[]
} }
/** /**
@ -20,29 +20,29 @@ interface IResponse {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('character:zone:request', async (data: IPayload, callback: (response: IResponse) => void) => { socket.on('character:zone:request', async (data: IPayload, callback: (response: IResponse) => void) => {
console.log(`---User ${socket.character?.id} has requested zone.`); console.log(`---User ${socket.character?.id} has requested zone.`)
if (!data.zoneId) { if (!data.zoneId) {
console.log(`---Zone id not provided.`); console.log(`---Zone id not provided.`)
return; return
} }
const zone = await ZoneRepository.getById(data.zoneId); const zone = await ZoneRepository.getById(data.zoneId)
if (!zone) { if (!zone) {
console.log(`---Zone not found.`); console.log(`---Zone not found.`)
return; return
} }
socket.join(zone.id.toString()); socket.join(zone.id.toString())
// send over zone and characters to socket // send over zone and characters to socket
callback({zone, characters: ZoneManager.getCharactersInZone(zone.id)}); callback({ zone, characters: ZoneManager.getCharactersInZone(zone.id) })
// let other clients know of new character // let other clients know of new character
io.to(zone.id.toString()).emit('zone:character:join', socket.character); io.to(zone.id.toString()).emit('zone:character:join', socket.character)
// add character to zone manager // add character to zone manager
ZoneManager.addCharacterToZone(zone.id, socket.character as Character); ZoneManager.addCharacterToZone(zone.id, socket.character as Character)
}); })
} }

View File

@ -1,10 +1,9 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import { Object } from '@prisma/client' import { Object } from '@prisma/client'
import ObjectRepository from '../../../repositories/ObjectRepository' import ObjectRepository from '../../../repositories/ObjectRepository'
interface IPayload { interface IPayload {}
}
/** /**
* Handle game master list object event * Handle game master list object event
@ -13,14 +12,13 @@ interface IPayload {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:object:list', async (data: any, callback: (response: Object[]) => void) => { socket.on('gm:object:list', async (data: any, callback: (response: Object[]) => void) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`); console.log(`---Character #${socket.character?.id} is not a game master.`)
return; return
} }
// get all objects // get all objects
const objects = await ObjectRepository.getAll(); const objects = await ObjectRepository.getAll()
callback(objects); callback(objects)
}); })
} }

View File

@ -1,11 +1,11 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import path from "path"; import path from 'path'
import fs from "fs"; import fs from 'fs'
import ObjectRepository from '../../../repositories/ObjectRepository' import prisma from '../../../utilities/Prisma'
interface IPayload { interface IPayload {
object: string; object: string
} }
/** /**
@ -15,31 +15,34 @@ interface IPayload {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:object:remove', async (data: IPayload, callback: (response: boolean) => void) => { socket.on('gm:object:remove', async (data: IPayload, callback: (response: boolean) => void) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
return; return
} }
try { try {
await ObjectRepository.delete(data.object); await prisma.object.delete({
where: {
id: data.object
}
})
// get root path // get root path
const public_folder = path.join(process.cwd(), 'public', 'objects'); const public_folder = path.join(process.cwd(), 'public', 'objects')
// remove the tile from the disk // remove the tile from the disk
const finalFilePath = path.join(public_folder, data.object + '.png'); const finalFilePath = path.join(public_folder, data.object + '.png')
fs.unlink(finalFilePath, (err) => { fs.unlink(finalFilePath, (err) => {
if (err) { if (err) {
console.log(err); console.log(err)
callback(false); callback(false)
return; return
} }
callback(true); callback(true)
}); })
} catch (e) { } catch (e) {
console.log(e); console.log(e)
callback(false); callback(false)
} }
}); })
} }

View File

@ -1,14 +1,14 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import ObjectRepository from '../../../repositories/ObjectRepository' import prisma from '../../../utilities/Prisma'
import { Object } from '@prisma/client'
interface IPayload { interface IPayload {
id: string; id: string
name: string; name: string
tags: string[]; tags: string[]
origin_x: number; origin_x: number
origin_y: number; origin_y: number
isAnimated: boolean
} }
/** /**
@ -18,18 +18,27 @@ interface IPayload {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:object:update', async (data: IPayload, callback: (success: boolean) => void) => { socket.on('gm:object:update', async (data: IPayload, callback: (success: boolean) => void) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
return; return
} }
try { try {
const object = await ObjectRepository.update(data.id, data.name, data.tags, data.origin_x, data.origin_y); const object = await prisma.object.update({
where: {
callback(true); id: data.id
},
data: {
name: data.name,
tags: data.tags,
origin_x: data.origin_x,
origin_y: data.origin_y,
isAnimated: data.isAnimated
}
})
callback(true)
} catch (error) { } catch (error) {
console.error(error); console.error(error)
callback(false); callback(false)
} }
}); })
} }

View File

@ -1,12 +1,12 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import { TSocket } from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import { writeFile } from "node:fs/promises"; import { writeFile } from 'node:fs/promises'
import path from "path"; import path from 'path'
import fs from "fs/promises"; import fs from 'fs/promises'
import objectRepository from '../../../repositories/ObjectRepository' import prisma from '../../../utilities/Prisma'
interface IObjectData { interface IObjectData {
[key: string]: Buffer; [key: string]: Buffer
} }
/** /**
@ -18,29 +18,38 @@ export default function (socket: TSocket, io: Server) {
socket.on('gm:object:upload', async (data: IObjectData, callback: (response: boolean) => void) => { socket.on('gm:object:upload', async (data: IObjectData, callback: (response: boolean) => void) => {
try { try {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
callback(false); callback(false)
return; return
} }
const public_folder = path.join(process.cwd(), 'public', 'objects'); const public_folder = path.join(process.cwd(), 'public', 'objects')
// Ensure the folder exists // Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true }); await fs.mkdir(public_folder, { recursive: true })
const uploadPromises = Object.entries(data).map(async ([key, objectData]) => { const uploadPromises = Object.entries(data).map(async ([key, objectData]) => {
const object = await objectRepository.create('New object', 0, 0); const object = await prisma.object.create({
const uuid = object.id; data: {
const filename = `${uuid}.png`; name: key,
const finalFilePath = path.join(public_folder, filename); tags: [],
await writeFile(finalFilePath, objectData); origin_x: 0,
}); origin_y: 0,
isAnimated: false
}
})
await Promise.all(uploadPromises); const uuid = object.id
const filename = `${uuid}.png`
const finalFilePath = path.join(public_folder, filename)
await writeFile(finalFilePath, objectData)
})
callback(true); await Promise.all(uploadPromises)
callback(true)
} catch (error) { } catch (error) {
console.error('Error uploading tile:', error); console.error('Error uploading tile:', error)
callback(false); callback(false)
} }
}); })
} }

View File

@ -1,10 +1,9 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import { Sprite } from '@prisma/client' import { Sprite } from '@prisma/client'
import SpriteRepository from '../../../repositories/SpriteRepository' import SpriteRepository from '../../../repositories/SpriteRepository'
interface IPayload { interface IPayload {}
}
/** /**
* Handle game master list sprite event * Handle game master list sprite event
@ -13,14 +12,13 @@ interface IPayload {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:sprite:list', async (data: any, callback: (response: Sprite[]) => void) => { socket.on('gm:sprite:list', async (data: any, callback: (response: Sprite[]) => void) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`); console.log(`---Character #${socket.character?.id} is not a game master.`)
return; return
} }
// get all sprites // get all sprites
const sprites = await SpriteRepository.getAll(); const sprites = await SpriteRepository.getAll()
callback(sprites); callback(sprites)
}); })
} }

View File

@ -1,11 +1,11 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import path from "path"; import path from 'path'
import fs from "fs"; import fs from 'fs'
import SpriteRepository from '../../../repositories/SpriteRepository' import SpriteRepository from '../../../repositories/SpriteRepository'
interface IPayload { interface IPayload {
sprite: string; sprite: string
} }
/** /**
@ -15,31 +15,30 @@ interface IPayload {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:sprite:remove', async (data: IPayload, callback: (response: boolean) => void) => { socket.on('gm:sprite:remove', async (data: IPayload, callback: (response: boolean) => void) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
return; return
} }
try { try {
await SpriteRepository.delete(data.sprite); await SpriteRepository.delete(data.sprite)
// get root path // get root path
const public_folder = path.join(process.cwd(), 'public', 'sprites'); const public_folder = path.join(process.cwd(), 'public', 'sprites')
// remove the tile from the disk // remove the tile from the disk
const finalFilePath = path.join(public_folder, data.sprite + '.png'); const finalFilePath = path.join(public_folder, data.sprite + '.png')
fs.unlink(finalFilePath, (err) => { fs.unlink(finalFilePath, (err) => {
if (err) { if (err) {
console.log(err); console.log(err)
callback(false); callback(false)
return; return
} }
callback(true); callback(true)
}); })
} catch (e) { } catch (e) {
console.log(e); console.log(e)
callback(false); callback(false)
} }
}); })
} }

View File

@ -1,13 +1,13 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import SpriteRepository from '../../../repositories/SpriteRepository' import SpriteRepository from '../../../repositories/SpriteRepository'
import { Sprite } from '@prisma/client' import { Sprite } from '@prisma/client'
interface IPayload { interface IPayload {
id: string; id: string
name: string; name: string
origin_x: number; origin_x: number
origin_y: number; origin_y: number
} }
/** /**
@ -17,18 +17,17 @@ interface IPayload {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:sprite:update', async (data: IPayload, callback: (success: boolean) => void) => { socket.on('gm:sprite:update', async (data: IPayload, callback: (success: boolean) => void) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
return; return
} }
try { try {
const sprite = await SpriteRepository.update(data.id, data.name, data.origin_x, data.origin_y); const sprite = await SpriteRepository.update(data.id, data.name, data.origin_x, data.origin_y)
callback(true); callback(true)
} catch (error) { } catch (error) {
console.error(error); console.error(error)
callback(false); callback(false)
} }
}); })
} }

View File

@ -1,12 +1,12 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import { TSocket } from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import { writeFile } from "node:fs/promises"; import { writeFile } from 'node:fs/promises'
import path from "path"; import path from 'path'
import fs from "fs/promises"; import fs from 'fs/promises'
import spriteRepository from '../../../repositories/SpriteRepository' import spriteRepository from '../../../repositories/SpriteRepository'
interface ISpriteData { interface ISpriteData {
[key: string]: Buffer; [key: string]: Buffer
} }
/** /**
@ -18,29 +18,29 @@ export default function (socket: TSocket, io: Server) {
socket.on('gm:sprite:upload', async (data: ISpriteData, callback: (response: boolean) => void) => { socket.on('gm:sprite:upload', async (data: ISpriteData, callback: (response: boolean) => void) => {
try { try {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
callback(false); callback(false)
return; return
} }
const public_folder = path.join(process.cwd(), 'public', 'sprites'); const public_folder = path.join(process.cwd(), 'public', 'sprites')
// Ensure the folder exists // Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true }); await fs.mkdir(public_folder, { recursive: true })
const uploadPromises = Object.entries(data).map(async ([key, spriteData]) => { const uploadPromises = Object.entries(data).map(async ([key, spriteData]) => {
const sprite = await spriteRepository.create('New sprite', 0, 0); const sprite = await spriteRepository.create('New sprite', 0, 0)
const uuid = sprite.id; const uuid = sprite.id
const filename = `${uuid}.png`; const filename = `${uuid}.png`
const finalFilePath = path.join(public_folder, filename); const finalFilePath = path.join(public_folder, filename)
await writeFile(finalFilePath, spriteData); await writeFile(finalFilePath, spriteData)
}); })
await Promise.all(uploadPromises); await Promise.all(uploadPromises)
callback(true); callback(true)
} catch (error) { } catch (error) {
console.error('Error uploading tile:', error); console.error('Error uploading tile:', error)
callback(false); callback(false)
} }
}); })
} }

View File

@ -1,10 +1,9 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import { Tile } from '@prisma/client' import { Tile } from '@prisma/client'
import TileRepository from '../../../repositories/TileRepository' import TileRepository from '../../../repositories/TileRepository'
interface IPayload { interface IPayload {}
}
/** /**
* Handle game master list tile event * Handle game master list tile event
@ -13,14 +12,13 @@ interface IPayload {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:tile:list', async (data: any, callback: (response: Tile[]) => void) => { socket.on('gm:tile:list', async (data: any, callback: (response: Tile[]) => void) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`); console.log(`---Character #${socket.character?.id} is not a game master.`)
return; return
} }
// get all tiles // get all tiles
const tiles = await TileRepository.getAll(); const tiles = await TileRepository.getAll()
callback(tiles); callback(tiles)
}); })
} }

View File

@ -1,11 +1,11 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import path from "path"; import path from 'path'
import fs from "fs"; import fs from 'fs'
import TileRepository from '../../../repositories/TileRepository' import TileRepository from '../../../repositories/TileRepository'
interface IPayload { interface IPayload {
tile: string; tile: string
} }
/** /**
@ -15,31 +15,30 @@ interface IPayload {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:tile:remove', async (data: IPayload, callback: (response: boolean) => void) => { socket.on('gm:tile:remove', async (data: IPayload, callback: (response: boolean) => void) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
return; return
} }
try { try {
await TileRepository.delete(data.tile); await TileRepository.delete(data.tile)
// get root path // get root path
const public_folder = path.join(process.cwd(), 'public', 'tiles'); const public_folder = path.join(process.cwd(), 'public', 'tiles')
// remove the tile from the disk // remove the tile from the disk
const finalFilePath = path.join(public_folder, data.tile + '.png'); const finalFilePath = path.join(public_folder, data.tile + '.png')
fs.unlink(finalFilePath, (err) => { fs.unlink(finalFilePath, (err) => {
if (err) { if (err) {
console.log(err); console.log(err)
callback(false); callback(false)
return; return
} }
callback(true); callback(true)
}); })
} catch (e) { } catch (e) {
console.log(e); console.log(e)
callback(false); callback(false)
} }
}); })
} }

View File

@ -1,11 +1,11 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import TileRepository from '../../../repositories/TileRepository' import TileRepository from '../../../repositories/TileRepository'
interface IPayload { interface IPayload {
id: string; id: string
name: string; name: string
tags: string[]; tags: string[]
} }
/** /**
@ -15,18 +15,17 @@ interface IPayload {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:tile:update', async (data: IPayload, callback: (success: boolean) => void) => { socket.on('gm:tile:update', async (data: IPayload, callback: (success: boolean) => void) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
return; return
} }
try { try {
const Tile = await TileRepository.update(data.id, data.name, data.tags); const Tile = await TileRepository.update(data.id, data.name, data.tags)
callback(true); callback(true)
} catch (error) { } catch (error) {
console.error(error); console.error(error)
callback(false); callback(false)
} }
}); })
} }

View File

@ -1,12 +1,12 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import { TSocket } from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import { writeFile } from "node:fs/promises"; import { writeFile } from 'node:fs/promises'
import path from "path"; import path from 'path'
import fs from "fs/promises"; import fs from 'fs/promises'
import tileRepository from '../../../repositories/TileRepository' import tileRepository from '../../../repositories/TileRepository'
interface ITileData { interface ITileData {
[key: string]: Buffer; [key: string]: Buffer
} }
/** /**
@ -18,29 +18,29 @@ export default function (socket: TSocket, io: Server) {
socket.on('gm:tile:upload', async (data: ITileData, callback: (response: boolean) => void) => { socket.on('gm:tile:upload', async (data: ITileData, callback: (response: boolean) => void) => {
try { try {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
callback(false); callback(false)
return; return
} }
const public_folder = path.join(process.cwd(), 'public', 'tiles'); const public_folder = path.join(process.cwd(), 'public', 'tiles')
// Ensure the folder exists // Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true }); await fs.mkdir(public_folder, { recursive: true })
const uploadPromises = Object.entries(data).map(async ([key, tileData]) => { const uploadPromises = Object.entries(data).map(async ([key, tileData]) => {
const tile = await tileRepository.create('New tile'); const tile = await tileRepository.create('New tile')
const uuid = tile.id; const uuid = tile.id
const filename = `${uuid}.png`; const filename = `${uuid}.png`
const finalFilePath = path.join(public_folder, filename); const finalFilePath = path.join(public_folder, filename)
await writeFile(finalFilePath, tileData); await writeFile(finalFilePath, tileData)
}); })
await Promise.all(uploadPromises); await Promise.all(uploadPromises)
callback(true); callback(true)
} catch (error) { } catch (error) {
console.error('Error uploading tile:', error); console.error('Error uploading tile:', error)
callback(false); callback(false)
} }
}); })
} }

View File

@ -1,13 +1,13 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import ZoneRepository from "../../../repositories/ZoneRepository"; import ZoneRepository from '../../../repositories/ZoneRepository'
import ZoneManager from "../../../managers/ZoneManager"; import ZoneManager from '../../../managers/ZoneManager'
import {Character, Zone} from "@prisma/client"; import { Character, Zone } from '@prisma/client'
interface IPayload { interface IPayload {
name: string; name: string
width: number; width: number
height: number; height: number
} }
/** /**
@ -17,29 +17,28 @@ interface IPayload {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:zone_editor:zone:create', async (data: IPayload, callback: (response: Zone[]) => void) => { socket.on('gm:zone_editor:zone:create', async (data: IPayload, callback: (response: Zone[]) => void) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`); console.log(`---Character #${socket.character?.id} is not a game master.`)
return; return
} }
console.log(`---GM ${socket.character?.id} has created a new zone via zone editor.`); console.log(`---GM ${socket.character?.id} has created a new zone via zone editor.`)
let zoneList: Zone[] = []; let zoneList: Zone[] = []
try { try {
const zone = await ZoneRepository.create( const zone = await ZoneRepository.create(
data.name, data.name,
data.width, data.width,
data.height, data.height,
Array.from({length: data.height}, () => Array.from({length: data.width}, () => 'blank_tile')), Array.from({ length: data.height }, () => Array.from({ length: data.width }, () => 'blank_tile'))
); )
zoneList = await ZoneRepository.getAll(); zoneList = await ZoneRepository.getAll()
callback(zoneList); callback(zoneList)
// send over zone and characters to socket // send over zone and characters to socket
} catch (e) { } catch (e) {
console.error(e); console.error(e)
socket.emit('notification', {message: 'Failed to create zone.'}); socket.emit('notification', { message: 'Failed to create zone.' })
callback(zoneList); callback(zoneList)
} }
}); })
} }

View File

@ -1,11 +1,11 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import ZoneRepository from "../../../repositories/ZoneRepository"; import ZoneRepository from '../../../repositories/ZoneRepository'
import ZoneManager from "../../../managers/ZoneManager"; import ZoneManager from '../../../managers/ZoneManager'
import {Character, Zone} from "@prisma/client"; import { Character, Zone } from '@prisma/client'
interface IPayload { interface IPayload {
zoneId: number; zoneId: number
} }
/** /**
@ -15,28 +15,27 @@ interface IPayload {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:zone_editor:zone:delete', async (data: IPayload, callback: (response: boolean) => void) => { socket.on('gm:zone_editor:zone:delete', async (data: IPayload, callback: (response: boolean) => void) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`); console.log(`---Character #${socket.character?.id} is not a game master.`)
return; return
} }
console.log(`---GM ${socket.character?.id} has deleted a zone via zone editor.`); console.log(`---GM ${socket.character?.id} has deleted a zone via zone editor.`)
try { try {
const zone = await ZoneRepository.getById(data.zoneId); const zone = await ZoneRepository.getById(data.zoneId)
if (!zone) { if (!zone) {
console.log(`---Zone not found.`); console.log(`---Zone not found.`)
return; return
} }
await ZoneRepository.delete(data.zoneId); await ZoneRepository.delete(data.zoneId)
callback(true); callback(true)
} catch (e) { } catch (e) {
console.error(e); console.error(e)
callback(false); callback(false)
} }
}); })
} }

View File

@ -1,4 +1,4 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types' import { TSocket } from '../../../utilities/Types'
import { Zone } from '@prisma/client' import { Zone } from '@prisma/client'
import ZoneRepository from '../../../repositories/ZoneRepository' import ZoneRepository from '../../../repositories/ZoneRepository'
@ -12,20 +12,19 @@ interface IPayload {}
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:zone_editor:zone:list', async (data: IPayload, callback: (response: Zone[]) => void) => { socket.on('gm:zone_editor:zone:list', async (data: IPayload, callback: (response: Zone[]) => void) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`); console.log(`---Character #${socket.character?.id} is not a game master.`)
return; return
} }
console.log(`---GM ${socket.character?.id} has requested zone list via zone editor.`); console.log(`---GM ${socket.character?.id} has requested zone list via zone editor.`)
try { try {
const zones = await ZoneRepository.getAll(); const zones = await ZoneRepository.getAll()
callback(zones); callback(zones)
} catch (e) { } catch (e) {
console.error(e); console.error(e)
callback([]); callback([])
} }
}); })
} }

View File

@ -1,10 +1,10 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import ZoneRepository from "../../../repositories/ZoneRepository"; import ZoneRepository from '../../../repositories/ZoneRepository'
import {Zone} from "@prisma/client"; import { Zone } from '@prisma/client'
interface IPayload { interface IPayload {
zoneId: number; zoneId: number
} }
/** /**
@ -14,29 +14,28 @@ interface IPayload {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:zone_editor:zone:request', async (data: IPayload, callback: (response: Zone) => void) => { socket.on('gm:zone_editor:zone:request', async (data: IPayload, callback: (response: Zone) => void) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
return; return
} }
console.log(`---GM ${socket.character?.id} has requested zone via zone editor.`); console.log(`---GM ${socket.character?.id} has requested zone via zone editor.`)
if (!data.zoneId) { if (!data.zoneId) {
console.log(`---Zone id not provided.`); console.log(`---Zone id not provided.`)
return; return
} }
try { try {
const zone = await ZoneRepository.getById(data.zoneId); const zone = await ZoneRepository.getById(data.zoneId)
if (!zone) { if (!zone) {
console.log(`---Zone not found.`); console.log(`---Zone not found.`)
return; return
} }
callback(zone); callback(zone)
} catch (e) { } catch (e) {
console.error(e); console.error(e)
} }
}); })
} }

View File

@ -1,17 +1,17 @@
import { Server } from "socket.io"; import { Server } from 'socket.io'
import {TSocket} from "../../../utilities/Types"; import { TSocket } from '../../../utilities/Types'
import ZoneRepository from "../../../repositories/ZoneRepository"; import ZoneRepository from '../../../repositories/ZoneRepository'
import ZoneManager from "../../../managers/ZoneManager"; import ZoneManager from '../../../managers/ZoneManager'
import { Character, Zone, ZoneEventTile, ZoneObject } from '@prisma/client' import { Character, Zone, ZoneEventTile, ZoneObject } from '@prisma/client'
interface IPayload { interface IPayload {
zoneId: number; zoneId: number
name: string; name: string
width: number; width: number
height: number; height: number
tiles: string[][]; tiles: string[][]
zoneEventTiles: ZoneEventTile[]; zoneEventTiles: ZoneEventTile[]
zoneObjects: ZoneObject[]; zoneObjects: ZoneObject[]
} }
/** /**
@ -21,43 +21,34 @@ interface IPayload {
*/ */
export default function (socket: TSocket, io: Server) { export default function (socket: TSocket, io: Server) {
socket.on('gm:zone_editor:zone:update', async (data: IPayload) => { socket.on('gm:zone_editor:zone:update', async (data: IPayload) => {
if (socket.character?.role !== 'gm') { if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`); console.log(`---Character #${socket.character?.id} is not a game master.`)
return; return
} }
console.log(`---GM ${socket.character?.id} has updated zone via zone editor.`); console.log(`---GM ${socket.character?.id} has updated zone via zone editor.`)
if (!data.zoneId) { if (!data.zoneId) {
console.log(`---Zone id not provided.`); console.log(`---Zone id not provided.`)
return; return
} }
try { try {
let zone = await ZoneRepository.getById(data.zoneId); let zone = await ZoneRepository.getById(data.zoneId)
if (!zone) { if (!zone) {
console.log(`---Zone not found.`); console.log(`---Zone not found.`)
return; return
} }
await ZoneRepository.update( await ZoneRepository.update(data.zoneId, data.name, data.width, data.height, data.tiles, data.zoneEventTiles, data.zoneObjects)
data.zoneId,
data.name,
data.width,
data.height,
data.tiles,
data.zoneEventTiles,
data.zoneObjects
);
zone = await ZoneRepository.getById(data.zoneId); zone = await ZoneRepository.getById(data.zoneId)
// send over zone and characters to socket // send over zone and characters to socket
socket.emit('gm:zone_editor:zone:load', zone); socket.emit('gm:zone_editor:zone:load', zone)
} catch (error: any) { } catch (error: any) {
console.log(`---Error updating zone: ${error.message}`); console.log(`---Error updating zone: ${error.message}`)
} }
}); })
} }

View File

@ -1,98 +1,95 @@
import * as readline from 'readline'; import * as readline from 'readline'
import * as fs from 'fs'; import * as fs from 'fs'
import * as path from 'path'; import * as path from 'path'
import { Server } from 'socket.io'; import { Server } from 'socket.io'
class CommandManager { class CommandManager {
private commands: Map<string, Function> = new Map(); private commands: Map<string, Function> = new Map()
private rl: readline.Interface; private rl: readline.Interface
private io: Server | null = null; private io: Server | null = null
private rlClosed: boolean = false; private rlClosed: boolean = false
constructor() { constructor() {
this.rl = readline.createInterface({ this.rl = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout
}); })
this.rl.on('close', () => { this.rl.on('close', () => {
this.rlClosed = true; this.rlClosed = true
}); })
} }
public async boot(io: Server) { public async boot(io: Server) {
this.io = io; this.io = io
await this.loadCommands(); await this.loadCommands()
console.log('[✅] Command manager loaded'); console.log('[✅] Command manager loaded')
this.startPrompt(); this.startPrompt()
} }
private startPrompt() { private startPrompt() {
if (this.rlClosed) return; if (this.rlClosed) return
this.rl.question('> ', (command: string) => { this.rl.question('> ', (command: string) => {
this.processCommand(command); this.processCommand(command)
this.startPrompt(); this.startPrompt()
}); })
} }
private async processCommand(command: string): Promise<void> { private async processCommand(command: string): Promise<void> {
const [cmd, ...args] = command.trim().split(' '); const [cmd, ...args] = command.trim().split(' ')
if (this.commands.has(cmd)) { if (this.commands.has(cmd)) {
this.commands.get(cmd)?.(args, this.io as Server); this.commands.get(cmd)?.(args, this.io as Server)
} else { } else {
this.handleUnknownCommand(cmd); this.handleUnknownCommand(cmd)
} }
} }
private handleUnknownCommand(command: string) { private handleUnknownCommand(command: string) {
switch (command) { switch (command) {
case 'exit': case 'exit':
console.log('Goodbye!'); console.log('Goodbye!')
this.rl.close(); this.rl.close()
break; break
default: default:
console.error(`Unknown command: ${command}`); console.error(`Unknown command: ${command}`)
break; break
} }
} }
private async loadCommands() { private async loadCommands() {
const commandsDir = path.resolve(__dirname, 'commands'); const commandsDir = path.resolve(__dirname, 'commands')
try { try {
const files: string[] = await fs.promises.readdir(commandsDir); const files: string[] = await fs.promises.readdir(commandsDir)
for (const file of files) { for (const file of files) {
await this.loadCommand(commandsDir, file); await this.loadCommand(commandsDir, file)
} }
} catch (error) { } catch (error) {
console.error('[❌] Failed to read commands directory:', error); console.error('[❌] Failed to read commands directory:', error)
} }
} }
private async loadCommand(commandsDir: string, file: string) { private async loadCommand(commandsDir: string, file: string) {
try { try {
const ext = path.extname(file); const ext = path.extname(file)
const commandName = path.basename(file, ext); const commandName = path.basename(file, ext)
const commandPath = path.join(commandsDir, file); const commandPath = path.join(commandsDir, file)
const module = await import(commandPath); const module = await import(commandPath)
this.registerCommand(commandName, module.default); this.registerCommand(commandName, module.default)
} catch (error) { } catch (error) {
console.error('[❌] Failed to load command:', file, error); console.error('[❌] Failed to load command:', file, error)
} }
} }
private registerCommand( private registerCommand(name: string, command: (args: string[], io: Server) => void) {
name: string,
command: (args: string[], io: Server) => void
) {
if (this.commands.has(name)) { if (this.commands.has(name)) {
console.warn(`Command '${name}' is already registered. Overwriting...`); console.warn(`Command '${name}' is already registered. Overwriting...`)
} }
this.commands.set(name, command); this.commands.set(name, command)
console.log(`Registered command: ${name}`); console.log(`Registered command: ${name}`)
} }
} }
export default new CommandManager(); export default new CommandManager()

View File

@ -1,37 +1,37 @@
import {User} from "@prisma/client"; import { User } from '@prisma/client'
type TLoggedInUsers = { type TLoggedInUsers = {
users: User[]; users: User[]
} }
class UserManager { class UserManager {
private loggedInUsers: TLoggedInUsers[] = []; private loggedInUsers: TLoggedInUsers[] = []
// Method to initialize user manager // Method to initialize user manager
public async boot() { public async boot() {
console.log('[✅] User manager loaded'); console.log('[✅] User manager loaded')
} }
// Function that adds user to logged in users // Function that adds user to logged in users
public loginUser(user: User) { public loginUser(user: User) {
this.loggedInUsers.push({ this.loggedInUsers.push({
users: [user] users: [user]
}); })
} }
// Function that checks if a user is already logged in // Function that checks if a user is already logged in
public findUser(user: User) { public findUser(user: User) {
return this.loggedInUsers.find((loggedInUser) => { return this.loggedInUsers.find((loggedInUser) => {
return loggedInUser.users.includes(user); return loggedInUser.users.includes(user)
}); })
} }
// Function that lists all logged in users // Function that lists all logged in users
public listUsers() { public listUsers() {
return this.loggedInUsers.map((loggedInUser) => { return this.loggedInUsers.map((loggedInUser) => {
return loggedInUser.users; return loggedInUser.users
}); })
} }
} }
export default new UserManager(); export default new UserManager()

View File

@ -1,29 +1,29 @@
import {Character, Zone} from "@prisma/client"; import { Character, Zone } from '@prisma/client'
import ZoneRepository from "../repositories/ZoneRepository"; import ZoneRepository from '../repositories/ZoneRepository'
import ZoneService from "../services/ZoneService"; import ZoneService from '../services/ZoneService'
type TLoadedZone = { type TLoadedZone = {
zone: Zone; zone: Zone
characters: Character[]; characters: Character[]
} }
class ZoneManager { class ZoneManager {
private loadedZones: TLoadedZone[] = []; private loadedZones: TLoadedZone[] = []
// Method to initialize zone manager // Method to initialize zone manager
public async boot() { public async boot() {
if (!await ZoneRepository.getById(1)) { if (!(await ZoneRepository.getById(1))) {
const zoneService = new ZoneService(); const zoneService = new ZoneService()
await zoneService.createDemoZone(); await zoneService.createDemoZone()
} }
const zones = await ZoneRepository.getAll(); const zones = await ZoneRepository.getAll()
for (const zone of zones) { for (const zone of zones) {
this.loadZone(zone); this.loadZone(zone)
} }
console.log('[✅] Zone manager loaded'); console.log('[✅] Zone manager loaded')
} }
// Method to handle individual zone loading // Method to handle individual zone loading
@ -31,63 +31,63 @@ class ZoneManager {
this.loadedZones.push({ this.loadedZones.push({
zone, zone,
characters: [] characters: []
}); })
console.log(`[✅] Zone ID ${zone.id} loaded`); console.log(`[✅] Zone ID ${zone.id} loaded`)
} }
// Method to handle individual zone unloading // Method to handle individual zone unloading
public unloadZone(zoneId: number) { public unloadZone(zoneId: number) {
this.loadedZones = this.loadedZones.filter((loadedZone) => { this.loadedZones = this.loadedZones.filter((loadedZone) => {
return loadedZone.zone.id !== zoneId; return loadedZone.zone.id !== zoneId
}); })
console.log(`[❌] Zone ID ${zoneId} unloaded`); console.log(`[❌] Zone ID ${zoneId} unloaded`)
} }
// Getter for loaded zones // Getter for loaded zones
public getLoadedZones(): TLoadedZone[] { public getLoadedZones(): TLoadedZone[] {
return this.loadedZones; return this.loadedZones
} }
public addCharacterToZone(zoneId: number, character: Character) { public addCharacterToZone(zoneId: number, character: Character) {
const loadedZone = this.loadedZones.find((loadedZone) => { const loadedZone = this.loadedZones.find((loadedZone) => {
return loadedZone.zone.id === zoneId; return loadedZone.zone.id === zoneId
}); })
if (loadedZone) { if (loadedZone) {
loadedZone.characters.push(character); loadedZone.characters.push(character)
} }
} }
public removeCharacterFromZone(zoneId: number, character: Character) { public removeCharacterFromZone(zoneId: number, character: Character) {
const loadedZone = this.loadedZones.find((loadedZone) => { const loadedZone = this.loadedZones.find((loadedZone) => {
return loadedZone.zone.id === zoneId; return loadedZone.zone.id === zoneId
}); })
if (loadedZone) { if (loadedZone) {
loadedZone.characters = loadedZone.characters.filter((loadedCharacter) => { loadedZone.characters = loadedZone.characters.filter((loadedCharacter) => {
return loadedCharacter.id !== character.id; return loadedCharacter.id !== character.id
}); })
} }
} }
public updateCharacterInZone(zoneId: number, character: Character) { public updateCharacterInZone(zoneId: number, character: Character) {
const loadedZone = this.loadedZones.find((loadedZone) => { const loadedZone = this.loadedZones.find((loadedZone) => {
return loadedZone.zone.id === zoneId; return loadedZone.zone.id === zoneId
}); })
if (loadedZone) { if (loadedZone) {
const characterIndex = loadedZone.characters.findIndex((loadedCharacter) => { const characterIndex = loadedZone.characters.findIndex((loadedCharacter) => {
return loadedCharacter.id === character.id; return loadedCharacter.id === character.id
}); })
if (characterIndex !== -1) { if (characterIndex !== -1) {
loadedZone.characters[characterIndex] = character; loadedZone.characters[characterIndex] = character
} }
} }
} }
public getCharactersInZone(zoneId: number): Character[] { public getCharactersInZone(zoneId: number): Character[] {
const loadedZone = this.loadedZones.find((loadedZone) => { const loadedZone = this.loadedZones.find((loadedZone) => {
return loadedZone.zone.id === zoneId; return loadedZone.zone.id === zoneId
}); })
return loadedZone ? loadedZone.characters : []; return loadedZone ? loadedZone.characters : []
} }
} }
export default new ZoneManager; export default new ZoneManager()

View File

@ -1,37 +1,36 @@
// socket io jwt auth middleware // socket io jwt auth middleware
import { verify } from 'jsonwebtoken'; import { verify } from 'jsonwebtoken'
import { TSocket } from '../utilities/Types'; import { TSocket } from '../utilities/Types'
import config from "../utilities/Config"; import config from '../utilities/Config'
import UserRepository from "../repositories/UserRepository"; import UserRepository from '../repositories/UserRepository'
import {User} from "@prisma/client"; import { User } from '@prisma/client'
export async function Authentication (socket: TSocket, next: any) export async function Authentication(socket: TSocket, next: any) {
{
if (!socket.request.headers.cookie) { if (!socket.request.headers.cookie) {
console.log('No cookie provided'); console.log('No cookie provided')
return next(new Error('Authentication error')); return next(new Error('Authentication error'))
} }
const cookies = socket.request.headers.cookie.split('; ').reduce((prev: any, current: any) => { const cookies = socket.request.headers.cookie.split('; ').reduce((prev: any, current: any) => {
const [name, value] = current.split('='); const [name, value] = current.split('=')
prev[name] = value; prev[name] = value
return prev; return prev
}, {}); }, {})
const token = cookies['token']; const token = cookies['token']
if (token) { if (token) {
verify(token, config.JWT_SECRET, async (err: any, decoded: any) => { verify(token, config.JWT_SECRET, async (err: any, decoded: any) => {
if (err) { if (err) {
console.log('err'); console.log('err')
return next(new Error('Authentication error')); return next(new Error('Authentication error'))
} }
socket.user = await UserRepository.getById(decoded.id) as User; socket.user = (await UserRepository.getById(decoded.id)) as User
next(); next()
}); })
} else { } else {
console.log('No token provided'); console.log('No token provided')
next(new Error('Authentication error')); next(new Error('Authentication error'))
} }
} }

View File

@ -1,17 +1,17 @@
import prisma from '../utilities/Prisma'; // Import the global Prisma instance import prisma from '../utilities/Prisma' // Import the global Prisma instance
import {Character} from '@prisma/client'; import { Character } from '@prisma/client'
class CharacterRepository { class CharacterRepository {
async getByUserId(userId: number): Promise<Character[] | null> { async getByUserId(userId: number): Promise<Character[] | null> {
try { try {
return await prisma.character.findMany({ return await prisma.character.findMany({
where: { where: {
userId, userId
}, }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to get character by user ID: ${error.message}`); throw new Error(`Failed to get character by user ID: ${error.message}`)
} }
} }
@ -20,15 +20,15 @@ class CharacterRepository {
return await prisma.character.findFirst({ return await prisma.character.findFirst({
where: { where: {
userId, userId,
id: characterId, id: characterId
}, },
include: { include: {
zone: true zone: true
} }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to get character by user ID and character ID: ${error.message}`); throw new Error(`Failed to get character by user ID and character ID: ${error.message}`)
} }
} }
@ -36,12 +36,12 @@ class CharacterRepository {
try { try {
return await prisma.character.findUnique({ return await prisma.character.findUnique({
where: { where: {
id, id
}, }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to get character by ID: ${error.message}`); throw new Error(`Failed to get character by ID: ${error.message}`)
} }
} }
@ -55,12 +55,12 @@ class CharacterRepository {
position_x: 0, // @TODO Set default registration values in the database position_x: 0, // @TODO Set default registration values in the database
position_y: 0, // @TODO Set default registration values in the database position_y: 0, // @TODO Set default registration values in the database
rotation: 0, // @TODO Set default registration values in the database rotation: 0, // @TODO Set default registration values in the database
zoneId: 1, // @TODO Set default registration values in the database zoneId: 1 // @TODO Set default registration values in the database
}, }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to create character: ${error.message}`); throw new Error(`Failed to create character: ${error.message}`)
} }
} }
@ -68,16 +68,16 @@ class CharacterRepository {
try { try {
return await prisma.character.update({ return await prisma.character.update({
where: { where: {
id: id, id: id
}, },
data: { data: {
position_x, position_x,
position_y, position_y
}, }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to update character: ${error.message}`); throw new Error(`Failed to update character: ${error.message}`)
} }
} }
@ -85,12 +85,12 @@ class CharacterRepository {
try { try {
return await prisma.character.delete({ return await prisma.character.delete({
where: { where: {
id, id
}, }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to delete character: ${error.message}`); throw new Error(`Failed to delete character: ${error.message}`)
} }
} }
@ -99,12 +99,12 @@ class CharacterRepository {
return await prisma.character.delete({ return await prisma.character.delete({
where: { where: {
userId, userId,
id: characterId, id: characterId
}, }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to delete character by user ID and character ID: ${error.message}`); throw new Error(`Failed to delete character by user ID and character ID: ${error.message}`)
} }
} }
@ -112,14 +112,14 @@ class CharacterRepository {
try { try {
return await prisma.character.findFirst({ return await prisma.character.findFirst({
where: { where: {
name, name
}, }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to get character by name: ${error.message}`); throw new Error(`Failed to get character by name: ${error.message}`)
} }
} }
} }
export default new CharacterRepository; export default new CharacterRepository()

View File

@ -1,44 +1,16 @@
import prisma from '../utilities/Prisma'; // Import the global Prisma instance import prisma from '../utilities/Prisma' // Import the global Prisma instance
import { Object } from '@prisma/client' import { Object } from '@prisma/client'
class ObjectRepository { class ObjectRepository {
async getById(id: string): Promise<Object | null> { async getById(id: string): Promise<Object | null> {
return prisma.object.findUnique({ return prisma.object.findUnique({
where: { id }, where: { id }
}); })
} }
async getAll(): Promise<Object[]> { async getAll(): Promise<Object[]> {
return prisma.object.findMany(); return prisma.object.findMany()
}
async create(name: string, origin_x: number, origin_y: number): Promise<Object> {
return prisma.object.create({
data: {
name,
origin_x,
origin_y
},
});
}
async update(id: string, name: string, tags: string[], origin_x: number, origin_y: number): Promise<Object> {
return prisma.object.update({
where: { id },
data: {
name,
tags,
origin_x,
origin_y
},
});
}
async delete(id: string): Promise<Object> {
return prisma.object.delete({
where: { id },
});
} }
} }
export default new ObjectRepository(); export default new ObjectRepository()

View File

@ -1,15 +1,15 @@
import prisma from '../utilities/Prisma'; // Import the global Prisma instance import prisma from '../utilities/Prisma' // Import the global Prisma instance
import { Sprite } from '@prisma/client' import { Sprite } from '@prisma/client'
class SpriteRepository { class SpriteRepository {
async getById(id: string): Promise<Sprite | null> { async getById(id: string): Promise<Sprite | null> {
return prisma.sprite.findUnique({ return prisma.sprite.findUnique({
where: { id }, where: { id }
}); })
} }
async getAll(): Promise<Sprite[]> { async getAll(): Promise<Sprite[]> {
return prisma.sprite.findMany(); return prisma.sprite.findMany()
} }
async create(name: string, origin_x: number, origin_y: number): Promise<Sprite> { async create(name: string, origin_x: number, origin_y: number): Promise<Sprite> {
@ -18,8 +18,8 @@ class SpriteRepository {
name, name,
origin_x, origin_x,
origin_y origin_y
}, }
}); })
} }
async update(id: string, name: string, origin_x: number, origin_y: number): Promise<Sprite> { async update(id: string, name: string, origin_x: number, origin_y: number): Promise<Sprite> {
@ -29,15 +29,15 @@ class SpriteRepository {
name, name,
origin_x, origin_x,
origin_y origin_y
}, }
}); })
} }
async delete(id: string): Promise<Sprite> { async delete(id: string): Promise<Sprite> {
return prisma.sprite.delete({ return prisma.sprite.delete({
where: { id }, where: { id }
}); })
} }
} }
export default new SpriteRepository(); export default new SpriteRepository()

View File

@ -1,15 +1,15 @@
import prisma from '../utilities/Prisma'; // Import the global Prisma instance import prisma from '../utilities/Prisma' // Import the global Prisma instance
import { Tile } from '@prisma/client' import { Tile } from '@prisma/client'
class TileRepository { class TileRepository {
async getById(id: string): Promise<Tile | null> { async getById(id: string): Promise<Tile | null> {
return prisma.tile.findUnique({ return prisma.tile.findUnique({
where: { id }, where: { id }
}); })
} }
async getAll(): Promise<Tile[]> { async getAll(): Promise<Tile[]> {
return prisma.tile.findMany(); return prisma.tile.findMany()
} }
async create(name: string): Promise<Tile> { async create(name: string): Promise<Tile> {
@ -17,8 +17,8 @@ class TileRepository {
data: { data: {
name, name,
tags: [] tags: []
}, }
}); })
} }
async update(id: string, name: string, tags: string[]): Promise<Tile> { async update(id: string, name: string, tags: string[]): Promise<Tile> {
@ -27,21 +27,21 @@ class TileRepository {
data: { data: {
name, name,
tags tags
}, }
}); })
} }
async delete(id: string): Promise<boolean> { async delete(id: string): Promise<boolean> {
try { try {
await prisma.tile.delete({ await prisma.tile.delete({
where: { id }, where: { id }
}); })
return true; return true
} catch (error) { } catch (error) {
console.log('Error deleting tile:', error) console.log('Error deleting tile:', error)
return false; return false
} }
} }
} }
export default new TileRepository(); export default new TileRepository()

View File

@ -1,29 +1,29 @@
import prisma from '../utilities/Prisma'; // Import the global Prisma instance import prisma from '../utilities/Prisma' // Import the global Prisma instance
import { User } from '@prisma/client'; import { User } from '@prisma/client'
class UserRepository { class UserRepository {
async getById(id: number): Promise<User | null> { async getById(id: number): Promise<User | null> {
try { try {
return await prisma.user.findUnique({ return await prisma.user.findUnique({
where: { where: {
id, id
}, }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to get user by ID: ${error.message}`); throw new Error(`Failed to get user by ID: ${error.message}`)
} }
} }
async getByUsername(username: string): Promise<User | null> { async getByUsername(username: string): Promise<User | null> {
try { try {
return await prisma.user.findUnique({ return await prisma.user.findUnique({
where: { where: {
username, username
}, }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to get user by username: ${error.message}`); throw new Error(`Failed to get user by username: ${error.message}`)
} }
} }
async create(username: string, password: string): Promise<User> { async create(username: string, password: string): Promise<User> {
@ -31,14 +31,14 @@ class UserRepository {
return await prisma.user.create({ return await prisma.user.create({
data: { data: {
username, username,
password, password
}, }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to create user: ${error.message}`); throw new Error(`Failed to create user: ${error.message}`)
} }
} }
} }
export default new UserRepository; export default new UserRepository()

View File

@ -1,22 +1,22 @@
import { Zone, ZoneEventTile, ZoneObject } from '@prisma/client' import { Zone, ZoneEventTile, ZoneObject } from '@prisma/client'
import prisma from '../utilities/Prisma'; // Import the global Prisma instance import prisma from '../utilities/Prisma' // Import the global Prisma instance
class ZoneRepository { class ZoneRepository {
async getFirst(): Promise<Zone | null> { async getFirst(): Promise<Zone | null> {
try { try {
return await prisma.zone.findFirst(); return await prisma.zone.findFirst()
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to get first zone: ${error.message}`); throw new Error(`Failed to get first zone: ${error.message}`)
} }
} }
async getAll(): Promise<Zone[]> { async getAll(): Promise<Zone[]> {
try { try {
return await prisma.zone.findMany(); return await prisma.zone.findMany()
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to get all zone: ${error.message}`); throw new Error(`Failed to get all zone: ${error.message}`)
} }
} }
@ -38,10 +38,10 @@ class ZoneRepository {
} }
} }
} }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to get zone by id: ${error.message}`); throw new Error(`Failed to get zone by id: ${error.message}`)
} }
} }
@ -54,10 +54,10 @@ class ZoneRepository {
height: height, height: height,
tiles: tiles tiles: tiles
} }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to create zone: ${error.message}`); throw new Error(`Failed to create zone: ${error.message}`)
} }
} }
@ -77,7 +77,7 @@ class ZoneRepository {
zoneId: id // Ensure only event tiles related to the zone are deleted zoneId: id // Ensure only event tiles related to the zone are deleted
}, },
// Save new zone event tiles // Save new zone event tiles
create: zoneEventTiles.map(zoneEventTile => ({ create: zoneEventTiles.map((zoneEventTile) => ({
type: zoneEventTile.type, type: zoneEventTile.type,
position_x: zoneEventTile.position_x, position_x: zoneEventTile.position_x,
position_y: zoneEventTile.position_y position_y: zoneEventTile.position_y
@ -88,7 +88,7 @@ class ZoneRepository {
zoneId: id // Ensure only objects related to the zone are deleted zoneId: id // Ensure only objects related to the zone are deleted
}, },
// Save new zone objects // Save new zone objects
create: zoneObjects.map(zoneObject => ({ create: zoneObjects.map((zoneObject) => ({
objectId: zoneObject.objectId, objectId: zoneObject.objectId,
depth: zoneObject.depth, depth: zoneObject.depth,
position_x: zoneObject.position_x, position_x: zoneObject.position_x,
@ -96,10 +96,10 @@ class ZoneRepository {
})) }))
} }
} }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to update zone: ${error.message}`); throw new Error(`Failed to update zone: ${error.message}`)
} }
} }
@ -109,12 +109,12 @@ class ZoneRepository {
where: { where: {
id: id id: id
} }
}); })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
throw new Error(`Failed to delete zone: ${error.message}`); throw new Error(`Failed to delete zone: ${error.message}`)
} }
} }
} }
export default new ZoneRepository; export default new ZoneRepository()

View File

@ -1,35 +1,34 @@
import fs from "fs"; import fs from 'fs'
import path from "path"; import path from 'path'
import express, {Application} from 'express'; import express, { Application } from 'express'
import {createServer as httpServer} from 'http'; import { createServer as httpServer } from 'http'
import {addHttpRoutes} from './utilities/Http'; import { addHttpRoutes } from './utilities/Http'
import cors from 'cors'; import cors from 'cors'
import {Server as SocketServer} from 'socket.io'; import { Server as SocketServer } from 'socket.io'
import {TSocket} from "./utilities/Types"; import { TSocket } from './utilities/Types'
import config from './utilities/Config'; import config from './utilities/Config'
import prisma from './utilities/Prisma'; import prisma from './utilities/Prisma'
import ZoneManager from "./managers/ZoneManager"; import ZoneManager from './managers/ZoneManager'
import UserManager from "./managers/UserManager"; import UserManager from './managers/UserManager'
import {Authentication} from "./middleware/Authentication"; import { Authentication } from './middleware/Authentication'
import CommandManager from "./managers/CommandManager"; import CommandManager from './managers/CommandManager'
import {Dirent} from "node:fs"; import { Dirent } from 'node:fs'
export class Server export class Server {
{ private readonly app: Application
private readonly app: Application; private readonly http: any
private readonly http: any; private readonly io: SocketServer
private readonly io: SocketServer;
/** /**
* Creates an instance of GameServer. * Creates an instance of GameServer.
*/ */
constructor() { constructor() {
this.app = express(); this.app = express()
this.app.use(cors()); this.app.use(cors())
this.app.use(express.json()); this.app.use(express.json())
this.app.use(express.urlencoded({ extended: true })); this.app.use(express.urlencoded({ extended: true }))
this.http = httpServer(this.app) this.http = httpServer(this.app)
this.io = new SocketServer(this.http); this.io = new SocketServer(this.http)
this.io.use(Authentication) this.io.use(Authentication)
} }
@ -39,34 +38,34 @@ export class Server
public async start() { public async start() {
// Check prisma connection // Check prisma connection
try { try {
await prisma.$connect(); await prisma.$connect()
console.log('[✅] Database connected'); console.log('[✅] Database connected')
} catch (error: any) { } catch (error: any) {
throw new Error(`[❌] Database connection failed: ${error.message}`); throw new Error(`[❌] Database connection failed: ${error.message}`)
} }
// Start the server // Start the server
try { try {
await this.http.listen(config.PORT, config.HOST); await this.http.listen(config.PORT, config.HOST)
console.log('[✅] Socket.IO running on port', config.PORT); console.log('[✅] Socket.IO running on port', config.PORT)
} catch (error: any) { } catch (error: any) {
throw new Error(`[❌] Socket.IO failed to start: ${error.message}`); throw new Error(`[❌] Socket.IO failed to start: ${error.message}`)
} }
// Add http API routes // Add http API routes
await addHttpRoutes(this.app); await addHttpRoutes(this.app)
// Load user manager // Load user manager
await UserManager.boot(); await UserManager.boot()
// Load zone manager // Load zone manager
await ZoneManager.boot(); await ZoneManager.boot()
// Load command manager - Disabled for now // Load command manager - Disabled for now
// await CommandManager.boot(this.io); // await CommandManager.boot(this.io);
// Listen for socket connections // Listen for socket connections
this.io.on('connection', this.handleConnection.bind(this)); this.io.on('connection', this.handleConnection.bind(this))
} }
/** /**
@ -75,30 +74,30 @@ export class Server
* @private * @private
*/ */
private async handleConnection(socket: TSocket) { private async handleConnection(socket: TSocket) {
const eventsPath = path.join(__dirname, 'events'); const eventsPath = path.join(__dirname, 'events')
try { try {
await this.loadEventHandlers(eventsPath, socket); await this.loadEventHandlers(eventsPath, socket)
} catch (error: any) { } catch (error: any) {
throw new Error('[❌] Failed to load event handlers: ' + error.message); throw new Error('[❌] Failed to load event handlers: ' + error.message)
} }
} }
private async loadEventHandlers(dir: string, socket: TSocket) { private async loadEventHandlers(dir: string, socket: TSocket) {
const files: Dirent[] = await fs.promises.readdir(dir, { withFileTypes: true }); const files: Dirent[] = await fs.promises.readdir(dir, { withFileTypes: true })
for (const file of files) { for (const file of files) {
const fullPath = path.join(dir, file.name); const fullPath = path.join(dir, file.name)
if (file.isDirectory()) { if (file.isDirectory()) {
await this.loadEventHandlers(fullPath, socket); await this.loadEventHandlers(fullPath, socket)
} else if (file.isFile()) { } else if (file.isFile()) {
const module = await import(fullPath); const module = await import(fullPath)
module.default(socket, this.io); module.default(socket, this.io)
} }
} }
} }
} }
// Start the server // Start the server
const server = new Server(); const server = new Server()
server.start(); server.start()

View File

@ -1,6 +1,3 @@
class AssetService {}
class AssetService export default AssetService
{
}
export default AssetService;

View File

@ -1,8 +1,5 @@
import {Character} from "@prisma/client"; import { Character } from '@prisma/client'
class CharacterService class CharacterService {}
{
} export default CharacterService
export default CharacterService;

View File

@ -1,31 +1,30 @@
import bcrypt from 'bcryptjs' import bcrypt from 'bcryptjs'
import UserRepository from '../repositories/UserRepository' import UserRepository from '../repositories/UserRepository'
class UserService class UserService {
{
async login(username: string, password: string): Promise<boolean | any> { async login(username: string, password: string): Promise<boolean | any> {
const user = await UserRepository.getByUsername(username); const user = await UserRepository.getByUsername(username)
if (!user) { if (!user) {
return false; return false
} }
const passwordMatch = await bcrypt.compare(password, user.password); const passwordMatch = await bcrypt.compare(password, user.password)
if (!passwordMatch) { if (!passwordMatch) {
return false; return false
} }
return user; return user
} }
async register(username: string, password: string): Promise<boolean | any> { async register(username: string, password: string): Promise<boolean | any> {
const user = await UserRepository.getByUsername(username); const user = await UserRepository.getByUsername(username)
if (user) { if (user) {
return false; return false
} }
const hashedPassword = await bcrypt.hash(password, 10); const hashedPassword = await bcrypt.hash(password, 10)
return await UserRepository.create(username, hashedPassword) return await UserRepository.create(username, hashedPassword)
} }
} }
export default UserService; export default UserService

View File

@ -1,12 +1,9 @@
import {Zone} from "@prisma/client"; import { Zone } from '@prisma/client'
import ZoneRepository from "../repositories/ZoneRepository"; import ZoneRepository from '../repositories/ZoneRepository'
class ZoneService class ZoneService {
{ async createDemoZone(): Promise<boolean> {
async createDemoZone(): Promise<boolean> await ZoneRepository.create('Demo Zone', 10, 10, [
{
await ZoneRepository.create("Demo Zone", 10, 10, [
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'], ['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'], ['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'], ['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
@ -16,10 +13,11 @@ class ZoneService
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'], ['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'], ['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'], ['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile']
]) ])
console.log("Demo zone created."); console.log('Demo zone created.')
return true; return true
} }
} }
export default ZoneService; export default ZoneService

View File

@ -1,17 +1,16 @@
import dotenv from "dotenv"; import dotenv from 'dotenv'
dotenv.config(); dotenv.config()
class config class config {
{ static ENV: string = process.env.ENV || 'prod'
static ENV: string = process.env.ENV || "prod"; static HOST: string = process.env.HOST || '0.0.0.0'
static HOST: string = process.env.HOST || "0.0.0.0"; static PORT: number = process.env.PORT ? parseInt(process.env.PORT) : 6969
static PORT: number = process.env.PORT ? parseInt(process.env.PORT) : 6969; static JWT_SECRET: string = process.env.JWT_SECRET || 'secret'
static JWT_SECRET: string = process.env.JWT_SECRET || "secret";
static DEFAULT_CHARACTER_ZONE: number = parseInt(process.env.DEFAULT_CHARACTER_ZONE || "1"); static DEFAULT_CHARACTER_ZONE: number = parseInt(process.env.DEFAULT_CHARACTER_ZONE || '1')
static DEFAULT_CHARACTER_X: number = parseInt(process.env.DEFAULT_CHARACTER_POS_X || "0"); static DEFAULT_CHARACTER_X: number = parseInt(process.env.DEFAULT_CHARACTER_POS_X || '0')
static DEFAULT_CHARACTER_Y: number = parseInt(process.env.DEFAULT_CHARACTER_POS_Y || "0"); static DEFAULT_CHARACTER_Y: number = parseInt(process.env.DEFAULT_CHARACTER_POS_Y || '0')
} }
export default config; export default config

View File

@ -2,12 +2,12 @@
* Resources: * Resources:
* https://stackoverflow.com/questions/76131891/what-is-the-best-method-for-socket-io-authentication * https://stackoverflow.com/questions/76131891/what-is-the-best-method-for-socket-io-authentication
*/ */
import {Application, Request, Response} from 'express'; import { Application, Request, Response } from 'express'
import UserService from '../services/UserService'; import UserService from '../services/UserService'
import jwt from "jsonwebtoken"; import jwt from 'jsonwebtoken'
import config from "./Config"; import config from './Config'
import {loginAccountSchema, registerAccountSchema} from "./ZodTypes"; import { loginAccountSchema, registerAccountSchema } from './ZodTypes'
import path from "path"; import path from 'path'
import { TAsset } from './Types' import { TAsset } from './Types'
import tileRepository from '../repositories/TileRepository' import tileRepository from '../repositories/TileRepository'
import objectRepository from '../repositories/ObjectRepository' import objectRepository from '../repositories/ObjectRepository'
@ -15,89 +15,90 @@ import spriteRepository from '../repositories/SpriteRepository'
async function addHttpRoutes(app: Application) { async function addHttpRoutes(app: Application) {
app.get('/assets', async (req: Request, res: Response) => { app.get('/assets', async (req: Request, res: Response) => {
let assets: TAsset[] = []; let assets: TAsset[] = []
const tiles = await tileRepository.getAll(); const tiles = await tileRepository.getAll()
tiles.forEach(tile => { tiles.forEach((tile) => {
assets.push({ assets.push({
key: tile.id, key: tile.id,
value: '/assets/tiles/' + tile.id + '.png', value: '/assets/tiles/' + tile.id + '.png',
group: 'tiles', group: 'tiles',
type: 'link' type: 'link'
}); })
}); })
const objects = await objectRepository.getAll(); const objects = await objectRepository.getAll()
objects.forEach(object => { objects.forEach((object) => {
assets.push({ assets.push({
key: object.id, key: object.id,
value: '/assets/objects/' + object.id + '.png', value: '/assets/objects/' + object.id + '.png',
group: 'objects', group: 'objects',
type: 'link' type: 'link'
}); })
}); })
res.json(assets); res.json(assets)
}); })
app.get('/assets/:type/:file', (req: Request, res: Response) => { app.get('/assets/:type/:file', (req: Request, res: Response) => {
const assetName = req.params.file; const assetName = req.params.file
// if (!isValidAsset(assetName)) { // if (!isValidAsset(assetName)) {
// return res.status(400).send('Invalid asset name'); // return res.status(400).send('Invalid asset name');
// } // }
const options = { const options = {
root: path.join(process.cwd(), 'public', req.params.type), root: path.join(process.cwd(), 'public', req.params.type)
}; }
res.sendFile(assetName, options, (err) => { res.sendFile(assetName, options, (err) => {
if (err) { if (err) {
console.error('Error sending file:', err); console.error('Error sending file:', err)
res.status(500).send('Error downloading the asset'); res.status(500).send('Error downloading the asset')
} }
}); })
}); })
app.post('/login', async (req: Request, res: Response) => { app.post('/login', async (req: Request, res: Response) => {
const { username, password } = req.body; const { username, password } = req.body
try { try {
loginAccountSchema.parse({ username, password }); loginAccountSchema.parse({ username, password })
} catch (error: any) { } catch (error: any) {
return res.status(400).json({ message: error.errors[0]?.message }); return res.status(400).json({ message: error.errors[0]?.message })
} }
const userService = new UserService(); const userService = new UserService()
const user = await userService.login(username, password); const user = await userService.login(username, password)
if (user) { //test
const token = jwt.sign({ id: user.id }, config.JWT_SECRET, { expiresIn: '4h' });
return res.status(200).json({ token });
}
return res.status(400).json({ message: 'Failed to login' });
});
app.post('/register', async (req: Request, res: Response) => {
const { username, password } = req.body;
try {
registerAccountSchema.parse({ username, password });
} catch (error: any) {
return res.status(400).json({ message: error.errors[0]?.message });
}
const userService = new UserService();
const user = await userService.register(username, password);
if (user) { if (user) {
const token = jwt.sign({ id: user.id }, config.JWT_SECRET, { expiresIn: '4h' }); //test
return res.status(200).json({ token }); const token = jwt.sign({ id: user.id }, config.JWT_SECRET, { expiresIn: '4h' })
return res.status(200).json({ token })
} }
return res.status(400).json({ message: 'Failed to register user' }); return res.status(400).json({ message: 'Failed to login' })
}); })
console.log('[✅] Web routes added'); app.post('/register', async (req: Request, res: Response) => {
const { username, password } = req.body
try {
registerAccountSchema.parse({ username, password })
} catch (error: any) {
return res.status(400).json({ message: error.errors[0]?.message })
} }
export { addHttpRoutes }; const userService = new UserService()
const user = await userService.register(username, password)
if (user) {
const token = jwt.sign({ id: user.id }, config.JWT_SECRET, { expiresIn: '4h' })
return res.status(200).json({ token })
}
return res.status(400).json({ message: 'Failed to register user' })
})
console.log('[✅] Web routes added')
}
export { addHttpRoutes }

View File

@ -1,5 +1,5 @@
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient(); const prisma = new PrismaClient()
export default prisma; export default prisma

View File

@ -1,5 +1,5 @@
import { Socket } from 'socket.io'; import { Socket } from 'socket.io'
import {Character, User} from "@prisma/client"; import { Character, User } from '@prisma/client'
export type TSocket = Socket & { export type TSocket = Socket & {
user?: User user?: User
@ -17,13 +17,11 @@ export type TSocket = Socket & {
} }
export type TCharacter = Socket & { export type TCharacter = Socket & {
user?: User, user?: User
character?: Character character?: Character
} }
export type TZoneCharacter = Character & { export type TZoneCharacter = Character & {}
}
export type TAsset = { export type TAsset = {
key: string key: string

View File

@ -1,28 +1,37 @@
import { z } from 'zod'; import { z } from 'zod'
export const loginAccountSchema = z.object({ export const loginAccountSchema = z.object({
username:z.string() username: z
.string()
.min(3, { message: 'Name must be at least 3 characters long' }) .min(3, { message: 'Name must be at least 3 characters long' })
.max(255, { message: 'Name must be at most 255 characters long' }) .max(255, { message: 'Name must be at most 255 characters long' })
.regex(/^[A-Za-z][A-Za-z0-9_-]*$/, { message: 'Name must start with a letter and can only contain letters, numbers, underscores, or dashes' }), .regex(/^[A-Za-z][A-Za-z0-9_-]*$/, { message: 'Name must start with a letter and can only contain letters, numbers, underscores, or dashes' }),
password: z.string().min(8, { password: z
.string()
.min(8, {
message: 'Password must be at least 8 characters long' message: 'Password must be at least 8 characters long'
}).max(255) })
}); .max(255)
})
export const registerAccountSchema = z.object({ export const registerAccountSchema = z.object({
username: z.string() username: z
.string()
.min(3, { message: 'Name must be at least 3 characters long' }) .min(3, { message: 'Name must be at least 3 characters long' })
.max(255, { message: 'Name must be at most 255 characters long' }) .max(255, { message: 'Name must be at most 255 characters long' })
.regex(/^[A-Za-z][A-Za-z0-9_-]*$/, { message: 'Name must start with a letter and can only contain letters, numbers, underscores, or dashes' }), .regex(/^[A-Za-z][A-Za-z0-9_-]*$/, { message: 'Name must start with a letter and can only contain letters, numbers, underscores, or dashes' }),
password: z.string().min(8, { password: z
.string()
.min(8, {
message: 'Password must be at least 8 characters long' message: 'Password must be at least 8 characters long'
}).max(255) })
}); .max(255)
})
export const ZCharacterCreate = z.object({ export const ZCharacterCreate = z.object({
name: z.string() name: z
.string()
.min(3, { message: 'Name must be at least 3 characters long' }) .min(3, { message: 'Name must be at least 3 characters long' })
.max(255, { message: 'Name must be at most 255 characters long' }) .max(255, { message: 'Name must be at most 255 characters long' })
.regex(/^[A-Za-z][A-Za-z0-9_-]*$/, { message: 'Name must start with a letter and can only contain letters, numbers, underscores, or dashes' }) .regex(/^[A-Za-z][A-Za-z0-9_-]*$/, { message: 'Name must start with a letter and can only contain letters, numbers, underscores, or dashes' })
}); })