Newer
Older
bremer / src / main / kotlin / service / PlaylistService.kt
/*
 * Copyright (c) 2023. yo-saito. All Rights Reserved.
 */

package net.piedpiper.bremer.service

import net.piedpiper.bremer.dao.AudioDao
import net.piedpiper.bremer.dao.PlaylistAudioDao
import net.piedpiper.bremer.dao.PlaylistDao
import net.piedpiper.bremer.entity.AudioEntity
import net.piedpiper.bremer.entity.PlaylistAudioEntity
import net.piedpiper.bremer.entity.PlaylistEntity
import net.piedpiper.bremer.exception.BadRequestException
import net.piedpiper.bremer.exception.NotFoundException
import net.piedpiper.bremer.model.api.AllPlaylistResponse
import net.piedpiper.bremer.model.api.PlaylistAudioRequest
import net.piedpiper.bremer.model.api.PlaylistAudioResponse
import net.piedpiper.bremer.model.api.PlaylistRequest
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.util.*

@Service("bremer.service.PlaylistService")
class PlaylistService(
    @Qualifier("bremer.dao.PlaylistDao")
    private val playlistDao: PlaylistDao,
    @Qualifier("bremer.dao.PlaylistAudioDao")
    private val playlistAudioDao: PlaylistAudioDao,
    @Qualifier("bremer.dao.AudioDao")
    private val audioDao: AudioDao
) {
    @Transactional
    fun getAllPlaylists(): AllPlaylistResponse =
        AllPlaylistResponse(playlistDao.findAllOrderBySequence())

    @Transactional
    fun getPlaylist(slug: String): PlaylistAudioResponse = playlistDao.findOneBySlug(slug)
        ?.let {
            PlaylistAudioResponse(
                playlistEntity = it,
                audioEntityList = it?.playlistAudio
                    ?.mapNotNull { it.audioId }
                    ?.let { ids ->
                        findAllByOrderedIds(ids)
                    }
            )
        } ?: throw NotFoundException()

    private fun findAllByOrderedIds(ids: List<Long>): List<AudioEntity> {
        if (ids.isEmpty()) {
            return emptyList()
        }
        val id2idx = ids.mapIndexed { index, id -> id to index }.toMap()
        return audioDao.findAllByIdIn(ids)
            .sortedBy { id2idx[it.id] }
    }

    private fun findAllByOrderedSlugs(slugs: List<String>): List<AudioEntity> {
        if (slugs.isEmpty()) {
            return emptyList()
        }
        val slug2idx = slugs.mapIndexed { index, slug -> slug to index }.toMap()
        return audioDao.findAllBySlugIn(slugs)
            .sortedBy { slug2idx[it.slug] }
    }

    @Transactional
    fun createPlaylist(request: PlaylistRequest) {
        playlistDao.insertOne(
            PlaylistEntity(
                slug = UUID.randomUUID().toString(),
                name = request.name
            )
        )
    }

    @Transactional
    fun updatePlaylist(slug: String, request: PlaylistRequest) {
        val entity = playlistDao.findOneBySlugWithLock(slug) ?: throw NotFoundException()
        entity.name = request.name
        playlistDao.updateOne(entity)
    }

    @Transactional
    fun deletePlaylist(slug: String) {
        val playlist = playlistDao.findOneBySlugWithLock(slug);
        if (playlist?.playlistAudio?.isNotEmpty() == true) {
            playlistAudioDao.deleteAllByIds(
                playlist.playlistAudio.map { it.id })
        }
        playlistDao.deleteBySlug(slug)
    }

    @Transactional
    fun updatePlaylistAudioList(
        slug: String,
        request: PlaylistAudioRequest
    ) {
        playlistDao.findOneBySlug(slug)
            ?.let { playlist ->
                val audioList = findAllByOrderedSlugs(request.audioSlugs)
                if (request.audioSlugs.size != audioList.size) {
                    throw BadRequestException()
                }
                val registeredAudioIds = playlist.playlistAudio.map { it.audioId }.toSet()
                val requestAudioIds = audioList.map { it.id }.toSet()
                val playlistSize = playlist.playlistAudio?.size ?: 0

                // delete
                val deleteIds = playlist.playlistAudio
                    .filter { !requestAudioIds.contains(it.audioId) }
                    .map { it.id }
                    .toList()
                if (deleteIds.isNotEmpty()) {
                    playlistAudioDao.deleteAllByIds(
                        playlist.playlistAudio
                            .filter { !requestAudioIds.contains(it.audioId) }
                            .map { it.id }
                            .toList()
                    )
                }
                audioList.forEachIndexed { idx, audio ->
                    if (registeredAudioIds.contains(audio.id)) {
                        // ignore
                        return@forEachIndexed
                    }
                    // add
                    playlistAudioDao.insertOne(
                        PlaylistAudioEntity(
                            id = if (idx >= playlistSize) 0L else playlist?.playlistAudio!![idx].id,
                            playlistId = playlist.id,
                            sequence = idx,
                            audioId = audio.id
                        )
                    )
                }
            } ?: throw NotFoundException()
    }

    private fun convertToPlaylistAudioResponse(entity: PlaylistEntity): PlaylistAudioResponse {
        val audioList = entity?.playlistAudio?.mapNotNull { it.audioId }
            ?.let {
                findAllByOrderedIds(it)
            } ?: emptyList()
        return PlaylistAudioResponse(
            playlistEntity = entity,
            audioEntityList = audioList
        )
    }
}