Skip to content

Commit b245185

Browse files
authored
Update CommentAndAttachmentController.java
1 parent 77f1d70 commit b245185

1 file changed

Lines changed: 76 additions & 187 deletions

File tree

Lines changed: 76 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,21 @@
11
package com.cognizant.controller;
22

33
import java.io.IOException;
4-
import java.nio.file.Files;
5-
import java.nio.file.Path;
6-
import java.nio.file.Paths;
7-
import java.nio.file.StandardCopyOption;
8-
import java.util.List;
9-
import java.util.Optional;
10-
import java.util.UUID;
4+
import java.nio.file.*;
5+
import java.util.*;
116
import java.util.stream.Collectors;
127

138
import org.slf4j.Logger;
149
import org.slf4j.LoggerFactory;
1510
import org.springframework.beans.factory.annotation.Value;
16-
import org.springframework.core.io.Resource;
17-
import org.springframework.core.io.UrlResource;
18-
import org.springframework.http.HttpHeaders;
19-
import org.springframework.http.HttpStatus;
20-
import org.springframework.http.MediaType;
21-
import org.springframework.http.ResponseEntity;
11+
import org.springframework.core.io.*;
12+
import org.springframework.http.*;
2213
import org.springframework.web.bind.annotation.*;
2314
import org.springframework.web.multipart.MultipartFile;
2415

25-
import com.cognizant.dto.AddCommentRequest;
26-
import com.cognizant.dto.AttachmentDTO;
27-
import com.cognizant.dto.CommentDTO;
28-
import com.cognizant.entities.Attachment;
29-
import com.cognizant.entities.Comment;
30-
import com.cognizant.entities.Defect;
31-
import com.cognizant.repositries.AttachmentRepository;
32-
import com.cognizant.repositries.CommentRepository;
33-
import com.cognizant.repositries.DefectEntityRepository;
34-
16+
import com.cognizant.dto.*;
17+
import com.cognizant.entities.*;
18+
import com.cognizant.repositries.*;
3519
import io.swagger.v3.oas.annotations.Operation;
3620
import io.swagger.v3.oas.annotations.tags.Tag;
3721

