Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d7cb868
feat: Implement controllers and models for filmorate
Darya346 Dec 11, 2025
87b2811
fix: Correctly handle validation exceptions
Darya346 Dec 11, 2025
0b3aa4c
fix: Add manual validation calls for unit tests
Darya346 Dec 11, 2025
218e5fd
fix: Return 404 on update for unknown entities
Darya346 Dec 11, 2025
c86dd39
Use @FieldDefaults
Darya346 Dec 11, 2025
1021353
feat: add friends and likes functionality
Darya346 Dec 24, 2025
7c42e67
исправлен чекстайл
Darya346 Dec 24, 2025
50611ea
исправление ошибок чекстайла
Darya346 Dec 24, 2025
616e7d5
исправлены ошибки чекстайла
Darya346 Dec 24, 2025
fa739a3
исрпавлены ошибки в тестах
Darya346 Dec 24, 2025
e23d473
добавлен FieldDefaults
Darya346 Dec 25, 2025
60bfcf0
docs: Add ER diagram and update README
Darya346 Jan 13, 2026
a0af734
Update README.md
Darya346 Jan 13, 2026
3e3057f
Delete filmorate_database.png
Darya346 Jan 13, 2026
8edc777
исправлено: переплетение связей в БД, изменение полей в friendships,…
Darya346 Jan 13, 2026
a6ff54a
Изменения: Добавление изображения новой БД и исправленные sql-запросы…
Darya346 Jan 13, 2026
3fb08f1
Update README.md
Darya346 Jan 13, 2026
a595ede
Merge branch 'main' into controllers-films-users
Darya346 Jan 13, 2026
5e70aea
удаление ReadME
Darya346 Jan 13, 2026
155fd0b
добавление чистого README.md
Darya346 Jan 13, 2026
d8e67c5
Merge branch 'main' into controllers-films-users
Darya346 Jan 13, 2026
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
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,73 @@


## Схема БД (ER-диаграмма)

![ER-диаграмма для Filmorate](filmorate_database.png)

## Описание таблиц

Основу данных составляют таблицы `user` и `film`, которые хранят информацию о пользователях и фильмах соответственно.

Для хранения стандартизированных значений, таких как жанры и возрастные рейтинги MPA, были созданы отдельные таблицы-справочники: `genre` и `mpa_rating`. Такой подход позволяет избежать дублирования данных и упрощает их обновление.

Связи "многие-ко-многим" реализуются через промежуточные таблицы. Таблица `film_genre` связывает фильмы с их жанрами. `film_like` отслеживает, какие пользователи поставили лайки фильмам, что необходимо для расчета популярности. Таблица `friendship` реализует механику дружбы между пользователями, включая статус подтверждения (`is_confirmed`).

## Примеры SQL-запросов

### Получение всех фильмов с их жанрами и рейтингом MPA

```sql
SELECT
f.id,
f.name,
f.description,
f.release_date,
f.duration,
mr.name AS mpa_rating,
GROUP_CONCAT(g.name SEPARATOR ', ') AS genres
FROM
film AS f
JOIN
mpa_rating AS mr ON f.mpa_rating_id = mr.id
LEFT JOIN
film_genre AS fg ON f.id = fg.film_id
LEFT JOIN
genre AS g ON fg.genre_id = g.id
GROUP BY
f.id;
```

### Запрос на получение 10 самых популярных фильмов

```sql
SELECT
f.id,
f.name,
COUNT(fl.user_id) AS likes_count
FROM
film AS f
LEFT JOIN
film_like AS fl ON f.id = fl.film_id
GROUP BY
f.id
ORDER BY
likes_count DESC
LIMIT 10;
```

### Поиск общих друзей для двух пользователей (id 1 и 2)

```sql
SELECT
u.*
FROM
friendship AS f1
JOIN
friendship AS f2 ON f1.friend_id = f2.friend_id
JOIN
user AS u ON f1.friend_id = u.id
WHERE
f1.user_id = 1 AND f2.user_id = 2
AND f1.is_confirmed = true AND f2.is_confirmed = true;
```

Binary file added filmorate_database.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 49 additions & 9 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,32 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/> <!-- lookup parent from repository -->
<version>3.2.0</version> <!-- Или ваша версия Spring Boot -->
<relativePath/>
</parent>

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

<properties>
<java.version>21</java.version>
<java.version>17</java.version>
<logbook.version>3.9.0</logbook.version>
</properties>

