package com.dalab.policyengine.web.rest; import java.util.List; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; 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 com.dalab.common.security.SecurityUtils; import com.dalab.policyengine.dto.PolicyDraftActionDTO; import com.dalab.policyengine.dto.PolicyDraftInputDTO; import com.dalab.policyengine.dto.PolicyDraftOutputDTO; import com.dalab.policyengine.dto.PolicyOutputDTO; import com.dalab.policyengine.model.PolicyDraftStatus; import com.dalab.policyengine.service.IPolicyService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; /** * REST controller for policy draft management operations. * Provides comprehensive draft workflow including creation, review, approval, and publication. */ @RestController @RequestMapping("/api/v1/policyengine/drafts") @Tag(name = "Policy Draft Management", description = "Endpoints for managing policy drafts and approval workflows") public class PolicyDraftController { private static final Logger log = LoggerFactory.getLogger(PolicyDraftController.class); private final IPolicyService policyService; @Autowired public PolicyDraftController(IPolicyService policyService) { this.policyService = policyService; } @PostMapping @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") @Operation( summary = "Create a new policy draft", description = "Creates a new policy draft that can be edited, submitted for review, and eventually published as an active policy." ) @ApiResponses(value = { @ApiResponse(responseCode = "201", description = "Policy draft created successfully"), @ApiResponse(responseCode = "400", description = "Invalid draft data provided"), @ApiResponse(responseCode = "409", description = "Draft with this name already exists"), @ApiResponse(responseCode = "403", description = "Insufficient permissions to create drafts") }) public ResponseEntity createDraft( @Parameter(description = "Policy draft creation data") @Valid @RequestBody PolicyDraftInputDTO draftInput) { UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); log.info("Creating policy draft: {} by user: {}", draftInput.getName(), currentUserId); PolicyDraftOutputDTO createdDraft = policyService.createDraft(draftInput, currentUserId); return ResponseEntity.status(HttpStatus.CREATED).body(createdDraft); } @GetMapping @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") @Operation( summary = "Get all policy drafts with filtering", description = "Retrieves paginated list of policy drafts with optional filtering by status, category, priority, creator, and name search." ) public ResponseEntity> getAllDrafts( @PageableDefault(size = 20, sort = "updatedAt", direction = Sort.Direction.DESC) Pageable pageable, @Parameter(description = "Filter by draft status") @RequestParam(required = false) PolicyDraftStatus status, @Parameter(description = "Filter by category") @RequestParam(required = false) String category, @Parameter(description = "Filter by priority level") @RequestParam(required = false) String priority, @Parameter(description = "Filter by creator user ID") @RequestParam(required = false) UUID createdBy, @Parameter(description = "Search in draft names") @RequestParam(required = false) String nameContains) { log.debug("Retrieving policy drafts with filters - status: {}, category: {}, priority: {}", status, category, priority); Page drafts = policyService.getAllDrafts( pageable, status, category, priority, createdBy, nameContains); return ResponseEntity.ok(drafts); } @GetMapping("/{draftId}") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") @Operation( summary = "Get policy draft by ID", description = "Retrieves a specific policy draft with complete workflow information and available actions." ) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Policy draft retrieved successfully"), @ApiResponse(responseCode = "404", description = "Policy draft not found"), @ApiResponse(responseCode = "403", description = "Insufficient permissions to view draft") }) public ResponseEntity getDraftById( @Parameter(description = "Draft ID") @PathVariable UUID draftId) { log.debug("Retrieving policy draft with ID: {}", draftId); PolicyDraftOutputDTO draft = policyService.getDraftById(draftId); return ResponseEntity.ok(draft); } @PutMapping("/{draftId}") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") @Operation( summary = "Update policy draft", description = "Updates an existing policy draft. Only allowed for drafts in CREATED or REQUIRES_CHANGES status." ) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Policy draft updated successfully"), @ApiResponse(responseCode = "400", description = "Invalid draft data provided"), @ApiResponse(responseCode = "404", description = "Policy draft not found"), @ApiResponse(responseCode = "409", description = "Draft cannot be updated in current status or name conflict"), @ApiResponse(responseCode = "403", description = "Insufficient permissions to update draft") }) public ResponseEntity updateDraft( @Parameter(description = "Draft ID") @PathVariable UUID draftId, @Parameter(description = "Updated draft data") @Valid @RequestBody PolicyDraftInputDTO draftInput) { UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); log.info("Updating policy draft: {} by user: {}", draftId, currentUserId); PolicyDraftOutputDTO updatedDraft = policyService.updateDraft(draftId, draftInput, currentUserId); return ResponseEntity.ok(updatedDraft); } @DeleteMapping("/{draftId}") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") @Operation( summary = "Delete policy draft", description = "Deletes a policy draft. Only allowed for drafts in CREATED status." ) @ApiResponses(value = { @ApiResponse(responseCode = "204", description = "Policy draft deleted successfully"), @ApiResponse(responseCode = "404", description = "Policy draft not found"), @ApiResponse(responseCode = "409", description = "Draft cannot be deleted in current status"), @ApiResponse(responseCode = "403", description = "Insufficient permissions to delete draft") }) public ResponseEntity deleteDraft( @Parameter(description = "Draft ID") @PathVariable UUID draftId) { UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); log.info("Deleting policy draft: {} by user: {}", draftId, currentUserId); policyService.deleteDraft(draftId, currentUserId); return ResponseEntity.noContent().build(); } // ========== WORKFLOW ACTION ENDPOINTS ========== @PostMapping("/{draftId}/submit") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") @Operation( summary = "Submit draft for review", description = "Submits a policy draft for review, transitioning it from CREATED or REQUIRES_CHANGES to SUBMITTED status." ) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Draft submitted successfully"), @ApiResponse(responseCode = "404", description = "Policy draft not found"), @ApiResponse(responseCode = "409", description = "Draft cannot be submitted in current status"), @ApiResponse(responseCode = "403", description = "Insufficient permissions to submit draft") }) public ResponseEntity submitDraft( @Parameter(description = "Draft ID") @PathVariable UUID draftId, @Parameter(description = "Submission action with optional comment") @Valid @RequestBody PolicyDraftActionDTO action) { UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); log.info("Submitting policy draft: {} by user: {}", draftId, currentUserId); PolicyDraftOutputDTO submittedDraft = policyService.submitDraft(draftId, action, currentUserId); return ResponseEntity.ok(submittedDraft); } @PostMapping("/{draftId}/approve") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") @Operation( summary = "Approve policy draft", description = "Approves a policy draft, transitioning it to APPROVED status and making it ready for publication." ) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Draft approved successfully"), @ApiResponse(responseCode = "404", description = "Policy draft not found"), @ApiResponse(responseCode = "409", description = "Draft cannot be approved in current status"), @ApiResponse(responseCode = "403", description = "Insufficient permissions to approve draft") }) public ResponseEntity approveDraft( @Parameter(description = "Draft ID") @PathVariable UUID draftId, @Parameter(description = "Approval action with optional comment") @Valid @RequestBody PolicyDraftActionDTO action) { UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); log.info("Approving policy draft: {} by user: {}", draftId, currentUserId); PolicyDraftOutputDTO approvedDraft = policyService.approveDraft(draftId, action, currentUserId); return ResponseEntity.ok(approvedDraft); } @PostMapping("/{draftId}/reject") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") @Operation( summary = "Reject policy draft", description = "Rejects a policy draft, transitioning it to REJECTED status. A comment explaining the rejection is required." ) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Draft rejected successfully"), @ApiResponse(responseCode = "400", description = "Comment is required for rejection"), @ApiResponse(responseCode = "404", description = "Policy draft not found"), @ApiResponse(responseCode = "409", description = "Draft cannot be rejected in current status"), @ApiResponse(responseCode = "403", description = "Insufficient permissions to reject draft") }) public ResponseEntity rejectDraft( @Parameter(description = "Draft ID") @PathVariable UUID draftId, @Parameter(description = "Rejection action with required comment") @Valid @RequestBody PolicyDraftActionDTO action) { UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); log.info("Rejecting policy draft: {} by user: {}", draftId, currentUserId); PolicyDraftOutputDTO rejectedDraft = policyService.rejectDraft(draftId, action, currentUserId); return ResponseEntity.ok(rejectedDraft); } @PostMapping("/{draftId}/request-changes") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") @Operation( summary = "Request changes to draft", description = "Requests changes to a policy draft, transitioning it to REQUIRES_CHANGES status. A comment explaining required changes is mandatory." ) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Changes requested successfully"), @ApiResponse(responseCode = "400", description = "Comment is required for change request"), @ApiResponse(responseCode = "404", description = "Policy draft not found"), @ApiResponse(responseCode = "409", description = "Changes cannot be requested for draft in current status"), @ApiResponse(responseCode = "403", description = "Insufficient permissions to request changes") }) public ResponseEntity requestChanges( @Parameter(description = "Draft ID") @PathVariable UUID draftId, @Parameter(description = "Change request action with required comment") @Valid @RequestBody PolicyDraftActionDTO action) { UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); log.info("Requesting changes for policy draft: {} by user: {}", draftId, currentUserId); PolicyDraftOutputDTO updatedDraft = policyService.requestChanges(draftId, action, currentUserId); return ResponseEntity.ok(updatedDraft); } @PostMapping("/{draftId}/publish") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") @Operation( summary = "Publish approved draft as policy", description = "Publishes an approved policy draft as an active policy. Only APPROVED drafts can be published." ) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Draft published successfully"), @ApiResponse(responseCode = "404", description = "Policy draft not found"), @ApiResponse(responseCode = "409", description = "Only approved drafts can be published"), @ApiResponse(responseCode = "403", description = "Insufficient permissions to publish draft") }) public ResponseEntity publishDraft( @Parameter(description = "Draft ID") @PathVariable UUID draftId, @Parameter(description = "Publication action with optional metadata") @Valid @RequestBody PolicyDraftActionDTO action) { UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); log.info("Publishing policy draft: {} by user: {}", draftId, currentUserId); PolicyOutputDTO publishedPolicy = policyService.publishDraft(draftId, action, currentUserId); return ResponseEntity.ok(publishedPolicy); } @PostMapping("/{draftId}/comments") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD')") @Operation( summary = "Add review comment to draft", description = "Adds a review comment to a policy draft for collaboration and feedback purposes." ) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Comment added successfully"), @ApiResponse(responseCode = "400", description = "Invalid comment data"), @ApiResponse(responseCode = "404", description = "Policy draft not found"), @ApiResponse(responseCode = "403", description = "Insufficient permissions to comment on draft") }) public ResponseEntity addComment( @Parameter(description = "Draft ID") @PathVariable UUID draftId, @Parameter(description = "Review comment") @RequestParam String comment) { UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); String userRole = "REVIEWER"; // Simplified - would get actual roles from security context log.debug("Adding comment to policy draft: {} by user: {}", draftId, currentUserId); PolicyDraftOutputDTO updatedDraft = policyService.addReviewComment(draftId, comment, currentUserId, userRole); return ResponseEntity.ok(updatedDraft); } // ========== SPECIALIZED QUERY ENDPOINTS ========== @GetMapping("/my-drafts") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") @Operation( summary = "Get drafts requiring my attention", description = "Retrieves drafts that require attention from the current user (created by them or they are a stakeholder)." ) public ResponseEntity> getMyDrafts( @PageableDefault(size = 20, sort = "updatedAt", direction = Sort.Direction.DESC) Pageable pageable) { UUID currentUserId = SecurityUtils.getAuthenticatedUserId(); log.debug("Retrieving drafts requiring attention for user: {}", currentUserId); Page drafts = policyService.getDraftsRequiringAttention(currentUserId, pageable); return ResponseEntity.ok(drafts); } @GetMapping("/pending-review") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") @Operation( summary = "Get drafts pending review", description = "Retrieves all drafts that are currently pending review (SUBMITTED or UNDER_REVIEW status)." ) public ResponseEntity> getDraftsPendingReview( @PageableDefault(size = 20, sort = "submittedAt", direction = Sort.Direction.ASC) Pageable pageable) { log.debug("Retrieving drafts pending review"); Page drafts = policyService.getDraftsPendingReview(pageable); return ResponseEntity.ok(drafts); } @GetMapping("/overdue") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") @Operation( summary = "Get overdue drafts", description = "Retrieves drafts that have passed their target implementation date but haven't been published yet." ) public ResponseEntity> getOverdueDrafts() { log.debug("Retrieving overdue drafts"); List overdueDrafts = policyService.getOverdueDrafts(); return ResponseEntity.ok(overdueDrafts); } // ========== METADATA ENDPOINTS ========== @GetMapping("/categories") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") @Operation( summary = "Get available draft categories", description = "Retrieves list of all categories used in policy drafts for filtering and organization." ) public ResponseEntity> getDraftCategories() { log.debug("Retrieving draft categories"); List categories = policyService.getDraftCategories(); return ResponseEntity.ok(categories); } @GetMapping("/tags") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER', 'ROLE_DATA_STEWARD', 'ROLE_USER')") @Operation( summary = "Get available draft tags", description = "Retrieves list of all tags used in policy drafts for filtering and organization." ) public ResponseEntity> getDraftTags() { log.debug("Retrieving draft tags"); List tags = policyService.getDraftTags(); return ResponseEntity.ok(tags); } @GetMapping("/statistics") @PreAuthorize("hasAnyAuthority('ROLE_ADMIN', 'ROLE_POLICY_MANAGER')") @Operation( summary = "Get draft statistics", description = "Retrieves statistical information about policy drafts grouped by status for dashboard displays." ) public ResponseEntity> getDraftStatistics() { log.debug("Retrieving draft statistics"); List statistics = policyService.getDraftStatistics(); return ResponseEntity.ok(statistics); } }