2 Commits

Author SHA1 Message Date
0346722ad5 lab5 2023-08-16 19:56:40 +04:00
d12e8a5f61 lab5 2023-06-11 00:25:47 +04:00
35 changed files with 1143 additions and 29 deletions

View File

@@ -17,7 +17,15 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2:2.1.210'
implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.5'
implementation 'org.jetbrains:annotations:24.0.0'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
implementation 'org.webjars:bootstrap:5.1.3'
implementation 'org.webjars:jquery:3.6.0'
implementation 'org.webjars:font-awesome:6.1.0'
implementation 'org.jetbrains:annotations:24.0.0'
implementation 'org.jetbrains:annotations:24.0.0'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.hibernate.validator:hibernate-validator'

View File

@@ -7,9 +7,6 @@ import ru.ulstu.is.sbapp.database.model.Album;
import java.util.List;
public interface IAlbumRepository extends JpaRepository<Album, Long> {
@Query("select a.albumName as album, s.songName as songs " +
"from Album a " +
"join a.songs s " +
"group by a.id, a.albumName, s.songName")
List<Object[]> getAll();
@Query("SELECT a FROM Album a WHERE a.albumName = :name")
List<Album> getAlbumsByName(String name);
}

View File

@@ -11,4 +11,6 @@ import java.util.List;
public interface IArtistRepository extends JpaRepository<Artist, Long> {
@Query(value = "SELECT * FROM artist_album", nativeQuery = true)
List<Object[]> getAllArtistAlbum();
@Query("SELECT a FROM Artist a WHERE a.artistName = :name")
List<Artist> getArtistsByName(String name);
}

View File

@@ -8,6 +8,8 @@ import ru.ulstu.is.sbapp.database.model.Song;
import java.util.List;
public interface ISongRepository extends JpaRepository<Song, Long> {
@Query("SELECT s.songs FROM Album s WHERE :song MEMBER OF s.songs")
@Query("SELECT a.songs FROM Album a WHERE :song MEMBER OF a.songs")
List<Song> findSongsInAlbum(@Param("song") Song song);
@Query("SELECT s FROM Song s WHERE s.songName = :name")
List<Song> getSongsByName(String name);
}

View File

@@ -2,12 +2,19 @@ package ru.ulstu.is.sbapp;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
class WebConfiguration implements WebMvcConfigurer {
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");
}
public static final String REST_API = "/api";
@Override
public void addViewControllers(ViewControllerRegistry registry) {
WebMvcConfigurer.super.addViewControllers(registry);
registry.addViewController("artist");
}
}

View File

