Skip to content

Commit 44d848d

Browse files
feat: add log file search by name (#540)
1 parent c259a53 commit 44d848d

9 files changed

Lines changed: 661 additions & 14 deletions

File tree

src/main/java/com/itasocialacademy/oitassist/core/web/GlobalExceptionHandler.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -250,26 +250,31 @@ public ResponseEntity<ErrorResponse> handleMissingServletRequestPart(
250250
/**
251251
* Handles {@link MissingServletRequestParameterException} by generating an
252252
* appropriate error response. This exception is thrown when a required request
253-
* parameter is missing.
253+
* parameter is missing from the HTTP request.
254254
*
255255
* @param ex the exception object containing details of the missing request
256256
* parameter
257257
* @param request the HTTP request that triggered the exception
258258
* @return a {@link ResponseEntity} containing an {@link ErrorResponse} with
259-
* error details, including the name of the missing parameter
259+
* error details, including the name of the missing request parameter
260260
*/
261261
@ExceptionHandler(MissingServletRequestParameterException.class)
262-
public ResponseEntity<ErrorResponse> handleMissingServletRequestParameter(
263-
MissingServletRequestParameterException ex, HttpServletRequest request) {
264-
log.warn("Missing request parameter: traceId={}, parameter={}",
265-
MDC.get(TRACE_ID_MDC), ex.getParameterName());
262+
public ResponseEntity<ErrorResponse> handleMissingRequestParameter(
263+
MissingServletRequestParameterException ex,
264+
HttpServletRequest request) {
265+
log.warn(
266+
"Missing request parameter: traceId={}, parameter={}",
267+
MDC.get(TRACE_ID_MDC),
268+
ex.getParameterName());
266269

267-
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
270+
HttpStatus status = HttpStatus.BAD_REQUEST;
271+
272+
return ResponseEntity.status(status)
268273
.body(buildResponse(
269274
request,
270275
ErrorCode.COMMON_VALIDATION_FAILED,
271276
"Required request parameter '" + ex.getParameterName() + "' is not present",
272-
HttpStatus.BAD_REQUEST.value(),
273-
null));
277+
status.value(),
278+
Map.of("parameter", ex.getParameterName())));
274279
}
275280
}

src/main/java/com/itasocialacademy/oitassist/logfile/controller/LogFileController.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.itasocialacademy.oitassist.logfile.api.PageResponse;
55
import com.itasocialacademy.oitassist.logfile.service.LogFileService;
66
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.Parameter;
78
import io.swagger.v3.oas.annotations.responses.ApiResponse;
89
import io.swagger.v3.oas.annotations.responses.ApiResponses;
910
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -14,6 +15,7 @@
1415
import org.springframework.security.access.prepost.PreAuthorize;
1516
import org.springframework.web.bind.annotation.GetMapping;
1617
import org.springframework.web.bind.annotation.RequestMapping;
18+
import org.springframework.web.bind.annotation.RequestParam;
1719
import org.springframework.web.bind.annotation.RestController;
1820

1921
@RestController
@@ -47,4 +49,26 @@ public PageResponse<LogFileResponse> getAll(
4749
direction = Sort.Direction.DESC) Pageable pageable) {
4850
return logFileService.getAll(pageable);
4951
}
52+
53+
@GetMapping("/search")
54+
@Operation(
55+
summary = "Search log files by name",
56+
description = """
57+
Searches application log files by a partial file name match.
58+
The search is case-insensitive and supports pagination and sorting.
59+
Access is restricted to administrators.
60+
""")
61+
@ApiResponses({
62+
@ApiResponse(responseCode = "200",
63+
description = "Log files matching the specified name were successfully retrieved"),
64+
@ApiResponse(responseCode = "400", description = "Invalid search or sorting parameters"),
65+
@ApiResponse(responseCode = "401", description = "Unauthorized - token is missing or invalid"),
66+
@ApiResponse(responseCode = "403", description = "Forbidden - insufficient permissions")})
67+
public PageResponse<LogFileResponse> searchByName(
68+
@Parameter(
69+
description = "Full or partial log file name to search for",
70+
example = "app") @RequestParam String name,
71+
@ParameterObject @PageableDefault(size = 10) Pageable pageable) {
72+
return logFileService.searchByName(name, pageable);
73+
}
5074
}

