diff --git a/.env b/.env index 32de33d..7591774 100644 --- a/.env +++ b/.env @@ -24,12 +24,14 @@ STORE_STATE_PORT=27017 STORE_STATE_USERNAME=root STORE_STATE_PASSWORD=example STORE_STATE_DB=hangman-position -MONGO_PROJECTIONS_HOSTNAME=127.0.0.1 -MONGO_PROJECTIONS_PORT=27017 -MONGO_PROJECTIONS_CREDENTIALS_USERNAME=root -MONGO_PROJECTIONS_CREDENTIALS_PASSWORD=example -MONGO_PROJECTIONS_DB_TYPE=mongodb -MONGO_PROJECTIONS_DATABASE=hangman-projections-mongo +MONGO_HOST=hangman-projections +MONGO_ROOT_USERNAME=admin +MONGO_ROOT_PASSWORD=admin +MONGO_DB=hangman-projections +MONGO_USER=user +MONGO_PASSWORD=userpwd +MONGO_HOST_PORT=27016 +MONGO_CONTAINER_PORT=27016 GQL_PLAYGROUND=enabled JWT_SECRET=test EXPIRES_IN=1h diff --git a/docker-compose.yaml b/docker-compose.yaml index 2960738..7fa7df4 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,21 +3,19 @@ version: '3.4' services: hangman-projections: container_name: hangman-projections - image: mysql - command: --default-authentication-plugin=mysql_native_password + image: mongo + command: mongod --port ${MONGO_CONTAINER_PORT} restart: always environment: - MYSQL_ROOT_PASSWORD: ${PROJECTIONS_CREDENTIALS_PASSWORD} - MYSQL_DATABASE: ${PROJECTIONS_DATABASE} - MYSQL_TCP_PORT: ${PROJECTIONS_PORT} - networks: - - backend + MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USERNAME} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD} + MONGO_INITDB_DATABASE: ${MONGO_DB} ports: - - ${PROJECTIONS_PORT}:${PROJECTIONS_PORT} + - ${MONGO_HOST_PORT}:${MONGO_CONTAINER_PORT} volumes: - - type: volume - source: projections-volume-data - target: /var/lib/projections + - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro + networks: + - backend hangman-eventstore: container_name: hangman-eventstore @@ -94,7 +92,7 @@ services: restart: always deploy: mode: replicated - replicas: ${CONTAINER_SCALE} + # replicas: ${CONTAINER_SCALE} # above is ignored by dc up and dc run (only when deploy to swarm with docker stack deploy) depends_on: - hangman-projections diff --git a/mongo-init.js b/mongo-init.js new file mode 100644 index 0000000..7ef4902 --- /dev/null +++ b/mongo-init.js @@ -0,0 +1,10 @@ +db.createUser({ + user: 'user', + pwd: 'userpwd', + roles: [ + { + role: 'readWrite', + db: 'hangman-projections', + }, + ], +}); diff --git a/package-lock.json b/package-lock.json index 4e4bca1..e9ae2fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "class-validator": "^0.13.2", "dotenv": "^16.0.2", "graphql": "^16.6.0", + "mongodb": "^3.7.4", "mongoose": "^6.6.1", "mysql2": "^2.3.3", "node-nats-streaming": "^0.3.2", @@ -9290,11 +9291,9 @@ } }, "node_modules/mongodb": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz", - "integrity": "sha512-Psm+g3/wHXhjBEktkxXsFMZvd3nemI0r3IPsE0bU+4//PnvNWKkzhZcEsbPcYiWqe8XqXJJEg4Tgtr7Raw67Yw==", - "optional": true, - "peer": true, + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz", + "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==", "dependencies": { "bl": "^2.2.1", "bson": "^1.1.4", @@ -9342,8 +9341,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "optional": true, - "peer": true, "dependencies": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -9353,8 +9350,6 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", - "optional": true, - "peer": true, "engines": { "node": ">=0.6.19" } @@ -9363,8 +9358,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", - "optional": true, - "peer": true, "engines": { "node": ">=0.10" } @@ -12470,8 +12463,6 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", - "optional": true, - "peer": true, "dependencies": { "require-at": "^1.0.6" }, @@ -13019,9 +13010,9 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "engines": { "node": ">=6" } @@ -13234,8 +13225,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", - "optional": true, - "peer": true, "engines": { "node": ">=4" } @@ -22431,11 +22420,9 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "mongodb": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz", - "integrity": "sha512-Psm+g3/wHXhjBEktkxXsFMZvd3nemI0r3IPsE0bU+4//PnvNWKkzhZcEsbPcYiWqe8XqXJJEg4Tgtr7Raw67Yw==", - "optional": true, - "peer": true, + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz", + "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==", "requires": { "bl": "^2.2.1", "bson": "^1.1.4", @@ -22449,8 +22436,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "optional": true, - "peer": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -22459,16 +22444,12 @@ "bson": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", - "optional": true, - "peer": true + "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" }, "denque": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", - "optional": true, - "peer": true + "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" } } }, @@ -24684,8 +24665,6 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", - "optional": true, - "peer": true, "requires": { "require-at": "^1.0.6" } @@ -25094,9 +25073,9 @@ } }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "qs": { "version": "6.11.0", @@ -25249,9 +25228,7 @@ "require-at": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", - "optional": true, - "peer": true + "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==" }, "require-directory": { "version": "2.1.1", diff --git a/package.json b/package.json index b4c9a9d..76accb2 100644 --- a/package.json +++ b/package.json @@ -9,15 +9,10 @@ "prebuild": "rimraf dist", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", - "prestart": "npm run migration:run", "start": "nodemon --config nodemon.json", "start:prod": "node dist/src/main", "start:debug": "nest start --debug --watch", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", - "migration:generate": "npm run typeorm -- -d ./src/datasources/MysqlDatasource.ts migration:generate", - "typeorm:generate-migration": "npm run typeorm -- -d ./src/datasources/MysqlDatasource.ts migration:generate ./src/migrations/", - "migration:run": "npm run typeorm migration:run -- -d ./src/datasources/MysqlDatasource.ts", - "migration:revert": "npm run typeorm migration:revert -- -d ./src/datasources/MysqlDatasource.ts", "typeorm": "ts-node ./node_modules/typeorm/cli", "reconstruct-view-db": "ts-node -r tsconfig-paths/register src/scripts/reconstruct-view-db.ts", "test": "jest", @@ -48,6 +43,7 @@ "class-validator": "^0.13.2", "dotenv": "^16.0.2", "graphql": "^16.6.0", + "mongodb": "^3.7.4", "mongoose": "^6.6.1", "mysql2": "^2.3.3", "node-nats-streaming": "^0.3.2", diff --git a/src/domains/Game/Game.aggregate.ts b/src/domains/Game/Game.aggregate.ts index 7343739..e9356e8 100644 --- a/src/domains/Game/Game.aggregate.ts +++ b/src/domains/Game/Game.aggregate.ts @@ -101,6 +101,8 @@ export class Game extends AggregateRoot { } async onNewGameStartedEvent(event: NewGameStartedEvent) { + this.logger.log('onNewGameStartedEvent'); + this.wordToGuess = Word.createReplay(event.wordToGuess); this.maxGuesses = MaxGuesses.createReplay(event.maxGuesses); this.lettersGuessed = LettersGuessed.createReplay([]); diff --git a/src/domains/Game/Updaters/LetterGuessed.updater.ts b/src/domains/Game/Updaters/LetterGuessed.updater.ts index 255722e..72e60c0 100644 --- a/src/domains/Game/Updaters/LetterGuessed.updater.ts +++ b/src/domains/Game/Updaters/LetterGuessed.updater.ts @@ -31,7 +31,7 @@ export class LetterGuessedUpdater implements IViewUpdater { lettersGuessed: game.lettersGuessed.value.map( (letter) => letter.value, ), - dateModified: event.dateModified, + dateModified: new Date(event.dateModified), }, ); } catch (error) { diff --git a/src/domains/Game/Updaters/NewGameStarted.updater.ts b/src/domains/Game/Updaters/NewGameStarted.updater.ts index 240ee77..4d8410c 100644 --- a/src/domains/Game/Updaters/NewGameStarted.updater.ts +++ b/src/domains/Game/Updaters/NewGameStarted.updater.ts @@ -32,8 +32,8 @@ export class NewGameStartedUpdater playerId: event.playerId, wordToGuess: event.wordToGuess, playerName: player.fullName.value, - dateCreated: event.dateCreated, - dateModified: event.dateModified, + dateCreated: new Date(event.dateCreated), + dateModified: new Date(event.dateModified), lettersGuessed: [], maxGuesses: event.maxGuesses, }); diff --git a/src/domains/User/Events/FullNameChanged.event.ts b/src/domains/User/Events/FullNameChanged.event.ts index d4dae83..30ee6cd 100644 --- a/src/domains/User/Events/FullNameChanged.event.ts +++ b/src/domains/User/Events/FullNameChanged.event.ts @@ -4,7 +4,11 @@ export class FullNameChangedEvent extends StorableEvent { public readonly eventVersion = 1; aggregateName = 'user'; - constructor(public readonly id: string, public readonly newFullName: string) { + constructor( + public readonly id: string, + public readonly newFullName: string, + public readonly dateModified: Date, + ) { super(); } } diff --git a/src/domains/User/Events/UserEventSerializers.ts b/src/domains/User/Events/UserEventSerializers.ts index 3d2911a..4bde088 100644 --- a/src/domains/User/Events/UserEventSerializers.ts +++ b/src/domains/User/Events/UserEventSerializers.ts @@ -39,7 +39,7 @@ export const UserEventSerializers = { UserLoggedOutEvent: ({ id, dateLoggedOut }) => { return new UserLoggedOutEvent(id, dateLoggedOut); }, - FullNameChangedEvent: ({ id, newFullName }) => { - return new FullNameChangedEvent(id, newFullName); + FullNameChangedEvent: ({ id, newFullName, dateModified }) => { + return new FullNameChangedEvent(id, newFullName, dateModified); }, }; diff --git a/src/domains/User/Updaters/FullNameChanged.updater.ts b/src/domains/User/Updaters/FullNameChanged.updater.ts index 2ee6aa3..8c3bafe 100644 --- a/src/domains/User/Updaters/FullNameChanged.updater.ts +++ b/src/domains/User/Updaters/FullNameChanged.updater.ts @@ -23,12 +23,18 @@ export class FullNameChangedUpdater async handle(event: FullNameChangedEvent) { try { this.logger.log(JSON.stringify(event)); + + const dateModified = event.dateModified + ? new Date(event.dateModified) + : null; + await this.userProjectionRepository.update( { userId: event.id, }, { fullName: event.newFullName, + dateModified, }, ); } catch (err) { diff --git a/src/domains/User/Updaters/UserCreated.updater.ts b/src/domains/User/Updaters/UserCreated.updater.ts index b898129..0b88fd9 100644 --- a/src/domains/User/Updaters/UserCreated.updater.ts +++ b/src/domains/User/Updaters/UserCreated.updater.ts @@ -26,6 +26,8 @@ export class UserCreatedUpdater implements IViewUpdater { userId: event.id, username: event.userName, password: event.password, + dateCreated: new Date(event.dateCreated), + dateModified: new Date(event.dateModified), }); await user.save(); } catch (err) { diff --git a/src/domains/User/Updaters/UserLoggedIn.updater.ts b/src/domains/User/Updaters/UserLoggedIn.updater.ts index d1b5a92..a0163df 100644 --- a/src/domains/User/Updaters/UserLoggedIn.updater.ts +++ b/src/domains/User/Updaters/UserLoggedIn.updater.ts @@ -26,8 +26,8 @@ export class UserLoggedInUpdater implements IViewUpdater { }, { numberLogins: event.numberLogins, - lastLoggedIn: event.dateLoggedIn, - dateModified: event.dateModified, + lastLoggedIn: new Date(event.dateLoggedIn), + dateModified: new Date(event.dateModified), }, ); } catch (err) { diff --git a/src/domains/User/User.aggregate.ts b/src/domains/User/User.aggregate.ts index e56cce9..eadf95c 100644 --- a/src/domains/User/User.aggregate.ts +++ b/src/domains/User/User.aggregate.ts @@ -79,7 +79,7 @@ export class User extends AggregateRoot { this.logger.debug(`newFullName: ${newFullName}`); const fullName = await FullName.create(newFullName); - this.apply(new FullNameChangedEvent(this.id, fullName.value)); + this.apply(new FullNameChangedEvent(this.id, fullName.value, new Date())); } catch (err) { throw new Error(err); } diff --git a/src/infrastructure/dto/Api.dto.ts b/src/infrastructure/dto/Api.dto.ts index cd599e1..bd1e422 100644 --- a/src/infrastructure/dto/Api.dto.ts +++ b/src/infrastructure/dto/Api.dto.ts @@ -17,13 +17,19 @@ export class GameResponse { status: number; @Field((type) => String, { nullable: true }) - loggedInUsername: string; + playerFullName: string; @Field((type) => [String], { nullable: true }) lettersGuessed: string[]; @Field((type) => String, { nullable: false }) wordToGuess: string; + + @Field((type) => Date, { nullable: false }) + dateCreated: Date; + + @Field((type) => Date, { nullable: false }) + dateModified: Date; } @ObjectType() @@ -31,8 +37,32 @@ export class AllGamesResponse { @Field((type) => Number) count: number; - @Field((type) => [GameProjection], { nullable: true }) - games: GameProjection[]; + @Field((type) => [GameResponse], { nullable: true }) + games: GameResponse[]; +} + +@ObjectType() +export class UserResponse { + @Field((type) => String) + userId?: string; + + @Field((type) => String) + username?: string; + + @Field((type) => String) + fullName?: string; + + @Field((type) => Number, { nullable: true }) + numberLogins?: number; + + @Field((type) => Date, { nullable: true }) + lastLoggedIn?: Date; + + @Field((type) => Date) + dateCreated?: Date; + + @Field((type) => Date) + dateModified?: Date; } @ObjectType() @@ -40,6 +70,6 @@ export class AllUsersResponse { @Field((type) => Number) count: number; - @Field((type) => [UserProjection], { nullable: true }) + @Field((type) => [UserResponse], { nullable: true }) users: UserProjection[]; } diff --git a/src/infrastructure/dto/User.dto.ts b/src/infrastructure/dto/User.dto.ts index 7f1bbe2..6f8901a 100644 --- a/src/infrastructure/dto/User.dto.ts +++ b/src/infrastructure/dto/User.dto.ts @@ -16,17 +16,17 @@ export class UserDto { fullName: string; } -@ObjectType() -export class UserResponse { - @ApiProperty() - @Field((type) => String) - userId: string; +// @ObjectType() +// export class UserResponse { +// @ApiProperty() +// @Field((type) => String) +// userId: string; - @ApiProperty() - @Field((type) => String) - username: string; +// @ApiProperty() +// @Field((type) => String) +// username: string; - @ApiProperty() - @Field((type) => String) - message: string; -} +// @ApiProperty() +// @Field((type) => String) +// message: string; +// } diff --git a/src/infrastructure/modules/app.module.ts b/src/infrastructure/modules/app.module.ts index ed7e2c5..ec06e31 100644 --- a/src/infrastructure/modules/app.module.ts +++ b/src/infrastructure/modules/app.module.ts @@ -2,7 +2,6 @@ import { redisStore } from 'cache-manager-redis-yet'; import { CacheModule, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; -import { options } from '../../../ormconfig'; import { GraphQLModule } from '@nestjs/graphql'; import { EventStoreModule } from '@peterdijk/nestjs-eventstoredb'; @@ -15,7 +14,6 @@ import { AuthModule } from './auth.module'; import { AppResolver } from '../resolvers/app.resolver'; import { MongoPositionStore } from '../../mongo/mongo-eventstore-adapter'; -export const mongoDbUri = `${config.STORE_STATE_SETTINGS.type}://${config.STORE_STATE_SETTINGS.credentials.username}:${config.STORE_STATE_SETTINGS.credentials.password}@${config.STORE_STATE_SETTINGS.hostname}:${config.STORE_STATE_SETTINGS.port}`; @Module({ imports: [ CacheModule.registerAsync({ @@ -44,8 +42,17 @@ export const mongoDbUri = `${config.STORE_STATE_SETTINGS.type}://${config.STORE_ insecure: true, lastPositionStorageFactory: MongoPositionStore, }), - TypeOrmModule.forRootAsync({ - useFactory: async () => options as any, + TypeOrmModule.forRoot({ + type: 'mongodb', + host: process.env.MONGO_HOST, + port: parseInt(process.env.MONGO_CONTAINER_PORT), + username: process.env.MONGO_USER, + password: process.env.MONGO_PASSWORD, + database: process.env.MONGO_DB, + useUnifiedTopology: true, + synchronize: true, + logger: 'debug', + autoLoadEntities: true, }), GamesModule, UserModule, diff --git a/src/infrastructure/read-models/game.entity.ts b/src/infrastructure/read-models/game.entity.ts index ec0a8c2..bb4e3b7 100644 --- a/src/infrastructure/read-models/game.entity.ts +++ b/src/infrastructure/read-models/game.entity.ts @@ -1,64 +1,48 @@ -import { Entity, BaseEntity, Column, PrimaryColumn } from 'typeorm'; +import { + Entity, + BaseEntity, + Column, + PrimaryColumn, + ObjectIdColumn, + ObjectID, +} from 'typeorm'; import { Field, ObjectType } from '@nestjs/graphql'; @ObjectType() @Entity() export class Game extends BaseEntity { + @ObjectIdColumn() + id: ObjectID; + @Field() - @PrimaryColumn({ - type: 'varchar', - nullable: false, - }) + @Column() gameId: string; @Field() - @Column({ - type: 'varchar', - nullable: false, - }) + @Column() playerId: string; @Field() - @Column({ - type: 'varchar', - nullable: false, - }) + @Column() playerName: string; @Field() - @Column({ - type: 'varchar', - nullable: false, - }) + @Column() wordToGuess: string; @Field() - @Column({ - type: 'varchar', - nullable: false, - }) + @Column() maxGuesses: number; @Field((type) => [String], { nullable: true }) - @Column({ - type: 'json', - nullable: true, - }) + @Column() lettersGuessed: string[]; @Field() - @Column({ - type: 'timestamp', - nullable: false, - default: () => 'CURRENT_TIMESTAMP', - }) + @Column() dateCreated: Date; @Field() - @Column({ - type: 'timestamp', - nullable: false, - default: () => 'CURRENT_TIMESTAMP', - }) + @Column() dateModified: Date; } diff --git a/src/infrastructure/read-models/user.entity.ts b/src/infrastructure/read-models/user.entity.ts index 9f36e44..dd6a398 100644 --- a/src/infrastructure/read-models/user.entity.ts +++ b/src/infrastructure/read-models/user.entity.ts @@ -1,67 +1,49 @@ -import { Entity, BaseEntity, Column, PrimaryColumn } from 'typeorm'; +import { + Entity, + BaseEntity, + Column, + PrimaryColumn, + ObjectIdColumn, + ObjectID, +} from 'typeorm'; import { Field, ObjectType } from '@nestjs/graphql'; import { Exclude } from 'class-transformer'; @ObjectType() @Entity() export class User extends BaseEntity { + @ObjectIdColumn() + id: ObjectID; + @Field() - @PrimaryColumn({ - type: 'varchar', - nullable: false, - }) + @Column() userId: string; @Field() - @Column({ - type: 'varchar', - nullable: false, - }) + @Column() username: string; - @Column({ - type: 'varchar', - nullable: false, - }) + @Column() @Exclude({ toPlainOnly: true }) password: string; @Field() - @Column({ - type: 'varchar', - nullable: false, - }) + @Column() fullName: string; @Field() - @Column({ - type: 'int', - nullable: false, - default: 0, - }) + @Column({ default: 0 }) numberLogins: number; @Field() - @Column({ - type: 'timestamp', - nullable: false, - default: () => 'CURRENT_TIMESTAMP', - }) + @Column() lastLoggedIn: Date; @Field() - @Column({ - type: 'timestamp', - nullable: false, - default: () => 'CURRENT_TIMESTAMP', - }) + @Column() dateCreated: Date; @Field() - @Column({ - type: 'timestamp', - nullable: false, - default: () => 'CURRENT_TIMESTAMP', - }) + @Column() dateModified: Date; } diff --git a/src/infrastructure/resolvers/game.resolver.ts b/src/infrastructure/resolvers/game.resolver.ts index 439523b..083ab22 100644 --- a/src/infrastructure/resolvers/game.resolver.ts +++ b/src/infrastructure/resolvers/game.resolver.ts @@ -20,8 +20,8 @@ export class GamesResolver { } @Query((returns) => AllGamesResponse) - async getAllGames(): Promise<{ count: number; games: GameProjection[] }> { - return await this.gameService.getAllGames(); + async getAllGames(): Promise<{ count: number; games: GameResponse[] }> { + return this.gameService.getAllGames(); } @UseGuards(GqlAuthGuard) @@ -30,7 +30,7 @@ export class GamesResolver { @Args('input') gameDto: GameDto, @CurrentUser() user: User, ): Promise { - return await this.gameService.startNewGame(gameDto, user); + return this.gameService.startNewGame(gameDto, user); } @UseGuards(GqlAuthGuard) @@ -39,10 +39,6 @@ export class GamesResolver { @Args('input') guesDto: GuessDto, @CurrentUser() user: User, ): Promise { - return await this.gameService.makeGuess( - guesDto.gameId, - guesDto.letter, - user, - ); + return this.gameService.makeGuess(guesDto.gameId, guesDto.letter, user); } } diff --git a/src/infrastructure/resolvers/user.resolver.ts b/src/infrastructure/resolvers/user.resolver.ts index 507a516..f2dee91 100644 --- a/src/infrastructure/resolvers/user.resolver.ts +++ b/src/infrastructure/resolvers/user.resolver.ts @@ -1,9 +1,9 @@ import { Inject, UseGuards } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; import { UserService } from '../services/user.service'; -import { UserDto, UserResponse } from '../dto/User.dto'; +import { UserDto } from '../dto/User.dto'; import { User } from '../../domains/User/User.aggregate'; -import { AllUsersResponse } from '../dto/Api.dto'; +import { AllUsersResponse, UserResponse } from '../dto/Api.dto'; import { User as UserProjection } from '../read-models/user.entity'; import { CurrentUser, GqlAuthGuard } from '../modules/graphql.guard'; diff --git a/src/infrastructure/services/games.service.ts b/src/infrastructure/services/games.service.ts index 65ce256..94c97d1 100644 --- a/src/infrastructure/services/games.service.ts +++ b/src/infrastructure/services/games.service.ts @@ -9,6 +9,7 @@ import { v4 as uuidv4 } from 'uuid'; import { GuessLetterCommand } from '../../domains/Game/Commands/GuessLetter.command'; import { User } from '../../domains/User/User.aggregate'; import { Game } from '../../domains/Game/Game.aggregate'; +import { GameResponse } from '../dto/Api.dto'; @Injectable() export class GamesService { @@ -31,9 +32,11 @@ export class GamesService { message: 'success', status: 201, gameId, - loggedInUsername: user.userName.value, + playerFullName: 'game.player.userName.value', wordToGuess: game.wordToGuess.value, lettersGuessed: game.lettersGuessed.value.map((l) => l.value), + dateCreated: game.dateCreated, + dateModified: game.dateModified, }; } catch (err) { this.logger.log(err); @@ -42,13 +45,25 @@ export class GamesService { } } - async getAllGames(): Promise<{ count: number; games: GameProjection[] }> { + async getAllGames() { const games = await this.gamesProjectionRepository.find({ order: { dateModified: 'DESC' }, }); + + // this.logger.debug(games); return { count: games.length, - games, + games: games.map( + (game) => + ({ + gameId: game.id, + playerFullName: game.playerName, + lettersGuessed: game.lettersGuessed, + wordToGuess: game.wordToGuess, + dateCreated: game.dateCreated, + dateModified: game.dateModified, + } as unknown as GameResponse), + ), }; } @@ -65,9 +80,9 @@ export class GamesService { letter, wordToGuess: game.wordToGuess.value, lettersGuessed: game.lettersGuessed.value.map((l) => l.value), - loggedInUsername: loggedInUser.userName.value, - gameModified: game.dateModified, - gameCreated: game.dateCreated, + playerFullName: 'game.player.userName.value', + dateModified: game.dateModified, + dateCreated: game.dateCreated, }; } catch (err) { this.logger.error(err.name, err.stack); diff --git a/src/infrastructure/services/user.service.ts b/src/infrastructure/services/user.service.ts index 5680ede..7f6ac16 100644 --- a/src/infrastructure/services/user.service.ts +++ b/src/infrastructure/services/user.service.ts @@ -62,6 +62,7 @@ export class UserService { const users = await this.usersProjectionRepository.find({ order: { dateModified: 'DESC' }, }); + return { count: users.length, users,