Loading apps/api/src/engine/dto/update-post.dto.ts 0 → 100644 +23 −0 Original line number Diff line number Diff line import { IsInt, IsOptional, IsString, Min } from 'class-validator'; /** Mise a jour d'un post (engagement pilote par l'animation, ou correction du texte). */ export class UpdatePostDto { @IsOptional() @IsInt() @Min(0) likes?: number; @IsOptional() @IsInt() @Min(0) shares?: number; @IsOptional() @IsInt() @Min(0) comments?: number; @IsOptional() @IsString() body?: string; } apps/api/src/engine/social.controller.ts +31 −1 Original line number Diff line number Diff line import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, UseGuards, } from '@nestjs/common'; import { PlayerGuard } from './player.guard'; import { CurrentPlayer } from './player.decorator'; import { AuthGuard } from '../auth/auth.guard'; Loading @@ -8,6 +19,7 @@ import { CurrentUser } from '../auth/current-user.decorator'; import type { AuthUser, PlayerContext } from '../auth/auth.types'; import { SocialService } from './social.service'; import { CreatePostDto } from './dto/create-post.dto'; import { UpdatePostDto } from './dto/update-post.dto'; /** Fil des reseaux sociaux cote joueur (lecture). */ @Controller('play/social') Loading Loading @@ -41,4 +53,22 @@ export class StaffSocialController { ) { return this.social.create(me.tenantId, exerciseId, dto); } @Patch(':id') @Roles('TENANT_ADMIN', 'DESIGNER', 'ANIMATOR') update( @CurrentUser() me: AuthUser, @Param('exerciseId') exerciseId: string, @Param('id') id: string, @Body() dto: UpdatePostDto, ) { return this.social.update(me.tenantId, exerciseId, id, dto); } @Delete(':id') @Roles('TENANT_ADMIN', 'DESIGNER', 'ANIMATOR') @HttpCode(HttpStatus.NO_CONTENT) remove(@CurrentUser() me: AuthUser, @Param('exerciseId') exerciseId: string, @Param('id') id: string) { return this.social.remove(me.tenantId, exerciseId, id); } } apps/api/src/engine/social.service.ts +25 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import { RealtimeGateway } from './realtime.gateway'; import { computeElapsedSeconds } from './clock'; import { RT } from './engine.types'; import type { CreatePostDto } from './dto/create-post.dto'; import type { UpdatePostDto } from './dto/update-post.dto'; @Injectable() export class SocialService { Loading Loading @@ -41,4 +42,28 @@ export class SocialService { this.rt.emitToExercise(exerciseId, RT.socialNew, post); return post; } async update(tenantId: string, exerciseId: string, id: string, dto: UpdatePostDto) { const data: { likes?: number; shares?: number; comments?: number; body?: string } = {}; if (dto.likes !== undefined) data.likes = dto.likes; if (dto.shares !== undefined) data.shares = dto.shares; if (dto.comments !== undefined) data.comments = dto.comments; if (dto.body !== undefined) data.body = dto.body; const res = await this.prisma.forTenant(tenantId).socialPost.updateMany({ where: { id, exerciseId }, data, }); if (res.count === 0) throw new NotFoundException('Publication introuvable'); const post = await this.prisma.forTenant(tenantId).socialPost.findFirst({ where: { id } }); this.rt.emitToExercise(exerciseId, RT.socialUpdate, post); return post; } async remove(tenantId: string, exerciseId: string, id: string): Promise<void> { const res = await this.prisma.forTenant(tenantId).socialPost.deleteMany({ where: { id, exerciseId } }); if (res.count === 0) throw new NotFoundException('Publication introuvable'); this.rt.emitToExercise(exerciseId, RT.socialDelete, { id }); } } Loading
apps/api/src/engine/dto/update-post.dto.ts 0 → 100644 +23 −0 Original line number Diff line number Diff line import { IsInt, IsOptional, IsString, Min } from 'class-validator'; /** Mise a jour d'un post (engagement pilote par l'animation, ou correction du texte). */ export class UpdatePostDto { @IsOptional() @IsInt() @Min(0) likes?: number; @IsOptional() @IsInt() @Min(0) shares?: number; @IsOptional() @IsInt() @Min(0) comments?: number; @IsOptional() @IsString() body?: string; }
apps/api/src/engine/social.controller.ts +31 −1 Original line number Diff line number Diff line import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, UseGuards, } from '@nestjs/common'; import { PlayerGuard } from './player.guard'; import { CurrentPlayer } from './player.decorator'; import { AuthGuard } from '../auth/auth.guard'; Loading @@ -8,6 +19,7 @@ import { CurrentUser } from '../auth/current-user.decorator'; import type { AuthUser, PlayerContext } from '../auth/auth.types'; import { SocialService } from './social.service'; import { CreatePostDto } from './dto/create-post.dto'; import { UpdatePostDto } from './dto/update-post.dto'; /** Fil des reseaux sociaux cote joueur (lecture). */ @Controller('play/social') Loading Loading @@ -41,4 +53,22 @@ export class StaffSocialController { ) { return this.social.create(me.tenantId, exerciseId, dto); } @Patch(':id') @Roles('TENANT_ADMIN', 'DESIGNER', 'ANIMATOR') update( @CurrentUser() me: AuthUser, @Param('exerciseId') exerciseId: string, @Param('id') id: string, @Body() dto: UpdatePostDto, ) { return this.social.update(me.tenantId, exerciseId, id, dto); } @Delete(':id') @Roles('TENANT_ADMIN', 'DESIGNER', 'ANIMATOR') @HttpCode(HttpStatus.NO_CONTENT) remove(@CurrentUser() me: AuthUser, @Param('exerciseId') exerciseId: string, @Param('id') id: string) { return this.social.remove(me.tenantId, exerciseId, id); } }
apps/api/src/engine/social.service.ts +25 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import { RealtimeGateway } from './realtime.gateway'; import { computeElapsedSeconds } from './clock'; import { RT } from './engine.types'; import type { CreatePostDto } from './dto/create-post.dto'; import type { UpdatePostDto } from './dto/update-post.dto'; @Injectable() export class SocialService { Loading Loading @@ -41,4 +42,28 @@ export class SocialService { this.rt.emitToExercise(exerciseId, RT.socialNew, post); return post; } async update(tenantId: string, exerciseId: string, id: string, dto: UpdatePostDto) { const data: { likes?: number; shares?: number; comments?: number; body?: string } = {}; if (dto.likes !== undefined) data.likes = dto.likes; if (dto.shares !== undefined) data.shares = dto.shares; if (dto.comments !== undefined) data.comments = dto.comments; if (dto.body !== undefined) data.body = dto.body; const res = await this.prisma.forTenant(tenantId).socialPost.updateMany({ where: { id, exerciseId }, data, }); if (res.count === 0) throw new NotFoundException('Publication introuvable'); const post = await this.prisma.forTenant(tenantId).socialPost.findFirst({ where: { id } }); this.rt.emitToExercise(exerciseId, RT.socialUpdate, post); return post; } async remove(tenantId: string, exerciseId: string, id: string): Promise<void> { const res = await this.prisma.forTenant(tenantId).socialPost.deleteMany({ where: { id, exerciseId } }); if (res.count === 0) throw new NotFoundException('Publication introuvable'); this.rt.emitToExercise(exerciseId, RT.socialDelete, { id }); } }