Skip to content

Commit dcb0504

Browse files
authored
Merge pull request #8 from DokPlay/add-database
sprint-12 add database schema docs
2 parents e93ebe3 + 206ca81 commit dcb0504

29 files changed

Lines changed: 1126 additions & 27 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
/.settings/
88
/out/
99
*.log
10+
db/*.mv.db
11+
db/*.trace.db
1012

1113
# OS
1214
.DS_Store
1315
Thumbs.db
16+
17+
*.patch

pom.xml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<artifactId>filmorate</artifactId>
99
<version>1.0.0</version>
1010
<name>Filmorate</name>
11-
<description>Filmorate Sprint 1</description>
11+
<description>Filmorate Sprint 12</description> <!-- SPRINT 12: обновили описание под текущий спринт -->
1212

1313
<parent>
1414
<groupId>org.springframework.boot</groupId>
@@ -37,6 +37,19 @@
3737
<artifactId>spring-boot-starter-validation</artifactId>
3838
</dependency>
3939

40+
<!-- SPRINT 12: JDBC и подключение к базе данных -->
41+
<dependency>
42+
<groupId>org.springframework.boot</groupId>
43+
<artifactId>spring-boot-starter-jdbc</artifactId>
44+
</dependency>
45+
46+
<!-- SPRINT 12: встроенная БД H2, в рантайме хранит данные в файле ./db/filmorate -->
47+
<dependency>
48+
<groupId>com.h2database</groupId>
49+
<artifactId>h2</artifactId>
50+
<scope>runtime</scope>
51+
</dependency>
52+
4053
<!-- Lombok -->
4154
<dependency>
4255
<groupId>org.projectlombok</groupId>
@@ -52,7 +65,7 @@
5265
<version>3.7.2</version>
5366
</dependency>
5467

55-
<!-- Tests -->
68+
<!-- SPRINT 12: тестирование, в том числе @JdbcTest с in-memory H2 -->
5669
<dependency>
5770
<groupId>org.springframework.boot</groupId>
5871
<artifactId>spring-boot-starter-test</artifactId>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package ru.yandex.practicum.filmorate.controller;
2+
3+
import java.util.List;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.springframework.web.bind.annotation.GetMapping;
7+
import org.springframework.web.bind.annotation.PathVariable;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
import ru.yandex.practicum.filmorate.model.Genre;
11+
import ru.yandex.practicum.filmorate.service.GenreService;
12+
13+
/** REST controller for film genres. */
14+
@Slf4j
15+
@RestController
16+
@RequestMapping("/genres")
17+
@RequiredArgsConstructor
18+
public class GenreController {
19+
20+
private final GenreService genreService;
21+
22+
@GetMapping
23+
public List<Genre> findAll() {
24+
log.debug("GET /genres — list");
25+
return genreService.findAll();
26+
}
27+
28+
@GetMapping("/{id}")
29+
public Genre getById(@PathVariable final int id) {
30+
log.info("GET /genres/{} — fetch", id);
31+
return genreService.getById(id);
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package ru.yandex.practicum.filmorate.controller;
2+
3+
import java.util.List;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.springframework.web.bind.annotation.GetMapping;
7+
import org.springframework.web.bind.annotation.PathVariable;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
import ru.yandex.practicum.filmorate.model.Mpa;
11+
import ru.yandex.practicum.filmorate.service.MpaService;
12+
13+
/** REST controller for MPA ratings. */
14+
@Slf4j
15+
@RestController
16+
@RequestMapping("/mpa")
17+
@RequiredArgsConstructor
18+
public class MpaController {
19+
20+
private final MpaService mpaService;
21+
22+
@GetMapping
23+
public List<Mpa> findAll() {
24+
log.debug("GET /mpa — list");
25+
return mpaService.findAll();
26+
}
27+
28+
@GetMapping("/{id}")
29+
public Mpa getById(@PathVariable final int id) {
30+
log.info("GET /mpa/{} — fetch", id);
31+
return mpaService.getById(id);
32+
}
33+
}

src/main/java/ru/yandex/practicum/filmorate/model/Film.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import jakarta.validation.constraints.Size;
88
import java.time.LocalDate;
99
import java.util.HashSet;
10+
import java.util.LinkedHashSet;
1011
import java.util.Set;
1112
import lombok.Data;
1213
import lombok.NoArgsConstructor;
@@ -43,6 +44,15 @@ public class Film {
4344
@Positive(message = "Продолжительность должна быть положительным числом.")
4445
private Integer duration;
4546

47+
@NotNull(message = "Рейтинг обязателен.")
48+
private Mpa mpa;
49+
50+
private Set<Genre> genres = new LinkedHashSet<>();
51+
4652
// SPRINT 11: лайки от пользователей (уникальные id)
4753
private final Set<Long> likes = new HashSet<>();
54+
55+
public void setGenres(Set<Genre> genres) {
56+
this.genres = genres == null ? new LinkedHashSet<>() : new LinkedHashSet<>(genres);
57+
}
4858
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package ru.yandex.practicum.filmorate.model;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
7+
/** Genre of a film. */
8+
@Data
9+
@NoArgsConstructor
10+
@AllArgsConstructor
11+
public class Genre {
12+
13+
private Integer id;
14+
15+
private String name;
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package ru.yandex.practicum.filmorate.model;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
7+
/** Motion Picture Association rating. */
8+
@Data
9+
@NoArgsConstructor
10+
@AllArgsConstructor
11+
public class Mpa {
12+
13+
private Integer id;
14+
15+
private String name;
16+
}

src/main/java/ru/yandex/practicum/filmorate/service/FilmService.java

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package ru.yandex.practicum.filmorate.service;
22

33
import java.time.LocalDate;
4+
import java.util.Collections;
5+
import java.util.Comparator;
6+
import java.util.HashSet;
7+
import java.util.LinkedHashSet;
48
import java.util.List;
9+
import java.util.Objects;
10+
import java.util.Set;
511
import lombok.RequiredArgsConstructor;
612
import lombok.extern.slf4j.Slf4j;
713
import org.springframework.stereotype.Service;
@@ -10,6 +16,7 @@
1016
// import ru.yandex.practicum.filmorate.exception.NotFoundException;
1117
import ru.yandex.practicum.filmorate.exception.ValidationException;
1218
import ru.yandex.practicum.filmorate.model.Film;
19+
import ru.yandex.practicum.filmorate.model.Genre;
1320
import ru.yandex.practicum.filmorate.storage.film.FilmStorage;
1421
import ru.yandex.practicum.filmorate.storage.user.UserStorage;
1522

@@ -41,6 +48,7 @@ public class FilmService {
4148

4249
// CHANGE: вынесена константа самой ранней корректной даты
4350
private static final LocalDate EARLIEST_DATE = LocalDate.of(1895, 12, 28);
51+
private static final int DEFAULT_POPULAR_LIMIT = FilmStorage.DEFAULT_POPULAR_LIMIT;
4452

4553
// SPRINT 11: внедряем зависимости от интерфейсов хранилищ
4654
private final FilmStorage filmStore;
@@ -64,6 +72,7 @@ public Film getById(final long id) {
6472

6573
public Film create(final Film film) {
6674
validateBusinessRules(film);
75+
normalizeGenres(film);
6776
// SPRINT 11: генерация id и сохранение — в storage
6877
final Film saved = filmStore.create(film);
6978
// CHANGE: безопасный лог
@@ -76,6 +85,7 @@ public Film update(final Film film) {
7685
throw new ValidationException("id обязателен для обновления фильма.");
7786
}
7887
validateBusinessRules(film);
88+
normalizeGenres(film);
7989
// SPRINT 11: обновление — через storage
8090
final Film saved = filmStore.update(film);
8191
// CHANGE
@@ -119,11 +129,9 @@ public void removeLike(final long filmId, final long userId) {
119129
}
120130

121131
public List<Film> getPopular(int count) {
122-
if (count <= 0) {
123-
count = 10; // SPRINT 11: дефолт, если параметр не задан/некорректен
124-
}
132+
final int effectiveLimit = count <= 0 ? DEFAULT_POPULAR_LIMIT : count; // SPRINT 11: дефолт, если параметр не задан/некорректен
125133
// SPRINT 11 FIX: сортировку и лимит выполняет хранилище (для будущей БД)
126-
return filmStore.findMostPopular(count);
134+
return filmStore.findMostPopular(effectiveLimit);
127135
}
128136

129137
// ----------- валидация -----------
@@ -133,5 +141,32 @@ private void validateBusinessRules(final Film film) {
133141
if (film.getReleaseDate() != null && film.getReleaseDate().isBefore(EARLIEST_DATE)) {
134142
throw new ValidationException("Дата релиза не может быть раньше " + EARLIEST_DATE + ".");
135143
}
144+
if (film.getMpa() == null || film.getMpa().getId() == null) {
145+
throw new ValidationException("Рейтинг MPA обязателен.");
146+
}
147+
if (film.getMpa().getId() <= 0) {
148+
throw new ValidationException("Некорректный идентификатор рейтинга.");
149+
}
150+
}
151+
152+
private void normalizeGenres(final Film film) {
153+
if (film.getGenres() == null || film.getGenres().isEmpty()) {
154+
film.setGenres(Collections.emptySet());
155+
return;
156+
}
157+
final Set<Genre> genres = film.getGenres();
158+
final int expectedSize = Math.max(genres.size(), 1);
159+
final Set<Integer> seen = new HashSet<>(expectedSize);
160+
final Set<Genre> normalized = new LinkedHashSet<>(expectedSize);
161+
genres.stream()
162+
.filter(Objects::nonNull)
163+
.filter(genre -> genre.getId() != null && genre.getId() > 0)
164+
.sorted(Comparator.comparingInt(Genre::getId))
165+
.forEach(genre -> {
166+
if (seen.add(genre.getId())) {
167+
normalized.add(new Genre(genre.getId(), genre.getName()));
168+
}
169+
});
170+
film.setGenres(normalized);
136171
}
137172
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package ru.yandex.practicum.filmorate.service;
2+
3+
import java.util.List;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.stereotype.Service;
6+
import ru.yandex.practicum.filmorate.model.Genre;
7+
import ru.yandex.practicum.filmorate.storage.genre.GenreStorage;
8+
9+
/** Service facade for working with genres. */
10+
@Service
11+
@RequiredArgsConstructor
12+
public class GenreService {
13+
14+
private final GenreStorage genreStorage;
15+
16+
public List<Genre> findAll() {
17+
return genreStorage.findAll();
18+
}
19+
20+
public Genre getById(int id) {
21+
return genreStorage.getById(id);
22+
}
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package ru.yandex.practicum.filmorate.service;
2+
3+
import java.util.List;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.stereotype.Service;
6+
import ru.yandex.practicum.filmorate.model.Mpa;
7+
import ru.yandex.practicum.filmorate.storage.mpa.MpaStorage;
8+
9+
/** Service facade for MPA ratings. */
10+
@Service
11+
@RequiredArgsConstructor
12+
public class MpaService {
13+
14+
private final MpaStorage mpaStorage;
15+
16+
public List<Mpa> findAll() {
17+
return mpaStorage.findAll();
18+
}
19+
20+
public Mpa getById(int id) {
21+
return mpaStorage.getById(id);
22+
}
23+
}

0 commit comments

Comments
 (0)