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
76 changes: 74 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,74 @@
# java-filmorate
Template repository for Filmorate project.
# Filmorate

## О проекте

Filmorate - это RESTful API для управления фильмами и пользователями.
Сейчас приложение позволяет добавлять, обновлять и просматривать информацию о фильмах и пользователях.

## Модели данных

### Film
```json
{
"id": 0, // требуется только для обновления
"name": "string", // обязательное поле
"description": "string", // опциональное поле, не может быть больше 200 символов
"releaseDate": "2000-01-01", // опциональное поле, должно быть после 1985-01-28
"duration": 10 // опциональное поле, продолжительность в минутах, должно быть больше 0
}
```



### User
```json
{
"id": 0, // требуется только для обновления
"email": "string@string.string", // обязательное поле, должно быть формата name@domain
"login": "string", // обязательное поле, не должно быть пустым и содержать пробелы
"name": "string", // опциональное поле, при отсутсвии совпадает с login
"birthday": 10 // опциональное поле, дата рождения, не должна быть в будущем
}
```

## API Endpoints

### Фильмы
```
GET /films - получить все фильмы
POST /films - создать новый фильм
PUT /films - обновить существующий фильм
```

### Пользователи
```
GET /users - получить всех пользователей
POST /users - создать нового пользователя
PUT /users - обновить существующего пользователя
```

## Обработка ошибок

Приложение возвращает стандартизированные ошибки в формате:
```json
{
"message": "Bad Request",
"status": 400,
"errors": {
"duration": "должно быть больше 0"
},
"timestamp": "2025-10-11T15:45:29.7558387",
"path": "/films"
}
```

или в формате:

```json
{
"timestamp": "2025-10-11T12:46:21.315+00:00",
"status": 405,
"error": "Method Not Allowed",
"path": "/films"
}
```
9 changes: 9 additions & 0 deletions application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
spring.application.name=Filmorate
server.port=8080

logging.file.path=./logs/
logging.logback.rollingpolicy.file-name-pattern=./logs/spring.%d{yyyy-MM-dd}.%i.log
logging.logback.rollingpolicy.max-file-size=10MB
logging.logback.rollingpolicy.total-size-cap=50MB
logging.logback.rollingpolicy.clean-history-on-start=true
spring.output.ansi.enabled=ALWAYS
69 changes: 67 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,48 @@
<version>3.2.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>ru.yandex.practicum</groupId>
<artifactId>filmorate</artifactId>
<version>0.0.1-SNAPSHOT</version>

<name>filmorate</name>
<description>filmorate</description>

<properties>
<java.version>21</java.version>
<maven-checkstyle-plugin.version>3.3.1</maven-checkstyle-plugin.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

<build>
Expand All @@ -40,6 +58,53 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>

<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>

<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${project.parent.version}</version>
</path>

</annotationProcessorPaths>
</configuration>
</plugin>


<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${maven-checkstyle-plugin.version}</version>

<configuration>
<failOnViolation>true</failOnViolation>
<logViolationsToConsole>true</logViolationsToConsole>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
<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
Expand Up @@ -5,8 +5,8 @@

@SpringBootApplication
public class FilmorateApplication {
public static void main(String[] args) {
SpringApplication.run(FilmorateApplication.class, args);
}
public static void main(String[] args) {
SpringApplication.run(FilmorateApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,89 @@
package ru.yandex.practicum.filmorate.controller;

import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import ru.yandex.practicum.filmorate.exception.NotFoundException;
import ru.yandex.practicum.filmorate.exception.ValidationException;
import ru.yandex.practicum.filmorate.model.Film;

import java.util.Collection;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicLong;

@RestController
@RequestMapping("/films")
@Slf4j
public class FilmController {

private final HashMap<Long, Film> films = new HashMap<>();
private final AtomicLong idGenerator = new AtomicLong();

/**
* Private constructor to initialize ID generator with starting ID = 1.
*/
private FilmController() {
idGenerator.set(1);
}

/**
* Handles GET method.
* <p>Retrieves all films from the storage.
*
* @return Collection of all films.
*/
@GetMapping
public Collection<Film> getFilms() {
log.info("GET /films - returning {} films", films.size());
return films.values();
}

/**
* Handles POST method.
* <p>Creates film in storage after validation.
*
* @return created film.
*/
@PostMapping
public Film addFilm(@Valid @RequestBody Film film) {

film.setId(idGenerator.getAndIncrement());
films.put(film.getId(), film);

log.info("POST /films - Film created: {}", film);

return film;
}

/**
* Handles PUT method.
* <p>Updates film in storage after validation.
*
* <p>Conditions:
* <ul>
* <li>ID must be provided in the request body</li>
* <li>Film with this ID should exist in the storage.</li>
* </ul>
*
* @return updated film.
* @throws ValidationException if ID is null or not valid
* @throws NotFoundException if film is not found
*/
@PutMapping
public Film updateFilm(@Valid @RequestBody Film newFilm) {
if (newFilm.getId() == null) {
throw new ValidationException("Id должен быть указан");
}

if (films.containsKey(newFilm.getId())) {
Film oldFilm = films.get(newFilm.getId());

films.put(newFilm.getId(), newFilm);
log.info("PUT /films - Film updated: {}", oldFilm);

return newFilm;
}

throw new NotFoundException("Фильм с id = " + newFilm.getId() + " не найден");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package ru.yandex.practicum.filmorate.controller;

import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import ru.yandex.practicum.filmorate.exception.NotFoundException;
import ru.yandex.practicum.filmorate.exception.ValidationException;
import ru.yandex.practicum.filmorate.model.User;

import java.util.Collection;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicLong;

@RestController
@RequestMapping("/users")
@Slf4j
public class UserController {

private final HashMap<Long, User> users = new HashMap<>();
private final AtomicLong idGenerator = new AtomicLong();

/**
* Private constructor to initialize ID generator with starting ID = 1.
*/
private UserController() {
idGenerator.set(1);
}

/**
* Handles GET method.
* <p>Retrieves all users from the storage.
*
* @return Collection of all users.
*/
@GetMapping
public Collection<User> getUsers() {
log.info("GET /users - returning {} users", users.size());
return users.values();
}

/**
* Handles POST method.
* <p>Creates user in storage after validation.
*
* @return created users.
*/
@PostMapping
public User addUser(@Valid @RequestBody User user) {

user.setId(idGenerator.getAndIncrement());

if (user.getName() == null) {
user.setName(user.getLogin());
}

log.info("POST /users - User created: {}", user);
users.put(user.getId(), user);

return user;
}

/**
* Handles PUT method.
* <p>Updates user in storage after validation.
*
* <p>Conditions:
* <ul>
* <li>ID must be provided in the request body</li>
* <li>User with this ID should exist in the storage.</li>
* </ul>
*
* @return updated user.
* @throws ValidationException if ID is null or not valid
* @throws NotFoundException if user is not found
*/
@PutMapping
public User updateUser(@Valid @RequestBody User newUser) {
if (newUser.getId() == null) {
throw new ValidationException("Id должен быть указан");
}

if (users.containsKey(newUser.getId())) {
User oldUser = users.get(newUser.getId());

users.put(newUser.getId(), newUser);

log.info("PUT /users - User updated: {}", oldUser);

return newUser;
}

throw new NotFoundException("Пользователь с id = " + newUser.getId() + " не найден");
}
}
Loading
Loading