Spaces:
Build error
Build error
File size: 21,358 Bytes
9373c61 5cfe5c4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 |
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<PolicyDraftOutputDTO> 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<Page<PolicyDraftOutputDTO>> 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<PolicyDraftOutputDTO> 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<PolicyDraftOutputDTO> 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<PolicyDraftOutputDTO> 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<Void> 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<PolicyDraftOutputDTO> 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<PolicyDraftOutputDTO> 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<PolicyDraftOutputDTO> 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<PolicyDraftOutputDTO> 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<PolicyOutputDTO> 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<PolicyDraftOutputDTO> 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<Page<PolicyDraftOutputDTO>> 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<PolicyDraftOutputDTO> 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<Page<PolicyDraftOutputDTO>> getDraftsPendingReview(
@PageableDefault(size = 20, sort = "submittedAt", direction = Sort.Direction.ASC) Pageable pageable) {
log.debug("Retrieving drafts pending review");
Page<PolicyDraftOutputDTO> 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<List<PolicyDraftOutputDTO>> getOverdueDrafts() {
log.debug("Retrieving overdue drafts");
List<PolicyDraftOutputDTO> 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<List<String>> getDraftCategories() {
log.debug("Retrieving draft categories");
List<String> 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<List<String>> getDraftTags() {
log.debug("Retrieving draft tags");
List<String> 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<List<Object[]>> getDraftStatistics() {
log.debug("Retrieving draft statistics");
List<Object[]> statistics = policyService.getDraftStatistics();
return ResponseEntity.ok(statistics);
}
} |