feat : add concept module

This commit is contained in:
2024-12-31 15:47:36 +01:00
parent 0500415252
commit 6bb83a3d5c
10 changed files with 162 additions and 41 deletions
+2
View File
@@ -15,6 +15,7 @@ import { WordModule } from './word/words.module';
import { Message } from './games/entities/message.entity'; import { Message } from './games/entities/message.entity';
import { LobbyModule } from './lobby/lobby.module'; import { LobbyModule } from './lobby/lobby.module';
import { Concept } from './concept/entities/concept.entity'; import { Concept } from './concept/entities/concept.entity';
import { ConceptModule } from './concept/concept.module';
const configService = new ConfigService(); const configService = new ConfigService();
@@ -48,6 +49,7 @@ const configService = new ConfigService();
GamesModule, GamesModule,
UsersModule, UsersModule,
WordModule, WordModule,
ConceptModule,
], ],
}) })
export class AppModule {} export class AppModule {}
+104
View File
@@ -0,0 +1,104 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
HttpStatus,
Param,
ParseIntPipe,
Post,
Put,
Sse,
UseGuards,
} from '@nestjs/common';
import { ApiBody, ApiOperation, ApiTags } from '@nestjs/swagger';
import { AuthGuard } from 'src/users/guards/auth.guard';
import { fromEvent, map, Observable } from 'rxjs';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ConceptService } from './concept.service';
import { Concept } from './entities/concept.entity';
import { ConceptPipe } from './concept.pipe';
import { ConceptRecieveDTO } from './dto/conceptrecieve.dto';
import { ConceptResponseDTO } from 'events/dto/conceptresponse.dto';
@ApiTags('concepts')
@Controller('concepts')
export class ConceptController {
constructor(
private readonly conceptService: ConceptService,
private eventEmitter: EventEmitter2,
) {}
@Sse('/subscribe')
subscribe(): Observable<{ data: string }> {
return fromEvent(this.eventEmitter, this.conceptService.sse_prefix).pipe(
map((payload) => {
return {
data: JSON.stringify(payload),
};
}),
);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete a concept' })
@UseGuards(AuthGuard)
async delete(@Param('id') id: number) {
const concept = await this.conceptService.getById(id);
if (!concept) {
throw new HttpException('Concept not found', HttpStatus.NOT_FOUND);
}
await this.conceptService.delete(concept);
}
@Post('/')
@ApiOperation({ summary: 'Submit a concept' })
@ApiBody({ type: ConceptRecieveDTO })
@UseGuards(AuthGuard)
async create(@Body('value') value: string) {
const concept = await this.conceptService.create(value, 'placeholder.jpg');
return new ConceptResponseDTO(concept);
}
@Get('/')
@ApiOperation({ summary: 'List enabled concept' })
async read() {
return (await this.conceptService.listActive()).map(
(w) => new ConceptResponseDTO(w),
);
}
@Get('/all')
@ApiOperation({ summary: 'List all concepts' })
async readAll() {
return (await this.conceptService.list()).map(
(w) => new ConceptResponseDTO(w),
);
}
@Put(':id/enable')
@ApiOperation({ summary: 'Enable a concept' })
@UseGuards(AuthGuard)
async enable(
@Param('id', ParseIntPipe) _id: string,
@Param('id', ConceptPipe) concept: Concept,
) {
await this.conceptService.enable(concept);
return new ConceptResponseDTO(concept);
}
@Put(':id/disable')
@ApiOperation({ summary: 'Disable a word' })
@UseGuards(AuthGuard)
async disable(
@Param('id', ParseIntPipe) _id: string,
@Param('id', ConceptPipe) concept: Concept,
) {
await this.conceptService.disable(concept);
return new ConceptResponseDTO(concept);
}
}
+4
View File
@@ -4,8 +4,12 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from 'src/users/users.module'; import { UsersModule } from 'src/users/users.module';
import { Concept } from './entities/concept.entity'; import { Concept } from './entities/concept.entity';
import { ConceptService } from './concept.service';
import { ConceptController } from './concept.controller';
@Module({ @Module({
imports: [UsersModule, TypeOrmModule.forFeature([Concept])], imports: [UsersModule, TypeOrmModule.forFeature([Concept])],
providers: [ConceptService],
controllers: [ConceptController],
}) })
export class ConceptModule {} export class ConceptModule {}
+20
View File
@@ -0,0 +1,20 @@
import {
HttpException,
HttpStatus,
Injectable,
PipeTransform,
} from '@nestjs/common';
import { ConceptService } from './concept.service';
@Injectable()
export class ConceptPipe implements PipeTransform {
constructor(private readonly conceptService: ConceptService) {}
async transform(id: number) {
const word = await this.conceptService.getById(id);
if (!word) {
throw new HttpException('Word not found', HttpStatus.NOT_FOUND);
}
return word;
}
}
+5 -5
View File
@@ -12,7 +12,7 @@ import {
} from 'events/concept.events'; } from 'events/concept.events';
@Injectable() @Injectable()
export class WordService { export class ConceptService {
sse_prefix = 'sse.concept'; sse_prefix = 'sse.concept';
constructor( constructor(
@InjectRepository(Concept) private conceptRepository: Repository<Concept>, @InjectRepository(Concept) private conceptRepository: Repository<Concept>,
@@ -42,13 +42,13 @@ export class WordService {
} }
async listActive() { async listActive() {
const words = await this.conceptRepository.findBy({ enabled: true }); const concepts = await this.conceptRepository.findBy({ enabled: true });
return words; return concepts;
} }
async list() { async list() {
const words = await this.conceptRepository.find(); const concepts = await this.conceptRepository.find();
return words; return concepts;
} }
async enable(concept: Concept) { async enable(concept: Concept) {
+7
View File
@@ -0,0 +1,7 @@
export class ConceptRecieveDTO {
constructor(partial: Partial<ConceptRecieveDTO>) {
Object.assign(this, partial);
}
value: string;
}
+16 -22
View File
@@ -6,7 +6,6 @@ import {
HttpStatus, HttpStatus,
Param, Param,
Post, Post,
Session,
Sse, Sse,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
@@ -19,11 +18,12 @@ 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';
// import { WordResponseDTO } from 'events/dto/wordresponse.dto'; // import { WordResponseDTO } from 'events/dto/wordresponse.dto';
import { WordRecieveDTO } from 'src/word/dto/wordrecieve.dto'; import { WordRecieveDTO } from 'src/word/dto/wordrecieve.dto';
import { LobbyService } from 'src/lobby/lobby.service'; import { LobbyService } from 'src/lobby/lobby.service';
import { WordResponseDTO } from 'events/dto/wordresponse.dto'; import { WordResponseDTO } from 'events/dto/wordresponse.dto';
import { GetUser } from 'src/users/user.pipe';
import { User } from 'src/users/entities/user.entity';
@ApiTags('game') @ApiTags('game')
@Controller('games/:id') @Controller('games/:id')
@@ -52,7 +52,7 @@ export class GamesController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
async start( async start(
@Param('id') id: string, @Param('id') id: string,
@Session() session: SessionData, @GetUser() user: User,
@Param('id', GamePipe) game: Game, @Param('id', GamePipe) game: Game,
) { ) {
if (game.started) { if (game.started) {
@@ -64,7 +64,7 @@ export class GamesController {
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
} }
if (game.ownerId != session.user?.id) { if (game.ownerId != 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,
@@ -79,42 +79,39 @@ export class GamesController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
surrender( surrender(
@Param('id') id: string, @Param('id') id: string,
@Session() session: SessionData, @GetUser() user: User,
@Param('id', GamePipe) game: Game, @Param('id', GameStartedPipe) game: Game,
) { ) {
if (!game.started) { return this.lobbyService.leaveGame(game, user);
throw new HttpException('Game has not started', HttpStatus.BAD_REQUEST);
}
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)
kick( kick(
@Session() session: SessionData, @GetUser() user: User,
@Param('id') id: string, @Param('id') id: string,
@Param('id', GamePipe) game: Game, @Param('id', GamePipe) game: Game,
@Param('playerId') playerId: string, @Param('playerId') playerId: string,
) { ) {
if (session.user.id != game.ownerId) { if (user.id != game.ownerId) {
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 this.lobbyService.kickPlayer(game, session.user, playerId); return this.lobbyService.kickPlayer(game, user, playerId);
} }
@Get('currentWord') @Get('currentWord')
@ApiOperation({ summary: 'Read Current Word' }) @ApiOperation({ summary: 'Read Current Word' })
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
getCurrentWord( getCurrentWord(
@Session() session: SessionData, @GetUser() user: User,
@Param('id') id: string, @Param('id') id: string,
@Param('id', GamePipe) game: Game, @Param('id', GameStartedPipe) game: Game,
) { ) {
if (session.user.id != game.currentPlayer.userId) { if (user.id != game.currentPlayer.userId) {
throw new HttpException( throw new HttpException(
'It is not your turn to play', 'It is not your turn to play',
HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN,
@@ -128,15 +125,12 @@ export class GamesController {
@ApiBody({ type: WordRecieveDTO }) @ApiBody({ type: WordRecieveDTO })
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
async postMessage( async postMessage(
@Session() session: SessionData, @GetUser() user: User,
@Param('id') id: string, @Param('id') id: string,
@Param('id', GamePipe) game: Game, @Param('id', GameStartedPipe) game: Game,
@Body('value') value: string, @Body('value') value: string,
) { ) {
const player = game.players.find((p) => p.userId == session.user.id); const player = game.players.find((p) => p.userId == user.id);
if (!game.started) {
throw new HttpException('Game not started', HttpStatus.BAD_REQUEST);
}
if (!player) { if (!player) {
throw new HttpException('You are not in this game', HttpStatus.FORBIDDEN); throw new HttpException('You are not in this game', HttpStatus.FORBIDDEN);
} }
+3 -3
View File
@@ -15,7 +15,7 @@ export class GamePipe implements PipeTransform {
@InjectRepository(Game) private gameRepository: Repository<Game>, @InjectRepository(Game) private gameRepository: Repository<Game>,
) {} ) {}
async transform(value: any) { async transform(value: number) {
const game = await this.gameRepository.findOneBy({ id: value }); const game = await this.gameRepository.findOneBy({ id: value });
if (!game) { if (!game) {
throw new HttpException('Game not found', HttpStatus.NOT_FOUND); throw new HttpException('Game not found', HttpStatus.NOT_FOUND);
@@ -30,8 +30,8 @@ export class GameStartedPipe implements PipeTransform {
@InjectRepository(Game) private gameRepository: Repository<Game>, @InjectRepository(Game) private gameRepository: Repository<Game>,
) {} ) {}
async transform(value: any) { async transform(value: number) {
const game = await this.gameRepository.findOne(value); const game = await this.gameRepository.findOneBy({ id: value });
if (!game) { if (!game) {
throw new HttpException('Game not found', HttpStatus.NOT_FOUND); throw new HttpException('Game not found', HttpStatus.NOT_FOUND);
} }
+1 -11
View File
@@ -1,14 +1,4 @@
import { User } from 'src/users/entities/user.entity'; import { Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
import {
Column,
Entity,
JoinTable,
ManyToMany,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
Unique,
} from 'typeorm';
import { Word } from './word.entity'; import { Word } from './word.entity';
@Entity() @Entity()