diff --git a/migrations/Migration20241225010519.ts b/migrations/Migration20241225010519.ts deleted file mode 100644 index 113f5b3..0000000 --- a/migrations/Migration20241225010519.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20241225010519 extends Migration { - - override async up(): Promise { - 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;`); - } - -} diff --git a/src/entities/mapObject.ts b/src/entities/mapObject.ts new file mode 100644 index 0000000..ef618e5 --- /dev/null +++ b/src/entities/mapObject.ts @@ -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(this) +} diff --git a/src/entities/tile.ts b/src/entities/tile.ts new file mode 100644 index 0000000..a04bff7 --- /dev/null +++ b/src/entities/tile.ts @@ -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() +} diff --git a/src/entities/zone.ts b/src/entities/zone.ts index 1b45b39..5c06e3a 100644 --- a/src/entities/zone.ts +++ b/src/entities/zone.ts @@ -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 { Chat } from './chat' @@ -7,9 +11,42 @@ export class Zone { @PrimaryKey() 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(this) + + @OneToMany(() => ZoneEventTile, (tile) => tile.zone) + zoneEventTiles = new Collection(this) + + @OneToMany(() => ZoneEventTileTeleport, (teleport) => teleport.toZone) + zoneEventTileTeleports = new Collection(this) + + @OneToMany(() => ZoneObject, (object) => object.zone) + zoneObjects = new Collection(this) + @OneToMany(() => Character, (character) => character.zone) characters = new Collection(this) @OneToMany(() => Chat, (chat) => chat.zone) chats = new Collection(this) + + @Property() + createdAt = new Date() + + @Property() + updatedAt = new Date() } diff --git a/src/entities/zoneEffect.ts b/src/entities/zoneEffect.ts new file mode 100644 index 0000000..f42ba0a --- /dev/null +++ b/src/entities/zoneEffect.ts @@ -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 +} diff --git a/src/entities/zoneEventTile.ts b/src/entities/zoneEventTile.ts new file mode 100644 index 0000000..e802a52 --- /dev/null +++ b/src/entities/zoneEventTile.ts @@ -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 +} diff --git a/src/entities/zoneEventTileTeleport.ts b/src/entities/zoneEventTileTeleport.ts new file mode 100644 index 0000000..a119ae0 --- /dev/null +++ b/src/entities/zoneEventTileTeleport.ts @@ -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 +} diff --git a/src/entities/zoneObject.ts b/src/entities/zoneObject.ts new file mode 100644 index 0000000..0d04cf9 --- /dev/null +++ b/src/entities/zoneObject.ts @@ -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 +} diff --git a/src/repositories/characterHairRepository.ts b/src/repositories/characterHairRepository.ts index c7be72d..7a8d49f 100644 --- a/src/repositories/characterHairRepository.ts +++ b/src/repositories/characterHairRepository.ts @@ -1,6 +1,6 @@ +import { appLogger } from '#utilities/logger' import { BaseRepository } from './baseRepository' import { CharacterHair } from '#entities/characterHair' -import { appLogger } from '#utilities/logger' class CharacterHairRepository extends BaseRepository { async getAll() { diff --git a/src/repositories/characterRepository.ts b/src/repositories/characterRepository.ts index 1c9feb2..3957a6d 100644 --- a/src/repositories/characterRepository.ts +++ b/src/repositories/characterRepository.ts @@ -1,127 +1,48 @@ -import prisma from '#utilities/prisma' // Import the global Prisma instance import { appLogger } from '#utilities/logger' +import { BaseRepository } from '#repositories/baseRepository' +import { Character } from '#entities/character' -class CharacterRepository { - async getByUserId(userId: number) { +class CharacterRepository extends BaseRepository { + async getByUserId(userId: number): Promise { try { - return await prisma.character.findMany({ - where: { - userId - }, - include: { - zone: true, - characterType: { - include: { - sprite: true - } - }, - characterHair: { - include: { - sprite: { - include: { - spriteActions: true - } - } - } - } - } + const repository = this.em.getRepository(Character) + return await repository.find({ + user: userId }) } catch (error: any) { - // Handle 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 { try { - return await prisma.character.findFirst({ - where: { - userId, - id: characterId - }, - include: { - zone: true, - characterType: { - include: { - sprite: true - } - }, - characterHair: { - include: { - sprite: { - include: { - spriteActions: true - } - } - } - } - } + const repository = this.em.getRepository(Character) + return await repository.findOne({ + user: userId, + id: characterId }) } catch (error: any) { - // Handle error appLogger.error(`Failed to get character by user ID and character ID: ${error instanceof Error ? error.message : String(error)}`) return null } } - async getById(id: number) { + async getById(id: number): Promise { try { - return await prisma.character.findUnique({ - where: { - id - }, - include: { - zone: true, - characterType: { - include: { - sprite: true - } - }, - characterHair: { - include: { - sprite: { - include: { - spriteActions: true - } - } - } - } - } - }) + const repository = this.em.getRepository(Character) + return await repository.findOne({ id }) } catch (error: any) { - // Handle error appLogger.error(`Failed to get character by ID: ${error instanceof Error ? error.message : String(error)}`) return null } } - async getByName(name: string) { + async getByName(name: string): Promise { try { - return await prisma.character.findFirst({ - where: { - name - }, - include: { - zone: true, - characterType: { - include: { - sprite: true - } - }, - characterHair: { - include: { - sprite: { - include: { - spriteActions: true - } - } - } - } - } - }) + const repository = this.em.getRepository(Character) + return await repository.findOne({ name }) } catch (error: any) { - // Handle error appLogger.error(`Failed to get character by name: ${error instanceof Error ? error.message : String(error)}`) return null } diff --git a/src/repositories/characterTypeRepository.ts b/src/repositories/characterTypeRepository.ts index 7498012..43c453f 100644 --- a/src/repositories/characterTypeRepository.ts +++ b/src/repositories/characterTypeRepository.ts @@ -1,21 +1,26 @@ -import prisma from '#utilities/prisma' // Import the global Prisma instance -import { CharacterType } from '@prisma/client' +import { appLogger } from '#utilities/logger' +import { BaseRepository } from '#repositories/baseRepository' +import { CharacterType } from '#entities/characterType' -class CharacterTypeRepository { - async getAll(): Promise { - return prisma.characterType.findMany({ - include: { - sprite: true - } - }) +class CharacterTypeRepository extends BaseRepository { + async getAll() { + try { + const repository = this.em.getRepository(CharacterType) + 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 { - return prisma.characterType.findUnique({ - where: { id }, - include: { - sprite: true - } - }) + + async getById(id: number) { + try { + const repository = this.em.getRepository(CharacterType) + 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 + } } } diff --git a/src/repositories/chatRepository.ts b/src/repositories/chatRepository.ts index 93bcd73..b229fc5 100644 --- a/src/repositories/chatRepository.ts +++ b/src/repositories/chatRepository.ts @@ -1,48 +1,52 @@ -import prisma from '#utilities/prisma' -import { Chat } from '@prisma/client' +import { appLogger } from '#utilities/logger' +import { BaseRepository } from '#repositories/baseRepository' +import { Chat } from '#entities/chat' -class ChatRepository { - async getById(id: number): Promise { - return prisma.chat.findUnique({ - where: { id }, - include: { - character: true, - zone: true - } - }) +class ChatRepository extends BaseRepository { + async getById(id: number): Promise { + try { + const repository = this.em.getRepository(Chat) + return await repository.find({ + id + }) + } catch (error: any) { + appLogger.error(`Failed to get chat by ID: ${error instanceof Error ? error.message : String(error)}`) + return [] + } } async getAll(): Promise { - return prisma.chat.findMany({ - include: { - character: true, - zone: true - } - }) + try { + const repository = this.em.getRepository(Chat) + return await repository.findAll() + } catch (error: any) { + appLogger.error(`Failed to get all chats: ${error instanceof Error ? error.message : String(error)}`) + return [] + } } async getByCharacterId(characterId: number): Promise { - return prisma.chat.findMany({ - where: { - characterId - }, - include: { - character: true, - zone: true - } - }) + try { + const repository = this.em.getRepository(Chat) + return await repository.find({ + character: characterId + }) + } 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 { - return prisma.chat.findMany({ - where: { - zoneId - }, - include: { - character: true, - zone: true - } - }) + try { + const repository = this.em.getRepository(Chat) + return await repository.find({ + zone: zoneId + }) + } catch (error: any) { + appLogger.error(`Failed to get chats by zone ID: ${error instanceof Error ? error.message : String(error)}`) + return [] + } } } diff --git a/src/repositories/itemRepository.ts b/src/repositories/itemRepository.ts index fb6aaa7..8811abc 100644 --- a/src/repositories/itemRepository.ts +++ b/src/repositories/itemRepository.ts @@ -1,38 +1,38 @@ -import prisma from '#utilities/prisma' // Import the global Prisma instance -import { Tile } from '@prisma/client' -import zoneRepository from './zoneRepository' -import { unduplicateArray } from '#utilities/utilities' -import { FlattenZoneArray } from '#utilities/zone' +import { appLogger } from '#utilities/logger' +import { BaseRepository } from '#repositories/baseRepository' +import { Item } from '#entities/item' -class ItemRepository { - async getById(id: string) { - return prisma.item.findUnique({ - where: { id }, - include: { - sprite: true - } - }) +class ItemRepository extends BaseRepository { + async getById(id: string): Promise { + try { + const repository = this.em.getRepository(Item) + return await repository.findOne({ id }) + } catch (error: any) { + appLogger.error(`Failed to get item by ID: ${error instanceof Error ? error.message : String(error)}`) + return null + } } - async getByIds(ids: string[]) { - return prisma.item.findMany({ - where: { - id: { - in: ids - } - }, - include: { - sprite: true - } - }) + async getByIds(ids: string[]): Promise { + try { + const repository = this.em.getRepository(Item) + return await repository.find({ + id: ids + }) + } catch (error: any) { + appLogger.error(`Failed to get items by IDs: ${error instanceof Error ? error.message : String(error)}`) + return null + } } - async getAll() { - return prisma.item.findMany({ - include: { - sprite: true - } - }) + async getAll(): Promise { + try { + const repository = this.em.getRepository(Item) + return await repository.findAll() + } catch (error: any) { + appLogger.error(`Failed to get all items: ${error instanceof Error ? error.message : String(error)}`) + return null + } } } diff --git a/src/repositories/objectRepository.ts b/src/repositories/objectRepository.ts index f3fb9eb..7ebe628 100644 --- a/src/repositories/objectRepository.ts +++ b/src/repositories/objectRepository.ts @@ -1,15 +1,22 @@ -import prisma from '#utilities/prisma' // Import the global Prisma instance -import { Object } from '@prisma/client' +import { BaseRepository } from '#repositories/baseRepository' -class ObjectRepository { - async getById(id: string): Promise { - return prisma.object.findUnique({ - where: { id } - }) +class ObjectRepository extends BaseRepository { + async getById(id: string): Promise { + try { + const repository = this.em.getRepository(Object) + return await repository.findOne({ id }) + } catch (error: any) { + return null + } } - async getAll(): Promise { - return prisma.object.findMany() + async getAll(): Promise { + try { + const repository = this.em.getRepository(Object) + return await repository.findAll() + } catch (error: any) { + return null + } } } diff --git a/src/repositories/passwordResetTokenRepository.ts b/src/repositories/passwordResetTokenRepository.ts index fd323c2..15ed509 100644 --- a/src/repositories/passwordResetTokenRepository.ts +++ b/src/repositories/passwordResetTokenRepository.ts @@ -1,14 +1,12 @@ -import prisma from '#utilities/prisma' -import { appLogger } from '#utilities/logger' // Import the global Prisma instance +import { appLogger } from '#utilities/logger' +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 { try { - return await prisma.passwordResetToken.findUnique({ - where: { - id - } - }) + const repository = this.em.getRepository(PasswordResetToken) + return await repository.findOne({ id }) } catch (error: any) { // Handle 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 { try { - return await prisma.passwordResetToken.findFirst({ - where: { - userId - } + const repository = this.em.getRepository(PasswordResetToken) + return await repository.findOne({ + user: userId }) } catch (error: any) { // Handle error @@ -30,11 +27,8 @@ class PasswordResetTokenRepository { async getByToken(token: string): Promise { try { - return await prisma.passwordResetToken.findFirst({ - where: { - token - } - }) + const repository = this.em.getRepository(PasswordResetToken) + return await repository.findOne({ token }) } catch (error: any) { // Handle error appLogger.error(`Failed to get password reset token by token: ${error instanceof Error ? error.message : String(error)}`) diff --git a/src/repositories/spriteRepository.ts b/src/repositories/spriteRepository.ts index 832128c..58014a8 100644 --- a/src/repositories/spriteRepository.ts +++ b/src/repositories/spriteRepository.ts @@ -1,31 +1,37 @@ -import prisma from '#utilities/prisma' // Import the global Prisma instance -import { SpriteAction } from '@prisma/client' +import { FilterValue } from '@mikro-orm/core' +import { BaseRepository } from '#repositories/baseRepository' +import { Sprite } from '#entities/sprite' -class SpriteRepository { - async getById(id: string) { - return prisma.sprite.findUnique({ - where: { id }, - include: { - spriteActions: true - } - }) +class SpriteRepository extends BaseRepository { + async getById(id: FilterValue<`${string}-${string}-${string}-${string}-${string}`>) { + try { + const repository = this.em.getRepository(Sprite) + return await repository.findOne({ id }) + } catch (error: any) { + return null + } } - async getAll() { - return prisma.sprite.findMany({ - include: { - spriteActions: true - } - }) + async getAll(): Promise { + try { + const repository = this.em.getRepository(Sprite) + return await repository.findAll() + } catch (error: any) { + return null + } } - async getSpriteActions(spriteId: string): Promise { - return prisma.spriteAction.findMany({ - where: { - spriteId - } - }) - } + // @TODO: THIS BROKEY + // async getSpriteActions(spriteId: string): Promise { + // try { + // const repository = this.em.getRepository(SpriteAction) + // return await repository.find({ + // sprite: spriteId + // }) + // } catch (error: any) { + // return null + // } + // } } export default new SpriteRepository() diff --git a/src/repositories/tileRepository.ts b/src/repositories/tileRepository.ts index 5cfc410..bddf5a9 100644 --- a/src/repositories/tileRepository.ts +++ b/src/repositories/tileRepository.ts @@ -1,37 +1,52 @@ -import prisma from '#utilities/prisma' // Import the global Prisma instance -import { Tile } from '@prisma/client' -import zoneRepository from './zoneRepository' +import { FilterValue } from '@mikro-orm/core' +import { BaseRepository } from '#repositories/baseRepository' +import { Tile } from '#entities/tile' +import { Zone } from '#entities/zone' import { unduplicateArray } from '#utilities/utilities' import { FlattenZoneArray } from '#utilities/zone' -class TileRepository { - async getById(id: string): Promise { - return prisma.tile.findUnique({ - where: { id } - }) +class TileRepository extends BaseRepository { + async getById(id: FilterValue<`${string}-${string}-${string}-${string}-${string}`>): Promise { + try { + const repository = this.em.getRepository(Tile) + return await repository.findOne({ id }) + } catch (error: any) { + return null + } } - async getByIds(ids: string[]): Promise { - return prisma.tile.findMany({ - where: { - id: { - in: ids - } - } - }) + async getByIds(ids: FilterValue<`${string}-${string}-${string}-${string}-${string}`>): Promise { + try { + const repository = this.em.getRepository(Tile) + return await repository.find({ + id: ids + }) + } catch (error: any) { + return null + } } - async getAll(): Promise { - return prisma.tile.findMany() + async getAll(): Promise { + try { + const repository = this.em.getRepository(Tile) + return await repository.findAll() + } catch (error: any) { + return null + } } - async getByZoneId(zoneId: number): Promise { - const zone = await zoneRepository.getById(zoneId) - if (!zone) return [] - - const zoneTileArray = unduplicateArray(FlattenZoneArray(JSON.parse(JSON.stringify(zone.tiles)))) - - return this.getByIds(zoneTileArray) + async getByZoneId(zoneId: number): Promise { + 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)))) + return await repository.find({ + zone: zoneId + }) + } catch (error: any) { + return null + } } } diff --git a/src/services/userService.ts b/src/services/userService.ts index 80e602f..feea80d 100644 --- a/src/services/userService.ts +++ b/src/services/userService.ts @@ -1,12 +1,13 @@ import bcrypt from 'bcryptjs' import UserRepository from '#repositories/userRepository' import PasswordResetTokenRepository from '#repositories/passwordResetTokenRepository' -import prisma from '#utilities/prisma' -import { User } from '@prisma/client' import config from '#utilities/config' import NodeMailer from 'nodemailer' 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 @@ -14,11 +15,6 @@ import PasswordResetTokenService from './passwordResetTokenService' * @class UserService */ class UserService { - /** - * Login user - * @param username - * @param password - */ async login(username: string, password: string): Promise { try { 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 { try { - const user = await UserRepository.getByUsername(username) - if (user) { - return false - } + // Check existing users + const [userByName, userByEmail] = await Promise.all([UserRepository.getByUsername(username), UserRepository.getByEmail(email)]) - const userByEmail = await UserRepository.getByEmail(email) - if (userByEmail) { - httpLogger.error(`User already exists: ${email}`) + if (userByName || userByEmail) { + httpLogger.error(`User already exists: ${userByEmail ? email : username}`) return false } const hashedPassword = await bcrypt.hash(password, 10) - return prisma.user.create({ - data: { - username, - email, - password: hashedPassword - } - }) + + const newUser = new User() + newUser.username = username + newUser.email = email + newUser.password = hashedPassword + await Database.save(newUser) + + return newUser } catch (error: any) { httpLogger.error(`Error registering user: ${error instanceof Error ? error.message : String(error)}`) return false } } - /** - * Reset password - * @param email - */ async requestPasswordReset(email: string): Promise { try { const user = await UserRepository.getByEmail(email) @@ -86,24 +70,20 @@ class UserService { // Check if password reset has been requested recently 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 if (!isTokenExpired) return false - await prisma.passwordResetToken.delete({ - where: { - id: latestToken.id - } - }) + // Delete existing token using MikroORM + await Database.delete(latestToken) } - await prisma.passwordResetToken.create({ - data: { - userId: user.id, - token: token - } - }) + // Create new token using MikroORM + const passwordResetToken = new PasswordResetToken() + passwordResetToken.user = user + passwordResetToken.token = token + await Database.save(passwordResetToken) const transporter = NodeMailer.createTransport({ host: config.SMTP_HOST, @@ -119,8 +99,8 @@ class UserService { from: config.SMTP_USER, to: email, subject: 'Reset your password', - text: 'A password reset has been requested, reset your password here: ' + config.CLIENT_URL + '#' + token, // Plain text body - html: "

A password reset has been requested, reset your password here: " + config.CLIENT_URL + '#' + token + '

' // Html body + text: `A password reset has been requested, reset your password here: ${config.CLIENT_URL}#${token}`, + html: `

A password reset has been requested, reset your password here: ${config.CLIENT_URL}#${token}

` }) return true @@ -130,11 +110,6 @@ class UserService { } } - /** - * Set new password - * @param urlToken - * @param password - */ async resetPassword(urlToken: string, password: string): Promise { try { const tokenData = await PasswordResetTokenRepository.getByToken(urlToken) @@ -143,16 +118,19 @@ class UserService { } const hashedPassword = await bcrypt.hash(password, 10) - await prisma.user.update({ - where: { id: tokenData.userId }, - data: { - password: hashedPassword - } - }) + + // Update user password using MikroORM + const orm = await Database.getInstance() + 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 - const passwordResetTokenService = new PasswordResetTokenService() - await passwordResetTokenService.delete(urlToken) + await em.removeAndFlush(tokenData) return true } catch (error: any) { diff --git a/src/utilities/database.ts b/src/utilities/database.ts index 53d3a33..8746de5 100644 --- a/src/utilities/database.ts +++ b/src/utilities/database.ts @@ -1,5 +1,6 @@ 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' /** @@ -26,6 +27,48 @@ export class Database { return Database.instance } + public static async save(entity: T): Promise { + 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(entity: T): Promise { + 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() { if (Database.instance) { await Database.instance.close() diff --git a/src/utilities/enums.ts b/src/utilities/enums.ts index 870b440..b738954 100644 --- a/src/utilities/enums.ts +++ b/src/utilities/enums.ts @@ -38,3 +38,10 @@ export enum CharacterEquipmentSlotType { NECK = 'NECK', RING = 'RING' } + +export enum ZoneEventTileType { + BLOCK = 'BLOCK', + TELEPORT = 'TELEPORT', + NPC = 'NPC', + ITEM = 'ITEM' +}