feat : add testing
This commit is contained in:
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"orta.vscode-jest",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"vue.volar"
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+6
-1
@@ -4,5 +4,10 @@
|
|||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||||
},
|
},
|
||||||
"javascript.preferences.quoteStyle": "single",
|
"javascript.preferences.quoteStyle": "single",
|
||||||
"typescript.preferences.quoteStyle": "single"
|
"typescript.preferences.quoteStyle": "single",
|
||||||
|
"eslint.format.enable": true,
|
||||||
|
"eslint.useFlatConfig": true,
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "vscode.json-language-features"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default [
|
|||||||
ignores: ['**/.eslintrc.js'],
|
ignores: ['**/.eslintrc.js'],
|
||||||
},
|
},
|
||||||
...compat.extends(
|
...compat.extends(
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended-type-checked',
|
||||||
'plugin:prettier/recommended',
|
'plugin:prettier/recommended',
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
@@ -48,7 +48,8 @@ export default [
|
|||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'require-await': 'off',
|
||||||
|
'@typescript-eslint/require-await': 'error',
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
'warn',
|
'warn',
|
||||||
{
|
{
|
||||||
Generated
+1513
-39
File diff suppressed because it is too large
Load Diff
+7
-2
@@ -65,12 +65,14 @@
|
|||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
|
"sqlite3": "^5.1.7",
|
||||||
"supertest": "^6.3.4",
|
"supertest": "^6.3.4",
|
||||||
"ts-jest": "^29.1.2",
|
"ts-jest": "^29.1.2",
|
||||||
"ts-loader": "^9.5.1",
|
"ts-loader": "^9.5.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3",
|
||||||
|
"typescript-eslint": "^8.18.2"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
@@ -78,7 +80,10 @@
|
|||||||
"json",
|
"json",
|
||||||
"ts"
|
"ts"
|
||||||
],
|
],
|
||||||
"rootDir": "src",
|
"rootDir": "./",
|
||||||
|
"modulePaths": [
|
||||||
|
"<rootDir>"
|
||||||
|
],
|
||||||
"testRegex": ".*\\.spec\\.ts$",
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { AppController } from './app.controller';
|
|
||||||
import { AppService } from './app.service';
|
|
||||||
|
|
||||||
describe('AppController', () => {
|
|
||||||
let app: TestingModule;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
app = await Test.createTestingModule({
|
|
||||||
controllers: [AppController],
|
|
||||||
providers: [AppService],
|
|
||||||
}).compile();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getHello', () => {
|
|
||||||
it('should return "Hello World!"', () => {
|
|
||||||
const appController = app.get(AppController);
|
|
||||||
expect(appController.getHello()).toBe('Hello World!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { Controller } from '@nestjs/common';
|
|
||||||
import { AppService } from './app.service';
|
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
|
||||||
|
|
||||||
@ApiTags('admin')
|
|
||||||
@Controller()
|
|
||||||
export class AppController {
|
|
||||||
constructor(private readonly appService: AppService) {}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
||||||
|
|
||||||
export class Migration1735396726613 implements MigrationInterface {
|
|
||||||
name = 'Migration1735396726613';
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(
|
|
||||||
`CREATE TABLE "user" ("userId" character varying NOT NULL, "avatar" character varying NOT NULL, "username" character varying NOT NULL, CONSTRAINT "PK_d72ea127f30e21753c9e229891e" PRIMARY KEY ("userId"))`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`CREATE TABLE "topic" ("id" SERIAL NOT NULL, CONSTRAINT "PK_33aa4ecb4e4f20aa0157ea7ef61" PRIMARY KEY ("id"))`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`CREATE TABLE "word" ("id" SERIAL NOT NULL, "value" character varying NOT NULL, "ownerUserId" character varying, CONSTRAINT "PK_ad026d65e30f80b7056ca31f666" PRIMARY KEY ("id"))`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`CREATE TABLE "concept" ("id" SERIAL NOT NULL, "value" character varying NOT NULL, CONSTRAINT "UQ_3275d953a4dc4dadeef5a8aeed4" UNIQUE ("value"), CONSTRAINT "PK_83c1330866b9866ac2d0ed2b36b" PRIMARY KEY ("id"))`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`CREATE TABLE "concept_in_turn" ("id" SERIAL NOT NULL, "order" integer NOT NULL DEFAULT '0', "subconcept" integer NOT NULL DEFAULT '0', "markers" integer NOT NULL DEFAULT '1', "conceptId" integer, "gameId" integer, CONSTRAINT "PK_c44692f972ab31a6245c8599b4b" PRIMARY KEY ("id"))`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`CREATE TABLE "game" ("id" SERIAL NOT NULL, "seed" character varying NOT NULL, "currentWord" character varying NOT NULL, "started" boolean NOT NULL, "ownerUserId" character varying, CONSTRAINT "PK_352a30652cd352f552fef73dec5" PRIMARY KEY ("id"))`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`CREATE TABLE "player" ("id" SERIAL NOT NULL, "score" integer NOT NULL, "userUserId" character varying, "gameId" integer, CONSTRAINT "PK_65edadc946a7faf4b638d5e8885" PRIMARY KEY ("id"))`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`CREATE TABLE "word_topics_topic" ("wordId" integer NOT NULL, "topicId" integer NOT NULL, CONSTRAINT "PK_cc5d13565912abf7d0e1dcfc3a7" PRIMARY KEY ("wordId", "topicId"))`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`CREATE INDEX "IDX_66e5a60dcdb89fc3be327eb139" ON "word_topics_topic" ("wordId") `,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`CREATE INDEX "IDX_3346a237bd507b395347a64ddc" ON "word_topics_topic" ("topicId") `,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "word" ADD CONSTRAINT "FK_4472fdeaf534031039106aa04de" FOREIGN KEY ("ownerUserId") REFERENCES "user"("userId") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "concept_in_turn" ADD CONSTRAINT "FK_6dfaa966f86139a62aba3b2114a" FOREIGN KEY ("conceptId") REFERENCES "concept"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "concept_in_turn" ADD CONSTRAINT "FK_f1c8614584bd8afcac818bacfd9" FOREIGN KEY ("gameId") REFERENCES "game"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "game" ADD CONSTRAINT "FK_9b45658e1fea65880fd35c9bacc" FOREIGN KEY ("ownerUserId") REFERENCES "user"("userId") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "player" ADD CONSTRAINT "FK_2b8a672d52e840e6c4e3bf79f58" FOREIGN KEY ("userUserId") REFERENCES "user"("userId") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "player" ADD CONSTRAINT "FK_7dfdd31fcd2b5aa3b08ed15fe8a" FOREIGN KEY ("gameId") REFERENCES "game"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "word_topics_topic" ADD CONSTRAINT "FK_66e5a60dcdb89fc3be327eb139a" FOREIGN KEY ("wordId") REFERENCES "word"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "word_topics_topic" ADD CONSTRAINT "FK_3346a237bd507b395347a64ddcb" FOREIGN KEY ("topicId") REFERENCES "topic"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "word_topics_topic" DROP CONSTRAINT "FK_3346a237bd507b395347a64ddcb"`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "word_topics_topic" DROP CONSTRAINT "FK_66e5a60dcdb89fc3be327eb139a"`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "player" DROP CONSTRAINT "FK_7dfdd31fcd2b5aa3b08ed15fe8a"`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "player" DROP CONSTRAINT "FK_2b8a672d52e840e6c4e3bf79f58"`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "game" DROP CONSTRAINT "FK_9b45658e1fea65880fd35c9bacc"`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "concept_in_turn" DROP CONSTRAINT "FK_f1c8614584bd8afcac818bacfd9"`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "concept_in_turn" DROP CONSTRAINT "FK_6dfaa966f86139a62aba3b2114a"`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`ALTER TABLE "word" DROP CONSTRAINT "FK_4472fdeaf534031039106aa04de"`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`DROP INDEX "public"."IDX_3346a237bd507b395347a64ddc"`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(
|
|
||||||
`DROP INDEX "public"."IDX_66e5a60dcdb89fc3be327eb139"`,
|
|
||||||
);
|
|
||||||
await queryRunner.query(`DROP TABLE "word_topics_topic"`);
|
|
||||||
await queryRunner.query(`DROP TABLE "player"`);
|
|
||||||
await queryRunner.query(`DROP TABLE "game"`);
|
|
||||||
await queryRunner.query(`DROP TABLE "concept_in_turn"`);
|
|
||||||
await queryRunner.query(`DROP TABLE "concept"`);
|
|
||||||
await queryRunner.query(`DROP TABLE "word"`);
|
|
||||||
await queryRunner.query(`DROP TABLE "topic"`);
|
|
||||||
await queryRunner.query(`DROP TABLE "user"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class Migration1735422446844 implements MigrationInterface {
|
||||||
|
name = 'Migration1735422446844'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`CREATE TABLE "user" ("id" character varying NOT NULL, "avatar" character varying NOT NULL, "username" character varying NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE "topic" ("id" SERIAL NOT NULL, CONSTRAINT "PK_33aa4ecb4e4f20aa0157ea7ef61" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE "word" ("id" SERIAL NOT NULL, "value" character varying NOT NULL, "ownerId" character varying, CONSTRAINT "PK_ad026d65e30f80b7056ca31f666" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE "concept" ("id" SERIAL NOT NULL, "value" character varying NOT NULL, CONSTRAINT "UQ_3275d953a4dc4dadeef5a8aeed4" UNIQUE ("value"), CONSTRAINT "PK_83c1330866b9866ac2d0ed2b36b" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE "concept_in_turn" ("id" SERIAL NOT NULL, "order" integer NOT NULL DEFAULT '0', "subconcept" integer NOT NULL DEFAULT '0', "markers" integer NOT NULL DEFAULT '1', "conceptId" integer, "gameId" integer, CONSTRAINT "PK_c44692f972ab31a6245c8599b4b" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE "game" ("id" SERIAL NOT NULL, "seed" character varying NOT NULL, "currentWord" character varying, "started" boolean NOT NULL DEFAULT false, "ownerId" character varying, CONSTRAINT "PK_352a30652cd352f552fef73dec5" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE "player" ("id" SERIAL NOT NULL, "score" integer NOT NULL DEFAULT '0', "userId" character varying, "gameId" integer, CONSTRAINT "PK_65edadc946a7faf4b638d5e8885" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`CREATE TABLE "word_topics_topic" ("wordId" integer NOT NULL, "topicId" integer NOT NULL, CONSTRAINT "PK_cc5d13565912abf7d0e1dcfc3a7" PRIMARY KEY ("wordId", "topicId"))`);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_66e5a60dcdb89fc3be327eb139" ON "word_topics_topic" ("wordId") `);
|
||||||
|
await queryRunner.query(`CREATE INDEX "IDX_3346a237bd507b395347a64ddc" ON "word_topics_topic" ("topicId") `);
|
||||||
|
await queryRunner.query(`ALTER TABLE "word" ADD CONSTRAINT "FK_e0edff82d908ba54dcd44d5cec6" FOREIGN KEY ("ownerId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "concept_in_turn" ADD CONSTRAINT "FK_6dfaa966f86139a62aba3b2114a" FOREIGN KEY ("conceptId") REFERENCES "concept"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "concept_in_turn" ADD CONSTRAINT "FK_f1c8614584bd8afcac818bacfd9" FOREIGN KEY ("gameId") REFERENCES "game"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "game" ADD CONSTRAINT "FK_d05575b5a28ec6dad65c2aef301" FOREIGN KEY ("ownerId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "player" ADD CONSTRAINT "FK_7687919bf054bf262c669d3ae21" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "player" ADD CONSTRAINT "FK_7dfdd31fcd2b5aa3b08ed15fe8a" FOREIGN KEY ("gameId") REFERENCES "game"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "word_topics_topic" ADD CONSTRAINT "FK_66e5a60dcdb89fc3be327eb139a" FOREIGN KEY ("wordId") REFERENCES "word"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "word_topics_topic" ADD CONSTRAINT "FK_3346a237bd507b395347a64ddcb" FOREIGN KEY ("topicId") REFERENCES "topic"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "word_topics_topic" DROP CONSTRAINT "FK_3346a237bd507b395347a64ddcb"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "word_topics_topic" DROP CONSTRAINT "FK_66e5a60dcdb89fc3be327eb139a"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "player" DROP CONSTRAINT "FK_7dfdd31fcd2b5aa3b08ed15fe8a"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "player" DROP CONSTRAINT "FK_7687919bf054bf262c669d3ae21"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "game" DROP CONSTRAINT "FK_d05575b5a28ec6dad65c2aef301"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "concept_in_turn" DROP CONSTRAINT "FK_f1c8614584bd8afcac818bacfd9"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "concept_in_turn" DROP CONSTRAINT "FK_6dfaa966f86139a62aba3b2114a"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "word" DROP CONSTRAINT "FK_e0edff82d908ba54dcd44d5cec6"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_3346a237bd507b395347a64ddc"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "public"."IDX_66e5a60dcdb89fc3be327eb139"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "word_topics_topic"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "player"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "game"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "concept_in_turn"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "concept"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "word"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "topic"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "user"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ const AppDataSource = new DataSource({
|
|||||||
password: configService.get('PG_PASSWORD'),
|
password: configService.get('PG_PASSWORD'),
|
||||||
username: configService.get('PG_USER'),
|
username: configService.get('PG_USER'),
|
||||||
database: 'concept',
|
database: 'concept',
|
||||||
|
applicationName: 'concept',
|
||||||
synchronize: false,
|
synchronize: false,
|
||||||
entities: ['**/*.entity.ts'],
|
entities: ['**/*.entity.ts'],
|
||||||
migrations: ['src/db/migrations/*-migration.ts'],
|
migrations: ['src/db/migrations/*-migration.ts'],
|
||||||
|
|||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
import { User } from './users/entities/user.entity';
|
||||||
|
declare module 'express-session' {
|
||||||
|
interface SessionData {
|
||||||
|
user: User;
|
||||||
|
tokenData: {
|
||||||
|
access_token: string;
|
||||||
|
token_type: string;
|
||||||
|
refresh_token: string;
|
||||||
|
expires: Date;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import { Observable, fromEvent, map } from 'rxjs';
|
|||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { GameService } from '../services/games.service';
|
import { GameService } from '../services/games.service';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { SessionData } from 'express-session';
|
||||||
|
|
||||||
@ApiTags('game')
|
@ApiTags('game')
|
||||||
@Controller('games/:id')
|
@Controller('games/:id')
|
||||||
@@ -47,7 +48,7 @@ export class GamesController {
|
|||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
async start(
|
async start(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Session() session: Record<string, any>,
|
@Session() session: SessionData,
|
||||||
@Param('id', GamePipe) game: Game,
|
@Param('id', GamePipe) game: Game,
|
||||||
) {
|
) {
|
||||||
if (game.started) {
|
if (game.started) {
|
||||||
@@ -59,7 +60,7 @@ export class GamesController {
|
|||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (game.owner.userId != session.user.userId) {
|
if (game.owner.id != session.user.id) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'You can only start games you own',
|
'You can only start games you own',
|
||||||
HttpStatus.FORBIDDEN,
|
HttpStatus.FORBIDDEN,
|
||||||
@@ -72,33 +73,33 @@ export class GamesController {
|
|||||||
@Post('surrender')
|
@Post('surrender')
|
||||||
@ApiOperation({ summary: 'Leave a game that has already started' })
|
@ApiOperation({ summary: 'Leave a game that has already started' })
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
async surrender(
|
surrender(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Session() session: Record<string, any>,
|
@Session() session: SessionData,
|
||||||
@Param('id', GamePipe) game: Game,
|
@Param('id', GamePipe) game: Game,
|
||||||
) {
|
) {
|
||||||
if (!game.started) {
|
if (!game.started) {
|
||||||
throw new HttpException('Game has not started', HttpStatus.BAD_REQUEST);
|
throw new HttpException('Game has not started', HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
return await this.lobbyService.leaveGame(game, session.user);
|
return this.lobbyService.leaveGame(game, session.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('kick/:playerId')
|
@Post('kick/:playerId')
|
||||||
@ApiOperation({ summary: 'Kick a player' })
|
@ApiOperation({ summary: 'Kick a player' })
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
async kick(
|
kick(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Session() session: Record<string, any>,
|
@Session() session: SessionData,
|
||||||
@Param('id', GamePipe) game: Game,
|
@Param('id', GamePipe) game: Game,
|
||||||
@Param('playerId') playerId: string,
|
@Param('playerId') playerId: string,
|
||||||
) {
|
) {
|
||||||
if (session.user.userId != game.owner.userId) {
|
if (session.user.id != game.owner.id) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'You do do not own this game',
|
'You do do not own this game',
|
||||||
HttpStatus.FORBIDDEN,
|
HttpStatus.FORBIDDEN,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return await this.lobbyService.kickPlayer(game, session.user, playerId);
|
return this.lobbyService.kickPlayer(game, session.user, playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Sse('/subscribe')
|
@Sse('/subscribe')
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { LobbyController } from './lobby.controller';
|
|
||||||
import { LobbyService } from '../services/lobby.service';
|
|
||||||
|
|
||||||
describe('GamesController', () => {
|
|
||||||
let controller: LobbyController;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [LobbyController],
|
|
||||||
providers: [LobbyService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
controller = module.get<LobbyController>(LobbyController);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(controller).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { LobbyController } from './lobby.controller';
|
||||||
|
import { LobbyService } from '../services/lobby.service';
|
||||||
|
import { Game } from '../entities/game.entity';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Player } from '../entities/player.entity';
|
||||||
|
import { GameService } from '../services/games.service';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
import { Concept } from '../entities/concept.entity';
|
||||||
|
import { ConceptInTurn } from '../entities/concept_in_turn.entity';
|
||||||
|
|
||||||
|
describe('LobbyController', () => {
|
||||||
|
let controller: LobbyController;
|
||||||
|
let service: LobbyService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [LobbyController],
|
||||||
|
providers: [LobbyService, GameService],
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forRoot({
|
||||||
|
type: 'sqlite',
|
||||||
|
database: ':memory:',
|
||||||
|
entities: [Game, Player, User, Concept, ConceptInTurn],
|
||||||
|
synchronize: true,
|
||||||
|
}),
|
||||||
|
TypeOrmModule.forFeature([Game, Player, User, Concept, ConceptInTurn]),
|
||||||
|
EventEmitterModule.forRoot(),
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<LobbyController>(LobbyController);
|
||||||
|
service = module.get<LobbyService>(LobbyService);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -18,6 +18,7 @@ import { Game } from '../entities/game.entity';
|
|||||||
import { Observable, fromEvent, map } from 'rxjs';
|
import { Observable, fromEvent, map } from 'rxjs';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { GameResponseDTO } from '../dto/gameresponse.dto';
|
import { GameResponseDTO } from '../dto/gameresponse.dto';
|
||||||
|
import { SessionData } from 'express-session';
|
||||||
|
|
||||||
@ApiTags('lobby')
|
@ApiTags('lobby')
|
||||||
@Controller('games')
|
@Controller('games')
|
||||||
@@ -40,15 +41,16 @@ export class LobbyController {
|
|||||||
})
|
})
|
||||||
@ApiResponse({ status: 403, description: 'Forbidden.' })
|
@ApiResponse({ status: 403, description: 'Forbidden.' })
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
async create(@Session() session: Record<string, any>) {
|
async create(@Session() session: SessionData) {
|
||||||
const games = await this.lobbyService.findByOwner(session.user);
|
const games = await this.lobbyService.findByOwner(session.user);
|
||||||
if (games.length > 0) {
|
if (games) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'You can only create one game at a time',
|
'You can only create one game at a time',
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return await this.lobbyService.create(session.user);
|
const createdGame = await this.lobbyService.create(session.user);
|
||||||
|
return new GameResponseDTO(createdGame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@@ -71,10 +73,10 @@ export class LobbyController {
|
|||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@ApiOperation({ summary: 'Get game information' })
|
@ApiOperation({ summary: 'Get game information' })
|
||||||
async findOne(
|
findOne(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Param('id', GamePipe) game: Game,
|
@Param('id', GamePipe) game: Game,
|
||||||
): Promise<GameResponseDTO> {
|
): GameResponseDTO {
|
||||||
return new GameResponseDTO(game);
|
return new GameResponseDTO(game);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,10 +85,10 @@ export class LobbyController {
|
|||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
async remove(
|
async remove(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Session() session: Record<string, any>,
|
@Session() session: SessionData,
|
||||||
@Param('id', GamePipe) game: Game,
|
@Param('id', GamePipe) game: Game,
|
||||||
) {
|
) {
|
||||||
if (game.owner.userId == session.user.userId) {
|
if (game.owner.id != session.user.id) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'You can only delete games you own',
|
'You can only delete games you own',
|
||||||
HttpStatus.FORBIDDEN,
|
HttpStatus.FORBIDDEN,
|
||||||
@@ -100,14 +102,14 @@ export class LobbyController {
|
|||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
async join(
|
async join(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Session() session: Record<string, any>,
|
@Session() session: SessionData,
|
||||||
@Param('id', GamePipe) game: Game,
|
@Param('id', GamePipe) game: Game,
|
||||||
) {
|
) {
|
||||||
if (game.started) {
|
if (game.started) {
|
||||||
throw new HttpException('Game already started', HttpStatus.BAD_REQUEST);
|
throw new HttpException('Game already started', HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.players.some((p) => p.user.userId == session.user.userId)) {
|
if (game.players.some((p) => p.user.id == session.user.id)) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'You are already in this game',
|
'You are already in this game',
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
@@ -119,14 +121,14 @@ export class LobbyController {
|
|||||||
@Post(':id/leave')
|
@Post(':id/leave')
|
||||||
@ApiOperation({ summary: "Leave a game that hasn't already started" })
|
@ApiOperation({ summary: "Leave a game that hasn't already started" })
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
async leave(
|
leave(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Session() session: Record<string, any>,
|
@Session() session: SessionData,
|
||||||
@Param('id', GamePipe) game: Game,
|
@Param('id', GamePipe) game: Game,
|
||||||
) {
|
) {
|
||||||
if (game.started) {
|
if (game.started) {
|
||||||
throw new HttpException('Game already started', HttpStatus.BAD_REQUEST);
|
throw new HttpException('Game already started', HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
return await this.lobbyService.leaveGame(game, session.user);
|
return this.lobbyService.leaveGame(game, session.user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Expose, Transform, Type } from 'class-transformer';
|
import { Expose, Type } from 'class-transformer';
|
||||||
import { UserResponseDTO } from 'src/users/dto/userresponse.dto';
|
import { UserResponseDTO } from 'src/users/dto/userresponse.dto';
|
||||||
|
|
||||||
export class PlayerResponseDTO {
|
export class PlayerResponseDTO {
|
||||||
@@ -11,11 +11,6 @@ export class PlayerResponseDTO {
|
|||||||
@Expose()
|
@Expose()
|
||||||
user: UserResponseDTO;
|
user: UserResponseDTO;
|
||||||
|
|
||||||
@Expose()
|
|
||||||
get userId(): string {
|
|
||||||
return this.user.userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Expose()
|
@Expose()
|
||||||
score: number;
|
score: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ export class Game {
|
|||||||
@ManyToOne(() => User, { eager: true })
|
@ManyToOne(() => User, { eager: true })
|
||||||
owner: User;
|
owner: User;
|
||||||
|
|
||||||
@OneToMany(() => Player, (player) => player.game, { eager: true })
|
@OneToMany(() => Player, (player) => player.game, {
|
||||||
|
eager: true,
|
||||||
|
})
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
players: Player[];
|
players: Player[];
|
||||||
|
|
||||||
@@ -35,6 +37,6 @@ export class Game {
|
|||||||
@Column({ default: false })
|
@Column({ default: false })
|
||||||
started: boolean;
|
started: boolean;
|
||||||
|
|
||||||
@OneToMany(() => ConceptInTurn, (cit) => cit.game)
|
@OneToMany(() => ConceptInTurn, (cit) => cit.game, { onDelete: 'CASCADE' })
|
||||||
currentConcepts: ConceptInTurn[];
|
currentConcepts: ConceptInTurn[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { User } from 'src/users/entities/user.entity';
|
import { User } from '../../users/entities/user.entity';
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
Entity,
|
Entity,
|
||||||
@@ -12,21 +12,16 @@ import {
|
|||||||
import { Game } from './game.entity';
|
import { Game } from './game.entity';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
// @Unique(['user, game'])
|
|
||||||
export class Player {
|
export class Player {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@ManyToOne(() => User, { eager: true })
|
@ManyToOne(() => User, { eager: true, onDelete: 'CASCADE' })
|
||||||
user: User;
|
user: User;
|
||||||
|
|
||||||
@ManyToOne(() => Game)
|
@ManyToOne(() => Game, { onDelete: 'CASCADE' })
|
||||||
game: Game;
|
game: Game;
|
||||||
|
|
||||||
@Column({ default: 0 })
|
@Column({ default: 0 })
|
||||||
score: number;
|
score: number;
|
||||||
|
|
||||||
get userId(): string {
|
|
||||||
return this.user.userId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { GameResponseDTO } from 'src/games/dto/gameresponse.dto';
|
||||||
|
import { PlayerResponseDTO } from 'src/games/dto/playerresponse.dto';
|
||||||
|
import { Game } from 'src/games/entities/game.entity';
|
||||||
|
|
||||||
|
export class GameCreateEvent extends GameResponseDTO {
|
||||||
|
type: 'create';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GameDeleteEvent {
|
||||||
|
type: 'delete';
|
||||||
|
constructor(public id?: number) {}
|
||||||
|
}
|
||||||
|
export class GameJoinEvent {
|
||||||
|
type: 'joingame';
|
||||||
|
gameId: number;
|
||||||
|
constructor(
|
||||||
|
public player: PlayerResponseDTO,
|
||||||
|
game?: Game,
|
||||||
|
) {
|
||||||
|
this.gameId = game.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,30 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { LobbyService } from './lobby.service';
|
import { LobbyService } from './lobby.service';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
import { Concept } from '../entities/concept.entity';
|
||||||
|
import { ConceptInTurn } from '../entities/concept_in_turn.entity';
|
||||||
|
import { Game } from '../entities/game.entity';
|
||||||
|
import { Player } from '../entities/player.entity';
|
||||||
|
import { GameService } from './games.service';
|
||||||
|
|
||||||
describe('GamesService', () => {
|
describe('LobbyService', () => {
|
||||||
let service: LobbyService;
|
let service: LobbyService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [LobbyService],
|
providers: [LobbyService, GameService],
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forRoot({
|
||||||
|
type: 'sqlite',
|
||||||
|
database: ':memory:',
|
||||||
|
entities: [Game, Player, User, Concept, ConceptInTurn],
|
||||||
|
synchronize: true,
|
||||||
|
}),
|
||||||
|
TypeOrmModule.forFeature([Game, Player, User, Concept, ConceptInTurn]),
|
||||||
|
EventEmitterModule.forRoot(),
|
||||||
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<LobbyService>(LobbyService);
|
service = module.get<LobbyService>(LobbyService);
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import { Game } from '../entities/game.entity';
|
|||||||
import { User } from 'src/users/entities/user.entity';
|
import { User } from 'src/users/entities/user.entity';
|
||||||
import { Player } from '../entities/player.entity';
|
import { Player } from '../entities/player.entity';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
|
import {
|
||||||
|
GameCreateEvent,
|
||||||
|
GameDeleteEvent,
|
||||||
|
GameJoinEvent,
|
||||||
|
} from 'src/games/events/lobby';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LobbyService {
|
export class LobbyService {
|
||||||
@@ -21,9 +26,9 @@ export class LobbyService {
|
|||||||
const createdGame = this.gameRepository.create();
|
const createdGame = this.gameRepository.create();
|
||||||
createdGame.owner = owner;
|
createdGame.owner = owner;
|
||||||
createdGame.seed = randomUUID();
|
createdGame.seed = randomUUID();
|
||||||
this.gameRepository.save(createdGame);
|
await this.gameRepository.save(createdGame);
|
||||||
await this.joinGame(createdGame, owner);
|
await this.joinGame(createdGame, owner);
|
||||||
this.eventEmitter.emit('sse.lobby', { type: 'create', ...createdGame });
|
this.eventEmitter.emit('sse.lobby', new GameCreateEvent(createdGame));
|
||||||
return createdGame;
|
return createdGame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,27 +37,20 @@ export class LobbyService {
|
|||||||
player.user = user;
|
player.user = user;
|
||||||
player.game = game;
|
player.game = game;
|
||||||
|
|
||||||
this.eventEmitter.emit('sse.lobby', {
|
this.eventEmitter.emit('sse.lobby', new GameJoinEvent(player, game));
|
||||||
type: 'joinGame',
|
this.eventEmitter.emit('sse.game.' + game.id, new GameJoinEvent(player));
|
||||||
game: game.id,
|
await this.playerRepository.save(player);
|
||||||
player: player,
|
|
||||||
});
|
|
||||||
this.eventEmitter.emit('sse.game.' + game.id, {
|
|
||||||
type: 'joinGame',
|
|
||||||
player: player,
|
|
||||||
});
|
|
||||||
this.playerRepository.save(player);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
findByOwner(user: User) {
|
findByOwner(user: User) {
|
||||||
return this.gameRepository.findBy({ owner: user });
|
return this.gameRepository.findOneBy({ owner: user });
|
||||||
}
|
}
|
||||||
|
|
||||||
async leaveGame(game: Game, user: User) {
|
leaveGame(game: Game, user: User) {
|
||||||
throw Error('not implemented');
|
throw Error('not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
async kickPlayer(game: Game, user: User, kickedPlayerId: string) {
|
kickPlayer(game: Game, user: User, kickedPlayerId: string) {
|
||||||
throw Error('not implemented');
|
throw Error('not implemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,13 +59,8 @@ export class LobbyService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async remove(game: Game) {
|
async remove(game: Game) {
|
||||||
this.eventEmitter.emit('sse.lobby', {
|
this.eventEmitter.emit('sse.lobby', new GameDeleteEvent(game.id));
|
||||||
type: 'delete',
|
this.eventEmitter.emit('sse.game.' + game.id, new GameDeleteEvent());
|
||||||
_id: game.id,
|
await this.gameRepository.remove(game);
|
||||||
});
|
|
||||||
this.eventEmitter.emit('sse.game.' + game.id, {
|
|
||||||
type: 'delete',
|
|
||||||
});
|
|
||||||
this.gameRepository.remove(game);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { Expose } from 'class-transformer';
|
|||||||
export class UserResponseDTO {
|
export class UserResponseDTO {
|
||||||
constructor(partial: Partial<UserResponseDTO>) {
|
constructor(partial: Partial<UserResponseDTO>) {
|
||||||
Object.assign(this, partial);
|
Object.assign(this, partial);
|
||||||
|
this.id = partial.id;
|
||||||
}
|
}
|
||||||
@Expose()
|
@Expose()
|
||||||
userId: string;
|
id: string;
|
||||||
|
|
||||||
@Expose()
|
@Expose()
|
||||||
username: string;
|
username: string;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { Entity, Column, PrimaryColumn } from 'typeorm';
|
|||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class User {
|
export class User {
|
||||||
@PrimaryColumn()
|
@PrimaryColumn({})
|
||||||
userId: string;
|
id: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { UsersController } from './users.controller';
|
|
||||||
import { UsersService } from './users.service';
|
|
||||||
|
|
||||||
describe('UsersController', () => {
|
|
||||||
let controller: UsersController;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [UsersController],
|
|
||||||
providers: [UsersService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
controller = module.get<UsersController>(UsersController);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(controller).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -12,6 +12,7 @@ import { UserResponseDTO } from './dto/userresponse.dto';
|
|||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { SessionData } from 'express-session';
|
||||||
|
|
||||||
const configService = new ConfigService();
|
const configService = new ConfigService();
|
||||||
|
|
||||||
@@ -22,26 +23,21 @@ export class UsersController {
|
|||||||
|
|
||||||
@Get('/login')
|
@Get('/login')
|
||||||
@Redirect()
|
@Redirect()
|
||||||
async login(
|
async login(@Query('code') code: string, @Session() session: SessionData) {
|
||||||
@Query('code') code: string,
|
|
||||||
@Session() session: Record<string, any>,
|
|
||||||
) {
|
|
||||||
await this.usersService.createDiscordToken({ code }, session);
|
await this.usersService.createDiscordToken({ code }, session);
|
||||||
await this.usersService.discordLogin(session);
|
await this.usersService.discordLogin(session);
|
||||||
return { url: configService.get('CLIENT_ENDPOINT') };
|
return { url: configService.get<string>('CLIENT_ENDPOINT') };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/logout')
|
@Get('/logout')
|
||||||
@Redirect()
|
@Redirect()
|
||||||
async logout(@Session() session: Record<string, any>) {
|
async logout(@Session() session: SessionData) {
|
||||||
await this.usersService.discordLogout(session);
|
await this.usersService.discordLogout(session);
|
||||||
return { url: configService.get('CLIENT_ENDPOINT') };
|
return { url: configService.get<string>('CLIENT_ENDPOINT') };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/whoami')
|
@Get('/whoami')
|
||||||
async remove(
|
async remove(@Session() session: SessionData): Promise<UserResponseDTO> {
|
||||||
@Session() session: Record<string, any>,
|
|
||||||
): Promise<UserResponseDTO> {
|
|
||||||
if (!session.tokenData) {
|
if (!session.tokenData) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
"Session doesn't contains auth information",
|
"Session doesn't contains auth information",
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { UsersService } from './users.service';
|
|
||||||
|
|
||||||
describe('UsersService', () => {
|
|
||||||
let service: UsersService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [UsersService],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
service = module.get<UsersService>(UsersService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
import { UserResponseDTO } from './dto/userresponse.dto';
|
import { UserResponseDTO } from './dto/userresponse.dto';
|
||||||
import { URLSearchParams } from 'url';
|
import { URLSearchParams } from 'url';
|
||||||
@@ -5,6 +8,7 @@ import { User } from './entities/user.entity';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { SessionData } from 'express-session';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
@@ -21,7 +25,7 @@ export class UsersService {
|
|||||||
code?: string;
|
code?: string;
|
||||||
refresh_token?: string;
|
refresh_token?: string;
|
||||||
},
|
},
|
||||||
session: Record<string, any>,
|
session: SessionData,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let body;
|
let body;
|
||||||
if (code) {
|
if (code) {
|
||||||
@@ -106,7 +110,7 @@ export class UsersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async discordLogin(
|
async discordLogin(
|
||||||
session: Record<string, any>,
|
session: SessionData,
|
||||||
recursive: boolean = false,
|
recursive: boolean = false,
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
const req2 = await fetch('https://discord.com/api/users/@me', {
|
const req2 = await fetch('https://discord.com/api/users/@me', {
|
||||||
@@ -135,24 +139,27 @@ export class UsersService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const data2 = await req2.json();
|
const data2 = await req2.json();
|
||||||
const user = await this.getOrCreateUser(data2.id, data2);
|
const user = await this.getOrCreateUser(
|
||||||
|
data2.id,
|
||||||
|
new UserResponseDTO(data2),
|
||||||
|
);
|
||||||
session.user = user;
|
session.user = user;
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrCreateUser(userId: string, data?: UserResponseDTO) {
|
async getOrCreateUser(id: string, data?: UserResponseDTO) {
|
||||||
let user = await this.usersRepository.findOneBy({ userId });
|
let user = await this.usersRepository.findOneBy({ id });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = this.usersRepository.create({ ...data, userId });
|
user = this.usersRepository.create({ ...data, id });
|
||||||
} else {
|
} else {
|
||||||
Object.assign(user, data);
|
Object.assign(user, data);
|
||||||
}
|
}
|
||||||
this.usersRepository.save(user);
|
await this.usersRepository.save(user);
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async discordLogout(session: Record<string, any>) {
|
async discordLogout(session: SessionData) {
|
||||||
this.revokeDiscordToken(session.tokenData.access_token);
|
await this.revokeDiscordToken(session.tokenData.access_token);
|
||||||
delete session.user;
|
delete session.user;
|
||||||
delete session.tokenData;
|
delete session.tokenData;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user