diff --git a/client/src/components/lobby/LobbyChat.vue b/client/src/components/lobby/LobbyChat.vue index bacfd265f..2d32547b5 100644 --- a/client/src/components/lobby/LobbyChat.vue +++ b/client/src/components/lobby/LobbyChat.vue @@ -6,7 +6,7 @@ :key="msg.username + msg.dateCreated" class="mt-2 backdrop rounded p-1" > -

+

{{ msg.username }}: {{ msg.message }}

diff --git a/server/src/entity/LobbyChatMessage.ts b/server/src/entity/LobbyChatMessage.ts new file mode 100644 index 000000000..8a14afa3f --- /dev/null +++ b/server/src/entity/LobbyChatMessage.ts @@ -0,0 +1,27 @@ +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { GameType } from "@port-of-mars/shared/types" +import { User } from "./User"; + +@Entity() +export class LobbyChatMessage { + @PrimaryGeneratedColumn() + id!: number; + + @Column() + dateCreated!: Date; + + @ManyToOne(type => User) + user!: User; + + @Column() + userId!: number; + + @Column() + message!: string; + + @Column() + roomId!: string; + + @Column() + lobbyType!: GameType; +} diff --git a/server/src/migration/1699482607231-AddLobbyChatMessage.ts b/server/src/migration/1699482607231-AddLobbyChatMessage.ts new file mode 100644 index 000000000..e7cb13425 --- /dev/null +++ b/server/src/migration/1699482607231-AddLobbyChatMessage.ts @@ -0,0 +1,16 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class AddLobbyChatMessage1699482607231 implements MigrationInterface { + name = 'AddLobbyChatMessage1699482607231' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "lobby_chat_message" ("id" SERIAL NOT NULL, "dateCreated" TIMESTAMP NOT NULL, "userId" integer NOT NULL, "message" character varying NOT NULL, "roomId" character varying NOT NULL, "lobbyType" character varying NOT NULL, CONSTRAINT "PK_4640a139c74b0d188cc5efdda33" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "lobby_chat_message" ADD CONSTRAINT "FK_96a0bc5d48c597dbd5ea6400feb" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "lobby_chat_message" DROP CONSTRAINT "FK_96a0bc5d48c597dbd5ea6400feb"`); + await queryRunner.query(`DROP TABLE "lobby_chat_message"`); + } + +} diff --git a/server/src/rooms/lobby/common/index.ts b/server/src/rooms/lobby/common/index.ts index c94a8e6e8..434b5d612 100644 --- a/server/src/rooms/lobby/common/index.ts +++ b/server/src/rooms/lobby/common/index.ts @@ -51,6 +51,12 @@ export abstract class LobbyRoom extends Ro onDispose() { logger.trace(`Disposing of ${this.roomName} ${this.roomId}, no connected clients`); + logger.debug("Saving all lobby chat messages"); + const chatMessages = [...this.state.chat] + logger.debug("lobby chat messages %s", chatMessages); + if (chatMessages) { + getServices().tournament.saveLobbyChatMessages(this.roomId, "tournament", chatMessages); + } } async getMaxConnections() { @@ -152,7 +158,7 @@ export abstract class LobbyRoom extends Ro logger.trace(`client ${client.auth.username} attempted to send a chat message while muted`); return; } - this.state.addChatMessage(client.auth.username, message.value); + this.state.addChatMessage(client.auth, message.value); }); this.onMessage("accept-invitation", (client: Client, message: AcceptInvitation) => { this.onAcceptInvitation(client, message); diff --git a/server/src/rooms/lobby/common/state.ts b/server/src/rooms/lobby/common/state.ts index 34a113c6d..cf68c6552 100644 --- a/server/src/rooms/lobby/common/state.ts +++ b/server/src/rooms/lobby/common/state.ts @@ -1,6 +1,7 @@ import { Schema, ArraySchema, type } from "@colyseus/schema"; import { Client } from "colyseus"; import { LobbyChatMessageData } from "@port-of-mars/shared/types"; +import { User } from "@port-of-mars/server/entity"; /** * Connected client with additional metadata @@ -30,11 +31,13 @@ export class LobbyClient extends Schema { export class LobbyChatMessage extends Schema implements LobbyChatMessageData { constructor(msg: LobbyChatMessageData) { super(); + this.userId = msg.userId; this.username = msg.username; this.message = msg.message; this.dateCreated = msg.dateCreated; } + @type("number") userId: number; @type("string") username: string; @type("string") message: string; @type("number") dateCreated: number; @@ -90,9 +93,10 @@ export abstract class LobbyRoomState extends Schema { } } - addChatMessage(username: string, message: string) { + addChatMessage(user: User, message: string) { const data = { - username, + userId: user.id, + username: user.username, message, dateCreated: new Date().getTime(), }; diff --git a/server/src/services/tournament.ts b/server/src/services/tournament.ts index 88b0e3c4d..c6375d62f 100644 --- a/server/src/services/tournament.ts +++ b/server/src/services/tournament.ts @@ -1,3 +1,4 @@ +import { MoreThanOrEqual, Not, SelectQueryBuilder } from "typeorm"; import { User, Player, @@ -7,12 +8,13 @@ import { Treatment, Game, } from "@port-of-mars/server/entity"; -import { MoreThanOrEqual, Not, SelectQueryBuilder } from "typeorm"; import { getServices } from "@port-of-mars/server/services"; import { BaseService } from "@port-of-mars/server/services/db"; +import { LobbyChatMessage } from "@port-of-mars/server/entity/LobbyChatMessage"; import { TournamentRoundDate } from "@port-of-mars/server/entity/TournamentRoundDate"; import { GameType, + LobbyChatMessageData, MarsEventOverride, TournamentRoundInviteStatus, TournamentStatus, @@ -418,4 +420,21 @@ export class TournamentService extends BaseService { const offset = await getServices().settings.tournamentLobbyOpenAfterOffset(); return offset * 60 * 1000; } + + /** + * FIXME: might move this into a separate service at some point + * @param roomId + * @param messages + */ + async saveLobbyChatMessages(roomId: string, roomType: GameType, messages: Array) { + const repository = this.em.getRepository(LobbyChatMessage); + const lobbyChatMessages = messages.map(message => repository.create({ + roomId, + lobbyType: roomType, + message: message.message, + dateCreated: new Date(message.dateCreated), + userId: message.userId + })); + await repository.save(lobbyChatMessages); + } } diff --git a/shared/src/types.ts b/shared/src/types.ts index ff4c7d1a9..cf7d3a27a 100644 --- a/shared/src/types.ts +++ b/shared/src/types.ts @@ -68,6 +68,7 @@ export type GameType = "freeplay" | "tournament"; export type LobbyType = GameType; export interface LobbyChatMessageData { + userId: number; username: string; message: string; dateCreated: number;