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
48 changes: 48 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
FROM maven:3.9.11-amazoncorretto-21-alpine AS builder

# Set working directory
WORKDIR /app

COPY pom.xml ./
COPY lombok.config ./

# Set up Maven local repository for caching
ENV MAVEN_OPTS="-Dmaven.repo.local=/app/.m2/repository"

# Download dependencies (caching layer)
RUN mvn dependency:go-offline -B

# Copy source code
COPY src ./src

# Build the application (skip tests for faster build)
RUN mvn clean package -DskipTests -Dcheckstyle.skip=true

# 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
RUN java -Djarmode=layertools -jar app.jar extract

# Stage 3: Runtime stage
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"]
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,46 @@

Repository for filmorate - social network for film rating. Written in java.

# Contributors
Search - [Alexander-skipper](https://github.com/Alexander-skipper)<br>
Feed - [n20va](https://github.com/n20va)<br>
Recommendations - [Basementdoor](https://github.com/basementdoor)<br>
Reviews, top popular films by year and genre - [Nikolay Aleksandrov](https://github.com/Alextrusk27)<br>

# DB schema

![db schema](assets/readme/db_schema.svg)

# Tables description
This section provides a detailed description of each table in the database schema.

* Events
This table logs user activities such as likes, reviews, and friendships, serving as an audit or history trail for operations.
* Friendships
This table stores user-to-user friendship relationships.
* Users
This table holds user profile information for personalization.
* Likes
This table records users' likes on films, for recommendation systems or popularity tracking.
* Film Reviews
This table links users and films to their reviews, acting as a junction for many-to-many relationships.
* Reviews
This table stores the content and metadata of user reviews.
* Review Likes
This table tracks likes on reviews, similar to upvoting for helpfulness.
* Films
This core table contains movie details for the platform's catalog.
* Film Directors
This junction table associates films with their directors.
* Directors
This table lists film directors.
* MPA Ratings
This table defines Motion Picture Association ratings (e.g., G, PG, R).
* Film Genres
This junction table links films to genres.
* Genres
This table categorizes film genres (e.g., Action, Drama).

## Examples of database queries

### Get all films
Expand Down Expand Up @@ -86,5 +122,5 @@ WHERE u.user_id IN (SELECT user_id2

SELECT user_id2
FROM friendships
WHERE user_id1 = 2
WHERE user_id1 = 2)
```
4 changes: 2 additions & 2 deletions assets/readme/db_schema.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -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: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package ru.yandex.practicum.filmorate.controller;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Positive;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import ru.yandex.practicum.filmorate.dto.director.DirectorDto;
import ru.yandex.practicum.filmorate.dto.director.NewDirectorRequest;
import ru.yandex.practicum.filmorate.dto.director.UpdateDirectorRequest;
import ru.yandex.practicum.filmorate.service.director.DirectorService;

import java.util.Collection;

@RestController
@RequestMapping("/directors")
@Slf4j
@RequiredArgsConstructor
@Validated
public class DirectorController {
private final DirectorService directorService;

@GetMapping
public Collection<DirectorDto> findAll() {
log.trace("Collection of all directors requested");
return directorService.findAll();
}

@GetMapping("/{id}")
public DirectorDto findById(@PathVariable @Positive long id) {
log.trace("Find director by id requested, id: {}", id);
return directorService.findById(id);
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public DirectorDto create(@RequestBody @Valid NewDirectorRequest request) {
log.trace("Create new director requested: {}", request);
return directorService.create(request);
}

@PutMapping
public DirectorDto update(@RequestBody @Valid UpdateDirectorRequest request) {
log.trace("Update director requested: {}", request);
return directorService.update(request);
}

@DeleteMapping("/{id}")
public void deleteById(@PathVariable @Positive long id) {
log.trace("Delete director by id requested, id: {}", id);
directorService.deleteById(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ru.yandex.practicum.filmorate.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import ru.yandex.practicum.filmorate.dto.event.EventDto;
import ru.yandex.practicum.filmorate.service.feed.FeedService;

import java.util.List;

@RestController
@RequestMapping("/users")
@Slf4j
@RequiredArgsConstructor
public class FeedController {
private final FeedService feedService;

@GetMapping("/{id}/feed")
public List<EventDto> getUserFeed(@PathVariable long id) {
log.info("GET /users/{}/feed", id);
return feedService.getUserFeed(id);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ru.yandex.practicum.filmorate.controller;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -10,9 +11,10 @@
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.service.FilmService;
import ru.yandex.practicum.filmorate.service.film.FilmService;

import java.util.Collection;
import java.util.List;

@RestController
@RequestMapping("/films")
Expand Down Expand Up @@ -42,36 +44,65 @@ 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);
}

@PutMapping("/{id}/like/{userId}")
public void addLike(@PathVariable @Positive long id,
@PathVariable @Positive long userId) {
public void addLike(@PathVariable long id,
@PathVariable long userId) {
log.trace("Add like requested film id: {}, user id: {}", id, userId);
filmService.addLike(id, userId);
}

@DeleteMapping("/{id}/like/{userId}")
public void deleteLike(@PathVariable @Positive long id,
@PathVariable @Positive long userId) {
public void deleteLike(@PathVariable long id,
@PathVariable long userId) {
log.trace("Delete like requested film id: {}, user id: {}", id, userId);
filmService.removeLike(id, userId);
}

@GetMapping("/search")
public Collection<FilmDto> searchFilms(@RequestParam @NotBlank String query,
@RequestParam(defaultValue = "title") String by) {
log.trace("Search films requested with query: {}, by: {}", query, by);

List<SearchFilmsBy> parsedInput = SearchFilmsBy.parseStrOrThrow(by, () ->
new IllegalArgumentException("Invalid search films requested: " + by));

return filmService.searchFilms(query, parsedInput);
}

@GetMapping("/common")
public Collection<FilmDto> findCommonFilms(@RequestParam(name = "userId") @Positive long userId,
@RequestParam(name = "friendId") @Positive long friendId) {
log.trace("Find common films requested for user {} and friend {}", userId, friendId);
return filmService.findCommonFilms(userId, friendId);
}

@GetMapping("/popular")
public Collection<FilmDto> findPopular(@RequestParam(defaultValue = "10") @Positive int count) {
log.trace("Find popular film requested with count: {}", count);
return filmService.findFilmsWithTopLikes(count);
public Collection<FilmDto> findPopular(@RequestParam(defaultValue = "10") @Positive int count,
@RequestParam(required = false) @Positive Integer genreId,
@RequestParam(required = false) @Positive Integer year) {
log.trace("Find popular film requested with count: {} by genre: {} for year: {}", count, genreId, year);
return filmService.findFilmsWithTopLikes(count, genreId, year);
}

@GetMapping("/director/{directorId}")
public Collection<FilmDto> findFilmsOfDirector(@PathVariable @Positive long directorId,
@RequestParam String 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ru.yandex.practicum.filmorate.controller;

import java.util.Optional;

public enum FilmsSortBy {
YEAR,
LIKES;

public static Optional<FilmsSortBy> fromString(String sortBy) {
return switch (sortBy.toLowerCase()) {
case "year" -> Optional.of(YEAR);
case "likes" -> Optional.of(LIKES);
default -> Optional.empty();
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.yandex.practicum.filmorate.dto.genre.GenreDto;
import ru.yandex.practicum.filmorate.service.GenreService;
import ru.yandex.practicum.filmorate.service.genre.GenreService;

import java.util.Collection;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.yandex.practicum.filmorate.dto.mparating.MPARatingDto;
import ru.yandex.practicum.filmorate.service.MPARatingService;
import ru.yandex.practicum.filmorate.service.mparating.MPARatingService;

import java.util.Collection;

Expand Down
Loading