diff --git a/laba4/.vscode/settings.json b/laba4/.vscode/settings.json index 95915c9..43b05f1 100644 --- a/laba4/.vscode/settings.json +++ b/laba4/.vscode/settings.json @@ -21,4 +21,5 @@ "cSpell.words": [ "classappend" ], + "java.debug.settings.onBuildFailureProceed": true, } \ No newline at end of file diff --git a/laba4/src/main/java/com/example/demo/DemoApplication.java b/laba4/src/main/java/com/example/demo/DemoApplication.java index e54d422..b87fb3d 100644 --- a/laba4/src/main/java/com/example/demo/DemoApplication.java +++ b/laba4/src/main/java/com/example/demo/DemoApplication.java @@ -16,6 +16,12 @@ import com.example.demo.subscriptions.model.SubscriptionEntity; import com.example.demo.subscriptions.service.SubscriptionService; import com.example.demo.types.model.TypeEntity; import com.example.demo.types.service.TypeService; +import com.example.demo.genres.model.GenreEntity; +import com.example.demo.genres.service.GenreService; +import com.example.demo.films.model.FilmEntity; +import com.example.demo.films.service.FilmService; +import com.example.demo.userfilms.model.UserFilmEntity; +import com.example.demo.userfilms.service.UserFilmService; import com.example.demo.users.model.UserEntity; import com.example.demo.users.service.UserService; @@ -24,16 +30,25 @@ public class DemoApplication implements CommandLineRunner { private final Logger log = LoggerFactory.getLogger(DemoApplication.class); private final TypeService typeService; + private final GenreService genreService; + private final FilmService filmService; + private final UserFilmService userFilmService; private final SubscriptionService subscriptionService; private final UserService userService; private final OrderService orderService; public DemoApplication( TypeService typeService, + GenreService genreService, + FilmService filmService, + UserFilmService userFilmService, SubscriptionService subscriptionService, UserService userService, OrderService orderService) { this.typeService = typeService; + this.genreService = genreService; + this.filmService = filmService; + this.userFilmService = userFilmService; this.subscriptionService = subscriptionService; this.userService = userService; this.orderService = orderService; @@ -51,6 +66,16 @@ public class DemoApplication implements CommandLineRunner { final var type2 = typeService.create(new TypeEntity("Телефон")); final var type3 = typeService.create(new TypeEntity("Игровая приставка")); + log.info("Create default genre values"); + final var genre1 = genreService.create(new GenreEntity("Comedy")); + final var genre2 = genreService.create(new GenreEntity("Triller")); + final var genre3 = genreService.create(new GenreEntity("Drama")); + + log.info("Create default film values"); + final var film1 = filmService.create(new FilmEntity("film1", genre1, 200d, 20)); + final var film2 = filmService.create(new FilmEntity("film2", genre2, 200d, 20)); + final var film3 = filmService.create(new FilmEntity("film3", genre3, 200d, 20)); + log.info("Create default subscription values"); subscriptionService.create(new SubscriptionEntity("Подписка 1")); subscriptionService.create(new SubscriptionEntity("Подписка 2")); @@ -73,6 +98,10 @@ public class DemoApplication implements CommandLineRunner { new OrderEntity(type3, 75000.00, 6), new OrderEntity(type3, 67800.00, 3)); orders.forEach(order -> orderService.create(user1.getId(), order)); + + userFilmService.create(new UserFilmEntity(user1, film1, false)); + userFilmService.create(new UserFilmEntity(user1, film2, false)); + userFilmService.create(new UserFilmEntity(user1, film3, false)); } } } diff --git a/laba4/src/main/java/com/example/demo/films/api/FilmController.java b/laba4/src/main/java/com/example/demo/films/api/FilmController.java new file mode 100644 index 0000000..131c082 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/films/api/FilmController.java @@ -0,0 +1,78 @@ +package com.example.demo.films.api; + +import java.util.List; + +import org.modelmapper.ModelMapper; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +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 com.example.demo.core.configuration.Constants; +import com.example.demo.films.model.FilmEntity; +import com.example.demo.films.service.FilmService; +import com.example.demo.genres.service.GenreService; + +import jakarta.validation.Valid; + +@Controller +@RequestMapping(FilmController.URL) +public class FilmController { + public static final String URL = Constants.ADMIN_PREFIX + "/film"; + private static final String FILM_VIEW = "film"; + private static final String FILM_EDIT_VIEW = "film-edit"; + private static final String FILM_ATTRIBUTE = "film"; + + private final FilmService filmService; + private final GenreService genreService; + private final ModelMapper modelMapper; + + public FilmController(FilmService filmService, GenreService genreService, ModelMapper modelMapper) { + this.filmService = filmService; + this.genreService = genreService; + this.modelMapper = modelMapper; + } + + private FilmDto toDto(FilmEntity entity) { + return modelMapper.map(entity, FilmDto.class); + } + + private FilmEntity toEntity(FilmDto dto) { + final FilmEntity entity = modelMapper.map(dto, FilmEntity.class); + entity.setGenre(genreService.get(dto.getGenreId())); + return entity; + } + + @GetMapping + public List getAll() { + return filmService.getAll().stream() + .map(this::toDto) + .toList(); + } + + @GetMapping("/{id}") + public FilmDto get(@PathVariable(name = "id") Long id) { + return toDto(filmService.get(id)); + } + + @PostMapping + public FilmDto create(@RequestBody @Valid FilmDto dto) { + return toDto(filmService.create(toEntity(dto))); + } + + @PutMapping("/{id}") + public FilmDto update( + @PathVariable(name = "id") Long id, + @RequestBody @Valid FilmDto dto) { + return toDto(filmService.update(id, toEntity(dto))); + } + + @DeleteMapping("/{id}") + public FilmDto delete(@PathVariable(name = "id") Long id) { + return toDto(filmService.delete(id)); + } +} diff --git a/laba4/src/main/java/com/example/demo/films/api/FilmDto.java b/laba4/src/main/java/com/example/demo/films/api/FilmDto.java new file mode 100644 index 0000000..0a5ca24 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/films/api/FilmDto.java @@ -0,0 +1,70 @@ +package com.example.demo.films.api; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +public class FilmDto { + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private Long id; + @NotBlank + @Size(min = 5, max = 50) + private String name; + @NotNull + @Min(1) + private Long genreId; + @NotNull + @Min(1) + private Double price; + @NotNull + @Min(1) + private Integer discount; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getGenreId() { + return genreId; + } + + public void setGenreId(Long genreId) { + this.genreId = genreId; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + public Integer getDiscount() { + return discount; + } + + public void setDiscount(Integer discount) { + this.discount = discount; + } + + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public Double getSum() { + return price - ((price * discount) * 0.01); + } +} diff --git a/laba4/src/main/java/com/example/demo/films/model/FilmEntity.java b/laba4/src/main/java/com/example/demo/films/model/FilmEntity.java index 825030a..5f41b62 100644 --- a/laba4/src/main/java/com/example/demo/films/model/FilmEntity.java +++ b/laba4/src/main/java/com/example/demo/films/model/FilmEntity.java @@ -3,7 +3,7 @@ package com.example.demo.films.model; import java.util.Objects; import com.example.demo.core.model.BaseEntity; -import com.example.demo.types.model.TypeEntity; +import com.example.demo.genres.model.GenreEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -14,26 +14,40 @@ import jakarta.persistence.Table; @Entity @Table(name = "films") public class FilmEntity extends BaseEntity { + @Column(nullable = false, unique = true, length = 50) + private String name; @ManyToOne - @JoinColumn(name = "typeId", nullable = false) - private TypeEntity type; + @JoinColumn(name = "genreId", nullable = false) + private GenreEntity genre; @Column(nullable = false) private Double price; + @Column(nullable = false) + private Integer discount; public FilmEntity() { } - public FilmEntity(TypeEntity type, Double price) { - this.type = type; + public FilmEntity(String name, GenreEntity genre, Double price, Integer discount) { + this.name = name; + this.genre = genre; this.price = price; + this.discount = discount; } - public TypeEntity getType() { - return type; + public String getName() { + return name; } - public void setType(TypeEntity type) { - this.type = type; + public void setName(String name) { + this.name = name; + } + + public GenreEntity getGenre() { + return genre; + } + + public void setGenre(GenreEntity genre) { + this.genre = genre; } public Double getPrice() { @@ -44,9 +58,17 @@ public class FilmEntity extends BaseEntity { this.price = price; } + public Integer getDiscount() { + return discount; + } + + public void setDiscount(Integer discount) { + this.discount = discount; + } + @Override public int hashCode() { - return Objects.hash(id, type, price); + return Objects.hash(id, name, genre, price, discount); } @Override @@ -55,9 +77,11 @@ public class FilmEntity extends BaseEntity { return true; if (obj == null || getClass() != obj.getClass()) return false; - final FilmEntity other = (FilmEntity) obj; - return Objects.equals(other.getId(), id) - && Objects.equals(other.getType(), type) - && Objects.equals(other.getPrice(), price); + FilmEntity other = (FilmEntity) obj; + return Objects.equals(id, other.id) + && Objects.equals(name, other.name) + && Objects.equals(genre, other.genre) + && Objects.equals(price, other.price) + && Objects.equals(discount, other.discount); } } diff --git a/laba4/src/main/java/com/example/demo/films/repository/FilmRepository.java b/laba4/src/main/java/com/example/demo/films/repository/FilmRepository.java new file mode 100644 index 0000000..495e722 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/films/repository/FilmRepository.java @@ -0,0 +1,14 @@ +package com.example.demo.films.repository; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.repository.CrudRepository; + +import com.example.demo.films.model.FilmEntity; + +public interface FilmRepository extends CrudRepository { + Optional findByNameIgnoreCase(String name); + + List findByGenreId(long genreId); +} diff --git a/laba4/src/main/java/com/example/demo/films/service/FilmService.java b/laba4/src/main/java/com/example/demo/films/service/FilmService.java new file mode 100644 index 0000000..ed82706 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/films/service/FilmService.java @@ -0,0 +1,72 @@ +package com.example.demo.films.service; + +import java.util.List; +import java.util.stream.StreamSupport; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.demo.core.error.NotFoundException; +import com.example.demo.films.model.FilmEntity; +import com.example.demo.films.repository.FilmRepository; + +@Service +public class FilmService { + private final FilmRepository repository; + + public FilmService(FilmRepository repository) { + this.repository = repository; + } + + private void checkName(String name) { + if (repository.findByNameIgnoreCase(name).isPresent()) { + throw new IllegalArgumentException( + String.format("Type with name %s is already exists", name)); + } + } + + // @Transactional(readOnly = true) + // public List getAll(long genreId) { + // return repository.findByGenreId(genreId); + // } + + @Transactional(readOnly = true) + public List getAll() { + return StreamSupport.stream(repository.findAll().spliterator(), + false).toList(); + } + + @Transactional(readOnly = true) + public FilmEntity get(long id) { + return repository.findById(id) + .orElseThrow(() -> new NotFoundException(FilmEntity.class, id)); + } + + @Transactional + public FilmEntity create(FilmEntity entity) { + if (entity == null) { + throw new IllegalArgumentException("Entity is null"); + } + checkName(entity.getName()); + return repository.save(entity); + } + + @Transactional + public FilmEntity update(Long id, FilmEntity entity) { + final FilmEntity existsEntity = get(id); + checkName(entity.getName()); + existsEntity.setName(entity.getName()); + existsEntity.setPrice(entity.getPrice()); + existsEntity.setDiscount(entity.getDiscount()); + existsEntity.setGenre(entity.getGenre()); + return repository.save(existsEntity); + } + + @Transactional + public FilmEntity delete(Long id) { + final FilmEntity existsEntity = get(id); + repository.delete(existsEntity); + return existsEntity; + } + +} diff --git a/laba4/src/main/java/com/example/demo/genres/api/GenreController.java b/laba4/src/main/java/com/example/demo/genres/api/GenreController.java new file mode 100644 index 0000000..9b079a2 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/genres/api/GenreController.java @@ -0,0 +1,104 @@ +package com.example.demo.genres.api; + +import org.modelmapper.ModelMapper; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.example.demo.core.configuration.Constants; +import com.example.demo.genres.model.GenreEntity; +import com.example.demo.genres.service.GenreService; + +import jakarta.validation.Valid; + +@Controller +@RequestMapping(GenreController.URL) +public class GenreController { + public static final String URL = Constants.ADMIN_PREFIX + "/genre"; + private static final String GENRE_VIEW = "genre"; + private static final String GENRE_EDIT_VIEW = "genre-edit"; + private static final String GENRE_ATTRIBUTE = "genre"; + + private final GenreService genreService; + private final ModelMapper modelMapper; + + public GenreController(GenreService genreService, ModelMapper modelMapper) { + this.genreService = genreService; + this.modelMapper = modelMapper; + } + + private GenreDto toDto(GenreEntity entity) { + return modelMapper.map(entity, GenreDto.class); + } + + private GenreEntity toEntity(GenreDto dto) { + return modelMapper.map(dto, GenreEntity.class); + } + + @GetMapping + public String getAll(Model model) { + model.addAttribute( + "items", + genreService.getAll().stream() + .map(this::toDto) + .toList()); + return GENRE_VIEW; + } + + @GetMapping("/edit/") + public String create(Model model) { + model.addAttribute(GENRE_ATTRIBUTE, new GenreDto()); + return GENRE_EDIT_VIEW; + } + + @PostMapping("/edit/") + public String create( + @ModelAttribute(name = GENRE_ATTRIBUTE) @Valid GenreDto genre, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return GENRE_EDIT_VIEW; + } + genreService.create(toEntity(genre)); + return Constants.REDIRECT_VIEW + URL; + } + + @GetMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + Model model) { + if (id <= 0) { + throw new IllegalArgumentException(); + } + model.addAttribute(GENRE_ATTRIBUTE, toDto(genreService.get(id))); + return GENRE_EDIT_VIEW; + } + + @PostMapping("/edit/{id}") + public String update( + @PathVariable(name = "id") Long id, + @ModelAttribute(name = GENRE_ATTRIBUTE) @Valid GenreDto genre, + BindingResult bindingResult, + Model model) { + if (bindingResult.hasErrors()) { + return GENRE_EDIT_VIEW; + } + if (id <= 0) { + throw new IllegalArgumentException(); + } + genreService.update(id, toEntity(genre)); + return Constants.REDIRECT_VIEW + URL; + } + + @PostMapping("/delete/{id}") + public String delete( + @PathVariable(name = "id") Long id) { + genreService.delete(id); + return Constants.REDIRECT_VIEW + URL; + } +} diff --git a/laba4/src/main/java/com/example/demo/genres/api/GenreDto.java b/laba4/src/main/java/com/example/demo/genres/api/GenreDto.java new file mode 100644 index 0000000..5fc3218 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/genres/api/GenreDto.java @@ -0,0 +1,27 @@ +package com.example.demo.genres.api; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class GenreDto { + private Long id; + @NotBlank + @Size(min = 5, max = 50) + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/laba4/src/main/java/com/example/demo/genres/model/GenreEntity.java b/laba4/src/main/java/com/example/demo/genres/model/GenreEntity.java new file mode 100644 index 0000000..b36a926 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/genres/model/GenreEntity.java @@ -0,0 +1,48 @@ +package com.example.demo.genres.model; + +import java.util.Objects; + +import com.example.demo.core.model.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +@Entity +@Table(name = "genres") +public class GenreEntity extends BaseEntity { + @Column(nullable = false, unique = true, length = 50) + private String name; + + public GenreEntity() { + } + + public GenreEntity(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + final GenreEntity other = (GenreEntity) obj; + return Objects.equals(other.getId(), id) + && Objects.equals(other.getName(), name); + } + +} diff --git a/laba4/src/main/java/com/example/demo/genres/repository/GenreRepository.java b/laba4/src/main/java/com/example/demo/genres/repository/GenreRepository.java new file mode 100644 index 0000000..292b1c4 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/genres/repository/GenreRepository.java @@ -0,0 +1,13 @@ +package com.example.demo.genres.repository; + +import java.util.Optional; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; + +import com.example.demo.genres.model.GenreEntity; + +public interface GenreRepository + extends CrudRepository, PagingAndSortingRepository { + Optional findByNameIgnoreCase(String name); +} diff --git a/laba4/src/main/java/com/example/demo/genres/service/GenreService.java b/laba4/src/main/java/com/example/demo/genres/service/GenreService.java new file mode 100644 index 0000000..9cc84ab --- /dev/null +++ b/laba4/src/main/java/com/example/demo/genres/service/GenreService.java @@ -0,0 +1,76 @@ +package com.example.demo.genres.service; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.StreamSupport; + +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.demo.core.error.NotFoundException; +import com.example.demo.genres.model.GenreEntity; +import com.example.demo.genres.repository.GenreRepository; + +@Service +public class GenreService { + private final GenreRepository repository; + + public GenreService(GenreRepository repository) { + this.repository = repository; + } + + private void checkName(Long id, String name) { + final Optional existsGenre = repository.findByNameIgnoreCase(name); + if (existsGenre.isPresent() && !existsGenre.get().getId().equals(id)) { + throw new IllegalArgumentException( + String.format("Genre with name %s is already exists", name)); + } + } + + @Transactional(readOnly = true) + public List getAll() { + return StreamSupport.stream(repository.findAll(Sort.by("id")).spliterator(), false).toList(); + } + + @Transactional(readOnly = true) + public List getByIds(Collection ids) { + final List genres = StreamSupport.stream(repository.findAllById(ids).spliterator(), false) + .toList(); + if (genres.size() < ids.size()) { + throw new IllegalArgumentException("Invalid genre"); + } + return genres; + } + + @Transactional(readOnly = true) + public GenreEntity get(long id) { + return repository.findById(id) + .orElseThrow(() -> new NotFoundException(GenreEntity.class, id)); + } + + @Transactional + public GenreEntity create(GenreEntity entity) { + if (entity == null) { + throw new IllegalArgumentException("Entity is null"); + } + checkName(null, entity.getName()); + return repository.save(entity); + } + + @Transactional + public GenreEntity update(Long id, GenreEntity entity) { + final GenreEntity existsEntity = get(id); + checkName(id, entity.getName()); + existsEntity.setName(entity.getName()); + return repository.save(existsEntity); + } + + @Transactional + public GenreEntity delete(Long id) { + final GenreEntity existsEntity = get(id); + repository.delete(existsEntity); + return existsEntity; + } +} diff --git a/laba4/src/main/java/com/example/demo/subscribes/api/SubscribeController.java b/laba4/src/main/java/com/example/demo/subscribes/api/SubscribeController.java new file mode 100644 index 0000000..33ded44 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/subscribes/api/SubscribeController.java @@ -0,0 +1,73 @@ +package com.example.demo.subscribes.api; + +import java.util.List; + +import org.modelmapper.ModelMapper; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +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 com.example.demo.core.configuration.Constants; +import com.example.demo.subscribes.model.SubscribeEntity; +import com.example.demo.subscribes.service.SubscribeService; + +import jakarta.validation.Valid; + +@Controller +@RequestMapping(SubscribeController.URL) +public class SubscribeController { + public static final String URL = Constants.ADMIN_PREFIX + "/subscribe"; + private static final String SUBSCRIBE_VIEW = "subscribe"; + private static final String SUBSCRIBE_EDIT_VIEW = "subscribe-edit"; + private static final String SUBSCRIBE_ATTRIBUTE = "subscribe"; + + private final SubscribeService subscribeService; + private final ModelMapper modelMapper; + + public SubscribeController(SubscribeService subscribeService, ModelMapper modelMapper) { + this.subscribeService = subscribeService; + this.modelMapper = modelMapper; + } + + private SubscribeDto toDto(SubscribeEntity entity) { + return modelMapper.map(entity, SubscribeDto.class); + } + + private SubscribeEntity toEntity(SubscribeDto dto) { + return modelMapper.map(dto, SubscribeEntity.class); + } + + @GetMapping + public List getAll() { + return subscribeService.getAll().stream() + .map(this::toDto) + .toList(); + } + + @GetMapping("/{id}") + public SubscribeDto get(@PathVariable(name = "id") Long id) { + return toDto(subscribeService.get(id)); + } + + @PostMapping + public SubscribeDto create(@RequestBody @Valid SubscribeDto dto) { + return toDto(subscribeService.create(toEntity(dto))); + } + + @PutMapping("/{id}") + public SubscribeDto update( + @PathVariable(name = "id") Long id, + @RequestBody @Valid SubscribeDto dto) { + return toDto(subscribeService.update(id, toEntity(dto))); + } + + @DeleteMapping("/{id}") + public SubscribeDto delete(@PathVariable(name = "id") Long id) { + return toDto(subscribeService.delete(id)); + } +} diff --git a/laba4/src/main/java/com/example/demo/subscribes/api/SubscribeDto.java b/laba4/src/main/java/com/example/demo/subscribes/api/SubscribeDto.java new file mode 100644 index 0000000..2a203b2 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/subscribes/api/SubscribeDto.java @@ -0,0 +1,43 @@ +package com.example.demo.subscribes.api; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +public class SubscribeDto { + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private Long id; + @NotBlank + @Size(min = 5, max = 50) + private String name; + @NotNull + @Min(1) + private Double price; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } +} diff --git a/laba4/src/main/java/com/example/demo/subscribes/model/SubscribeEntity.java b/laba4/src/main/java/com/example/demo/subscribes/model/SubscribeEntity.java new file mode 100644 index 0000000..60bd966 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/subscribes/model/SubscribeEntity.java @@ -0,0 +1,60 @@ +package com.example.demo.subscribes.model; + +import java.util.Objects; + +import com.example.demo.core.model.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +@Entity +@Table(name = "subscribes") +public class SubscribeEntity extends BaseEntity { + @Column(nullable = false, unique = true, length = 50) + private String name; + @Column(nullable = false) + private Double price; + + public SubscribeEntity() { + } + + public SubscribeEntity(String name, Double price) { + this.name = name; + this.price = price; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + @Override + public int hashCode() { + return Objects.hash(id, name, price); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + final SubscribeEntity other = (SubscribeEntity) obj; + return Objects.equals(other.getId(), id) + && Objects.equals(other.getName(), name) + && Objects.equals(other.getPrice(), price); + } + +} diff --git a/laba4/src/main/java/com/example/demo/subscribes/repository/SubscribeRepository.java b/laba4/src/main/java/com/example/demo/subscribes/repository/SubscribeRepository.java new file mode 100644 index 0000000..0dbfd6e --- /dev/null +++ b/laba4/src/main/java/com/example/demo/subscribes/repository/SubscribeRepository.java @@ -0,0 +1,11 @@ +package com.example.demo.subscribes.repository; + +import java.util.Optional; + +import com.example.demo.subscribes.model.SubscribeEntity; + +import org.springframework.data.repository.CrudRepository; + +public interface SubscribeRepository extends CrudRepository { + Optional findByNameIgnoreCase(String name); +} diff --git a/laba4/src/main/java/com/example/demo/subscribes/service/SubscribeService.java b/laba4/src/main/java/com/example/demo/subscribes/service/SubscribeService.java new file mode 100644 index 0000000..72c99a1 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/subscribes/service/SubscribeService.java @@ -0,0 +1,63 @@ +package com.example.demo.subscribes.service; + +import java.util.List; +import java.util.stream.StreamSupport; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.demo.core.error.NotFoundException; +import com.example.demo.subscribes.model.SubscribeEntity; +import com.example.demo.subscribes.repository.SubscribeRepository; + +@Service +public class SubscribeService { + private final SubscribeRepository repository; + + public SubscribeService(SubscribeRepository repository) { + this.repository = repository; + } + + private void checkName(String name) { + if (repository.findByNameIgnoreCase(name).isPresent()) { + throw new IllegalArgumentException( + String.format("Subscribe with name %s is already exists", name)); + } + } + + @Transactional(readOnly = true) + public List getAll() { + return StreamSupport.stream(repository.findAll().spliterator(), false).toList(); + } + + @Transactional(readOnly = true) + public SubscribeEntity get(long id) { + return repository.findById(id) + .orElseThrow(() -> new NotFoundException(SubscribeEntity.class, id)); + } + + @Transactional + public SubscribeEntity create(SubscribeEntity entity) { + if (entity == null) { + throw new IllegalArgumentException("Entity is null"); + } + checkName(entity.getName()); + return repository.save(entity); + } + + @Transactional + public SubscribeEntity update(Long id, SubscribeEntity entity) { + final SubscribeEntity existsEntity = get(id); + checkName(entity.getName()); + existsEntity.setName(entity.getName()); + existsEntity.setPrice(entity.getPrice()); + return repository.save(existsEntity); + } + + @Transactional + public SubscribeEntity delete(Long id) { + final SubscribeEntity existsEntity = get(id); + repository.delete(existsEntity); + return existsEntity; + } +} diff --git a/laba4/src/main/java/com/example/demo/userfilms/api/UserFilmController.java b/laba4/src/main/java/com/example/demo/userfilms/api/UserFilmController.java new file mode 100644 index 0000000..5eb4497 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/userfilms/api/UserFilmController.java @@ -0,0 +1,110 @@ +package com.example.demo.userfilms.api; + +import java.util.List; + +import org.modelmapper.ModelMapper; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +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 com.example.demo.core.configuration.Constants; +import com.example.demo.films.service.FilmService; +import com.example.demo.userfilms.model.UserFilmEntity; +import com.example.demo.userfilms.model.UserFilmGrouped; +import com.example.demo.userfilms.service.UserFilmService; +import com.example.demo.users.service.UserService; + +import jakarta.validation.Valid; + +@Controller +@RequestMapping(UserFilmController.URL) +public class UserFilmController { + public static final String URL = Constants.ADMIN_PREFIX + "/userfilm"; + private static final String USERFILM_VIEW = "userfilm"; + private static final String USERFILM_EDIT_VIEW = "userfilm-edit"; + private static final String USERFILM_ATTRIBUTE = "userfilm"; + + private final UserFilmService userFilmService; + private final UserService userService; + private final FilmService filmService; + private final ModelMapper modelMapper; + + public UserFilmController(UserFilmService userFilmService, UserService userService, FilmService filmService, + ModelMapper modelMapper) { + this.userFilmService = userFilmService; + this.userService = userService; + this.filmService = filmService; + this.modelMapper = modelMapper; + } + + private UserFilmDto toDto(UserFilmEntity userFilmEntity) { + return modelMapper.map(userFilmEntity, UserFilmDto.class); + } + + private UserFilmGroupedDto toGroupedDto(UserFilmGrouped entity) { + return modelMapper.map(entity, UserFilmGroupedDto.class); + } + + private UserFilmEntity toEntity(UserFilmDto dto) { + final UserFilmEntity entity = modelMapper.map(dto, UserFilmEntity.class); + entity.setUser(userService.get(dto.getUserId())); + entity.setFilm(filmService.get(dto.getFilmId())); + return entity; + } + + @GetMapping + public List getAll( + @PathVariable(name = "user") Long userId) { + return userFilmService.getAll().stream() + .map(this::toDto) + .toList(); + } + + @GetMapping("/{film}") + public UserFilmDto get( + @PathVariable(name = "user") Long userId, + @PathVariable(name = "film") Long filmId) { + return toDto(userFilmService.get(userId, filmId)); + } + + @PostMapping("/{film}") + public UserFilmDto create( + @PathVariable(name = "user") Long userId, + @PathVariable(name = "film") Long filmId) { + return toDto( + userFilmService.create(new UserFilmEntity(userService.get(userId), filmService.get(filmId), false))); + } + + @PutMapping("/{id}") + public UserFilmDto update( + @PathVariable(name = "id") Long id, + @RequestBody @Valid UserFilmDto dto) { + return toDto(userFilmService.update(id, toEntity(dto))); + } + + @PutMapping("/{id}/view") + public UserFilmDto updateViewed( + @PathVariable(name = "id") Long id, + @PathVariable(name = "view") Boolean view) { + return toDto(userFilmService.viewed(id, view)); + } + + @DeleteMapping("/{film}") + public UserFilmDto delete( + @PathVariable(name = "user") Long userId, + @PathVariable(name = "film") Long filmId) { + return toDto(userFilmService.delete(userId, filmId)); + } + + @GetMapping("/total") + public List getMethodName(@PathVariable(name = "user") Long userId) { + return userFilmService.getTotal(userId).stream() + .map(this::toGroupedDto) + .toList(); + } +} diff --git a/laba4/src/main/java/com/example/demo/userfilms/api/UserFilmDto.java b/laba4/src/main/java/com/example/demo/userfilms/api/UserFilmDto.java new file mode 100644 index 0000000..c90dd1a --- /dev/null +++ b/laba4/src/main/java/com/example/demo/userfilms/api/UserFilmDto.java @@ -0,0 +1,50 @@ +package com.example.demo.userfilms.api; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +public class UserFilmDto { + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private Long id; + @NotNull + @Min(1) + private Long userId; + @NotNull + @Min(1) + private Long filmId; + private boolean viewed; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Long getFilmId() { + return filmId; + } + + public void setFilmId(Long filmId) { + this.filmId = filmId; + } + + public Boolean getViewed() { + return viewed; + } + + public void setViewed(Boolean viewed) { + this.viewed = viewed; + } +} diff --git a/laba4/src/main/java/com/example/demo/userfilms/api/UserFilmGroupedDto.java b/laba4/src/main/java/com/example/demo/userfilms/api/UserFilmGroupedDto.java new file mode 100644 index 0000000..cc945b3 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/userfilms/api/UserFilmGroupedDto.java @@ -0,0 +1,22 @@ +package com.example.demo.userfilms.api; + +public class UserFilmGroupedDto { + private Long filmCount; + private String genreName; + + public Long getfilmCount() { + return filmCount; + } + + public void setfilmCount(Long filmCount) { + this.filmCount = filmCount; + } + + public String getGenreName() { + return genreName; + } + + public void setGenreName(String genreName) { + this.genreName = genreName; + } +} diff --git a/laba4/src/main/java/com/example/demo/userfilms/model/UserFilmEntity.java b/laba4/src/main/java/com/example/demo/userfilms/model/UserFilmEntity.java new file mode 100644 index 0000000..f1519da --- /dev/null +++ b/laba4/src/main/java/com/example/demo/userfilms/model/UserFilmEntity.java @@ -0,0 +1,87 @@ +package com.example.demo.userfilms.model; + +import com.example.demo.users.model.UserEntity; + +import java.util.Objects; + +import com.example.demo.core.model.BaseEntity; +import com.example.demo.films.model.FilmEntity; + +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; + +@Entity +@Table(name = "users_films") +public class UserFilmEntity extends BaseEntity { + private Long id; + @ManyToOne + @JoinColumn(name = "userId", nullable = false) + private UserEntity user; + @ManyToOne + @JoinColumn(name = "filmId", nullable = false) + private FilmEntity film; + private boolean viewed; + + public UserFilmEntity() { + } + + public UserFilmEntity(UserEntity user, FilmEntity film, boolean viewed) { + this.user = user; + this.film = film; + this.viewed = viewed; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public UserEntity getUser() { + return user; + } + + public void setUser(UserEntity user) { + this.user = user; + + } + + public FilmEntity getFilm() { + return film; + } + + public void setFilm(FilmEntity film) { + this.film = film; + + } + + public boolean isViewed() { + return viewed; + } + + public void setViewed(boolean viewed) { + this.viewed = viewed; + } + + @Override + public int hashCode() { + return Objects.hash(id, user.getId(), film.getId(), viewed); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + UserFilmEntity other = (UserFilmEntity) obj; + return Objects.equals(id, other.id) + && Objects.equals(user.getId(), other.user.getId()) + && Objects.equals(film.getId(), other.film.getId()) + && viewed == other.viewed; + } +} diff --git a/laba4/src/main/java/com/example/demo/userfilms/model/UserFilmGrouped.java b/laba4/src/main/java/com/example/demo/userfilms/model/UserFilmGrouped.java new file mode 100644 index 0000000..ee1b1d0 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/userfilms/model/UserFilmGrouped.java @@ -0,0 +1,9 @@ +package com.example.demo.userfilms.model; + +import com.example.demo.genres.model.GenreEntity; + +public interface UserFilmGrouped { + Long getCount(); + + GenreEntity getGenre(); +} diff --git a/laba4/src/main/java/com/example/demo/userfilms/repository/UserFilmRepository.java b/laba4/src/main/java/com/example/demo/userfilms/repository/UserFilmRepository.java new file mode 100644 index 0000000..29980ce --- /dev/null +++ b/laba4/src/main/java/com/example/demo/userfilms/repository/UserFilmRepository.java @@ -0,0 +1,30 @@ +package com.example.demo.userfilms.repository; + +import com.example.demo.userfilms.model.UserFilmEntity; +import com.example.demo.userfilms.model.UserFilmGrouped; + +import org.springframework.data.jpa.repository.Query; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.repository.CrudRepository; + +public interface UserFilmRepository extends CrudRepository { + Optional findOneByUserIdAndFilmId(long userId, long filmId); + + List findByUserId(long userId); + + List findByFilmId(long filmId); + + @Query("select " + + "count(*) as count, " + + "g as genre " + + "from FilmEntity f " + + "left join GenreEntity g " + + "on f.genre = g " + + "right join UserFilmEntity uf " + + "on uf.film = f and uf.user.id = ?1 " + + "group by g order by g.name") + List getFilmsTotalByGenre(long userId); +} diff --git a/laba4/src/main/java/com/example/demo/userfilms/service/UserFilmService.java b/laba4/src/main/java/com/example/demo/userfilms/service/UserFilmService.java new file mode 100644 index 0000000..be91860 --- /dev/null +++ b/laba4/src/main/java/com/example/demo/userfilms/service/UserFilmService.java @@ -0,0 +1,125 @@ +package com.example.demo.userfilms.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.StreamSupport; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.demo.core.error.NotFoundException; +import com.example.demo.films.model.FilmEntity; +import com.example.demo.films.service.FilmService; +import com.example.demo.userfilms.model.UserFilmEntity; +import com.example.demo.userfilms.model.UserFilmGrouped; +import com.example.demo.userfilms.repository.UserFilmRepository; +import com.example.demo.users.model.UserEntity; +import com.example.demo.users.service.UserService; + +@Service +public class UserFilmService { + private final UserFilmRepository userFilmRepository; + private final FilmService filmService; + private final UserService userService; + + public UserFilmService( + UserFilmRepository userFilmRepository, FilmService filmService, UserService userService) { + this.userFilmRepository = userFilmRepository; + this.filmService = filmService; + this.userService = userService; + } + + private void checkUserFilm(UserEntity user, FilmEntity film) { + if (userFilmRepository.findOneByUserIdAndFilmId(user.getId(), film.getId()).isPresent()) { + throw new IllegalArgumentException( + String.format("UserFilm with login is already exists")); + } + } + + @Transactional(readOnly = true) + public List getAll() { + return StreamSupport.stream(userFilmRepository.findAll().spliterator(), false).toList(); + } + + @Transactional(readOnly = true) + public UserFilmEntity get(Long id) { + return userFilmRepository.findById(id) + .orElseThrow(() -> new NotFoundException(UserFilmEntity.class, id)); + } + + @Transactional(readOnly = true) + public UserFilmEntity get(Long userId, Long filmId) { + return userFilmRepository.findOneByUserIdAndFilmId(userId, filmId) + .orElseThrow(() -> new NotFoundException(UserFilmEntity.class, userId)); + } + + @Transactional + public UserFilmEntity create(UserFilmEntity entity) { + if (entity == null) { + throw new IllegalArgumentException("Entity is null"); + } + checkUserFilm(entity.getUser(), entity.getFilm()); + return userFilmRepository.save(entity); + } + + @Transactional + public UserFilmEntity update(Long id, UserFilmEntity entity) { + final UserFilmEntity existsEntity = get(id); + existsEntity.setUser(entity.getUser()); + existsEntity.setFilm(entity.getFilm()); + existsEntity.setViewed(entity.isViewed()); + userFilmRepository.save(existsEntity); + return existsEntity; + } + + @Transactional + public UserFilmEntity delete(long userId, long filmId) { + final UserFilmEntity existsEntity = userFilmRepository.findOneByUserIdAndFilmId(userId, filmId).get(); + userFilmRepository.delete(existsEntity); + return existsEntity; + } + + @Transactional + public UserFilmEntity delete(long id) { + final UserFilmEntity existsEntity = get(id); + userFilmRepository.delete(existsEntity); + return existsEntity; + } + + @Transactional + public List addAllFilm(UserEntity user) { + List list = new ArrayList(); + filmService.getAll().forEach(film -> { + final UserFilmEntity userFilm = new UserFilmEntity(user, film, true); + userFilm.setUser(user); + userFilm.setFilm(film); + list.add(userFilm); + }); + userFilmRepository.saveAll(list); + return list; + } + + @Transactional + public UserFilmEntity update(long id, UserFilmEntity entity) { + final UserFilmEntity existsEntity = get(id); + existsEntity.setUser(entity.getUser()); + existsEntity.setFilm(entity.getFilm()); + existsEntity.setViewed(entity.isViewed()); + userFilmRepository.save(existsEntity); + return existsEntity; + } + + @Transactional + public UserFilmEntity viewed(long id, Boolean viewed) { + final UserFilmEntity existsEntity = get(id); + existsEntity.setViewed(viewed); + userFilmRepository.save(existsEntity); + return existsEntity; + } + + @Transactional(readOnly = true) + public List getTotal(long userId) { + userService.get(userId); + return userFilmRepository.getFilmsTotalByGenre(userId); + } +} \ No newline at end of file diff --git a/laba4/src/main/java/com/example/demo/users/api/UserProfileController.java b/laba4/src/main/java/com/example/demo/users/api/UserProfileController.java index c00af8f..df81a12 100644 --- a/laba4/src/main/java/com/example/demo/users/api/UserProfileController.java +++ b/laba4/src/main/java/com/example/demo/users/api/UserProfileController.java @@ -24,6 +24,9 @@ import com.example.demo.orders.service.OrderService; import com.example.demo.types.api.TypeDto; import com.example.demo.types.model.TypeEntity; import com.example.demo.types.service.TypeService; +import com.example.demo.userfilms.api.UserFilmDto; +import com.example.demo.userfilms.model.UserFilmEntity; +import com.example.demo.userfilms.service.UserFilmService; import com.example.demo.users.model.UserSubscriptionWithStatus; import com.example.demo.users.service.UserService; @@ -38,16 +41,19 @@ public class UserProfileController { private static final String PROFILE_ATTRIBUTE = "profile"; private final OrderService orderService; + private final UserFilmService userFilmService; private final TypeService typeService; private final UserService userService; private final ModelMapper modelMapper; public UserProfileController( OrderService orderService, + UserFilmService userFilmService, TypeService typeService, UserService userService, ModelMapper modelMapper) { this.orderService = orderService; + this.userFilmService = userFilmService; this.typeService = typeService; this.userService = userService; this.modelMapper = modelMapper; @@ -57,6 +63,10 @@ public class UserProfileController { return modelMapper.map(entity, OrderDto.class); } + private UserFilmDto toUserFilmDto(UserFilmEntity entity) { + return modelMapper.map(entity, UserFilmDto.class); + } + private OrderGroupedDto toGroupedDto(OrderGrouped entity) { return modelMapper.map(entity, OrderGroupedDto.class); } @@ -87,6 +97,10 @@ public class UserProfileController { model.addAllAttributes(PageAttributesMapper.toAttributes( orderService.getAll(userId, typeId, page, Constants.DEFUALT_PAGE_SIZE), this::toDto)); + model.addAttribute("userfilms", + userFilmService.getAll().stream() + .map(this::toUserFilmDto) + .toList()); model.addAttribute("stats", orderService.getTotal(userId).stream() .map(this::toGroupedDto) diff --git a/laba4/src/main/resources/templates/default.html b/laba4/src/main/resources/templates/default.html index 7e928b7..bf13d6a 100644 --- a/laba4/src/main/resources/templates/default.html +++ b/laba4/src/main/resources/templates/default.html @@ -38,6 +38,10 @@ th:classappend="${activeLink.startsWith('/admin/subscription') ? 'active' : ''}"> Списки рассылки + + Жанры фильмов + Консоль H2 Ошибка diff --git a/laba4/src/main/resources/templates/genre-edit.html b/laba4/src/main/resources/templates/genre-edit.html new file mode 100644 index 0000000..44291e7 --- /dev/null +++ b/laba4/src/main/resources/templates/genre-edit.html @@ -0,0 +1,28 @@ + + + + + Редакторовать жанр фильма + + + +
+
+
+ + +
+
+ + +
+
+
+ + Отмена +
+
+
+ + + \ No newline at end of file diff --git a/laba4/src/main/resources/templates/genre.html b/laba4/src/main/resources/templates/genre.html new file mode 100644 index 0000000..c5d5959 --- /dev/null +++ b/laba4/src/main/resources/templates/genre.html @@ -0,0 +1,50 @@ + + + + + Жанры фильмов + + + +
+ +

Данные отсутствуют

+ +

Жанры фильмов

+ + + + + + + + + + + + + + + + + + + +
IDЖанр фильма
+
+ +
+
+
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/laba4/src/main/resources/templates/profile.html b/laba4/src/main/resources/templates/profile.html index f8ea866..56c1fa7 100644 --- a/laba4/src/main/resources/templates/profile.html +++ b/laba4/src/main/resources/templates/profile.html @@ -11,6 +11,9 @@ + @@ -26,6 +29,12 @@ totalPages=${totalPages}, currentPage=${currentPage}) }" /> +
+ +
  • diff --git a/laba4/src/main/resources/templates/userfilms.html b/laba4/src/main/resources/templates/userfilms.html new file mode 100644 index 0000000..821dd62 --- /dev/null +++ b/laba4/src/main/resources/templates/userfilms.html @@ -0,0 +1,61 @@ + + + + + + +

    Данные отсутствуют

    + +
    +
    + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + +
    IDТип заказаЦенаКоличествоСумма
    +
    + + + +
    +
    +
    + + +
    + + + + \ No newline at end of file