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);
    }
}