@@ -42,170 +26,84 @@
4226
public class CommentAndAttachmentController {
4327

4428
private final Logger logger = LoggerFactory.getLogger(CommentAndAttachmentController.class);
45-
46-
private final CommentRepository commentRepository;
29+
private final CommentRepository commentRepository;
4730
private final AttachmentRepository attachmentRepository;
4831
private final DefectEntityRepository defectRepository;
4932

50-
// Where uploaded files are saved on disk
51-
// Set this in application.properties: app.upload.dir=./uploads
52-
@Value("${app.upload.dir:./uploads}")
33+
@Value("${app.upload.dir:/home/uploads}")
5334
private String uploadDir;
5435

55-
public CommentAndAttachmentController(
56-
CommentRepository commentRepository,
57-
AttachmentRepository attachmentRepository,
58-
DefectEntityRepository defectRepository) {
59-
this.commentRepository = commentRepository;
36+
public CommentAndAttachmentController(CommentRepository commentRepository,
37+
AttachmentRepository attachmentRepository,
38+
DefectEntityRepository defectRepository) {
39+
this.commentRepository = commentRepository;
6040
this.attachmentRepository = attachmentRepository;
61-
this.defectRepository = defectRepository;
41+
this.defectRepository = defectRepository;
6242
}
6343

64-
// ════════════════════════════════════════════════════════════════
65-
// COMMENTS
66-
// ════════════════════════════════════════════════════════════════
44+
// --- COMMENTS ---
6745

68-
// GET /api/defects/{defectId}/comments — list all comments on a bug
69-
@Operation(description = "Get all comments for a defect (oldest first)")
46+
@Operation(description = "Get all comments for a defect")
7047
@GetMapping("/defects/{defectId}/comments")
7148
public ResponseEntity<?> getComments(@PathVariable Integer defectId) {
72-
Optional<Defect> opt = defectRepository.findById(defectId);
73-
if (opt.isEmpty()) {
74-
return ResponseEntity.status(HttpStatus.NOT_FOUND)
75-
.body("{\"error\": \"Defect not found\"}");
76-
}
77-
List<Comment> comments = commentRepository
78-
.findByDefectOrderByCreatedAtAsc(opt.get());
79-
80-
List<CommentDTO> dtos = comments.stream().map(c -> {
81-
CommentDTO dto = new CommentDTO();
82-
dto.setId(c.getId());
83-
dto.setDefectId(defectId);
84-
dto.setAuthor(c.getAuthor());
85-
dto.setAuthorRole(c.getAuthorRole());
86-
dto.setContent(c.getContent());
87-
dto.setCreatedAt(c.getCreatedAt());
88-
return dto;
89-
}).collect(Collectors.toList());
90-
91-
return ResponseEntity.ok(dtos);
49+
return defectRepository.findById(defectId)
50+
.map(defect -> ResponseEntity.ok(commentRepository.findByDefectOrderByCreatedAtAsc(defect)
51+
.stream().map(c -> toCommentDTO(c, defectId)).collect(Collectors.toList())))
52+
.orElse(ResponseEntity.status(HttpStatus.NOT_FOUND).body(Collections.singletonMap("error", "Defect not found")));
9253
}
9354

94-
// POST /api/defects/{defectId}/comments — add a comment
95-
@Operation(description = "Add a comment to a defect (tester or developer)")
55+
@Operation(description = "Add a comment to a defect")
9656
@PostMapping("/defects/{defectId}/comments")
97-
public ResponseEntity<?> addComment(
98-
@PathVariable Integer defectId,
99-
@RequestBody AddCommentRequest request) {
100-
101-
logger.info("Adding comment to defect #{} by {}", defectId, request.getAuthor());
102-
57+
public ResponseEntity<?> addComment(@PathVariable Integer defectId, @RequestBody AddCommentRequest request) {
10358
if (request.getContent() == null || request.getContent().trim().isEmpty()) {
104-
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
105-
.body("{\"error\": \"Comment content cannot be empty\"}");
106-
}
107-
108-
Optional<Defect> opt = defectRepository.findById(defectId);
109-
if (opt.isEmpty()) {
110-
return ResponseEntity.status(HttpStatus.NOT_FOUND)
111-
.body("{\"error\": \"Defect not found\"}");
59+
return ResponseEntity.badRequest().body(Collections.singletonMap("error", "Comment content empty"));
11260
}
113-
114-
Comment comment = new Comment();
115-
comment.setDefect(opt.get());
116-
comment.setAuthor(request.getAuthor());
117-
comment.setAuthorRole(request.getAuthorRole());
118-
comment.setContent(request.getContent().trim());
119-
120-
Comment saved = commentRepository.save(comment);
121-
122-
CommentDTO dto = new CommentDTO();
123-
dto.setId(saved.getId());
124-
dto.setDefectId(defectId);
125-
dto.setAuthor(saved.getAuthor());
126-
dto.setAuthorRole(saved.getAuthorRole());
127-
dto.setContent(saved.getContent());
128-
dto.setCreatedAt(saved.getCreatedAt());
129-
130-
return ResponseEntity.status(HttpStatus.CREATED).body(dto);
61+
return defectRepository.findById(defectId).map(defect -> {
62+
Comment comment = new Comment();
63+
comment.setDefect(defect);
64+
comment.setAuthor(request.getAuthor());
65+
comment.setAuthorRole(request.getAuthorRole());
66+
comment.setContent(request.getContent().trim());
67+
return ResponseEntity.status(HttpStatus.CREATED).body(toCommentDTO(commentRepository.save(comment), defectId));
68+
}).orElse(ResponseEntity.notFound().build());
13169
}
13270

133-
// DELETE /api/comments/{commentId} — delete a comment (author only)
13471
@Operation(description = "Delete a comment by ID")
13572
@DeleteMapping("/comments/{commentId}")
13673
public ResponseEntity<?> deleteComment(@PathVariable Integer commentId) {
137-
if (!commentRepository.existsById(commentId)) {
138-
return ResponseEntity.status(HttpStatus.NOT_FOUND)
139-
.body("{\"error\": \"Comment not found\"}");
140-
}
74+
if (!commentRepository.existsById(commentId)) return ResponseEntity.notFound().build();
14175
commentRepository.deleteById(commentId);
142-
return ResponseEntity.ok("{\"message\": \"Comment deleted\"}");
76+
return ResponseEntity.ok(Collections.singletonMap("message", "Comment deleted"));
14377
}
14478

145-
// ════════════════════════════════════════════════════════════════
146-
// ATTACHMENTS
147-
// ════════════════════════════════════════════════════════════════
79+
// --- ATTACHMENTS ---
14880

149-
// GET /api/defects/{defectId}/attachments — list attachments
15081
@Operation(description = "List all attachments for a defect")
15182
@GetMapping("/defects/{defectId}/attachments")
15283
public ResponseEntity<?> getAttachments(@PathVariable Integer defectId) {
153-
Optional<Defect> opt = defectRepository.findById(defectId);
154-
if (opt.isEmpty()) {
155-
return ResponseEntity.status(HttpStatus.NOT_FOUND)
156-
.body("{\"error\": \"Defect not found\"}");
157-
}
158-
List<Attachment> attachments = attachmentRepository.findByDefect(opt.get());
159-
List<AttachmentDTO> dtos = attachments.stream().map(a -> toDTO(a, defectId)).collect(Collectors.toList());
160-
return ResponseEntity.ok(dtos);
84+
return defectRepository.findById(defectId)
85+
.map(defect -> ResponseEntity.ok(attachmentRepository.findByDefect(defect)
86+
.stream().map(a -> toDTO(a, defectId)).collect(Collectors.toList())))
87+
.orElse(ResponseEntity.notFound().build());
16188
}
16289

163-
// POST /api/defects/{defectId}/attachments — upload a file
164-
@Operation(description = "Upload a file attachment to a defect (screenshot, log, etc.)")
90+
@Operation(description = "Upload a file attachment")
16591
@PostMapping(value = "/defects/{defectId}/attachments", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
166-
public ResponseEntity<?> uploadAttachment(
167-
@PathVariable Integer defectId,
168-
@RequestParam MultipartFile file,
169-
@RequestParam String uploadedBy) {
170-
171-
logger.info("Uploading attachment '{}' for defect #{} by {}",
172-
file.getOriginalFilename(), defectId, uploadedBy);
173-
174-
// 1. Validate defect exists
92+
public ResponseEntity<?> uploadAttachment(@PathVariable Integer defectId, @RequestParam MultipartFile file, @RequestParam String uploadedBy) {
17593
Optional<Defect> opt = defectRepository.findById(defectId);
176-
if (opt.isEmpty()) {
177-
return ResponseEntity.status(HttpStatus.NOT_FOUND)
178-
.body("{\"error\": \"Defect not found\"}");
179-
}
180-
181-
// 2. Validate file not empty
182-
if (file.isEmpty()) {
183-
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
184-
.body("{\"error\": \"File is empty\"}");
185-
}
186-
187-
// 3. Validate file size (max 10 MB)
188-
if (file.getSize() > 10 * 1024 * 1024) {
189-
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
190-
.body("{\"error\": \"File too large. Max 10MB.\"}");
191-
}
94+
if (opt.isEmpty()) return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Collections.singletonMap("error", "Defect not found"));
95+
if (file.isEmpty()) return ResponseEntity.badRequest().body(Collections.singletonMap("error", "File is empty"));
19296

193-
// 4. Save file to disk
19497
try {
19598
Path uploadPath = Paths.get(uploadDir).toAbsolutePath().normalize();
196-
Files.createDirectories(uploadPath);
99+
if (!Files.exists(uploadPath)) Files.createDirectories(uploadPath);
197100

198-
// Generate a unique filename to avoid collisions
199-
String original = file.getOriginalFilename();
200-
String extension = original != null && original.contains(".")
201-
? original.substring(original.lastIndexOf('.'))
202-
: "";
101+
String original = file.getOriginalFilename();
102+
String extension = original != null && original.contains(".") ? original.substring(original.lastIndexOf('.')) : "";
203103
String storedName = UUID.randomUUID().toString().replace("-", "") + extension;
204104

205-
Path targetPath = uploadPath.resolve(storedName);
206-
Files.copy(file.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING);
105+
Files.copy(file.getInputStream(), uploadPath.resolve(storedName), StandardCopyOption.REPLACE_EXISTING);
207106

208-
// 5. Save metadata to DB
209107
Attachment attachment = new Attachment();
210108
attachment.setDefect(opt.get());
211109
attachment.setOriginalName(original);
@@ -214,63 +112,43 @@ public ResponseEntity<?> uploadAttachment(
214112
attachment.setFileSize(file.getSize());
215113
attachment.setUploadedBy(uploadedBy);
216114

217-
Attachment saved = attachmentRepository.save(attachment);
218-
return ResponseEntity.status(HttpStatus.CREATED).body(toDTO(saved, defectId));
219-
115+
return ResponseEntity.status(HttpStatus.CREATED).body(toDTO(attachmentRepository.save(attachment), defectId));
220116
} catch (IOException e) {
221-
logger.error("Failed to save file", e);
222-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
223-
.body("{\"error\": \"Failed to save file: " + e.getMessage() + "\"}");
117+
logger.error("Storage error", e);
118+
return ResponseEntity.internalServerError().body(Collections.singletonMap("error", "Failed to save file"));
224119
}
225120
}
226121

227-
// GET /api/attachments/download/{storedName} — download/view file
228-
@Operation(description = "Download or view an attachment by its stored filename")
122+
@Operation(description = "Download/View attachment")
229123
@GetMapping("/attachments/download/{storedName}")
230124
public ResponseEntity<Resource> downloadAttachment(@PathVariable String storedName) {
231125
try {
232126
Path filePath = Paths.get(uploadDir).toAbsolutePath().normalize().resolve(storedName);
233127
Resource resource = new UrlResource(filePath.toUri());
234-
235-
if (!resource.exists()) {
236-
return ResponseEntity.notFound().build();
237-
}
128+
if (!resource.exists()) return ResponseEntity.notFound().build();
238129

239130
String contentType = Files.probeContentType(filePath);
240-
if (contentType == null) contentType = "application/octet-stream";
241-
242131
return ResponseEntity.ok()
243-
.contentType(MediaType.parseMediaType(contentType))
244-
.header(HttpHeaders.CONTENT_DISPOSITION,
245-
"inline; filename=\"" + resource.getFilename() + "\"")
246-
.body(resource);
247-
} catch (IOException e) {
248-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
249-
}
132+
.contentType(MediaType.parseMediaType(contentType != null ? contentType : "application/octet-stream"))
133+
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + storedName + "\"")
134+
.body(resource);
135+
} catch (IOException e) { return ResponseEntity.internalServerError().build(); }
250136
}
251137

252-
// DELETE /api/attachments/{attachmentId} — delete an attachment
253-
@Operation(description = "Delete an attachment by ID (also removes file from disk)")
138+
@Operation(description = "Delete attachment")
254139
@DeleteMapping("/attachments/{attachmentId}")
255140
public ResponseEntity<?> deleteAttachment(@PathVariable Integer attachmentId) {
256-
Optional<Attachment> opt = attachmentRepository.findById(attachmentId);
257-
if (opt.isEmpty()) {
258-
return ResponseEntity.status(HttpStatus.NOT_FOUND)
259-
.body("{\"error\": \"Attachment not found\"}");
260-
}
261-
// Delete file from disk
262-
try {
263-
Path filePath = Paths.get(uploadDir).toAbsolutePath().normalize()
264-
.resolve(opt.get().getStoredName());
265-
Files.deleteIfExists(filePath);
266-
} catch (IOException e) {
267-
logger.warn("Could not delete file from disk: {}", e.getMessage());
268-
}
269-
attachmentRepository.deleteById(attachmentId);
270-
return ResponseEntity.ok("{\"message\": \"Attachment deleted\"}");
141+
return attachmentRepository.findById(attachmentId).map(a -> {
142+
try {
143+
Files.deleteIfExists(Paths.get(uploadDir).resolve(a.getStoredName()));
144+
} catch (IOException e) { logger.warn("Disk deletion failed: {}", e.getMessage()); }
145+
attachmentRepository.deleteById(attachmentId);
146+
return ResponseEntity.ok(Collections.singletonMap("message", "Deleted"));
147+
}).orElse(ResponseEntity.notFound().build());
271148
}
272149

273-
// Helper — entity to DTO
150+
// --- HELPERS ---
151+
274152
private AttachmentDTO toDTO(Attachment a, Integer defectId) {
275153
AttachmentDTO dto = new AttachmentDTO();
276154
dto.setId(a.getId());
@@ -284,4 +162,15 @@ private AttachmentDTO toDTO(Attachment a, Integer defectId) {
284162
dto.setDownloadUrl("/api/attachments/download/" + a.getStoredName());
285163
return dto;
286164
}
165+
166+
private CommentDTO toCommentDTO(Comment c, Integer defectId) {
167+
CommentDTO dto = new CommentDTO();
168+
dto.setId(c.getId());
169+
dto.setDefectId(defectId);
170+
dto.setAuthor(c.getAuthor());
171+
dto.setAuthorRole(c.getAuthorRole());
172+
dto.setContent(c.getContent());
173+
dto.setCreatedAt(c.getCreatedAt());
174+
return dto;
175+
}
287176
}

0 commit comments

Comments
 (0)