@@ -3,6 +3,7 @@ package ru.ulstu.is.sbapp.controllers;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import ru.ulstu.is.sbapp.WebConfiguration;
import ru.ulstu.is.sbapp.database.model.Artist;
import ru.ulstu.is.sbapp.database.model.Song;
import ru.ulstu.is.sbapp.database.service.AlbumService;
@@ -11,7 +12,7 @@ import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/album")
@RequestMapping(WebConfiguration.REST_API + "/album")
public class AlbumController {
private final AlbumService albumService;
@@ -78,8 +79,4 @@ public class AlbumController {
public void addArtistToAlbum(@PathVariable Long id, @RequestBody @Valid List<Long> artistIds){
albumService.addArtistToAlbum(id, artistIds);
}
@GetMapping("/getAll")
public Map<String, List<String>> getAll(){
return albumService.getAll();
}
}

View File

@@ -0,0 +1,132 @@
package ru.ulstu.is.sbapp.controllers;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import ru.ulstu.is.sbapp.database.model.Artist;
import ru.ulstu.is.sbapp.database.model.Song;
import ru.ulstu.is.sbapp.database.service.AlbumService;
import ru.ulstu.is.sbapp.database.service.ArtistService;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Controller
@RequestMapping("/album")
public class AlbumMvcController {
private final AlbumService albumService;
private final ArtistService artistService;
public AlbumMvcController(AlbumService albumService, ArtistService artistService) {
this.albumService = albumService;
this.artistService = artistService;
}
@GetMapping
public String getAlbums(Model model) {
model.addAttribute("albums",
albumService.findAllAlbums().stream()
.map(AlbumDTO::new)
.toList());
return "album";
}
@GetMapping(value = {"/edit", "/edit/{id}"})
public String editAlbum(@PathVariable(required = false) Long id,
Model model) {
if (id == null || id <= 0) {
model.addAttribute("albumDTO", new AlbumDTO());
} else {
model.addAttribute("albumId", id);
model.addAttribute("albumDTO", new AlbumDTO(albumService.findAlbum(id)));
}
return "album-edit";
}
@PostMapping(value = {"/", "/{id}"})
public String saveAlbum(@PathVariable(required = false) Long id,
@ModelAttribute @Valid AlbumDTO albumDTO,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "album-edit";
}
if (id == null || id <= 0) {
albumService.addAlbum(albumDTO.getAlbumName());
} else {
albumService.updateAlbum(id, albumDTO.getAlbumName());
}
return "redirect:/album";
}
@PostMapping("/delete/{id}")
public String deleteAlbum(@PathVariable Long id) {
albumService.deleteAlbum(id);
return "redirect:/album";
}
@GetMapping("/songs/{id}")
public String getSongsFromAlbum(@PathVariable Long id, Model model) {
List<Song> songs = albumService.getSongFromAlbum(id);
model.addAttribute("songs", songs);
return "view-songs";
}
@GetMapping("/artists/{id}")
public String getArtistsFromAlbum(@PathVariable Long id, Model model) {
List<Artist> artists = albumService.getArtistInAlbum(id);
model.addAttribute("artists", artists);
return "view-artists";
}
@GetMapping("/getSongsUndefined/{id}")
public String getSongsFromUndefinedAlbum(@PathVariable Long id, Model model) {
List<Song> songs = albumService.getSongsUndefined();
model.addAttribute("undefinedSongs", songs);
model.addAttribute("albumId", id);
model.addAttribute("albumDTO", new AlbumDTO(albumService.findAlbum(id)));
return "add-songs-to-album";
}
@PostMapping("/addSongs/{id}")
public String addSongToAlbum(@PathVariable Long id,
@RequestParam("songId") List<String> songsIds) {
List<Long> songIdsAsLong = songsIds.stream()
.map(Long::parseLong)
.collect(Collectors.toList());
albumService.addSongToAlbum(id, songIdsAsLong);
return "redirect:/album";
}
@PostMapping("/deleteSongFromAlbum/{id}")
public String deleteSongFromAlbum(@PathVariable Long id) {
albumService.deleteSongFromAlbum(id);
return "redirect:/album";
}
@GetMapping("/getArtistsUndefined/{id}")
public String getArtistsFromUndefinedAlbum(@PathVariable Long id, Model model) {
List<Artist> artists = albumService.getArtistsUndefined(id);
model.addAttribute("undefinedArtists", artists);
model.addAttribute("albumId", id);
model.addAttribute("albumDTO", new AlbumDTO(albumService.findAlbum(id)));
return "add-artist-to-album";
}
@PostMapping("/addArtists/{id}")
public String addArtistToAlbum(@PathVariable Long id,
@RequestParam("artistId") List<String> artistIds) {
List<Long> artistIdsAsLong = artistIds.stream()
.map(Long::parseLong)
.collect(Collectors.toList());
albumService.addArtistToAlbum(id, artistIdsAsLong);
return "redirect:/album";
}
}

View File

