Changes between Initial Version and Version 1 of AdvancedApplicationDevelopment


Ignore:
Timestamp:
02/12/26 10:12:41 (2 weeks ago)
Author:
231136
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AdvancedApplicationDevelopment

    v1 v1  
     1= Напреден апликативен развој
     2
     3== Трансакции
     4
     5=== Зачувување на нов корисник
     6
     7{{{
     8@Transactional
     9public UserResponseDto registerAndLogIn(HttpServletResponse response, AuthRequestDto authRequestDto)
     10    throws IOException {
     11    if (userRepository.findByUsername(authRequestDto.username()).isPresent()){
     12       throw new UserAlreadyExistsException();
     13    }
     14    User user = createNonAdminUser(authRequestDto);
     15    return authenticateAndRespond(response, user);
     16}
     17
     18
     19@Transactional
     20public User createNonAdminUser(AuthRequestDto authRequestDto) throws IOException {
     21    User.UserBuilder userBuilder = User.builder()
     22            .username(authRequestDto.username())
     23            .fullName(authRequestDto.fullname())
     24            .email(authRequestDto.email())
     25            .password(passwordEncoder.encode(authRequestDto.password()))
     26            .role(Role.NONADMIN)
     27            .artist(authRequestDto.userType().contains(UserType.ARTIST))
     28            .listener(authRequestDto.userType().contains(UserType.LISTENER));
     29
     30
     31    MultipartFile profilePhoto = authRequestDto.profilePhoto();
     32    if (profilePhoto != null && !profilePhoto.isEmpty()) {
     33        String contentType = profilePhoto.getContentType();
     34        if (contentType == null || !contentType.startsWith("image/")) {
     35            throw InvalidFileException.invalidType();
     36        }
     37
     38        if (profilePhoto.getSize() > MAX_FILE_SIZE) {
     39            throw InvalidFileException.tooLarge(MAX_FILE_SIZE);
     40        }
     41
     42        String filename = UUID.randomUUID() + "-" + profilePhoto.getOriginalFilename();
     43        Path path = Paths.get("uploads/profile-pictures", filename);
     44        Files.copy(profilePhoto.getInputStream(), path);
     45        userBuilder.profilePhoto("profile-pictures/" + filename);
     46    }
     47
     48    User user = userBuilder.build();
     49
     50    NonAdminUser nonAdminUser = new NonAdminUser();
     51    nonAdminUser.setUser(user);
     52    user.setNonAdminUser(nonAdminUser);
     53    nonAdminUserRepository.save(nonAdminUser);
     54
     55    if (user.isArtist()){
     56        Artist artist = new Artist();
     57        artist.setNonAdminUser(nonAdminUser);
     58        artistRepository.save(artist);
     59    }
     60    if (user.isListener()){
     61        Listener listener = new Listener();
     62        listener.setNonAdminUser(nonAdminUser);
     63        listenerRepository.save(listener);
     64    }
     65
     66    return user;
     67}
     68}}}
     69
     70=== Објавување на нова песна
     71
     72{{{
     73@Transactional
     74public void handleSongPublish(PublishSongRequestDto requestDto) throws IOException {
     75    Long userId = authService.getCurrentUserID();
     76    Artist artist = artistService.getArtistById(userId);
     77
     78    MusicalEntity musicalEntity = createMusicalEntity(
     79            requestDto.getTitle(),
     80            requestDto.getGenre(),
     81            requestDto.getCover(),
     82            artist
     83    );
     84
     85    Song song = new Song();
     86    song.setAlbum(null);
     87    song.setLink(requestDto.getLink());
     88    song.setMusicalEntities(musicalEntity);
     89    songRepository.save(song);
     90
     91    // add publishing artist
     92    saveContribution(artist, musicalEntity, MAIN_VOCAL_ROLE);
     93
     94    // add additional contributors
     95    saveContributionsFromDto(musicalEntity, requestDto.getContributors());
     96}
     97
     98@Transactional
     99protected MusicalEntity createMusicalEntity(String title, String genre, MultipartFile cover, Artist releasedBy)
     100        throws IOException {
     101    MusicalEntity.MusicalEntityBuilder builder = MusicalEntity.builder()
     102            .title(title)
     103            .genre(genre)
     104            .releaseDate(LocalDate.now())
     105            .releasedBy(releasedBy);
     106
     107    if (cover != null && !cover.isEmpty()) {
     108        try {
     109            String filename = saveCoverPhoto(cover);
     110            builder.cover("profile-pictures/" + filename);
     111        } catch (Exception ignored) {}
     112    }
     113
     114    return musicalEntityRepository.save(builder.build());
     115}
     116
     117@Transactional
     118protected void saveContribution(Artist artist, MusicalEntity musicalEntity, String role) {
     119    ArtistContributionId id = new ArtistContributionId();
     120    id.setArtistId(artist.getId());
     121    id.setMusicalEntityId(musicalEntity.getId());
     122
     123    ArtistContribution contribution = new ArtistContribution();
     124    contribution.setId(id);
     125    contribution.setArtist(artist);
     126    contribution.setMusicalEntity(musicalEntity);
     127    contribution.setRole(role);
     128
     129    artistContributionRepository.save(contribution);
     130}
     131
     132@Transactional
     133protected void saveContributionsFromDto(MusicalEntity musicalEntity, List<ArtistContributionSummaryDto> contributors) {
     134    if (contributors == null) return;
     135
     136    for (ArtistContributionSummaryDto contributorDto : contributors) {
     137        if (contributorDto.getId() == null) continue;
     138
     139        Artist contributor = artistService.getArtistById(contributorDto.getId());
     140        saveContribution(contributor, musicalEntity, contributorDto.getRole());
     141    }
     142}
     143}}}
     144
     145=== Објавување на нов албум
     146
     147{{{
     148@Transactional
     149public void handleAlbumPublish(PublishAlbumRequestDto requestDto) throws IOException {
     150    Long userId = authService.getCurrentUserID();
     151    Artist artist = artistService.getArtistById(userId);
     152
     153    MusicalEntity albumMusicalEntity = createMusicalEntity(
     154            requestDto.getTitle(),
     155            requestDto.getGenre(),
     156            requestDto.getCover(),
     157            artist
     158    );
     159
     160    Album album = new Album();
     161    album.setMusicalEntities(albumMusicalEntity);
     162    album = albumRepository.save(album);
     163
     164    // add publishing artist
     165    saveContribution(artist, albumMusicalEntity, MAIN_ROLE);
     166
     167    // track artists already added to avoid duplicates
     168    Set<Long> albumContributorIds = new HashSet<>();
     169    albumContributorIds.add(artist.getId());
     170
     171    List<AlbumSongsDto> albumSongs = requestDto.getAlbumSongs();
     172    for (AlbumSongsDto songDto : albumSongs) {
     173        createSongForAlbum(albumMusicalEntity, album, songDto, artist, albumContributorIds);
     174    }
     175}
     176
     177
     178@Transactional
     179protected void createSongForAlbum(MusicalEntity albumMe, Album album, AlbumSongsDto songDto,
     180                                    Artist publishingArtist, Set<Long> albumContributorIds) {
     181    MusicalEntity songMusicalEntity = MusicalEntity.builder()
     182            .title(songDto.getTitle())
     183            .releaseDate(albumMe.getReleaseDate())
     184            .releasedBy(albumMe.getReleasedBy())
     185            .cover(albumMe.getCover())
     186            .genre(albumMe.getGenre())
     187            .build();
     188    songMusicalEntity = musicalEntityRepository.save(songMusicalEntity);
     189
     190    Song song = new Song();
     191    song.setLink(songDto.getLink());
     192    song.setAlbum(album);
     193    song.setMusicalEntities(songMusicalEntity);
     194    songRepository.save(song);
     195
     196    saveContribution(publishingArtist, songMusicalEntity, MAIN_VOCAL_ROLE);
     197
     198    // add song-specific contributors
     199    List<ArtistContributionSummaryDto> songContributors = songDto.getContributors();
     200    if (songContributors != null) {
     201        for (ArtistContributionSummaryDto contributorDto : songContributors) {
     202            if (contributorDto.getId() == null) continue;
     203           
     204            Artist contributor = artistService.getArtistById(contributorDto.getId());
     205           
     206            // first add to song
     207            saveContribution(contributor, songMusicalEntity, contributorDto.getRole());
     208           
     209            // then add to album if not already added
     210            if (!albumContributorIds.contains(contributor.getId())) {
     211                saveContribution(contributor, album.getMusicalEntities(), contributorDto.getRole());
     212                albumContributorIds.add(contributor.getId());
     213            }
     214        }
     215    }
     216}
     217}}}
     218
     219== Pooling
     220
     221Бидејќи користиме Spring Boot за развој на backend делот од нашата апликација, не треба рачно да ги правиме конекциите кон базата. Pooling во Spring Boot се менаџира од страна на HikariCP.
     222
     223HikariCP е зависност на `spring-boot-starter-jdbc`, што е зависност на `spring-boot-starter-data-jpa`, така што доколку во `pom.xml` додадеме:
     224{{{
     225<dependency>
     226      <groupId>org.springframework.boot</groupId>
     227      <artifactId>spring-boot-starter-data-jpa</artifactId>
     228</dependency>
     229}}}
     230
     231HikariCP ќе ни биде додадено како транзитивна зависност.
     232
     233Во нашиот проект не ја менувавме конфигурацијата на Hikari, така што се користат default вредности, кои се следниве:
     234{{{
     235spring.datasource.hikari.maximum-pool-size  = 10
     236spring.datasource.hikari.minimum-idle       = 10
     237spring.datasource.hikari.connection-timeout = 30000   #(30 seconds)
     238spring.datasource.hikari.idle-timeout       = 600000  #(10 minutes)
     239spring.datasource.hikari.max-lifetime       = 1800000 #(30 minutes)
     240spring.datasource.hikari.auto-commit        = true
     241}}}
     242
     243По потреба истите можат да се менуваат.
     244
     245Дополнително, воспоставување на конекциите можеме да забележиме во логовите на тунел скриптата кога ќе ја започнеме Spring Boot апликацијата
     246{{{
     247debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     248debug1: channel 2: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     249debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     250debug1: channel 3: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     251debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     252debug1: channel 4: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     253debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     254debug1: channel 5: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     255debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     256debug1: channel 6: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     257debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     258debug1: channel 7: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     259debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     260debug1: channel 8: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     261debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     262debug1: channel 9: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     263debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     264debug1: channel 10: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     265debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
     266debug1: channel 11: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
     267}}}