Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version>
</dependency>
</dependencies>

<build>
Expand All @@ -40,6 +49,22 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<configLocation>checkstyle.xml</configLocation>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ru.yandex.practicum.filmorate.annotation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import ru.yandex.practicum.filmorate.util.NotBeforeCinemaBirthdayValidator;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Constraint(validatedBy = NotBeforeCinemaBirthdayValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotBeforeCinemaBirthday {
String message() default "The date cannot be earlier than the cinema's birthday — December 28, 1895";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,51 @@
package ru.yandex.practicum.filmorate.controller;

import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
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.RestController;
import ru.yandex.practicum.filmorate.exception.FilmNotFoundException;
import ru.yandex.practicum.filmorate.model.Film;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/films")
public class FilmController {
private final Map<Long, Film> films = new HashMap<>();
private long lastGeneratedID = 0;

@PostMapping
public Film createFilm(@Valid @RequestBody Film film) {
film.setId(++lastGeneratedID);
films.put(film.getId(), film);

log.info("Film created: id={}, name={}", film.getId(), film.getName());
return film;
}

@GetMapping
public List<Film> getAllFilms() {
log.debug("Retrieving all films (total={})", films.size());
return new ArrayList<>(films.values());
}

@PutMapping
public Film updateFilm(@Valid @RequestBody Film film) {
if (!films.containsKey(film.getId())) {
log.warn("Attempt to update non-existent film id={}", film.getId());
throw new FilmNotFoundException();
}
films.put(film.getId(), film);
log.info("Film updated: id={}, name={}", film.getId(), film.getName());
return film;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package ru.yandex.practicum.filmorate.controller;

import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
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.RestController;
import ru.yandex.practicum.filmorate.exception.UserNotFoundException;
import ru.yandex.practicum.filmorate.model.User;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/users")
public class UserController {
private final Map<Long, User> users = new HashMap<>();
private long lastGeneratedID = 0;

@PostMapping
public User createUser(@Valid @RequestBody User user) {
user.setId(++lastGeneratedID);
users.put(user.getId(), user);

log.info("User created: id={}, email={}, login={}", user.getId(), user.getEmail(), user.getLogin());
return user;
}

@GetMapping
public List<User> getAllUsers() {
log.debug("Retrieving all users (total={})", users.size());
return new ArrayList<>(users.values());
}

@PutMapping
public User updateUser(@Valid @RequestBody User user) {
if (!users.containsKey(user.getId())) {
log.warn("Attempt to update non-existent user id={}", user.getId());
throw new UserNotFoundException();
}
users.put(user.getId(), user);
log.info("User updated: id={}, email={}, login={}", user.getId(), user.getEmail(), user.getLogin());
return user;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ru.yandex.practicum.filmorate.exception;

public class FilmNotFoundException extends RuntimeException {
public FilmNotFoundException() {
super("Film not found");
}

public FilmNotFoundException(String message) {
super(message);
}

public FilmNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ru.yandex.practicum.filmorate.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(ValidationException.class)
public ResponseEntity<Map<String, String>> handleValidationException(ValidationException ex) {
log.error("Validation error: {}", ex.getMessage());
Map<String, String> body = new HashMap<>();
body.put("error", ex.getMessage());
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}

@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<Map<String, String>> handleUserNotFoundException(UserNotFoundException ex) {
log.warn("User not found: {}", ex.getMessage());
Map<String, String> body = new HashMap<>();
body.put("error", ex.getMessage() != null ? ex.getMessage() : "User not found");
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
}

@ExceptionHandler(FilmNotFoundException.class)
public ResponseEntity<Map<String, String>> handleFilmNotFoundException(FilmNotFoundException ex) {
log.warn("Film not found: {}", ex.getMessage());
Map<String, String> body = new HashMap<>();
body.put("error", ex.getMessage() != null ? ex.getMessage() : "Film not found");
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
}

// Для любых неожиданных ошибок
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, String>> handleOtherExceptions(Exception ex) {
log.error("Unexpected error", ex);
Map<String, String> body = new HashMap<>();
body.put("error", "Unexpected error: " + ex.getMessage());
return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ru.yandex.practicum.filmorate.exception;

public class UserNotFoundException extends RuntimeException {
public UserNotFoundException() {
super("User not found");
}

public UserNotFoundException(String message) {
super(message);
}

public UserNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ru.yandex.practicum.filmorate.exception;

public class ValidationException extends RuntimeException {
public ValidationException(String message) {
super(message);
}

public ValidationException(String message, Throwable cause) {
super(message, cause);
}
}
44 changes: 36 additions & 8 deletions src/main/java/ru/yandex/practicum/filmorate/model/Film.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,40 @@
package ru.yandex.practicum.filmorate.model;

import lombok.Getter;
import lombok.Setter;

/**
* Film.
*/
@Getter
@Setter
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import jakarta.validation.ValidationException;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import ru.yandex.practicum.filmorate.annotation.NotBeforeCinemaBirthday;
import ru.yandex.practicum.filmorate.util.DurationMinutesDeserializer;
import ru.yandex.practicum.filmorate.util.DurationMinutesSerializer;

import java.time.Duration;
import java.time.LocalDate;

@Data
public class Film {

private Long id;

@NotBlank(message = "The film name cannot be empty")
private String name;

@Size(max = 200, message = "The film description must not exceed 200 characters")
private String description;

@NotBeforeCinemaBirthday
private LocalDate releaseDate;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше создать свою собственную аннотацию для условия, что дата релиза должна быть не раньше 28 декабря 1895 года. Если не получится, то ничего страшного приму так, если получится, то будет супер)


@JsonSerialize(using = DurationMinutesSerializer.class)
@JsonDeserialize(using = DurationMinutesDeserializer.class)
private Duration duration;

public void setDuration(Duration duration) {
if (duration == null || duration.isNegative() || duration.isZero()) {
throw new ValidationException("Film duration must be positive");
}
this.duration = duration;
}
}
35 changes: 35 additions & 0 deletions src/main/java/ru/yandex/practicum/filmorate/model/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ru.yandex.practicum.filmorate.model;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.PastOrPresent;
import jakarta.validation.constraints.Pattern;
import lombok.Data;

import java.time.LocalDate;

@Data
public class User {

private Long id;

@NotBlank(message = "Email cannot be empty")
@Email(message = "Email must contain '@' and be valid")
private String email;

@NotBlank(message = "Login cannot be empty")
@Pattern(regexp = "\\S+", message = "Login must not contain spaces")
private String login;

private String name;

@PastOrPresent(message = "Birthday cannot be in the future")
private LocalDate birthday;

public void setLogin(String login) {
this.login = login;
if (this.name == null || this.name.isBlank()) {
this.name = login;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ru.yandex.practicum.filmorate.util;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.time.Duration;

public class DurationMinutesDeserializer extends StdDeserializer<Duration> {

public DurationMinutesDeserializer() {
super(Duration.class);
}

@Override
public Duration deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
long minutes = p.getLongValue();
return Duration.ofMinutes(minutes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ru.yandex.practicum.filmorate.util;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

import java.io.IOException;
import java.time.Duration;

public class DurationMinutesSerializer extends StdSerializer<Duration> {

public DurationMinutesSerializer() {
super(Duration.class);
}

@Override
public void serialize(Duration duration, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeNumber(duration.toMinutes());
}
}
Loading
Loading