<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

<!-- Logbook для логирования HTTP запросов -->
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-spring-boot-starter</artifactId>
<version>${logbook.version}</version>
</dependency>

<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>

<!-- Тестирование -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand All @@ -40,7 +61,26 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

</project>
<repositories>
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</repositories>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ public class FilmorateApplication {
public static void main(String[] args) {
SpringApplication.run(FilmorateApplication.class, args);
}

}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,57 @@
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.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import ru.yandex.practicum.filmorate.model.Film;
import ru.yandex.practicum.filmorate.service.FilmService;

import java.util.List;

@RestController
@RequestMapping("/films")
@Slf4j
public class FilmController {
}
private final FilmService filmService;

@Autowired
public FilmController(FilmService filmService) {
this.filmService = filmService;
}

@GetMapping
public List<Film> getAllFilms() {
return filmService.getAll();
}

@GetMapping("/{id}")
public Film getFilmById(@PathVariable int id) {
return filmService.getById(id);
}

@PostMapping
public Film createFilm(@Valid @RequestBody Film film) {
return filmService.create(film);
}

@PutMapping
public Film updateFilm(@Valid @RequestBody Film film) {
return filmService.update(film);
}

@PutMapping("/{id}/like/{userId}")
public void addLike(@PathVariable int id, @PathVariable int userId) {
filmService.addLike(id, userId);
}

@DeleteMapping("/{id}/like/{userId}")
public void removeLike(@PathVariable int id, @PathVariable int userId) {
filmService.removeLike(id, userId);
}

@GetMapping("/popular")
public List<Film> getPopularFilms(@RequestParam(defaultValue = "10") int count) {
return filmService.getPopularFilms(count);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package ru.yandex.practicum.filmorate.controller;

import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import ru.yandex.practicum.filmorate.model.User;
import ru.yandex.practicum.filmorate.service.UserService;

import java.util.List;

@RestController
@RequestMapping("/users")
@Slf4j
public class UserController {
private final UserService userService;

@Autowired
public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping
public List<User> getAllUsers() {
return userService.getAll();
}

@GetMapping("/{id}")
public User getUserById(@PathVariable int id) {
return userService.getById(id);
}

@PostMapping
public User createUser(@Valid @RequestBody User user) {
return userService.create(user);
}

@PutMapping
public User updateUser(@Valid @RequestBody User user) {
return userService.update(user);
}

@PutMapping("/{id}/friends/{friendId}")
public void addFriend(@PathVariable int id, @PathVariable int friendId) {
userService.addFriend(id, friendId);
}

@DeleteMapping("/{id}/friends/{friendId}")
public void removeFriend(@PathVariable int id, @PathVariable int friendId) {
userService.removeFriend(id, friendId);
}

@GetMapping("/{id}/friends")
public List<User> getFriends(@PathVariable int id) {
return userService.getFriends(id);
}

@GetMapping("/{id}/friends/common/{otherId}")
public List<User> getCommonFriends(@PathVariable int id, @PathVariable int otherId) {
return userService.getCommonFriends(id, otherId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package ru.yandex.practicum.filmorate.error;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import ru.yandex.practicum.filmorate.exception.NotFoundException;
import ru.yandex.practicum.filmorate.exception.ValidationException;

@RestControllerAdvice
@Slf4j
public class ErrorHandler {

@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidationException(final MethodArgumentNotValidException e) {
log.error("Ошибка валидации: {}", e.getMessage());
return new ErrorResponse(
e.getBindingResult().getFieldError().getDefaultMessage()
);
}

@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleCustomValidationException(final ValidationException e) {
log.error("Ошибка валидации: {}", e.getMessage());
return new ErrorResponse(e.getMessage());
}

@ExceptionHandler
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFoundException(final NotFoundException e) {
log.error("Объект не найден: {}", e.getMessage());
return new ErrorResponse(e.getMessage());
}

@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleThrowable(final Throwable e) {
log.error("Внутренняя ошибка сервера: ", e);
return new ErrorResponse("Произошла непредвиденная ошибка.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ru.yandex.practicum.filmorate.error;

public class ErrorResponse {
private final String error;

public ErrorResponse(String error) {
this.error = error;
}

public String getError() {
return error;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.yandex.practicum.filmorate.exception;

public class NotFoundException extends RuntimeException {
public NotFoundException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.yandex.practicum.filmorate.exception;

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