src/main/java/com/itasocialacademy/oitassist/logfile/dao/FileSystemLogFileDao.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@
33
import com.itasocialacademy.oitassist.logfile.exceptions.LogFileListingException;
44
import java.io.IOException;
55
import java.io.UncheckedIOException;
6-
import java.nio.file.*;
6+
import java.nio.file.Files;
7+
import java.nio.file.InvalidPathException;
8+
import java.nio.file.LinkOption;
9+
import java.nio.file.NoSuchFileException;
10+
import java.nio.file.Path;
711
import java.nio.file.attribute.BasicFileAttributes;
812
import java.util.List;
13+
import java.util.Locale;
914
import java.util.Optional;
15+
import java.util.function.Predicate;
1016
import java.util.stream.Stream;
1117
import lombok.extern.slf4j.Slf4j;
1218
import org.springframework.beans.factory.annotation.Value;
@@ -25,10 +31,24 @@ public FileSystemLogFileDao(
2531

2632
@Override
2733
public List<LogFileMetadata> findAll() {
34+
return findFiles(path -> true);
35+
}
36+
37+
@Override
38+
public List<LogFileMetadata> findByNameContainingIgnoreCase(String name) {
39+
String normalizedName = name.toLowerCase(Locale.ROOT);
40+
return findFiles(path -> path.getFileName()
41+
.toString()
42+
.toLowerCase(Locale.ROOT)
43+
.contains(normalizedName));
44+
}
45+
46+
private List<LogFileMetadata> findFiles(Predicate<Path> filter) {
2847
validateLogDirectory();
2948

3049
try (Stream<Path> paths = Files.list(logDirectory)) {
3150
return paths
51+
.filter(filter)
3252
.map(this::readMetadata)
3353
.flatMap(Optional::stream)
3454
.toList();
@@ -37,6 +57,7 @@ public List<LogFileMetadata> findAll() {
3757
"Failed to read metadata from log directory: {}",
3858
logDirectory,
3959
exception.getCause());
60+
4061
throw new LogFileListingException();
4162
} catch (IOException | SecurityException exception) {
4263
log.error("Failed to access log directory: {}", logDirectory, exception);

src/main/java/com/itasocialacademy/oitassist/logfile/dao/LogFileDao.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@
44

55
public interface LogFileDao {
66
List<LogFileMetadata> findAll();
7+
8+
List<LogFileMetadata> findByNameContainingIgnoreCase(String name);
79
}

src/main/java/com/itasocialacademy/oitassist/logfile/service/LogFileService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@
66

77
public interface LogFileService {
88
PageResponse<LogFileResponse> getAll(Pageable pageable);
9+
10+
PageResponse<LogFileResponse> searchByName(String name, Pageable pageable);
911
}

src/main/java/com/itasocialacademy/oitassist/logfile/service/LogFileServiceImpl.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,33 @@ public PageResponse<LogFileResponse> getAll(Pageable pageable) {
4646
Comparator<LogFileMetadata> sortOrder =
4747
resolveSortOrder(pageable.getSort());
4848

49-
List<LogFileMetadata> sortedFiles = logFileDao.findAll().stream()
49+
return createPageResponse(logFileDao.findAll(), pageable, sortOrder);
50+
}
51+
52+
@Override
53+
public PageResponse<LogFileResponse> searchByName(String name, Pageable pageable) {
54+
if (name == null || name.isBlank()) {
55+
throw new ValidationException(
56+
"Log file name must not be blank",
57+
ErrorCode.COMMON_VALIDATION_FAILED);
58+
}
59+
String searchName = name.trim();
60+
61+
log.debug(
62+
"Searching log files by name: '{}', page={}, size={}, sort={}",
63+
searchName,
64+
pageable.getPageNumber(),
65+
pageable.getPageSize(),
66+
pageable.getSort());
67+
Comparator<LogFileMetadata> sortOrder =
68+
resolveSortOrder(pageable.getSort());
69+
70+
return createPageResponse(logFileDao.findByNameContainingIgnoreCase(searchName), pageable, sortOrder);
71+
}
72+
73+
private PageResponse<LogFileResponse> createPageResponse(List<LogFileMetadata> files,
74+
Pageable pageable, Comparator<LogFileMetadata> sortOrder) {
75+
List<LogFileMetadata> sortedFiles = files.stream()
5076
.sorted(sortOrder)
5177
.toList();
5278

@@ -56,7 +82,6 @@ public PageResponse<LogFileResponse> getAll(Pageable pageable) {
5682
.toList();
5783

5884
Page<LogFileMetadata> metadataPage = new PageImpl<>(pageContent, pageable, sortedFiles.size());
59-
6085
Page<LogFileResponse> responsePage = metadataPage.map(logFileMapper::toResponse);
6186

6287
log.debug(

0 commit comments

Comments
 (0)