ForumController.java

package com.wavii.controller;

import com.wavii.dto.forum.CreateForumRequest;
import com.wavii.dto.forum.CreatePostRequest;
import com.wavii.dto.forum.ForumResponse;
import com.wavii.dto.forum.ForumSummaryResponse;
import com.wavii.dto.forum.PostResponse;
import com.wavii.dto.forum.UpdateForumMemberRoleRequest;
import com.wavii.dto.forum.UpdateForumRequest;
import com.wavii.model.User;
import com.wavii.service.ChatRealtimeBroadcaster;
import com.wavii.service.ForumService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * Controlador REST para la gestión de foros y comunidades.
 * 
 * @author danielrguezh
 */
@RestController
@RequestMapping("/api/forums")
@RequiredArgsConstructor
public class ForumController {

    private final ForumService forumService;
    private final ChatRealtimeBroadcaster chatRealtimeBroadcaster;

    @Value("${wavii.app.base-url:http://localhost:8080}")
    private String appBaseUrl;

    @Value("${pdf.storage.path:./uploads/pdfs}")
    private String pdfStoragePath;

    @GetMapping
    public ResponseEntity<List<ForumSummaryResponse>> getForums(
            @RequestParam(required = false) String search,
            @RequestParam(required = false) String city,
            @RequestParam(required = false) String category,
            @RequestParam(required = false) String sort,
            @AuthenticationPrincipal User currentUser
    ) {
        return ResponseEntity.ok(forumService.getForums(search, city, category, sort, currentUser));
    }

    @GetMapping("/my")
    public ResponseEntity<List<ForumSummaryResponse>> getMyForums(
            @AuthenticationPrincipal User currentUser
    ) {
        return ResponseEntity.ok(forumService.getMyForums(currentUser));
    }

    @GetMapping("/{id}")
    public ResponseEntity<ForumResponse> getForum(
            @PathVariable UUID id,
            @AuthenticationPrincipal User currentUser
    ) {
        return ResponseEntity.ok(forumService.getForumById(id, currentUser));
    }

    @PostMapping
    public ResponseEntity<ForumResponse> createForum(
            @RequestBody CreateForumRequest request,
            @AuthenticationPrincipal User currentUser
    ) {
        return ResponseEntity.status(201).body(forumService.createForum(request, currentUser));
    }

    @PatchMapping("/{id}")
    public ResponseEntity<ForumResponse> updateForum(
            @PathVariable UUID id,
            @RequestBody UpdateForumRequest request,
            @AuthenticationPrincipal User currentUser
    ) {
        return ResponseEntity.ok(forumService.updateForum(id, request, currentUser));
    }

    @PostMapping("/images")
    public ResponseEntity<?> uploadForumImage(@RequestParam("file") MultipartFile file) {
        return uploadImage(file, "forums");
    }

    @PostMapping("/{id}/join")
    public ResponseEntity<Void> joinForum(
            @PathVariable UUID id,
            @AuthenticationPrincipal User currentUser
    ) {
        forumService.joinForum(id, currentUser);
        return ResponseEntity.ok().build();
    }

    @DeleteMapping("/{id}/join")
    public ResponseEntity<Void> leaveForum(
            @PathVariable UUID id,
            @AuthenticationPrincipal User currentUser
    ) {
        forumService.leaveForum(id, currentUser);
        return ResponseEntity.ok().build();
    }

    @PostMapping("/{id}/like")
    public ResponseEntity<ForumResponse> likeForum(
            @PathVariable UUID id,
            @AuthenticationPrincipal User currentUser
    ) {
        return ResponseEntity.ok(forumService.likeForum(id, currentUser));
    }

    @DeleteMapping("/{id}/like")
    public ResponseEntity<ForumResponse> unlikeForum(
            @PathVariable UUID id,
            @AuthenticationPrincipal User currentUser
    ) {
        return ResponseEntity.ok(forumService.unlikeForum(id, currentUser));
    }

    @GetMapping("/{id}/members")
    public ResponseEntity<?> getMembers(
            @PathVariable UUID id,
            @AuthenticationPrincipal User currentUser
    ) {
        return ResponseEntity.ok(forumService.getMembers(id, currentUser));
    }

    @PatchMapping("/{id}/members/{userId}/role")
    public ResponseEntity<ForumResponse> updateMemberRole(
            @PathVariable UUID id,
            @PathVariable UUID userId,
            @RequestBody UpdateForumMemberRoleRequest request,
            @AuthenticationPrincipal User currentUser
    ) {
        return ResponseEntity.ok(forumService.updateMemberRole(id, userId, request.role(), currentUser));
    }

    @DeleteMapping("/{id}/members/{userId}")
    public ResponseEntity<Void> removeMember(
            @PathVariable UUID id,
            @PathVariable UUID userId,
            @AuthenticationPrincipal User currentUser
    ) {
        forumService.removeMember(id, userId, currentUser);
        return ResponseEntity.noContent().build();
    }

    @GetMapping("/{id}/posts")
    public ResponseEntity<Page<PostResponse>> getPosts(
            @PathVariable UUID id,
            @RequestParam(defaultValue = "0") int page,
            @AuthenticationPrincipal User currentUser
    ) {
        return ResponseEntity.ok(forumService.getPosts(id, page, currentUser));
    }

    @PostMapping("/{id}/posts")
    public ResponseEntity<PostResponse> createPost(
            @PathVariable UUID id,
            @RequestBody CreatePostRequest request,
            @AuthenticationPrincipal User currentUser
    ) {
        PostResponse saved = forumService.createPost(id, request, currentUser);
        chatRealtimeBroadcaster.broadcast(chatRealtimeBroadcaster.forumRoom(id), saved);
        return ResponseEntity.status(201).body(saved);
    }

    private ResponseEntity<?> uploadImage(MultipartFile file, String folder) {
        if (file == null || file.isEmpty()) {
            return ResponseEntity.badRequest().body(Map.of("message", "El archivo no puede estar vacio"));
        }
        String contentType = file.getContentType();
        if (contentType == null || !contentType.startsWith("image/")) {
            return ResponseEntity.badRequest().body(Map.of("message", "Solo se permiten imagenes"));
        }

        try {
            Path uploadPath = uploadRoot().resolve(folder);
            Files.createDirectories(uploadPath);
            String extension = extensionOf(file.getOriginalFilename());
            String storedName = UUID.randomUUID() + extension;
            Files.copy(file.getInputStream(), uploadPath.resolve(storedName), StandardCopyOption.REPLACE_EXISTING);
            return ResponseEntity.ok(Map.of(
                    "url", appBaseUrl + "/uploads/" + folder + "/" + storedName,
                    "fileName", storedName
            ));
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(Map.of("message", "No se pudo guardar la imagen"));
        }
    }

    private String extensionOf(String originalFilename) {
        if (originalFilename == null || !originalFilename.contains(".")) {
            return ".jpg";
        }
        String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
        return extension.isBlank() ? ".jpg" : extension;
    }

    private Path uploadRoot() {
        Path root = Paths.get(pdfStoragePath).getParent();
        return root != null ? root : Paths.get("uploads");
    }
}