@@ -2,12 +2,13 @@ package ru.ulstu.is.sbapp.controllers;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
import ru.ulstu.is.sbapp.WebConfiguration;
import ru.ulstu.is.sbapp.database.service.ArtistService;
import java.util.List;
@RestController
@RequestMapping("/artist")
@RequestMapping(WebConfiguration.REST_API + "/artist")
public class ArtistController {
private final ArtistService artistService;

View File

@@ -37,6 +37,9 @@ public class ArtistDTO {
public void setGenre(String genre){
this.genre = genre;
}
public void setArtistName(String artistName){
this.artistName = artistName;
}
public List<Long> getAlbumIds(){
return albumIds;

View File

@@ -0,0 +1,84 @@
package ru.ulstu.is.sbapp.controllers;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import ru.ulstu.is.sbapp.database.service.AlbumService;
import ru.ulstu.is.sbapp.database.service.ArtistService;
import java.util.List;
@Controller
@RequestMapping("/artist")
public class ArtistMvcController {
private final ArtistService artistService;
private final AlbumService albumService;
public ArtistMvcController(ArtistService artistService, AlbumService albumService)
{
this.artistService = artistService;
this.albumService = albumService;
}
@GetMapping
public String getArtists(Model model) {
model.addAttribute("artists",
artistService.findAllArtists().stream()
.map(ArtistDTO::new)
.toList());
return "artist";
}
@GetMapping(value = {"/edit", "/edit/{id}"})
public String editArtist(@PathVariable(required = false) Long id,
Model model) {
if (id == null || id <= 0) {
model.addAttribute("artistDTO", new ArtistDTO());
} else {
model.addAttribute("artistId", id);
model.addAttribute("artistDTO", new ArtistDTO(artistService.findArtist(id)));
}
return "artist-edit";
}
@PostMapping(value = {"/", "/{id}"})
public String saveArtist(@PathVariable(required = false) Long id,
@ModelAttribute @Valid ArtistDTO artistDTO,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "artist-edit";
}
if (id == null || id <= 0) {
artistService.addArtist(artistDTO.getArtistName(), artistDTO.getGenre());
} else {
artistService.updateArtist(id, artistDTO.getArtistName(), artistDTO.getGenre());
}
return "redirect:/artist";
}
@PostMapping("/delete/{id}")
public String deleteArtist(@PathVariable Long id) {
artistService.deleteArtist(id);
return "redirect:/artist";
}
@GetMapping("/addArtistToAlbum/{id}")
public String addArtistToAlbumForm(@PathVariable Long id, Model model) {
model.addAttribute("artistDTO", new ArtistDTO(artistService.findArtist(id)));
model.addAttribute("artistId", id);
model.addAttribute("albums", albumService.findAllAlbums());
return "add-artist-to-album";
}
@PostMapping("/addArtistToAlbum/{id}")
public String addArtistToAlbum(@PathVariable Long id,
@RequestParam("albumId") List<Long> albumIds) {
artistService.addArtistToAlbum(id, albumIds);
return "redirect:/artist";
}
}

View File

@@ -0,0 +1,26 @@
package ru.ulstu.is.sbapp.controllers;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ru.ulstu.is.sbapp.WebConfiguration;
import ru.ulstu.is.sbapp.database.service.SearchService;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping(WebConfiguration.REST_API + "/search")
public class SearchController {
private final SearchService searchService;
public SearchController(SearchService searchService) {
this.searchService = searchService;
}
@GetMapping
public Map<String, List<Object>> getByName(@RequestParam(value = "name", defaultValue = "песня") String name) {
return searchService.getByName(name);
}
}

View File

@@ -0,0 +1,32 @@
package ru.ulstu.is.sbapp.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import ru.ulstu.is.sbapp.database.service.SearchService;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/search")
public class SearchMvcController {
private final SearchService searchService;
public SearchMvcController(SearchService searchService) {
this.searchService = searchService;
}
@GetMapping
public String getByName(@RequestParam(value = "name", defaultValue = "песня") String name, Model model) {
Map<String, List<Object>> searchResult = searchService.getByName(name);
model.addAttribute("name", name);
model.addAttribute("searchResult", searchResult != null);
if (searchResult != null) {
model.addAttribute("songs", searchResult.get("songs"));
model.addAttribute("albums", searchResult.get("albums"));
model.addAttribute("artists", searchResult.get("artists"));
}
return "search";
}
}

View File

@@ -2,13 +2,14 @@ package ru.ulstu.is.sbapp.controllers;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
import ru.ulstu.is.sbapp.WebConfiguration;
import ru.ulstu.is.sbapp.database.service.AlbumService;
import ru.ulstu.is.sbapp.database.service.SongService;
import java.util.List;
@RestController
@RequestMapping("/song")
@RequestMapping(WebConfiguration.REST_API + "/song")
public class SongController {
private final SongService songService;
private final AlbumService albumService;
@@ -51,5 +52,4 @@ public class SongController {
.map(AlbumDTO::new)
.toList();
}
}

View File

@@ -39,11 +39,11 @@ public class SongDTO {
return albumId;
}
public void setAlbumId(long id){
public void setAlbumId(Long id){
this.albumId = id;
}
public void setId(long id){
public void setId(Long id){
this.id = id;
}

View File

@@ -0,0 +1,67 @@
package ru.ulstu.is.sbapp.controllers;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import ru.ulstu.is.sbapp.database.service.AlbumService;
import ru.ulstu.is.sbapp.database.service.SongService;
@Controller
@RequestMapping("/song")
public class SongMvcController {
private final SongService songService;
private final AlbumService albumService;
public SongMvcController(SongService songService, AlbumService albumService)
{
this.songService = songService;
this.albumService = albumService;
}
@GetMapping
public String getSongs(Model model) {
model.addAttribute("songs",
songService.findAllSongs().stream()
.map(SongDTO::new)
.toList());
return "song";
}
@GetMapping(value = {"/edit", "/edit/{id}"})
public String editSong(@PathVariable(required = false) Long id,
Model model) {
model.addAttribute("Albums", albumService.findAllAlbums());
if (id == null || id <= 0) {
model.addAttribute("songDTO", new SongDTO());
} else {
model.addAttribute("songId", id);
model.addAttribute("songDTO", new SongDTO(songService.findSong(id)));
}
return "song-edit";
}
@PostMapping(value = {"/", "/{id}"})
public String saveSong(@PathVariable(required = false) Long id,
@ModelAttribute @Valid SongDTO songDTO,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("errors", bindingResult.getAllErrors());
return "song-edit";
}
if (id == null || id <= 0) {
songService.addSong(songDTO.getSongName(), songDTO.getDuration());
} else {
songService.updateSong(id, songDTO.getSongName(), songDTO.getDuration());
}
return "redirect:/song";
}
@PostMapping("/delete/{id}")
public String deleteSong(@PathVariable Long id) {
songService.deleteSong(id);
return "redirect:/song";
}
}

View File

@@ -128,13 +128,13 @@ public class AlbumService {
}
@Transactional
public Map<String, List<String>> getAll(){
return albumRepository.getAll().stream()
.collect(
Collectors.groupingBy(
o -> (String) o[0],
Collectors.mapping( o -> (String) o[1], Collectors.toList() )
)
);
public List<Artist> getArtistsUndefined(Long albumId){
List<Artist> artists = new ArrayList<>();
for(Artist artist : artistService.findAllArtists()){
if(!artist.getAlbumIds().contains(albumId)){
artists.add(artist);
}
}
return artists;
}
}

View File

@@ -47,6 +47,7 @@ public class ArtistService {
}
final Artist currentArtist = findArtist(id);
currentArtist.setArtistName(name);
currentArtist.setGenre(genre);
return artistRepository.save(currentArtist);
}

View File

@@ -0,0 +1,49 @@
package ru.ulstu.is.sbapp.database.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.ulstu.is.sbapp.Repository.IAlbumRepository;
import ru.ulstu.is.sbapp.Repository.IArtistRepository;
import ru.ulstu.is.sbapp.Repository.ISongRepository;
import ru.ulstu.is.sbapp.database.model.Album;
import ru.ulstu.is.sbapp.database.model.Artist;
import ru.ulstu.is.sbapp.database.model.Song;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class SearchService {
private final IAlbumRepository albumRepository;
private final ISongRepository songRepository;
private final IArtistRepository artistRepository;
public SearchService(IAlbumRepository albumRepository, ISongRepository songRepository, IArtistRepository artistRepository) {
this.albumRepository = albumRepository;
this.songRepository = songRepository;
this.artistRepository = artistRepository;
}
@Transactional
public Map<String, List<Object>> getByName(String name) {
Map<String, List<Object>> resultMap = new HashMap<>();
List<Song> songs = songRepository.getSongsByName(name).stream().toList();
List<Object> songsResult = new ArrayList<>(songs);
resultMap.put("songs", songsResult);
List<Album> albums = albumRepository.getAlbumsByName(name).stream().toList();
List<Object> albumsResult = new ArrayList<>(albums);
resultMap.put("albums", albumsResult);
List<Artist> artists = artistRepository.getArtistsByName(name).stream().toList();
List<Object> artistsResult = new ArrayList<>(artists);
resultMap.put("artists", artistsResult);
return resultMap;
}
}

View File

@@ -23,7 +23,7 @@ public class SongService {
@Transactional
public Song addSong(String songName, Double duration){
if (!StringUtils.hasText(songName) || !StringUtils.hasText(String.valueOf(duration))) {
if (!StringUtils.hasText(songName) || duration == null) {
throw new IllegalArgumentException("Song name or duration is null or empty");
}
final Song song = new Song(songName, duration);
@@ -43,7 +43,7 @@ public class SongService {
@Transactional
public Song updateSong(Long id, String name, Double duration) {
if (!StringUtils.hasText(name) || !StringUtils.hasText(String.valueOf(duration))) {
if (!StringUtils.hasText(name) || duration == null) {
throw new IllegalArgumentException("Song name or duration is null or empty");
}
final Song currentSong = findSong(id);

View File

@@ -6,6 +6,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import ru.ulstu.is.sbapp.database.service.AlbumNotFoundException;
import ru.ulstu.is.sbapp.database.service.ArtistNotFoundException;
import ru.ulstu.is.sbapp.database.service.SongNotFoundException;
@@ -13,7 +14,7 @@ import ru.ulstu.is.sbapp.database.util.validation.ValidationException;
import java.util.stream.Collectors;
@ControllerAdvice
@ControllerAdvice(annotations = RestController.class)
public class AdviceController {
@ExceptionHandler({
AlbumNotFoundException.class,

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="content">
<div th:text="${errors}" class="margin-bottom alert-danger"></div>
<form action="#" th:action="@{/album/addArtists/{id}(id=${albumId})}" method="post">
<div class="mb-3">
<label for="albumName" class="form-label">Название</label>
<input type="text" class="form-control" id="albumName" th:field="${albumDTO.albumName}" readonly>
</div>
<div class="form-group">
<label for="albumName">Выберите исполнителей для добавления:</label>
<ul class="list-group">
<li class="list-group-item" th:each="artist, iterator: ${undefinedArtists}">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="artistId" th:value="${artist.id}">
<label class="form-check-label" th:text="${artist.artistName}"></label>
</div>
</li>
</ul>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary button-fixed">
<span>Добавить</span>
</button>
<a class="btn btn-secondary button-fixed" th:href="@{/album}">
Назад
</a>
</div>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="content">
<div th:text="${errors}" class="margin-bottom alert-danger"></div>
<form action="#" th:action="@{/album/addSongs/{id}(id=${albumId})}" method="post">
<div class="mb-3">
<label for="albumName" class="form-label">Название</label>
<input type="text" class="form-control" id="albumName" th:field="${albumDTO.albumName}" readonly>
</div>
<div class="form-group">
<label for="albumName">Выберите песни:</label>
<ul class="list-group">
<li class="list-group-item" th:each="song, iterator: ${undefinedSongs}">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="songId" th:value="${song.id}">
<label class="form-check-label" th:text="${song.songName}"></label>
</div>
</li>
</ul>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary button-fixed">
<span>Добавить</span>
</button>
<a class="btn btn-secondary button-fixed" th:href="@{/album}">
Назад
</a>
</div>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="content">
<div th:text="${errors}" class="margin-bottom alert-danger"></div>
<form action="#" th:action="@{/album/{id}(id=${id})}" th:object="${albumDTO}" method="post">
<div class="mb-3">
<label for="albumName" class="form-label">Название</label>
<input type="text" class="form-control" id="albumName" th:field="${albumDTO.albumName}" required="true">
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary button-fixed">
<span th:if="${albumId == null}">Добавить</span>
<span th:if="${albumId != null}">Обновить</span>
</button>
<a class="btn btn-secondary button-fixed" th:href="@{/album}">
Назад
</a>
</div>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<body>
<div layout:fragment="content">
<h1 class="text-center mb-4">Альбомы</h1>
<div>
<a class="btn btn-success button-fixed"
th:href="@{/album/edit}">
<i class="fa-solid fa-plus"></i> Добавить
</a>
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">Название</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr th:each="album, iterator: ${albums}">
<td th:text="${album.albumName}"></td>
<td>
<div class="btn-album" role="group" aria-label="Basic example">
<a class="btn btn-warning button-fixed button-sm"
th:href="@{/album/edit/{id}(id=${album.id})}">
<i class="fa fa-pencil" aria-hidden="true"></i> Изменить
</a>
<button type="button" class="btn btn-danger button-fixed button-sm"
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${album.id}').click()|">
<i class="fa fa-trash" aria-hidden="true"></i> Удалить
</button>
<a class="btn btn-primary button-fixed button-sm"
th:href="@{/album/songs/{id}(id=${album.id})}">Посмотреть песни</a>
<a class="btn btn-primary button-fixed button-sm"
th:href="@{/album/getSongsUndefined/{id}(id=${album.id})}">Добавить песни</a>
<a class="btn btn-primary button-fixed button-sm"
th:href="@{/album/getArtistsUndefined/{id}(id=${album.id})}"> Добавить исполнителей</a>
<a class="btn btn-primary button-fixed button-sm"
th:href="@{/album/artists/{id}(id=${album.id})}">Посмотреть исполнителей</a>
</div>
<form th:action="@{/album/delete/{id}(id=${album.id})}" method="post">
<button th:id="'remove-' + ${album.id}" type="submit" style="display: none">
Удалить
</button>
</form>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="content">
<div th:text="${errors}" class="margin-bottom alert-danger"></div>
<form action="#" th:action="@{/artist/{id}(id=${id})}" th:object="${artistDTO}" method="post">
<div class="mb-3">
<label for="artistName" class="form-label">Имя</label>
<input type="text" class="form-control" id="artistName" th:field="${artistDTO.artistName}" required="true">
<label for="genre" class="form-label">Жанр</label>
<input type="text" class="form-control" id="genre" th:field="${artistDTO.genre}" required="true">
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary button-fixed">
<span th:if="${artistId == null}">Добавить</span>
<span th:if="${artistId != null}">Обновить</span>
</button>
<a class="btn btn-secondary button-fixed" th:href="@{/artist}">
Назад
</a>
</div>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<body>
<div layout:fragment="content">
<h1 class="text-center mb-4">Исполнители</h1>
<div>
<a class="btn btn-success button-fixed"
th:href="@{/artist/edit}">
<i class="fa-solid fa-plus"></i> Добавить
</a>
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">Имя</th>
<th scope="col">Жанр</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr th:each="artist, iterator: ${artists}">
<td th:text="${artist.artistName}"></td>
<td th:text="${artist.genre}"></td>
<td>
<div class="btn-album" role="group" aria-label="Basic example">
<a class="btn btn-warning button-fixed button-sm"
th:href="@{/artist/edit/{id}(id=${artist.id})}">
<i class="fa fa-pencil" aria-hidden="true"></i> Изменить
</a>
<button type="button" class="btn btn-danger button-fixed button-sm"
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${artist.id}').click()|">
<i class="fa fa-trash" aria-hidden="true"></i> Удалить
</button>
</div>
<form th:action="@{/artist/delete/{id}(id=${artist.id})}" method="post">
<button th:id="'remove-' + ${artist.id}" type="submit" style="display: none">
Удалить
</button>
</form>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="ru"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Streaming Service</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="icon" href="/favicon.svg">
<script type="text/javascript" src="/webjars/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="/webjars/bootstrap/5.1.3/css/bootstrap.min.css"/>
<link rel="stylesheet" href="/webjars/font-awesome/6.1.0/css/all.min.css"/>
<!-- <link rel="stylesheet" href="/css/style.css"/>-->
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<a class="nav-link">Стриминговый сервис</a>
<a class="nav-link" href="/song" th:classappend="${#strings.equals(activeLink, '/song')} ? 'active' : ''">Песни</a>
<a class="nav-link" href="/album" th:classappend="${#strings.equals(activeLink, '/album')} ? 'active' : ''">Альбомы</a>
<a class="nav-link" href="/artist" th:classappend="${#strings.equals(activeLink, '/artist')} ? 'active' : ''">Исполнители</a>
<a class="nav-link" href="/search" th:classappend="${#strings.equals(activeLink, '/search')} ? 'active' : ''">Поиск</a>
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="container container-padding" layout:fragment="content">
</div>
</div>
</body>
<th:block layout:fragment="scripts">
</th:block>
</html>

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="content">
<div><span th:text="${error}"></span></div>
<a href="/">На главную</a>
</div>
</body>
</html>

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}">
<head>
</head>
<body>
<div layout:fragment="content">
<div>It's works!</div>
<a href="123">ERROR</a>
</div>
</body>
</html>

View File

@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<body>
<div layout:fragment="content">
<form action="#" th:action="@{/search}" method="get">
<div class="search-box">
<label for="name"><h2>Введите имя для поиска:</h2></label>
<input type="text" class="form-control" id="name" th:name="name" th:value="${name}" required>
<br>
<div>
<button type="submit" class="btn btn-primary" >Поиск</button>
</div>
</div>
<br>
<div>
<h2>Результат поиска "<span th:text="${name}"></span>"</h2>
<div th:if="searchResult">
<h3>Песни</h3>
<ul>
<li th:each="song : ${songs}">
<span th:text="${song.songName}"></span>
</li>
</ul>
<h3>Альбомы</h3>
<ul>
<li th:each="album : ${albums}">
<span th:text="${album.albumName}"></span>
</li>
</ul>
<h3>Исполнители</h3>
<ul>
<li th:each="artist : ${artists}">
<span th:text="${artist.artistName}"></span>
</li>
</ul>
</div>
</div>
</form>
</div>
</body>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="content">
<div th:text="${errors}" class="margin-bottom alert-danger"></div>
<form action="#" th:action="@{/song/{id}(id=${id})}" th:object="${songDTO}" method="post">
<div class="mb-3">
<label for="songName" class="form-label">Название</label>
<input type="text" class="form-control" id="songName" th:field="${songDTO.songName}" required>
<label for="duration" class="form-label">Продолжительность</label>
<input type="number" step="0.1" class="form-control" id="duration" th:field="${songDTO.duration}" required>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary button-fixed">
<span th:if="${songId == null}">Добавить</span>
<span th:if="${songId != null}">Обновить</span>
</button>
<a class="btn btn-secondary button-fixed" th:href="@{/song}">
Назад
</a>
</div>
</form>
</div>
</body>
</html>

View File

@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="content">
<h1 class="text-center mb-4">Песни</h1>
<div>
<a class="btn btn-success button-fixed"
th:href="@{/song/edit}">
<i class="fa-solid fa-plus"></i> Добавить
</a>
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">Название</th>
<th scope="col">Продолжительность</th>
<th scope="col">Альбом</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr th:each="song, iterator: ${songs}">
<td th:text="${song.songName}"></td>
<td th:text="${song.duration}"></td>
<td th:text="${song.albumName}"></td>
<td>
<div class="btn-album" role="group" aria-label="Basic example">
<a class="btn btn-warning button-fixed button-sm"
th:href="@{/song/edit/{id}(id=${song.id})}">
<i class="fa fa-pencil" aria-hidden="true"></i> Изменить
</a>
<button type="button" class="btn btn-danger button-fixed button-sm"
th:attr="onclick=|confirm('Удалить запись?') && document.getElementById('remove-${song.id}').click()|">
<i class="fa fa-trash" aria-hidden="true"></i> Удалить
</button>
</div>
<form th:action="@{/song/delete/{id}(id=${song.id})}" method="post">
<button th:id="'remove-' + ${song.id}" type="submit" style="display: none">
Удалить
</button>
</form>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="content">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">Имя</th>
<th scope="col">Жанр</th>
</tr>
</thead>
<tbody>
<tr th:each="artist, iterator: ${artists}">
<td th:text="${artist.artistName}"></td>
<td th:text="${artist.genre}"></td>
</tr>
</tbody>
<a class="btn btn-secondary button-fixed" th:href="@{/album}">
Назад
</a>
</table>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{default}" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div layout:fragment="content">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">Название</th>
<th scope="col">Продолжительность</th>
<th></th>
</tr>
</thead>
<tbody>
<tr th:each="song, iterator: ${songs}">
<td th:text="${song.songName}"></td>
<td th:text="${song.duration}"></td>
<td>
<form method="post" th:action="@{/album/deleteSongFromAlbum/{id}(id=${song.id})}">
<button type="submit" class="btn btn-danger button-fixed button-sm">Удалить</button>
</form>
</td>
</tr>
</tbody>
<a class="btn btn-secondary button-fixed" th:href="@{/album}">
Назад
</a>
</table>
</div>
</div>
</body>
</html>

View File

@@ -1,4 +1,215 @@
package ru.ulstu.is.sbapp;
import jakarta.transaction.Transactional;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.platform.commons.logging.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import ru.ulstu.is.sbapp.database.model.Artist;
import ru.ulstu.is.sbapp.database.model.Song;
import ru.ulstu.is.sbapp.database.model.Album;
import ru.ulstu.is.sbapp.database.service.ArtistService;
import ru.ulstu.is.sbapp.database.service.FindByNameService;
import ru.ulstu.is.sbapp.database.service.SongService;
import ru.ulstu.is.sbapp.database.service.AlbumService;
import jakarta.persistence.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
public class SbappApplicationTests {
@Autowired
private AlbumService albumService;
@Autowired
private ArtistService artistService;
@Autowired
private SongService songService;
@Autowired
private FindByNameService findService;
@Test
void testSongCreate(){
songService.deleteAllSongs();
final Song song = songService.addSong("Song",2.36);
Assertions.assertNotNull(song.getId());
}
@Test
void testArtistCreate(){
artistService.deleteAllArtists();
final Artist artist = artistService.addArtist("Artist", "genre");
Assertions.assertNotNull(artist.getId());
}
@Test
void testAlbumCreate() throws IOException {
albumService.deleteAllAlbums();
artistService.deleteAllArtists();
songService.deleteAllSongs();
final Artist artist1 = artistService.addArtist("Artist", "genre");
final Artist artist2 = artistService.addArtist("Artist", "genre");
final Album album1= albumService.addAlbum("Album");
final Album album2= albumService.addAlbum("Album");
album1.addArtist(artist1);
album2.addArtist(artist1);
album2.addArtist(artist2);
Assertions.assertNotNull(album1.getId());
Assertions.assertNotNull(album2.getId());
}
@Test
void ReadAlbum() throws IOException {
albumService.deleteAllAlbums();
artistService.deleteAllArtists();
songService.deleteAllSongs();
final Artist artist1 = artistService.addArtist("Artist", "genre");
final Song song = songService.addSong("Song",3.28);
final Album album2= albumService.addAlbum("Album");
album2.addArtist(artist1);
final Album findAlbum = albumService.findAlbum(album2.getId());
Assertions.assertEquals(album2, findAlbum);
}
@Test
void ReadAlbumTrue() throws IOException {
albumService.deleteAllAlbums();
artistService.deleteAllArtists();
songService.deleteAllSongs();
final Artist artist1 = artistService.addArtist("Artist", "genre");
final Artist artist2 = artistService.addArtist("Artist2", "genre");
final Song song = songService.addSong("Song",3.19);
Album album2= albumService.addAlbum("Album");
album2 =albumService.addArtist(album2.getId(),artist1.getId());
album2 =albumService.addArtist(album2.getId(),artist2.getId());
final Album findAlbum = albumService.findAlbum(album2.getId());
Assertions.assertEquals(album2, findAlbum);
}
@Test
void ReadSong(){
songService.deleteAllSongs();
final Song song = songService.addSong("Song",3.29);
final Album album = albumService.addAlbum("Album");
song.setAlbum(album);
final Song findSong = songService.findSong(song.getId());
Assertions.assertEquals(song, findSong);
}
@Test
void ReadArtist(){
artistService.deleteAllArtists();
final Artist artist = artistService.addArtist("Artist", "genre");
final Artist findArtist = artistService.findArtist(artist.getId());
Assertions.assertEquals(artist, findArtist);
}
@Test
void testAlbumCheck() throws IOException {
albumService.deleteAllAlbums();
artistService.deleteAllArtists();
songService.deleteAllSongs();
final Artist artist1 = artistService.addArtist("Artist", "genre");
final Artist artist2 = artistService.addArtist("Artist1", "genre1");
final Song song = songService.addSong("Song",3.28);
final Album album2= albumService.addAlbum("Album");
album2.addArtist(artist1);
album2.addArtist(artist2);
Assertions.assertEquals(album2.getArtists().size(),2);
}
@Test
void testAlbumPreviewCheck() throws IOException {
albumService.deleteAllAlbums();
artistService.deleteAllArtists();
songService.deleteAllSongs();
final Artist artist1 = artistService.addArtist("Artist", "genre");
final Artist artist2 = artistService.addArtist("Artist1", "genre1");
final Song song = songService.addSong("Song",2.10);
final Album album2= albumService.addAlbum("Album");
album2.addArtist(artist1);
album2.addArtist(artist2);
Album v = albumService.findAlbum(album2.getId());
}
@Test
void testSongReadNotFound() {
songService.deleteAllSongs();
Assertions.assertThrows(EntityNotFoundException.class, () -> songService.findSong(-1L));
}
@Test
void testArtistReadNotFound() {
artistService.deleteAllArtists();
Assertions.assertThrows(EntityNotFoundException.class, () -> artistService.findArtist(-1L));
}
@Test
void testAlbumReadNotFound() {
albumService.deleteAllAlbums();
Assertions.assertThrows(EntityNotFoundException.class, () -> albumService.findAlbum(-1L));
}
@Test
void testSongCount(){
songService.deleteAllSongs();
final Song song1 = songService.addSong("Song",3.15);
final Song song2 = songService.addSong("Song1",2.43);
final List<Song> songs = songService.findAllSongs();
Assertions.assertEquals(songs.size(),2);
}
@Test
void testArtistCount(){
artistService.deleteAllArtists();
final Artist cat1 = artistService.addArtist("Shorts", "genre");
final Artist ca2 = artistService.addArtist("Comedy", "genre");
final List<Artist> categories = artistService.findAllArtists();
assertEquals(categories.size(),2);
}
@Test
void testAlbumCount() throws IOException {
albumService.deleteAllAlbums();
artistService.deleteAllArtists();
songService.deleteAllSongs();
final Artist cat1 = artistService.addArtist("Artist", "genre");
final Song song2 = songService.addSong("Song",3.17);
final Album album1 = albumService.addAlbum("Album");
final Album album2 = albumService.addAlbum("Album1");
album1.addArtist(cat1);
album2.addArtist(cat1);
final List<Album> albums = albumService.findAllAlbums();
Assertions.assertEquals(albums.size(),2);
}
@Test
void testDop() {
String nameToTest = "a";
// создаем данные для тестирования
albumService.deleteAllAlbums();
artistService.deleteAllArtists();
songService.deleteAllSongs();
final Artist artist1 = artistService.addArtist(nameToTest, "genre");
final Album album1= albumService.addAlbum(nameToTest);
final Song song1 = songService.addSong(nameToTest, 3.29);
album1.addArtist(artist1);
artist1.addAlbum(album1);
album1.addSong(song1);
Map<String, List<Object>> resultMap = findService.GetResult(nameToTest);
List<Object> artistsList = resultMap.get("artists");
assertEquals(1, artistsList.size());
Object artistResult = artistsList.get(0);
assertEquals(nameToTest, ((Artist) artistResult).getArtistName());
List<Object> albumsList = resultMap.get("albums");
assertEquals(1, albumsList.size());
Object albumResult = albumsList.get(0);
assertEquals(nameToTest, ((Album) albumResult).getAlbumName());
List<Object> songsList = resultMap.get("songs");
assertEquals(1, songsList.size());
Object songResult = songsList.get(0);
assertEquals(nameToTest, ((Song) songResult).getSongName());
}
}