use string tag in domain layer

This commit is contained in:
2026-06-21 21:38:56 +03:00
parent 21a768edd5
commit b66fe60e48
25 changed files with 195 additions and 169 deletions
@@ -1,11 +1,13 @@
package ru.shadowsparky.vbox.backend.data
import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import ru.shadowsparky.backend.data.Logger
import ru.shadowsparky.vbox.backend.AppDatabase
import ru.shadowsparky.vbox.server.Saved_movie
import ru.shadowsparky.vbox.shared.di.factory.DispatcherProvider
import ru.shadowsparky.vbox.shared.domain.RemoteEvent
import ru.shadowsparky.vbox.shared.domain.RemoteEventHandler
@@ -23,26 +25,22 @@ class BackendSavedMovieRepository(
) : SavedMovieRepository {
override fun getAll(): Flow<List<VideoDetails>> {
val request = db.saved_movieQueries.selectByUserId(userId)
return flow {
emit(request.executeAsList().parse().reversed())
}
}
private suspend fun List<Saved_movie>.parse(): List<VideoDetails> =
withContext(dispatcherProvider.io) {
mapNotNull {
val movie = db.movieQueries.selectMovie(it.movie_id)
.executeAsOneOrNull() ?: return@mapNotNull null
VideoDetails(
movie.movie_id,
movie.poster_url,
movie.description,
movie.title,
(movie.flags and SERIAL_FLAG) != 0
)
return db.saved_movieQueries
.selectDetailsByUserId(userId)
.asFlow()
.mapToList(dispatcherProvider.io)
.map { list ->
list.map { movie ->
VideoDetails(
id = movie.movie_id,
poster = movie.poster_url,
desc = movie.description,
title = movie.title,
isSerial = (movie.flags and SERIAL_FLAG) != 0
)
}
}
}
}
override suspend fun save(details: VideoDetails) {
logger.debug(TAG, "save(${details.id}) ${details.title}")
@@ -3,10 +3,14 @@ package ru.shadowsparky.vbox.backend.data.tags
import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.koin.core.annotation.Factory
import org.koin.core.annotation.Named
import ru.shadowsparky.domain.DispatcherProvider
import ru.shadowsparky.http.domain.BadRequestException
import ru.shadowsparky.vbox.backend.AppDatabase
import ru.shadowsparky.vbox.shared.domain.EventType
import ru.shadowsparky.vbox.shared.domain.RemoteEvent
@@ -46,19 +50,30 @@ class BackendMovieTagRepository(
list.map { row -> TagInfo(id = row.id, tag = row.tag) }
}
override fun queryMovies(tagId: Long): Flow<Set<Long>> = dbQueries
.getMoviesByTagId(tagId, userId)
.asFlow()
.mapToList(dispatcherProvider.io)
.map { list -> list.toSet() }
override fun queryMovies(tag: String): Flow<Set<Long>> {
return flow {
val id = findTagId(tag)
emitAll(
dbQueries.getMoviesByTagId(id, userId)
.asFlow()
.mapToList(dispatcherProvider.io)
.map { list -> list.toSet() }
)
}
}
override suspend fun link(movieId: Long, tagId: Long) {
dbQueries.linkMovieTag(movieId, tagId, userId).await()
override suspend fun link(movieId: Long, tag: String) {
dbQueries.linkMovieTag(movieId, findTagId(tag), userId).await()
movieEventHandler.notify(RemoteEvent.OnMovieTag(userId, movieId))
}
override suspend fun unlink(movieId: Long, tagId: Long) {
dbQueries.unlinkMovieTag(movieId, tagId, userId).await()
override suspend fun unlink(movieId: Long, tag: String) {
dbQueries.unlinkMovieTag(movieId, findTagId(tag), userId).await()
movieEventHandler.notify(RemoteEvent.OnMovieTag(userId, movieId))
}
private suspend fun findTagId(tag: String): Long = withContext(dispatcherProvider.io) {
db.tagsQueries.findTagId(tag, userId).executeAsOneOrNull()
?: throw BadRequestException("tag $tag not found for user with id $userId")
}
}
@@ -4,7 +4,6 @@ import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.koin.core.annotation.Factory
import org.koin.core.annotation.Named
import ru.shadowsparky.vbox.backend.AppDatabase
@@ -53,20 +52,13 @@ class BackendUserTagRepository(
userTagEventHandler.notify(RemoteEvent.OnUserTag(userId))
}
override suspend fun edit(id: Long, newTag: String) {
checkOwner(id)
queries.updateTagText(newTag, id).await()
override suspend fun edit(oldTag: String, newTag: String) {
queries.updateTagText(newTag, oldTag, userId).await()
userTagEventHandler.notify(RemoteEvent.OnUserTag(userId))
}
override suspend fun delete(id: Long) {
checkOwner(id)
queries.deleteTagById(id).await()
override suspend fun delete(tag: String) {
queries.deleteTagById(tag, userId).await()
userTagEventHandler.notify(RemoteEvent.OnUserTag(userId))
}
private suspend fun checkOwner(id: Long) = withContext(dispatcherProvider.io) {
val ownerId = queries.getUserIdByTagId(id).executeAsOneOrNull()
check(ownerId == userId) { "tag owner mismatch. owner: $ownerId, current user: $userId" }
}
}
@@ -7,6 +7,7 @@ import io.ktor.server.routing.Route
import io.ktor.server.routing.delete
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.util.getOrFail
import kotlinx.coroutines.flow.first
import ru.shadowsparky.vbox.backend.data.tags.MovieTagRepositoryFactory
import ru.shadowsparky.vbox.backend.data.tags.UserTagRepositoryFactory
@@ -41,12 +42,12 @@ private fun Route.setupUserTag(userTagFactory: UserTagRepositoryFactory) {
post(UserTagRepository.EDIT) {
val userTag = userTagFactory.create(call.obtainUserId())
val edit = call.receive<EditRequest>()
userTag.edit(edit.id, edit.newTag)
userTag.edit(edit.oldTag, edit.newTag)
call.respond(HttpStatusCode.OK)
}
delete(UserTagRepository.DELETE) {
val userTag = userTagFactory.create(call.obtainUserId())
userTag.delete(call.receive<DeleteRequest>().id)
userTag.delete(call.receive<DeleteRequest>().tag)
call.respond(HttpStatusCode.OK)
}
}
@@ -54,26 +55,26 @@ private fun Route.setupUserTag(userTagFactory: UserTagRepositoryFactory) {
private fun Route.setupMovieTag(movieTagFactory: MovieTagRepositoryFactory) {
get(MovieTagRepository.QUERY) {
val movieTag = movieTagFactory.create(call.obtainUserId())
val movieId = call.parameters[MovieTagRepository.MOVIE_ID]?.toLong()
val tags = movieTag.query(checkNotNull(movieId) { "movieId required" }).first()
val movieId = call.parameters.getOrFail<Long>(MovieTagRepository.MOVIE_ID)
val tags = movieTag.query(movieId).first()
call.respond(TagResponse(tags))
}
get(MovieTagRepository.QUERY_MOVIES) {
val movieTag = movieTagFactory.create(call.obtainUserId())
val tagId = call.parameters[MovieTagRepository.TAG_ID]?.toLong()
val ids = movieTag.queryMovies(checkNotNull(tagId) { "tagId required" }).first()
val tagName = call.parameters.getOrFail(MovieTagRepository.TAG_NAME)
val ids = movieTag.queryMovies(tagName).first()
call.respond(LinkedMovies(ids))
}
post(MovieTagRepository.LINK) {
val movieTag = movieTagFactory.create(call.obtainUserId())
val link = call.receive<LinkRequest>()
movieTag.link(link.movieId, link.tagId)
movieTag.link(link.movieId, link.tag)
call.respond(HttpStatusCode.OK)
}
delete(MovieTagRepository.UNLINK) {
val movieTag = movieTagFactory.create(call.obtainUserId())
val link = call.receive<LinkRequest>()
movieTag.unlink(link.movieId, link.tagId)
movieTag.unlink(link.movieId, link.tag)
call.respond(HttpStatusCode.OK)
}
}
@@ -11,3 +11,15 @@ addSavedMovie:
removeSavedMovie:
DELETE FROM "saved_movie" WHERE user_id = ? AND movie_id = ?;
selectDetailsByUserId:
SELECT
m.movie_id,
m.poster_url,
m.description,
m.title,
m.flags
FROM saved_movie AS sm
JOIN movie AS m ON sm.movie_id = m.movie_id
WHERE sm.user_id = ?
ORDER BY sm.saved_movie_id DESC;
@@ -3,11 +3,6 @@ SELECT id, user_id, tag
FROM user_tag
WHERE user_id = ?;
getUserIdByTagId:
SELECT user_id
FROM user_tag
WHERE id = ?;
insertUserTag:
INSERT INTO user_tag (user_id, tag)
VALUES (?, ?);
@@ -15,11 +10,15 @@ VALUES (?, ?);
updateTagText:
UPDATE user_tag
SET tag = ?
WHERE id = ?;
WHERE tag = ? AND user_id = ?;
deleteTagById:
DELETE FROM user_tag
WHERE id = ?;
WHERE tag = ? AND user_id = ?;
findTagId:
SELECT id FROM user_tag
WHERE tag = ? AND user_id = ?;
getTagsForMovie:
SELECT ut.id, ut.tag