Added missing entities( zoneEffect, zoneEventTile, zoneEventTileTeleport, zoneObject)

This commit is contained in:
Dennis Postma 2024-12-25 03:19:53 +01:00
parent b4989aac26
commit 95f4c58110
20 changed files with 489 additions and 391 deletions

View File

@ -1,76 +0,0 @@
import { Migration } from '@mikro-orm/migrations';
export class Migration20241225010519 extends Migration {
override async up(): Promise<void> {
this.addSql(`create table \`sprite\` (\`id\` varchar(255) not null, \`name\` varchar(255) not null, \`created_at\` datetime not null, \`updated_at\` datetime not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
this.addSql(`create table \`item\` (\`id\` varchar(255) not null, \`name\` varchar(255) not null, \`description\` varchar(255) null, \`item_type\` enum('WEAPON', 'HELMET', 'CHEST', 'LEGS', 'BOOTS', 'GLOVES', 'RING', 'NECKLACE') not null, \`stackable\` tinyint(1) not null default false, \`rarity\` enum('COMMON', 'UNCOMMON', 'RARE', 'EPIC', 'LEGENDARY') not null default 'COMMON', \`sprite_id\` varchar(255) null, \`created_at\` datetime not null, \`updated_at\` datetime not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
this.addSql(`alter table \`item\` add index \`item_sprite_id_index\`(\`sprite_id\`);`);
this.addSql(`create table \`character_type\` (\`id\` int unsigned not null auto_increment primary key, \`name\` varchar(255) not null, \`gender\` enum('MALE', 'FEMALE') not null, \`race\` enum('HUMAN', 'ELF', 'DWARF', 'ORC', 'GOBLIN') not null, \`is_selectable\` tinyint(1) not null default false, \`sprite_id\` varchar(255) null, \`created_at\` datetime not null, \`updated_at\` datetime not null) default character set utf8mb4 engine = InnoDB;`);
this.addSql(`alter table \`character_type\` add index \`character_type_sprite_id_index\`(\`sprite_id\`);`);
this.addSql(`create table \`character_hair\` (\`id\` int unsigned not null auto_increment primary key, \`name\` varchar(255) not null, \`gender\` varchar(255) not null default 'MALE', \`is_selectable\` tinyint(1) not null default false, \`sprite_id\` varchar(255) null) default character set utf8mb4 engine = InnoDB;`);
this.addSql(`alter table \`character_hair\` add index \`character_hair_sprite_id_index\`(\`sprite_id\`);`);
this.addSql(`create table \`sprite_action\` (\`id\` varchar(255) not null, \`sprite_id\` varchar(255) not null, \`action\` varchar(255) not null, \`sprites\` json null, \`origin_x\` int not null default 0, \`origin_y\` int not null default 0, \`is_animated\` tinyint(1) not null default false, \`is_looping\` tinyint(1) not null default false, \`frame_width\` int not null default 0, \`frame_height\` int not null default 0, \`frame_rate\` int not null default 0, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
this.addSql(`alter table \`sprite_action\` add index \`sprite_action_sprite_id_index\`(\`sprite_id\`);`);
this.addSql(`create table \`user\` (\`id\` int unsigned not null auto_increment primary key, \`username\` varchar(255) not null, \`email\` varchar(255) not null, \`password\` varchar(255) not null, \`online\` tinyint(1) not null default false) default character set utf8mb4 engine = InnoDB;`);
this.addSql(`alter table \`user\` add unique \`user_username_unique\`(\`username\`);`);
this.addSql(`alter table \`user\` add unique \`user_email_unique\`(\`email\`);`);
this.addSql(`create table \`password_reset_token\` (\`id\` int unsigned not null auto_increment primary key, \`user_id\` int unsigned not null, \`token\` varchar(255) not null, \`created_at\` datetime not null) default character set utf8mb4 engine = InnoDB;`);
this.addSql(`alter table \`password_reset_token\` add index \`password_reset_token_user_id_index\`(\`user_id\`);`);
this.addSql(`alter table \`password_reset_token\` add unique \`password_reset_token_token_unique\`(\`token\`);`);
this.addSql(`create table \`world\` (\`date\` datetime not null, \`is_rain_enabled\` tinyint(1) not null default false, \`rain_percentage\` int not null default 0, \`is_fog_enabled\` tinyint(1) not null default false, \`fog_density\` int not null default 0, primary key (\`date\`)) default character set utf8mb4 engine = InnoDB;`);
this.addSql(`create table \`zone\` (\`id\` int unsigned not null auto_increment primary key) default character set utf8mb4 engine = InnoDB;`);
this.addSql(`create table \`character\` (\`id\` int unsigned not null auto_increment primary key, \`user_id\` int unsigned not null, \`name\` varchar(255) not null, \`online\` tinyint(1) not null default false, \`role\` varchar(255) not null default 'player', \`zone_id\` int unsigned not null, \`position_x\` int not null default 0, \`position_y\` int not null default 0, \`rotation\` int not null default 0, \`character_type_id\` int unsigned null, \`character_hair_id\` int unsigned null, \`alignment\` int not null default 50, \`hitpoints\` int not null default 100, \`mana\` int not null default 100, \`level\` int not null default 1, \`experience\` int not null default 0, \`strength\` int not null default 10, \`dexterity\` int not null default 10, \`intelligence\` int not null default 10, \`wisdom\` int not null default 10) default character set utf8mb4 engine = InnoDB;`);
this.addSql(`alter table \`character\` add index \`character_user_id_index\`(\`user_id\`);`);
this.addSql(`alter table \`character\` add unique \`character_name_unique\`(\`name\`);`);
this.addSql(`alter table \`character\` add index \`character_zone_id_index\`(\`zone_id\`);`);
this.addSql(`alter table \`character\` add index \`character_character_type_id_index\`(\`character_type_id\`);`);
this.addSql(`alter table \`character\` add index \`character_character_hair_id_index\`(\`character_hair_id\`);`);
this.addSql(`create table \`chat\` (\`id\` int unsigned not null auto_increment primary key, \`character_id\` int unsigned not null, \`zone_id\` int unsigned not null, \`message\` varchar(255) not null, \`created_at\` datetime not null) default character set utf8mb4 engine = InnoDB;`);
this.addSql(`alter table \`chat\` add index \`chat_character_id_index\`(\`character_id\`);`);
this.addSql(`alter table \`chat\` add index \`chat_zone_id_index\`(\`zone_id\`);`);
this.addSql(`create table \`character_item\` (\`id\` int unsigned not null auto_increment primary key, \`character_id\` int unsigned not null, \`item_id\` varchar(255) not null, \`quantity\` int not null) default character set utf8mb4 engine = InnoDB;`);
this.addSql(`alter table \`character_item\` add index \`character_item_character_id_index\`(\`character_id\`);`);
this.addSql(`alter table \`character_item\` add index \`character_item_item_id_index\`(\`item_id\`);`);
this.addSql(`create table \`character_equipment\` (\`id\` int unsigned not null auto_increment primary key, \`slot\` enum('HEAD', 'BODY', 'ARMS', 'LEGS', 'NECK', 'RING') not null, \`character_id\` int unsigned not null, \`character_item_id\` int unsigned not null) default character set utf8mb4 engine = InnoDB;`);
this.addSql(`alter table \`character_equipment\` add index \`character_equipment_character_id_index\`(\`character_id\`);`);
this.addSql(`alter table \`character_equipment\` add index \`character_equipment_character_item_id_index\`(\`character_item_id\`);`);
this.addSql(`alter table \`item\` add constraint \`item_sprite_id_foreign\` foreign key (\`sprite_id\`) references \`sprite\` (\`id\`) on update cascade on delete set null;`);
this.addSql(`alter table \`character_type\` add constraint \`character_type_sprite_id_foreign\` foreign key (\`sprite_id\`) references \`sprite\` (\`id\`) on update cascade on delete set null;`);
this.addSql(`alter table \`character_hair\` add constraint \`character_hair_sprite_id_foreign\` foreign key (\`sprite_id\`) references \`sprite\` (\`id\`) on update cascade on delete set null;`);
this.addSql(`alter table \`sprite_action\` add constraint \`sprite_action_sprite_id_foreign\` foreign key (\`sprite_id\`) references \`sprite\` (\`id\`) on update cascade;`);
this.addSql(`alter table \`password_reset_token\` add constraint \`password_reset_token_user_id_foreign\` foreign key (\`user_id\`) references \`user\` (\`id\`) on update cascade;`);
this.addSql(`alter table \`character\` add constraint \`character_user_id_foreign\` foreign key (\`user_id\`) references \`user\` (\`id\`) on update cascade;`);
this.addSql(`alter table \`character\` add constraint \`character_zone_id_foreign\` foreign key (\`zone_id\`) references \`zone\` (\`id\`) on update cascade;`);
this.addSql(`alter table \`character\` add constraint \`character_character_type_id_foreign\` foreign key (\`character_type_id\`) references \`character_type\` (\`id\`) on update cascade on delete set null;`);
this.addSql(`alter table \`character\` add constraint \`character_character_hair_id_foreign\` foreign key (\`character_hair_id\`) references \`character_hair\` (\`id\`) on update cascade on delete set null;`);
this.addSql(`alter table \`chat\` add constraint \`chat_character_id_foreign\` foreign key (\`character_id\`) references \`character\` (\`id\`) on update cascade;`);
this.addSql(`alter table \`chat\` add constraint \`chat_zone_id_foreign\` foreign key (\`zone_id\`) references \`zone\` (\`id\`) on update cascade;`);
this.addSql(`alter table \`character_item\` add constraint \`character_item_character_id_foreign\` foreign key (\`character_id\`) references \`character\` (\`id\`) on update cascade;`);
this.addSql(`alter table \`character_item\` add constraint \`character_item_item_id_foreign\` foreign key (\`item_id\`) references \`item\` (\`id\`) on update cascade;`);
this.addSql(`alter table \`character_equipment\` add constraint \`character_equipment_character_id_foreign\` foreign key (\`character_id\`) references \`character\` (\`id\`) on update cascade;`);
this.addSql(`alter table \`character_equipment\` add constraint \`character_equipment_character_item_id_foreign\` foreign key (\`character_item_id\`) references \`character_item\` (\`id\`) on update cascade;`);
}
}

