From b1ae676a08054f57ec9edb9125664ed9ba4f04c6 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 20 Oct 2025 17:32:10 +0300 Subject: [PATCH 1/8] feat: add dockerfile and docker compose --- Dockerfile | 56 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yaml | 13 +++++++++++ 2 files changed, 69 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yaml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..48d3360 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,56 @@ +FROM amazoncorretto:21.0.8 AS builder + +# Set working directory +WORKDIR /app + +# Install tar and gzip (required for mvnw to unpack Maven) +RUN yum install -y tar gzip && yum clean all + +COPY mvnw ./ +COPY .mvn ./.mvn +COPY pom.xml ./ +COPY lombok.config ./ + +# Ensure mvnw is executable +RUN chmod +x mvnw + +# Set up Maven local repository for caching +ENV MAVEN_OPTS="-Dmaven.repo.local=/app/.m2/repository" + +# Download dependencies (caching layer) +RUN ./mvnw dependency:go-offline -B + +# Copy source code +COPY src ./src + +# Build the application (skip tests for faster build) +RUN ./mvnw clean package -DskipTests -Dcheckstyle.skip=true + +# Stage 2: Layers stage - Use Amazon Corretto JDK for layer extraction +FROM amazoncorretto:21.0.8-alpine AS layers +WORKDIR /app +COPY --from=builder /app/target/*.jar app.jar +# Extract layers from the JAR for better caching in runtime (Spring Boot specific best practice) +RUN java -Djarmode=layertools -jar app.jar extract + +# Stage 3: Runtime stage - Use a slim Amazon Corretto image for smaller footprint +FROM amazoncorretto:21.0.8-alpine + +# Set non-root user for security +RUN addgroup -g 1001 appgroup && adduser -u 1001 -G appgroup -s /bin/sh -D appuser +USER appuser + +# Set working directory +WORKDIR /app + +# Copy extracted layers from builder stage +COPY --from=layers /app/dependencies/ ./ +COPY --from=layers /app/spring-boot-loader/ ./ +COPY --from=layers /app/snapshot-dependencies/ ./ +COPY --from=layers /app/application/ ./ + +# Expose the application port +EXPOSE 8080 + +# Entry point to run the application +ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"] diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..086972f --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,13 @@ +services: + filmorate: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" + volumes: + - maven-repo:/app/.m2/repository + - ./db:/app/db + +volumes: + maven-repo: {} From 3db9f7f8f6b69101aeab090367e0e128406dcf39 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 20 Oct 2025 19:48:00 +0300 Subject: [PATCH 2/8] fix: removed @Validated unnecessary annotations --- .../practicum/filmorate/controller/DirectorController.java | 2 -- .../yandex/practicum/filmorate/controller/FilmController.java | 2 -- .../yandex/practicum/filmorate/controller/UserController.java | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java index b8ffea9..1cb9e1e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/DirectorController.java @@ -36,14 +36,12 @@ public DirectorDto findById(@PathVariable @Positive long id) { @PostMapping @ResponseStatus(HttpStatus.CREATED) - @Validated public DirectorDto create(@RequestBody @Valid NewDirectorRequest request) { log.trace("Create new director requested: {}", request); return directorService.create(request); } @PutMapping - @Validated public DirectorDto update(@RequestBody @Valid UpdateDirectorRequest request) { log.trace("Update director requested: {}", request); return directorService.update(request); diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java index a86e957..d3e545f 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -44,14 +44,12 @@ public void deleteById(@PathVariable @Positive long filmId) { @PostMapping @ResponseStatus(HttpStatus.CREATED) - @Validated public FilmDto create(@RequestBody @Valid NewFilmRequest request) { log.trace("Create new film requested {}", request); return filmService.create(request); } @PutMapping - @Validated public FilmDto update(@RequestBody @Valid UpdateFilmRequest request) { log.trace("Update film requested {}", request); return filmService.update(request); diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java index 397b773..a4ca3c7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/UserController.java @@ -46,14 +46,12 @@ public void deleteById(@PathVariable @Positive long userId) { @PostMapping @ResponseStatus(HttpStatus.CREATED) - @Validated public UserDto create(@RequestBody @Valid NewUserRequest user) { log.trace("create new user requested {}", user); return userService.create(user); } @PutMapping - @Validated public UserDto update(@RequestBody @Valid UpdateUserRequest user) { log.trace("Update user requested {}", user); return userService.update(user); From d168b009580ee3df9c109a90fa949f7685764695 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 20 Oct 2025 20:04:26 +0300 Subject: [PATCH 3/8] fix: update Dockerfile --- Dockerfile | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 48d3360..183ee28 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,39 +1,31 @@ -FROM amazoncorretto:21.0.8 AS builder +FROM maven:3.9.11-amazoncorretto-21-alpine AS builder # Set working directory WORKDIR /app -# Install tar and gzip (required for mvnw to unpack Maven) -RUN yum install -y tar gzip && yum clean all - -COPY mvnw ./ -COPY .mvn ./.mvn COPY pom.xml ./ COPY lombok.config ./ -# Ensure mvnw is executable -RUN chmod +x mvnw - # Set up Maven local repository for caching ENV MAVEN_OPTS="-Dmaven.repo.local=/app/.m2/repository" # Download dependencies (caching layer) -RUN ./mvnw dependency:go-offline -B +RUN mvn dependency:go-offline -B # Copy source code COPY src ./src # Build the application (skip tests for faster build) -RUN ./mvnw clean package -DskipTests -Dcheckstyle.skip=true +RUN mvn clean package -DskipTests -Dcheckstyle.skip=true -# Stage 2: Layers stage - Use Amazon Corretto JDK for layer extraction +# Stage 2: Layers stage FROM amazoncorretto:21.0.8-alpine AS layers WORKDIR /app COPY --from=builder /app/target/*.jar app.jar -# Extract layers from the JAR for better caching in runtime (Spring Boot specific best practice) +# Extract layers from the JAR for better caching in runtime RUN java -Djarmode=layertools -jar app.jar extract -# Stage 3: Runtime stage - Use a slim Amazon Corretto image for smaller footprint +# Stage 3: Runtime stage FROM amazoncorretto:21.0.8-alpine # Set non-root user for security From 1436f05693baa18d7d75355a5fc1264b52efe3a0 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 20 Oct 2025 20:56:33 +0300 Subject: [PATCH 4/8] fix: changed timestamp name in bd to created_at --- .../practicum/filmorate/repository/feed/EventRowMapper.java | 4 ++-- src/main/resources/schema.sql | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/repository/feed/EventRowMapper.java b/src/main/java/ru/yandex/practicum/filmorate/repository/feed/EventRowMapper.java index de91215..68994d6 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/repository/feed/EventRowMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/repository/feed/EventRowMapper.java @@ -15,11 +15,11 @@ public class EventRowMapper implements RowMapper { public Event mapRow(ResultSet rs, int rowNum) throws SQLException { return Event.builder() .eventId(rs.getLong("event_id")) - .timestamp(rs.getLong("timestamp")) + .timestamp(rs.getLong("created_at")) .userId(rs.getLong("user_id")) .eventType(EventType.valueOf(rs.getString("event_type"))) .operation(Operation.valueOf(rs.getString("operation"))) .entityId(rs.getLong("entity_id")) .build(); } -} \ No newline at end of file +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 40ecaab..0dc4b91 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS review_likes CREATE TABLE IF NOT EXISTS feed_events ( event_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - timestamp BIGINT NOT NULL, + created_at BIGINT NOT NULL, user_id BIGINT NOT NULL, event_type ENUM('LIKE', 'REVIEW', 'FRIEND') NOT NULL, operation ENUM('REMOVE', 'ADD', 'UPDATE') NOT NULL, From f1bc2b185de2ceabd43f0a52ffe46e66d227b82a Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 20 Oct 2025 21:26:25 +0300 Subject: [PATCH 5/8] chore: changed return dto's to records --- .../dto/director/NewDirectorRequest.java | 3 +++ .../filmorate/dto/event/EventDto.java | 17 +++++------- .../practicum/filmorate/dto/film/FilmDto.java | 27 +++++++------------ .../filmorate/dto/review/ReviewDto.java | 23 +++++----------- .../practicum/filmorate/dto/user/UserDto.java | 22 +++++---------- .../filmorate/mapper/EventMapper.java | 16 +++++------ .../filmorate/mapper/FilmMapper.java | 26 +++++++++--------- .../filmorate/mapper/ReviewMapper.java | 17 ++++++------ .../filmorate/mapper/UserMapper.java | 13 +++++---- 9 files changed, 64 insertions(+), 100 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/director/NewDirectorRequest.java b/src/main/java/ru/yandex/practicum/filmorate/dto/director/NewDirectorRequest.java index cf7c130..8f118b8 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/director/NewDirectorRequest.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/director/NewDirectorRequest.java @@ -1,9 +1,12 @@ package ru.yandex.practicum.filmorate.dto.director; import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; import lombok.Data; +import lombok.experimental.FieldDefaults; @Data +@FieldDefaults(level = AccessLevel.PRIVATE) public class NewDirectorRequest { @NotBlank String name; diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/event/EventDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/event/EventDto.java index bcd9a40..306c8f6 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/event/EventDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/event/EventDto.java @@ -1,18 +1,13 @@ package ru.yandex.practicum.filmorate.dto.event; -import lombok.Builder; -import lombok.Data; import ru.yandex.practicum.filmorate.model.EventType; import ru.yandex.practicum.filmorate.model.Operation; -@Data -@Builder -public class EventDto { - private Long timestamp; - private Long userId; - private EventType eventType; - private Operation operation; - private Long eventId; - private Long entityId; +public record EventDto(Long timestamp, + Long userId, + EventType eventType, + Operation operation, + Long eventId, + Long entityId) { } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/film/FilmDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/film/FilmDto.java index 2536314..58753ed 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/film/FilmDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/film/FilmDto.java @@ -1,10 +1,5 @@ package ru.yandex.practicum.filmorate.dto.film; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Data; -import lombok.Singular; -import lombok.experimental.FieldDefaults; import ru.yandex.practicum.filmorate.dto.director.DirectorDto; import ru.yandex.practicum.filmorate.dto.genre.GenreDto; import ru.yandex.practicum.filmorate.dto.mparating.MPARatingDto; @@ -12,17 +7,13 @@ import java.time.LocalDate; import java.util.List; -@Data -@Builder -@FieldDefaults(level = AccessLevel.PRIVATE) -public class FilmDto { - Long id; - String name; - String description; - LocalDate releaseDate; - Integer duration; - MPARatingDto mpa; - @Singular - List genres; - List directors; +public record FilmDto( + Long id, + String name, + String description, + LocalDate releaseDate, + Integer duration, + MPARatingDto mpa, + List genres, + List directors) { } diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/review/ReviewDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/review/ReviewDto.java index 0c61df4..62c89a2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/review/ReviewDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/review/ReviewDto.java @@ -1,19 +1,10 @@ package ru.yandex.practicum.filmorate.dto.review; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Data; -import lombok.experimental.FieldDefaults; - -@Data -@Builder -@FieldDefaults(level = AccessLevel.PRIVATE) -public class ReviewDto { - Long reviewId; - String content; - Boolean isPositive; - Long userId; - Long filmId; - Long useful; +public record ReviewDto( + Long reviewId, + String content, + Boolean isPositive, + Long userId, + Long filmId, + Long useful) { } - diff --git a/src/main/java/ru/yandex/practicum/filmorate/dto/user/UserDto.java b/src/main/java/ru/yandex/practicum/filmorate/dto/user/UserDto.java index 531d172..edd64ff 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/dto/user/UserDto.java +++ b/src/main/java/ru/yandex/practicum/filmorate/dto/user/UserDto.java @@ -1,21 +1,11 @@ package ru.yandex.practicum.filmorate.dto.user; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Data; -import lombok.experimental.FieldDefaults; - import java.time.LocalDate; -@Data -@Builder -@FieldDefaults(level = AccessLevel.PRIVATE) -public class UserDto { - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - long id; - String email; - String login; - String name; - LocalDate birthday; +public record UserDto( + long id, + String email, + String login, + String name, + LocalDate birthday) { } diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java index 9ac3f6f..d02e99e 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/EventMapper.java @@ -8,14 +8,12 @@ public class EventMapper { public EventDto toEventDto(Event event) { - return EventDto.builder() - .timestamp(event.getTimestamp()) - .userId(event.getUserId()) - .eventType(event.getEventType()) - .operation(event.getOperation()) - .eventId(event.getEventId()) - .entityId(event.getEntityId()) - .build(); + return new EventDto( + event.getTimestamp(), + event.getUserId(), + event.getEventType(), + event.getOperation(), + event.getEventId(), + event.getEntityId()); } } - diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java index b97e912..3a048bb 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/FilmMapper.java @@ -4,28 +4,26 @@ import ru.yandex.practicum.filmorate.dto.film.FilmDto; import ru.yandex.practicum.filmorate.dto.film.NewFilmRequest; import ru.yandex.practicum.filmorate.dto.film.UpdateFilmRequest; -import ru.yandex.practicum.filmorate.dto.genre.GenreDto; import ru.yandex.practicum.filmorate.model.Film; @UtilityClass public class FilmMapper { public FilmDto toFilmDto(Film film) { - return FilmDto.builder() - .id(film.getId()) - .description(film.getDescription()) - .name(film.getName()) - .releaseDate(film.getReleaseDate()) - .duration(film.getDuration()) - .mpa(MPARatingMapper.toMPARatingDto(film.getMpaRating())) - .genres(film.getGenres() + return new FilmDto( + film.getId(), + film.getName(), + film.getDescription(), + film.getReleaseDate(), + film.getDuration(), + MPARatingMapper.toMPARatingDto(film.getMpaRating()), + film.getGenres() .stream() - .map(genre -> new GenreDto(genre.getId(), genre.getName())) - .toList()) - .directors(film.getDirectors() + .map(GenreMapper::toGenreDto) + .toList(), + film.getDirectors() .stream() .map(DirectorMapper::toDirectorDto) - .toList()) - .build(); + .toList()); } public Film toFilm(NewFilmRequest request) { diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/ReviewMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/ReviewMapper.java index c51ba37..1911f92 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mapper/ReviewMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/ReviewMapper.java @@ -9,14 +9,13 @@ @UtilityClass public class ReviewMapper { public ReviewDto toDto(Review review) { - return ReviewDto.builder() - .reviewId(review.getId()) - .content(review.getContent()) - .isPositive(review.getIsPositive()) - .userId(review.getUserId()) - .filmId(review.getFilmId()) - .useful(review.getUsefulRating()) - .build(); + return new ReviewDto( + review.getId(), + review.getContent(), + review.getIsPositive(), + review.getUserId(), + review.getFilmId(), + review.getUsefulRating()); } public Review toReview(NewReviewRequest request) { @@ -37,4 +36,4 @@ public Review updateReviewFields(Review review, UpdateReviewRequest request) { } return review; } -} \ No newline at end of file +} diff --git a/src/main/java/ru/yandex/practicum/filmorate/mapper/UserMapper.java b/src/main/java/ru/yandex/practicum/filmorate/mapper/UserMapper.java index 099ad58..cc1391a 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/mapper/UserMapper.java +++ b/src/main/java/ru/yandex/practicum/filmorate/mapper/UserMapper.java @@ -10,13 +10,12 @@ @UtilityClass public class UserMapper { public UserDto toUserDto(User user) { - return UserDto.builder() - .id(user.getId()) - .email(user.getEmail()) - .login(user.getLogin()) - .name(user.getName()) - .birthday(user.getBirthday()) - .build(); + return new UserDto( + user.getId(), + user.getEmail(), + user.getLogin(), + user.getName(), + user.getBirthday()); } public User mapToUser(NewUserRequest request) { From c8ba944ef298542efc5927af252b7e39cf2ac5cc Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 20 Oct 2025 21:26:55 +0300 Subject: [PATCH 6/8] fix: changed null to optional --- .../filmorate/controller/FilmController.java | 15 ++++----------- .../filmorate/controller/FilmsSortBy.java | 10 ++++++---- .../filmorate/controller/SearchFilmsBy.java | 18 +++++++++++------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java index d3e545f..d82a7e3 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmController.java @@ -74,13 +74,8 @@ public Collection searchFilms(@RequestParam @NotBlank String query, @RequestParam(defaultValue = "title") String by) { log.trace("Search films requested with query: {}, by: {}", query, by); - List parsedInput = SearchFilmsBy.parseStr(by); - - for (SearchFilmsBy searchFilmsBy : parsedInput) { - if (searchFilmsBy == null) { - throw new IllegalArgumentException("Unknown search params %s".formatted(by)); - } - } + List parsedInput = SearchFilmsBy.parseStrOrThrow(by, () -> + new IllegalArgumentException("Invalid search films requested: " + by)); return filmService.searchFilms(query, parsedInput); } @@ -104,10 +99,8 @@ public Collection findPopular(@RequestParam(defaultValue = "10") @Posit public Collection findFilmsOfDirector(@PathVariable @Positive long directorId, @RequestParam String sortBy) { - FilmsSortBy sortFilmsBy = FilmsSortBy.fromString(sortBy); - if (sortFilmsBy == null) { - throw new IllegalArgumentException("invalid sort by: %s".formatted(sortBy)); - } + FilmsSortBy sortFilmsBy = FilmsSortBy.fromString(sortBy).orElseThrow( + () -> new IllegalArgumentException("invalid sort by: %s".formatted(sortBy))); log.trace("Find films of director with id {} requested", directorId); return filmService.findFilmsOfDirector(directorId, sortFilmsBy); diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmsSortBy.java b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmsSortBy.java index 0fac786..dc2fda2 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/FilmsSortBy.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/FilmsSortBy.java @@ -1,14 +1,16 @@ package ru.yandex.practicum.filmorate.controller; +import java.util.Optional; + public enum FilmsSortBy { YEAR, LIKES; - public static FilmsSortBy fromString(String sortBy) { + public static Optional fromString(String sortBy) { return switch (sortBy.toLowerCase()) { - case "year" -> YEAR; - case "likes" -> LIKES; - default -> null; + case "year" -> Optional.of(YEAR); + case "likes" -> Optional.of(LIKES); + default -> Optional.empty(); }; } } diff --git a/src/main/java/ru/yandex/practicum/filmorate/controller/SearchFilmsBy.java b/src/main/java/ru/yandex/practicum/filmorate/controller/SearchFilmsBy.java index 2e598f8..99c1434 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/controller/SearchFilmsBy.java +++ b/src/main/java/ru/yandex/practicum/filmorate/controller/SearchFilmsBy.java @@ -2,8 +2,11 @@ import lombok.Getter; +import java.awt.*; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; @Getter public enum SearchFilmsBy { @@ -16,23 +19,24 @@ public enum SearchFilmsBy { this.value = value; } - public static List parseStr(String value) { + public static List parseStrOrThrow(String value, + Supplier exceptionSupplier) throws T { String[] splittedStr = value.trim().toLowerCase().split(","); List parsedList = new ArrayList<>(); for (String str : splittedStr) { - SearchFilmsBy searchFilmsBy = SearchFilmsBy.fromString(str); - parsedList.add(searchFilmsBy); + parsedList.add(SearchFilmsBy.fromString(str) + .orElseThrow(exceptionSupplier)); } return parsedList; } - public static SearchFilmsBy fromString(String value) { + public static Optional fromString(String value) { return switch (value) { - case "title" -> SearchFilmsBy.TITLE; - case "director" -> SearchFilmsBy.DIRECTOR; - default -> null; + case "title" -> Optional.of(TITLE); + case "director" -> Optional.of(DIRECTOR); + default -> Optional.empty(); }; } From 612f92c99c31066f88f14d5147d3064ca2108e41 Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 20 Oct 2025 21:27:17 +0300 Subject: [PATCH 7/8] fix: corrected likes count --- .../filmorate/repository/film/JdbcFilmRepository.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/repository/film/JdbcFilmRepository.java b/src/main/java/ru/yandex/practicum/filmorate/repository/film/JdbcFilmRepository.java index 253b22a..0f3b4d7 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/repository/film/JdbcFilmRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/repository/film/JdbcFilmRepository.java @@ -125,7 +125,7 @@ public Collection findTopPopularFilms(int count, Integer genreId, Integer WHERE (g.genre_id = :genreId OR :genreId IS NULL) AND (YEAR(f.release_date) =:year OR :year IS NULL) GROUP BY f.film_id - ORDER BY COUNT(fl.user_id) DESC, f.film_id + ORDER BY COUNT(DISTINCT fl.user_id) DESC, f.film_id LIMIT :count"""); MapSqlParameterSource params = new MapSqlParameterSource() .addValue("count", count) @@ -147,7 +147,7 @@ WHERE f.film_id in (SELECT l1.film_id FROM likes l2 WHERE l2.user_id = :friend_id) GROUP BY f.film_id, g.genre_id - ORDER BY COUNT(fl.user_id) DESC, f.film_id + ORDER BY COUNT(DISTINCT fl.user_id) DESC, f.film_id """); MapSqlParameterSource params = new MapSqlParameterSource() .addValue("user_id", userId) @@ -160,7 +160,7 @@ ORDER BY COUNT(fl.user_id) DESC, f.film_id public Collection findFilmsOfDirector(long directorId, FilmsSortBy sortFilmsBy) { String sortBySql = switch (sortFilmsBy) { case YEAR -> "EXTRACT(YEAR from f.release_date)"; - case LIKES -> "COUNT(fl.user_id) DESC"; + case LIKES -> "COUNT(DISTINCT fl.user_id) DESC"; }; String selectFilmsOfDirectorSortedSql = BASE_SELECT_SQL.concat(""" From 530a831a425dbc211c1ba8964ca6280f3922ec7c Mon Sep 17 00:00:00 2001 From: Ilia Egorov Date: Mon, 20 Oct 2025 21:27:39 +0300 Subject: [PATCH 8/8] fix: changed timestamp to created_at --- .../filmorate/repository/feed/JdbcFeedRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/yandex/practicum/filmorate/repository/feed/JdbcFeedRepository.java b/src/main/java/ru/yandex/practicum/filmorate/repository/feed/JdbcFeedRepository.java index c2b17c2..cffbeda 100644 --- a/src/main/java/ru/yandex/practicum/filmorate/repository/feed/JdbcFeedRepository.java +++ b/src/main/java/ru/yandex/practicum/filmorate/repository/feed/JdbcFeedRepository.java @@ -17,7 +17,7 @@ public class JdbcFeedRepository implements FeedRepository { @Override public List findByUserId(long userId) { - String sql = "SELECT * FROM feed_events WHERE user_id = :user_id ORDER BY timestamp ASC"; + String sql = "SELECT * FROM feed_events WHERE user_id = :user_id ORDER BY created_at ASC"; return jdbc.query(sql, new MapSqlParameterSource("user_id", userId), eventRowMapper); @@ -33,7 +33,7 @@ public Event save(Event event) { .addValue("operation", event.getOperation().toString()) .addValue("entity_id", event.getEntityId()); String sql = """ - INSERT INTO feed_events (timestamp, user_id, event_type, operation, entity_id) + INSERT INTO feed_events (created_at, user_id, event_type, operation, entity_id) VALUES (:timestamp, :user_id, :event_type, :operation, :entity_id) """; jdbc.update(sql, params, keyHolder, new String[]{"event_id"});