Commit ca1cc06d authored by Jordan Grossemy's avatar Jordan Grossemy
Browse files

API réseaux sociaux : édition de l'engagement et suppression des posts

Endpoints PATCH/DELETE /exercises/:id/social/:postId ; mise à jour
likes/commentaires/partages/corps + suppression, diffusés en temps réel.

Co-Authored-By: Claude (RCA)
parent 193d459d
Loading
Loading
Loading
Loading
+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;
}
+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';
@@ -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')
@@ -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);
  }
}
+25 −0
Original line number Diff line number Diff line
@@ -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 {
@@ -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 });
  }
}