42
src/entities/mapObject.ts Normal file
View File

@ -0,0 +1,42 @@
import { randomUUID } from 'node:crypto'
import { Collection, Entity, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
import { ZoneObject } from './zoneObject'
1
@Entity()
export class MapObject {
@PrimaryKey()
id = randomUUID()
@Property()
name!: string
@Property({ type: 'json', nullable: true })
tags?: any
@Property()
originX = 0
@Property()
originY = 0
@Property()
isAnimated = false
@Property()
frameRate = 0
@Property()
frameWidth = 0
@Property()
frameHeight = 0
@Property()
createdAt = new Date()
@Property()
updatedAt = new Date()
@OneToMany(() => ZoneObject, (zoneObject) => zoneObject.object)
zoneObjects = new Collection<ZoneObject>(this)
}

20
src/entities/tile.ts Normal file
View File

@ -0,0 +1,20 @@
import { randomUUID } from 'node:crypto'
import { Entity, PrimaryKey, Property } from '@mikro-orm/core'
@Entity()
export class Tile {
@PrimaryKey()
id = randomUUID()
@Property()
name!: string
@Property({ type: 'json', nullable: true })
tags?: any
@Property()
createdAt = new Date()
@Property()
updatedAt = new Date()
}

View File

@ -1,4 +1,8 @@
import { Collection, Entity, OneToMany, PrimaryKey } from '@mikro-orm/core' import { Collection, Entity, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
import { ZoneEffect } from './zoneEffect'
import { ZoneEventTile } from './zoneEventTile'
import { ZoneEventTileTeleport } from './zoneEventTileTeleport'
import { ZoneObject } from './zoneObject'
import { Character } from './character' import { Character } from './character'
import { Chat } from './chat' import { Chat } from './chat'
@ -7,9 +11,42 @@ export class Zone {
@PrimaryKey() @PrimaryKey()
id!: number id!: number
@Property()
name!: string
@Property()
width = 10
@Property()
height = 10
@Property({ type: 'json', nullable: true })
tiles?: any
@Property()
pvp = false
@OneToMany(() => ZoneEffect, (effect) => effect.zone)
zoneEffects = new Collection<ZoneEffect>(this)
@OneToMany(() => ZoneEventTile, (tile) => tile.zone)
zoneEventTiles = new Collection<ZoneEventTile>(this)
@OneToMany(() => ZoneEventTileTeleport, (teleport) => teleport.toZone)
zoneEventTileTeleports = new Collection<ZoneEventTileTeleport>(this)
@OneToMany(() => ZoneObject, (object) => object.zone)
zoneObjects = new Collection<ZoneObject>(this)
@OneToMany(() => Character, (character) => character.zone) @OneToMany(() => Character, (character) => character.zone)
characters = new Collection<Character>(this) characters = new Collection<Character>(this)
@OneToMany(() => Chat, (chat) => chat.zone) @OneToMany(() => Chat, (chat) => chat.zone)
chats = new Collection<Chat>(this) chats = new Collection<Chat>(this)
@Property()
createdAt = new Date()
@Property()
updatedAt = new Date()
} }

View File

@ -0,0 +1,17 @@
import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'
import { Zone } from './zone'
@Entity()
export class ZoneEffect {
@PrimaryKey()
id!: string
@ManyToOne(() => Zone)
zone!: Zone
@Property()
effect!: string
@Property()
strength!: number
}

View File

@ -0,0 +1,25 @@
import { Entity, ManyToOne, OneToOne, PrimaryKey, Property } from '@mikro-orm/core'
import { Zone } from './zone'
import { ZoneEventTileType } from '#utilities/enums'
import { ZoneEventTileTeleport } from './zoneEventTileTeleport'
@Entity()
export class ZoneEventTile {
@PrimaryKey()
id!: string
@ManyToOne(() => Zone)
zone!: Zone
@Property()
type!: ZoneEventTileType
@Property()
positionX!: number
@Property()
positionY!: number
@OneToOne(() => ZoneEventTileTeleport, (teleport) => teleport.zoneEventTile)
teleport?: ZoneEventTileTeleport
}

View File

@ -0,0 +1,25 @@
import { randomUUID } from 'node:crypto'
import { Entity, ManyToOne, OneToOne, PrimaryKey, Property } from '@mikro-orm/core'
import { Zone } from './zone'
import { ZoneEventTile } from './ZoneEventTile'
@Entity()
export class ZoneEventTileTeleport {
@PrimaryKey()
id = randomUUID()
@OneToOne(() => ZoneEventTile)
zoneEventTile!: ZoneEventTile
@ManyToOne(() => Zone)
toZone!: Zone
@Property()
toRotation!: number
@Property()
toPositionX!: number
@Property()
toPositionY!: number
}

View File

@ -0,0 +1,28 @@
import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'
import { Zone } from './zone'
import { MapObject } from '#entities/mapObject'
//@TODO : Rename mapObject
@Entity()
export class ZoneObject {
@PrimaryKey()
id!: string
@ManyToOne(() => Zone)
zone!: Zone
@ManyToOne(() => MapObject)
mapObject!: MapObject
@Property()
depth = 0
@Property()
isRotated = false
@Property()
positionX = 0
@Property()
positionY = 0
}

View File

@ -1,6 +1,6 @@
import { appLogger } from '#utilities/logger'
import { BaseRepository } from './baseRepository' import { BaseRepository } from './baseRepository'
import { CharacterHair } from '#entities/characterHair' import { CharacterHair } from '#entities/characterHair'
import { appLogger } from '#utilities/logger'
class CharacterHairRepository extends BaseRepository { class CharacterHairRepository extends BaseRepository {
async getAll() { async getAll() {

View File

@ -1,127 +1,48 @@
import prisma from '#utilities/prisma' // Import the global Prisma instance
import { appLogger } from '#utilities/logger' import { appLogger } from '#utilities/logger'
import { BaseRepository } from '#repositories/baseRepository'
import { Character } from '#entities/character'
class CharacterRepository { class CharacterRepository extends BaseRepository {
async getByUserId(userId: number) { async getByUserId(userId: number): Promise<Character[]> {
try { try {
return await prisma.character.findMany({ const repository = this.em.getRepository(Character)
where: { return await repository.find({
userId user: userId
},
include: {
zone: true,
characterType: {
include: {
sprite: true
}
},
characterHair: {
include: {
sprite: {
include: {
spriteActions: true
}
}
}
}
}
}) })
} catch (error: any) { } catch (error: any) {
// Handle error
appLogger.error(`Failed to get character by user ID: ${error instanceof Error ? error.message : String(error)}`) appLogger.error(`Failed to get character by user ID: ${error instanceof Error ? error.message : String(error)}`)
return null return []
} }
} }
async getByUserAndId(userId: number, characterId: number) { async getByUserAndId(userId: number, characterId: number): Promise<Character | null> {
try { try {
return await prisma.character.findFirst({ const repository = this.em.getRepository(Character)
where: { return await repository.findOne({
userId, user: userId,
id: characterId id: characterId
},
include: {
zone: true,
characterType: {
include: {
sprite: true
}
},
characterHair: {
include: {
sprite: {
include: {
spriteActions: true
}
}
}
}
}
}) })
} catch (error: any) { } catch (error: any) {
// Handle error
appLogger.error(`Failed to get character by user ID and character ID: ${error instanceof Error ? error.message : String(error)}`) appLogger.error(`Failed to get character by user ID and character ID: ${error instanceof Error ? error.message : String(error)}`)
return null return null
} }
} }
async getById(id: number) { async getById(id: number): Promise<Character | null> {
try { try {
return await prisma.character.findUnique({ const repository = this.em.getRepository(Character)
where: { return await repository.findOne({ id })
id
},
include: {
zone: true,
characterType: {
include: {
sprite: true
}
},
characterHair: {
include: {
sprite: {
include: {
spriteActions: true
}
}
}
}
}
})
} catch (error: any) { } catch (error: any) {
// Handle error
appLogger.error(`Failed to get character by ID: ${error instanceof Error ? error.message : String(error)}`) appLogger.error(`Failed to get character by ID: ${error instanceof Error ? error.message : String(error)}`)
return null return null
} }
} }
async getByName(name: string) { async getByName(name: string): Promise<Character | null> {
try { try {
return await prisma.character.findFirst({ const repository = this.em.getRepository(Character)
where: { return await repository.findOne({ name })
name
},
include: {
zone: true,
characterType: {
include: {
sprite: true
}
},
characterHair: {
include: {
sprite: {
include: {
spriteActions: true
}
}
}
}
}
})
} catch (error: any) { } catch (error: any) {
// Handle error
appLogger.error(`Failed to get character by name: ${error instanceof Error ? error.message : String(error)}`) appLogger.error(`Failed to get character by name: ${error instanceof Error ? error.message : String(error)}`)
return null return null
} }

View File

@ -1,21 +1,26 @@
import prisma from '#utilities/prisma' // Import the global Prisma instance import { appLogger } from '#utilities/logger'
import { CharacterType } from '@prisma/client' import { BaseRepository } from '#repositories/baseRepository'
import { CharacterType } from '#entities/characterType'
class CharacterTypeRepository { class CharacterTypeRepository extends BaseRepository {
async getAll(): Promise<CharacterType[]> { async getAll() {
return prisma.characterType.findMany({ try {
include: { const repository = this.em.getRepository(CharacterType)
sprite: true return await repository.findAll()
} catch (error: any) {
appLogger.error(`Failed to get all character types: ${error instanceof Error ? error.message : String(error)}`)
return null
} }
})
} }
async getById(id: number): Promise<CharacterType | null> {
return prisma.characterType.findUnique({ async getById(id: number) {
where: { id }, try {
include: { const repository = this.em.getRepository(CharacterType)
sprite: true return await repository.findOne({ id })
} catch (error: any) {
appLogger.error(`Failed to get character type by ID: ${error instanceof Error ? error.message : String(error)}`)
return null
} }
})
} }
} }

View File

@ -1,48 +1,52 @@
import prisma from '#utilities/prisma' import { appLogger } from '#utilities/logger'
import { Chat } from '@prisma/client' import { BaseRepository } from '#repositories/baseRepository'
import { Chat } from '#entities/chat'
class ChatRepository { class ChatRepository extends BaseRepository {
async getById(id: number): Promise<Chat | null> { async getById(id: number): Promise<Chat[]> {
return prisma.chat.findUnique({ try {
where: { id }, const repository = this.em.getRepository(Chat)
include: { return await repository.find({
character: true, id
zone: true
}
}) })
} catch (error: any) {
appLogger.error(`Failed to get chat by ID: ${error instanceof Error ? error.message : String(error)}`)
return []
}
} }
async getAll(): Promise<Chat[]> { async getAll(): Promise<Chat[]> {
return prisma.chat.findMany({ try {
include: { const repository = this.em.getRepository(Chat)
character: true, return await repository.findAll()
zone: true } catch (error: any) {
appLogger.error(`Failed to get all chats: ${error instanceof Error ? error.message : String(error)}`)
return []
} }
})
} }
async getByCharacterId(characterId: number): Promise<Chat[]> { async getByCharacterId(characterId: number): Promise<Chat[]> {
return prisma.chat.findMany({ try {
where: { const repository = this.em.getRepository(Chat)
characterId return await repository.find({
}, character: characterId
include: {
character: true,
zone: true
}
}) })
} catch (error: any) {
appLogger.error(`Failed to get chats by character ID: ${error instanceof Error ? error.message : String(error)}`)
return []
}
} }
async getByZoneId(zoneId: number): Promise<Chat[]> { async getByZoneId(zoneId: number): Promise<Chat[]> {
return prisma.chat.findMany({ try {
where: { const repository = this.em.getRepository(Chat)
zoneId return await repository.find({
}, zone: zoneId
include: {
character: true,
zone: true
}
}) })
} catch (error: any) {
appLogger.error(`Failed to get chats by zone ID: ${error instanceof Error ? error.message : String(error)}`)
return []
}
} }
} }

View File

@ -1,38 +1,38 @@
import prisma from '#utilities/prisma' // Import the global Prisma instance import { appLogger } from '#utilities/logger'
import { Tile } from '@prisma/client' import { BaseRepository } from '#repositories/baseRepository'
import zoneRepository from './zoneRepository' import { Item } from '#entities/item'
import { unduplicateArray } from '#utilities/utilities'
import { FlattenZoneArray } from '#utilities/zone'
class ItemRepository { class ItemRepository extends BaseRepository {
async getById(id: string) { async getById(id: string): Promise<any> {
return prisma.item.findUnique({ try {
where: { id }, const repository = this.em.getRepository(Item)
include: { return await repository.findOne({ id })
sprite: true } catch (error: any) {
appLogger.error(`Failed to get item by ID: ${error instanceof Error ? error.message : String(error)}`)
return null
} }
})
} }
async getByIds(ids: string[]) { async getByIds(ids: string[]): Promise<any> {
return prisma.item.findMany({ try {
where: { const repository = this.em.getRepository(Item)
id: { return await repository.find({
in: ids id: ids
}
},
include: {
sprite: true
}
}) })
} catch (error: any) {
appLogger.error(`Failed to get items by IDs: ${error instanceof Error ? error.message : String(error)}`)
return null
}
} }
async getAll() { async getAll(): Promise<any> {
return prisma.item.findMany({ try {
include: { const repository = this.em.getRepository(Item)
sprite: true return await repository.findAll()
} catch (error: any) {
appLogger.error(`Failed to get all items: ${error instanceof Error ? error.message : String(error)}`)
return null
} }
})
} }
} }

View File

@ -1,15 +1,22 @@
import prisma from '#utilities/prisma' // Import the global Prisma instance import { BaseRepository } from '#repositories/baseRepository'
import { Object } from '@prisma/client'
class ObjectRepository { class ObjectRepository extends BaseRepository {
async getById(id: string): Promise<Object | null> { async getById(id: string): Promise<any> {
return prisma.object.findUnique({ try {
where: { id } const repository = this.em.getRepository(Object)
}) return await repository.findOne({ id })
} catch (error: any) {
return null
}
} }
async getAll(): Promise<Object[]> { async getAll(): Promise<any> {
return prisma.object.findMany() try {
const repository = this.em.getRepository(Object)
return await repository.findAll()
} catch (error: any) {
return null
}
} }
} }

View File

@ -1,14 +1,12 @@
import prisma from '#utilities/prisma' import { appLogger } from '#utilities/logger'
import { appLogger } from '#utilities/logger' // Import the global Prisma instance import { BaseRepository } from '#repositories/baseRepository' // Import the global Prisma instance
import { PasswordResetToken } from '#entities/passwordResetToken'
class PasswordResetTokenRepository { class PasswordResetTokenRepository extends BaseRepository {
async getById(id: number): Promise<any> { async getById(id: number): Promise<any> {
try { try {
return await prisma.passwordResetToken.findUnique({ const repository = this.em.getRepository(PasswordResetToken)
where: { return await repository.findOne({ id })
id
}
})
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
appLogger.error(`Failed to get password reset token by ID: ${error instanceof Error ? error.message : String(error)}`) appLogger.error(`Failed to get password reset token by ID: ${error instanceof Error ? error.message : String(error)}`)
@ -17,10 +15,9 @@ class PasswordResetTokenRepository {
async getByUserId(userId: number): Promise<any> { async getByUserId(userId: number): Promise<any> {
try { try {
return await prisma.passwordResetToken.findFirst({ const repository = this.em.getRepository(PasswordResetToken)
where: { return await repository.findOne({
userId user: userId
}
}) })
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
@ -30,11 +27,8 @@ class PasswordResetTokenRepository {
async getByToken(token: string): Promise<any> { async getByToken(token: string): Promise<any> {
try { try {
return await prisma.passwordResetToken.findFirst({ const repository = this.em.getRepository(PasswordResetToken)
where: { return await repository.findOne({ token })
token
}
})
} catch (error: any) { } catch (error: any) {
// Handle error // Handle error
appLogger.error(`Failed to get password reset token by token: ${error instanceof Error ? error.message : String(error)}`) appLogger.error(`Failed to get password reset token by token: ${error instanceof Error ? error.message : String(error)}`)

View File

@ -1,31 +1,37 @@
import prisma from '#utilities/prisma' // Import the global Prisma instance import { FilterValue } from '@mikro-orm/core'
import { SpriteAction } from '@prisma/client' import { BaseRepository } from '#repositories/baseRepository'
import { Sprite } from '#entities/sprite'
class SpriteRepository { class SpriteRepository extends BaseRepository {
async getById(id: string) { async getById(id: FilterValue<`${string}-${string}-${string}-${string}-${string}`>) {
return prisma.sprite.findUnique({ try {
where: { id }, const repository = this.em.getRepository(Sprite)
include: { return await repository.findOne({ id })
spriteActions: true } catch (error: any) {
return null
} }
})
} }
async getAll() { async getAll(): Promise<any> {
return prisma.sprite.findMany({ try {
include: { const repository = this.em.getRepository(Sprite)
spriteActions: true return await repository.findAll()
} catch (error: any) {
return null
} }
})
} }
async getSpriteActions(spriteId: string): Promise<SpriteAction[]> { // @TODO: THIS BROKEY
return prisma.spriteAction.findMany({ // async getSpriteActions(spriteId: string): Promise<any> {
where: { // try {
spriteId // const repository = this.em.getRepository(SpriteAction)
} // return await repository.find({
}) // sprite: spriteId
} // })
// } catch (error: any) {
// return null
// }
// }
} }
export default new SpriteRepository() export default new SpriteRepository()

View File

@ -1,37 +1,52 @@
import prisma from '#utilities/prisma' // Import the global Prisma instance import { FilterValue } from '@mikro-orm/core'
import { Tile } from '@prisma/client' import { BaseRepository } from '#repositories/baseRepository'
import zoneRepository from './zoneRepository' import { Tile } from '#entities/tile'
import { Zone } from '#entities/zone'
import { unduplicateArray } from '#utilities/utilities' import { unduplicateArray } from '#utilities/utilities'
import { FlattenZoneArray } from '#utilities/zone' import { FlattenZoneArray } from '#utilities/zone'
class TileRepository { class TileRepository extends BaseRepository {
async getById(id: string): Promise<Tile | null> { async getById(id: FilterValue<`${string}-${string}-${string}-${string}-${string}`>): Promise<any> {
return prisma.tile.findUnique({ try {
where: { id } const repository = this.em.getRepository(Tile)
return await repository.findOne({ id })
} catch (error: any) {
return null
}
}
async getByIds(ids: FilterValue<`${string}-${string}-${string}-${string}-${string}`>): Promise<any> {
try {
const repository = this.em.getRepository(Tile)
return await repository.find({
id: ids
}) })
} catch (error: any) {
return null
}
} }
async getByIds(ids: string[]): Promise<Tile[]> { async getAll(): Promise<any> {
return prisma.tile.findMany({ try {
where: { const repository = this.em.getRepository(Tile)
id: { return await repository.findAll()
in: ids } catch (error: any) {
return null
} }
} }
})
}
async getAll(): Promise<Tile[]> {
return prisma.tile.findMany()
}
async getByZoneId(zoneId: number): Promise<Tile[]> {
const zone = await zoneRepository.getById(zoneId)
if (!zone) return []
async getByZoneId(zoneId: number): Promise<any> {
try {
const repository = this.em.getRepository(Zone)
const zone = await repository.findOne({ id: zoneId })
if (!zone) return null
const zoneTileArray = unduplicateArray(FlattenZoneArray(JSON.parse(JSON.stringify(zone.tiles)))) const zoneTileArray = unduplicateArray(FlattenZoneArray(JSON.parse(JSON.stringify(zone.tiles))))
return await repository.find({
return this.getByIds(zoneTileArray) zone: zoneId
})
} catch (error: any) {
return null
}
} }
} }

View File

@ -1,12 +1,13 @@
import bcrypt from 'bcryptjs' import bcrypt from 'bcryptjs'
import UserRepository from '#repositories/userRepository' import UserRepository from '#repositories/userRepository'
import PasswordResetTokenRepository from '#repositories/passwordResetTokenRepository' import PasswordResetTokenRepository from '#repositories/passwordResetTokenRepository'
import prisma from '#utilities/prisma'
import { User } from '@prisma/client'
import config from '#utilities/config' import config from '#utilities/config'
import NodeMailer from 'nodemailer' import NodeMailer from 'nodemailer'
import { httpLogger } from '#utilities/logger' import { httpLogger } from '#utilities/logger'
import PasswordResetTokenService from './passwordResetTokenService' import PasswordResetTokenService from './passwordResetTokenService' // @TODO: Correctly implement this
import { User } from '#entities/user'
import { Database } from '#utilities/database'
import { PasswordResetToken } from '#entities/passwordResetToken'
/** /**
* User service * User service
@ -14,11 +15,6 @@ import PasswordResetTokenService from './passwordResetTokenService'
* @class UserService * @class UserService
*/ */
class UserService { class UserService {
/**
* Login user
* @param username
* @param password
*/
async login(username: string, password: string): Promise<boolean | User> { async login(username: string, password: string): Promise<boolean | User> {
try { try {
const user = await UserRepository.getByUsername(username) const user = await UserRepository.getByUsername(username)
@ -39,43 +35,31 @@ class UserService {
} }
} }
/**
* Register user
* @param username
* @param email
* @param password
*/
async register(username: string, email: string, password: string): Promise<boolean | User> { async register(username: string, email: string, password: string): Promise<boolean | User> {
try { try {
const user = await UserRepository.getByUsername(username) // Check existing users
if (user) { const [userByName, userByEmail] = await Promise.all([UserRepository.getByUsername(username), UserRepository.getByEmail(email)])
return false
}
const userByEmail = await UserRepository.getByEmail(email) if (userByName || userByEmail) {
if (userByEmail) { httpLogger.error(`User already exists: ${userByEmail ? email : username}`)
httpLogger.error(`User already exists: ${email}`)
return false return false
} }
const hashedPassword = await bcrypt.hash(password, 10) const hashedPassword = await bcrypt.hash(password, 10)
return prisma.user.create({
data: { const newUser = new User()
username, newUser.username = username
email, newUser.email = email
password: hashedPassword newUser.password = hashedPassword
} await Database.save(newUser)
})
return newUser
} catch (error: any) { } catch (error: any) {
httpLogger.error(`Error registering user: ${error instanceof Error ? error.message : String(error)}`) httpLogger.error(`Error registering user: ${error instanceof Error ? error.message : String(error)}`)
return false return false
} }
} }
/**
* Reset password
* @param email
*/
async requestPasswordReset(email: string): Promise<boolean> { async requestPasswordReset(email: string): Promise<boolean> {
try { try {
const user = await UserRepository.getByEmail(email) const user = await UserRepository.getByEmail(email)
@ -86,24 +70,20 @@ class UserService {
// Check if password reset has been requested recently // Check if password reset has been requested recently
if (latestToken) { if (latestToken) {
const tokenExpiryDate = new Date(Date.now() - 24 * 60 * 60 * 1000) // 24 hours const tokenExpiryDate = new Date(Date.now() - 24 * 60 * 60 * 1000)
const isTokenExpired = latestToken.createdAt < tokenExpiryDate const isTokenExpired = latestToken.createdAt < tokenExpiryDate
if (!isTokenExpired) return false if (!isTokenExpired) return false
await prisma.passwordResetToken.delete({ // Delete existing token using MikroORM
where: { await Database.delete(latestToken)
id: latestToken.id
}
})
} }
await prisma.passwordResetToken.create({ // Create new token using MikroORM
data: { const passwordResetToken = new PasswordResetToken()
userId: user.id, passwordResetToken.user = user
token: token passwordResetToken.token = token
} await Database.save(passwordResetToken)
})
const transporter = NodeMailer.createTransport({ const transporter = NodeMailer.createTransport({
host: config.SMTP_HOST, host: config.SMTP_HOST,
@ -119,8 +99,8 @@ class UserService {
from: config.SMTP_USER, from: config.SMTP_USER,
to: email, to: email,
subject: 'Reset your password', subject: 'Reset your password',
text: 'A password reset has been requested, reset your password here: ' + config.CLIENT_URL + '#' + token, // Plain text body text: `A password reset has been requested, reset your password here: ${config.CLIENT_URL}#${token}`,
html: "<p>A password reset has been requested, reset your password here: <a href='" + config.CLIENT_URL + '#' + token + "'>" + config.CLIENT_URL + '#' + token + '</a></p>' // Html body html: `<p>A password reset has been requested, reset your password here: <a href="${config.CLIENT_URL}#${token}">${config.CLIENT_URL}#${token}</a></p>`
}) })
return true return true
@ -130,11 +110,6 @@ class UserService {
} }
} }
/**
* Set new password
* @param urlToken
* @param password
*/
async resetPassword(urlToken: string, password: string): Promise<boolean> { async resetPassword(urlToken: string, password: string): Promise<boolean> {
try { try {
const tokenData = await PasswordResetTokenRepository.getByToken(urlToken) const tokenData = await PasswordResetTokenRepository.getByToken(urlToken)
@ -143,16 +118,19 @@ class UserService {
} }
const hashedPassword = await bcrypt.hash(password, 10) const hashedPassword = await bcrypt.hash(password, 10)
await prisma.user.update({
where: { id: tokenData.userId }, // Update user password using MikroORM
data: { const orm = await Database.getInstance()
password: hashedPassword const em = orm.em.fork()
} const user = await em.findOne(User, { id: tokenData.userId })
})
if (!user) return false
user.password = hashedPassword
await em.persistAndFlush(user)
// Delete the token // Delete the token
const passwordResetTokenService = new PasswordResetTokenService() await em.removeAndFlush(tokenData)
await passwordResetTokenService.delete(urlToken)
return true return true
} catch (error: any) { } catch (error: any) {

View File

@ -1,5 +1,6 @@
import config from '../../mikro-orm.config' import config from '../../mikro-orm.config'
import { MikroORM } from '@mikro-orm/mariadb' // import { MikroORM } from '@mikro-orm/mariadb'
import { MikroORM } from '@mikro-orm/mysql'
import { appLogger } from '#utilities/logger' import { appLogger } from '#utilities/logger'
/** /**
@ -26,6 +27,48 @@ export class Database {
return Database.instance return Database.instance
} }
public static async save<T extends object>(entity: T): Promise<T> {
try {
const orm = await Database.getInstance()
const em = orm.em.fork() // Create a new EntityManager instance
await em.begin() // Start transaction
try {
em.persist(entity)
await em.flush()
await em.commit()
return entity
} catch (error) {
await em.rollback()
throw error
}
} catch (error) {
appLogger.error(`Failed to save entity: ${error instanceof Error ? error.message : String(error)}`)
throw error
}
}
public static async delete<T extends object>(entity: T): Promise<T> {
try {
const orm = await Database.getInstance()
const em = orm.em.fork() // Create a new EntityManager instance
await em.begin() // Start transaction
try {
em.remove(entity)
await em.flush()
await em.commit()
return entity
} catch (error) {
await em.rollback()
throw error
}
} catch (error) {
appLogger.error(`Failed to remove entity: ${error instanceof Error ? error.message : String(error)}`)
throw error
}
}
public static async close() { public static async close() {
if (Database.instance) { if (Database.instance) {
await Database.instance.close() await Database.instance.close()

View File

@ -38,3 +38,10 @@ export enum CharacterEquipmentSlotType {
NECK = 'NECK', NECK = 'NECK',
RING = 'RING' RING = 'RING'
} }
export enum ZoneEventTileType {
BLOCK = 'BLOCK',
TELEPORT = 'TELEPORT',
NPC = 'NPC',
ITEM = 'ITEM'
}