use string tag in domain layer
This commit is contained in:
+15
-17
@@ -1,11 +1,13 @@
|
|||||||
package ru.shadowsparky.vbox.backend.data
|
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.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import ru.shadowsparky.backend.data.Logger
|
import ru.shadowsparky.backend.data.Logger
|
||||||
import ru.shadowsparky.vbox.backend.AppDatabase
|
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.di.factory.DispatcherProvider
|
||||||
import ru.shadowsparky.vbox.shared.domain.RemoteEvent
|
import ru.shadowsparky.vbox.shared.domain.RemoteEvent
|
||||||
import ru.shadowsparky.vbox.shared.domain.RemoteEventHandler
|
import ru.shadowsparky.vbox.shared.domain.RemoteEventHandler
|
||||||
@@ -23,26 +25,22 @@ class BackendSavedMovieRepository(
|
|||||||
) : SavedMovieRepository {
|
) : SavedMovieRepository {
|
||||||
|
|
||||||
override fun getAll(): Flow<List<VideoDetails>> {
|
override fun getAll(): Flow<List<VideoDetails>> {
|
||||||
val request = db.saved_movieQueries.selectByUserId(userId)
|
return db.saved_movieQueries
|
||||||
return flow {
|
.selectDetailsByUserId(userId)
|
||||||
emit(request.executeAsList().parse().reversed())
|
.asFlow()
|
||||||
}
|
.mapToList(dispatcherProvider.io)
|
||||||
}
|
.map { list ->
|
||||||
|
list.map { movie ->
|
||||||
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(
|
VideoDetails(
|
||||||
movie.movie_id,
|
id = movie.movie_id,
|
||||||
movie.poster_url,
|
poster = movie.poster_url,
|
||||||
movie.description,
|
desc = movie.description,
|
||||||
movie.title,
|
title = movie.title,
|
||||||
(movie.flags and SERIAL_FLAG) != 0
|
isSerial = (movie.flags and SERIAL_FLAG) != 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun save(details: VideoDetails) {
|
override suspend fun save(details: VideoDetails) {
|
||||||
logger.debug(TAG, "save(${details.id}) ${details.title}")
|
logger.debug(TAG, "save(${details.id}) ${details.title}")
|
||||||
|
|||||||
+21
-6
@@ -3,10 +3,14 @@ package ru.shadowsparky.vbox.backend.data.tags
|
|||||||
import app.cash.sqldelight.coroutines.asFlow
|
import app.cash.sqldelight.coroutines.asFlow
|
||||||
import app.cash.sqldelight.coroutines.mapToList
|
import app.cash.sqldelight.coroutines.mapToList
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.emitAll
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import ru.shadowsparky.domain.DispatcherProvider
|
import ru.shadowsparky.domain.DispatcherProvider
|
||||||
|
import ru.shadowsparky.http.domain.BadRequestException
|
||||||
import ru.shadowsparky.vbox.backend.AppDatabase
|
import ru.shadowsparky.vbox.backend.AppDatabase
|
||||||
import ru.shadowsparky.vbox.shared.domain.EventType
|
import ru.shadowsparky.vbox.shared.domain.EventType
|
||||||
import ru.shadowsparky.vbox.shared.domain.RemoteEvent
|
import ru.shadowsparky.vbox.shared.domain.RemoteEvent
|
||||||
@@ -46,19 +50,30 @@ class BackendMovieTagRepository(
|
|||||||
list.map { row -> TagInfo(id = row.id, tag = row.tag) }
|
list.map { row -> TagInfo(id = row.id, tag = row.tag) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun queryMovies(tagId: Long): Flow<Set<Long>> = dbQueries
|
override fun queryMovies(tag: String): Flow<Set<Long>> {
|
||||||
.getMoviesByTagId(tagId, userId)
|
return flow {
|
||||||
|
val id = findTagId(tag)
|
||||||
|
emitAll(
|
||||||
|
dbQueries.getMoviesByTagId(id, userId)
|
||||||
.asFlow()
|
.asFlow()
|
||||||
.mapToList(dispatcherProvider.io)
|
.mapToList(dispatcherProvider.io)
|
||||||
.map { list -> list.toSet() }
|
.map { list -> list.toSet() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun link(movieId: Long, tagId: Long) {
|
override suspend fun link(movieId: Long, tag: String) {
|
||||||
dbQueries.linkMovieTag(movieId, tagId, userId).await()
|
dbQueries.linkMovieTag(movieId, findTagId(tag), userId).await()
|
||||||
movieEventHandler.notify(RemoteEvent.OnMovieTag(userId, movieId))
|
movieEventHandler.notify(RemoteEvent.OnMovieTag(userId, movieId))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unlink(movieId: Long, tagId: Long) {
|
override suspend fun unlink(movieId: Long, tag: String) {
|
||||||
dbQueries.unlinkMovieTag(movieId, tagId, userId).await()
|
dbQueries.unlinkMovieTag(movieId, findTagId(tag), userId).await()
|
||||||
movieEventHandler.notify(RemoteEvent.OnMovieTag(userId, movieId))
|
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
-12
@@ -4,7 +4,6 @@ import app.cash.sqldelight.coroutines.asFlow
|
|||||||
import app.cash.sqldelight.coroutines.mapToList
|
import app.cash.sqldelight.coroutines.mapToList
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import ru.shadowsparky.vbox.backend.AppDatabase
|
import ru.shadowsparky.vbox.backend.AppDatabase
|
||||||
@@ -53,20 +52,13 @@ class BackendUserTagRepository(
|
|||||||
userTagEventHandler.notify(RemoteEvent.OnUserTag(userId))
|
userTagEventHandler.notify(RemoteEvent.OnUserTag(userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun edit(id: Long, newTag: String) {
|
override suspend fun edit(oldTag: String, newTag: String) {
|
||||||
checkOwner(id)
|
queries.updateTagText(newTag, oldTag, userId).await()
|
||||||
queries.updateTagText(newTag, id).await()
|
|
||||||
userTagEventHandler.notify(RemoteEvent.OnUserTag(userId))
|
userTagEventHandler.notify(RemoteEvent.OnUserTag(userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun delete(id: Long) {
|
override suspend fun delete(tag: String) {
|
||||||
checkOwner(id)
|
queries.deleteTagById(tag, userId).await()
|
||||||
queries.deleteTagById(id).await()
|
|
||||||
userTagEventHandler.notify(RemoteEvent.OnUserTag(userId))
|
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" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-8
@@ -7,6 +7,7 @@ import io.ktor.server.routing.Route
|
|||||||
import io.ktor.server.routing.delete
|
import io.ktor.server.routing.delete
|
||||||
import io.ktor.server.routing.get
|
import io.ktor.server.routing.get
|
||||||
import io.ktor.server.routing.post
|
import io.ktor.server.routing.post
|
||||||
|
import io.ktor.server.util.getOrFail
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import ru.shadowsparky.vbox.backend.data.tags.MovieTagRepositoryFactory
|
import ru.shadowsparky.vbox.backend.data.tags.MovieTagRepositoryFactory
|
||||||
import ru.shadowsparky.vbox.backend.data.tags.UserTagRepositoryFactory
|
import ru.shadowsparky.vbox.backend.data.tags.UserTagRepositoryFactory
|
||||||
@@ -41,12 +42,12 @@ private fun Route.setupUserTag(userTagFactory: UserTagRepositoryFactory) {
|
|||||||
post(UserTagRepository.EDIT) {
|
post(UserTagRepository.EDIT) {
|
||||||
val userTag = userTagFactory.create(call.obtainUserId())
|
val userTag = userTagFactory.create(call.obtainUserId())
|
||||||
val edit = call.receive<EditRequest>()
|
val edit = call.receive<EditRequest>()
|
||||||
userTag.edit(edit.id, edit.newTag)
|
userTag.edit(edit.oldTag, edit.newTag)
|
||||||
call.respond(HttpStatusCode.OK)
|
call.respond(HttpStatusCode.OK)
|
||||||
}
|
}
|
||||||
delete(UserTagRepository.DELETE) {
|
delete(UserTagRepository.DELETE) {
|
||||||
val userTag = userTagFactory.create(call.obtainUserId())
|
val userTag = userTagFactory.create(call.obtainUserId())
|
||||||
userTag.delete(call.receive<DeleteRequest>().id)
|
userTag.delete(call.receive<DeleteRequest>().tag)
|
||||||
call.respond(HttpStatusCode.OK)
|
call.respond(HttpStatusCode.OK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,26 +55,26 @@ private fun Route.setupUserTag(userTagFactory: UserTagRepositoryFactory) {
|
|||||||
private fun Route.setupMovieTag(movieTagFactory: MovieTagRepositoryFactory) {
|
private fun Route.setupMovieTag(movieTagFactory: MovieTagRepositoryFactory) {
|
||||||
get(MovieTagRepository.QUERY) {
|
get(MovieTagRepository.QUERY) {
|
||||||
val movieTag = movieTagFactory.create(call.obtainUserId())
|
val movieTag = movieTagFactory.create(call.obtainUserId())
|
||||||
val movieId = call.parameters[MovieTagRepository.MOVIE_ID]?.toLong()
|
val movieId = call.parameters.getOrFail<Long>(MovieTagRepository.MOVIE_ID)
|
||||||
val tags = movieTag.query(checkNotNull(movieId) { "movieId required" }).first()
|
val tags = movieTag.query(movieId).first()
|
||||||
call.respond(TagResponse(tags))
|
call.respond(TagResponse(tags))
|
||||||
}
|
}
|
||||||
get(MovieTagRepository.QUERY_MOVIES) {
|
get(MovieTagRepository.QUERY_MOVIES) {
|
||||||
val movieTag = movieTagFactory.create(call.obtainUserId())
|
val movieTag = movieTagFactory.create(call.obtainUserId())
|
||||||
val tagId = call.parameters[MovieTagRepository.TAG_ID]?.toLong()
|
val tagName = call.parameters.getOrFail(MovieTagRepository.TAG_NAME)
|
||||||
val ids = movieTag.queryMovies(checkNotNull(tagId) { "tagId required" }).first()
|
val ids = movieTag.queryMovies(tagName).first()
|
||||||
call.respond(LinkedMovies(ids))
|
call.respond(LinkedMovies(ids))
|
||||||
}
|
}
|
||||||
post(MovieTagRepository.LINK) {
|
post(MovieTagRepository.LINK) {
|
||||||
val movieTag = movieTagFactory.create(call.obtainUserId())
|
val movieTag = movieTagFactory.create(call.obtainUserId())
|
||||||
val link = call.receive<LinkRequest>()
|
val link = call.receive<LinkRequest>()
|
||||||
movieTag.link(link.movieId, link.tagId)
|
movieTag.link(link.movieId, link.tag)
|
||||||
call.respond(HttpStatusCode.OK)
|
call.respond(HttpStatusCode.OK)
|
||||||
}
|
}
|
||||||
delete(MovieTagRepository.UNLINK) {
|
delete(MovieTagRepository.UNLINK) {
|
||||||
val movieTag = movieTagFactory.create(call.obtainUserId())
|
val movieTag = movieTagFactory.create(call.obtainUserId())
|
||||||
val link = call.receive<LinkRequest>()
|
val link = call.receive<LinkRequest>()
|
||||||
movieTag.unlink(link.movieId, link.tagId)
|
movieTag.unlink(link.movieId, link.tag)
|
||||||
call.respond(HttpStatusCode.OK)
|
call.respond(HttpStatusCode.OK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,3 +11,15 @@ addSavedMovie:
|
|||||||
|
|
||||||
removeSavedMovie:
|
removeSavedMovie:
|
||||||
DELETE FROM "saved_movie" WHERE user_id = ? AND movie_id = ?;
|
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
|
FROM user_tag
|
||||||
WHERE user_id = ?;
|
WHERE user_id = ?;
|
||||||
|
|
||||||
getUserIdByTagId:
|
|
||||||
SELECT user_id
|
|
||||||
FROM user_tag
|
|
||||||
WHERE id = ?;
|
|
||||||
|
|
||||||
insertUserTag:
|
insertUserTag:
|
||||||
INSERT INTO user_tag (user_id, tag)
|
INSERT INTO user_tag (user_id, tag)
|
||||||
VALUES (?, ?);
|
VALUES (?, ?);
|
||||||
@@ -15,11 +10,15 @@ VALUES (?, ?);
|
|||||||
updateTagText:
|
updateTagText:
|
||||||
UPDATE user_tag
|
UPDATE user_tag
|
||||||
SET tag = ?
|
SET tag = ?
|
||||||
WHERE id = ?;
|
WHERE tag = ? AND user_id = ?;
|
||||||
|
|
||||||
deleteTagById:
|
deleteTagById:
|
||||||
DELETE FROM user_tag
|
DELETE FROM user_tag
|
||||||
WHERE id = ?;
|
WHERE tag = ? AND user_id = ?;
|
||||||
|
|
||||||
|
findTagId:
|
||||||
|
SELECT id FROM user_tag
|
||||||
|
WHERE tag = ? AND user_id = ?;
|
||||||
|
|
||||||
getTagsForMovie:
|
getTagsForMovie:
|
||||||
SELECT ut.id, ut.tag
|
SELECT ut.id, ut.tag
|
||||||
|
|||||||
+2
-2
@@ -71,7 +71,7 @@ class SyncClientServerRecentlyWatchedRepository(
|
|||||||
OperationInfo(
|
OperationInfo(
|
||||||
type = AggregateType.RECENTLY_WATCHED,
|
type = AggregateType.RECENTLY_WATCHED,
|
||||||
actionType = ACTION_WRITE,
|
actionType = ACTION_WRITE,
|
||||||
payload = json.encodeToJsonElement(RecentlyWatchedInfo.serializer(), info)
|
payload = json.encodeToJsonElement(info)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ class SyncClientServerRecentlyWatchedRepository(
|
|||||||
OperationInfo(
|
OperationInfo(
|
||||||
type = AggregateType.RECENTLY_WATCHED,
|
type = AggregateType.RECENTLY_WATCHED,
|
||||||
actionType = ACTION_REMOVE,
|
actionType = ACTION_REMOVE,
|
||||||
payload = json.encodeToJsonElement(RecentlyWatchedInfo.serializer(), info)
|
payload = json.encodeToJsonElement(info)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ class SyncClientSavedMovieRepository(
|
|||||||
OperationInfo(
|
OperationInfo(
|
||||||
type = AggregateType.SAVED_MOVIE,
|
type = AggregateType.SAVED_MOVIE,
|
||||||
actionType = ACTION_SAVE,
|
actionType = ACTION_SAVE,
|
||||||
payload = json.encodeToJsonElement(VideoDetails.serializer(), details)
|
payload = json.encodeToJsonElement(details)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -22,11 +22,11 @@ class MovieTagSyncStrategy(
|
|||||||
val payloadObj = payload.jsonObject
|
val payloadObj = payload.jsonObject
|
||||||
val movieId = payloadObj[SyncMovieTagRepository.KEY_MOVIE_ID]?.jsonPrimitive?.long
|
val movieId = payloadObj[SyncMovieTagRepository.KEY_MOVIE_ID]?.jsonPrimitive?.long
|
||||||
?: error("Missing ${SyncMovieTagRepository.KEY_MOVIE_ID}")
|
?: error("Missing ${SyncMovieTagRepository.KEY_MOVIE_ID}")
|
||||||
val tagId = payloadObj[SyncMovieTagRepository.KEY_TAG_ID]?.jsonPrimitive?.long
|
val tagName = payloadObj[SyncMovieTagRepository.KEY_TAG_NAME]?.jsonPrimitive?.content
|
||||||
?: error("Missing ${SyncMovieTagRepository.KEY_TAG_ID}")
|
?: error("Missing ${SyncMovieTagRepository.KEY_TAG_NAME}")
|
||||||
when (actionType) {
|
when (actionType) {
|
||||||
SyncMovieTagRepository.ACTION_LINK -> remote.link(movieId, tagId)
|
SyncMovieTagRepository.ACTION_LINK -> remote.link(movieId, tagName)
|
||||||
SyncMovieTagRepository.ACTION_UNLINK -> remote.unlink(movieId, tagId)
|
SyncMovieTagRepository.ACTION_UNLINK -> remote.unlink(movieId, tagName)
|
||||||
else -> error("Unknown action type: $actionType")
|
else -> error("Unknown action type: $actionType")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -27,16 +27,16 @@ class UserTagSyncStrategy(
|
|||||||
remote.add(tag)
|
remote.add(tag)
|
||||||
}
|
}
|
||||||
SyncUserTagRepository.ACTION_EDIT -> {
|
SyncUserTagRepository.ACTION_EDIT -> {
|
||||||
val id = payloadObj[SyncUserTagRepository.KEY_ID]?.jsonPrimitive?.long
|
val oldTag = payloadObj[SyncUserTagRepository.KEY_OLD_TAG]?.jsonPrimitive?.content
|
||||||
?: error("Missing ${SyncUserTagRepository.KEY_ID}")
|
?: error("Missing ${SyncUserTagRepository.KEY_OLD_TAG}")
|
||||||
val newTag = payloadObj[SyncUserTagRepository.KEY_NEW_TAG]?.jsonPrimitive?.content
|
val newTag = payloadObj[SyncUserTagRepository.KEY_NEW_TAG]?.jsonPrimitive?.content
|
||||||
?: error("Missing ${SyncUserTagRepository.KEY_NEW_TAG}")
|
?: error("Missing ${SyncUserTagRepository.KEY_NEW_TAG}")
|
||||||
remote.edit(id, newTag)
|
remote.edit(oldTag, newTag)
|
||||||
}
|
}
|
||||||
SyncUserTagRepository.ACTION_DELETE -> {
|
SyncUserTagRepository.ACTION_DELETE -> {
|
||||||
val id = payloadObj[SyncUserTagRepository.KEY_ID]?.jsonPrimitive?.long
|
val oldTag = payloadObj[SyncUserTagRepository.KEY_OLD_TAG]?.jsonPrimitive?.content
|
||||||
?: error("Missing ${SyncUserTagRepository.KEY_ID}")
|
?: error("Missing ${SyncUserTagRepository.KEY_OLD_TAG}")
|
||||||
remote.delete(id)
|
remote.delete(oldTag)
|
||||||
}
|
}
|
||||||
else -> error("Unknown action type: $actionType")
|
else -> error("Unknown action type: $actionType")
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-9
@@ -52,34 +52,34 @@ class RemoteMovieTagRepository(
|
|||||||
).tags
|
).tags
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun queryMovies(tagId: Long): Flow<Set<Long>> {
|
override fun queryMovies(tag: String): Flow<Set<Long>> {
|
||||||
return movieTagListener.event.filterIsInstance<RemoteEvent.OnMovieTag>()
|
return movieTagListener.event.filterIsInstance<RemoteEvent.OnMovieTag>()
|
||||||
.map { queryMoviesSingle(tagId) }
|
.map { queryMoviesSingle(tag) }
|
||||||
.onStart { emit(queryMoviesSingle(tagId)) }
|
.onStart { emit(queryMoviesSingle(tag)) }
|
||||||
.flowOn(dispatcherProvider.io)
|
.flowOn(dispatcherProvider.io)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun queryMoviesSingle(tagId: Long): Set<Long> {
|
private suspend fun queryMoviesSingle(tag: String): Set<Long> {
|
||||||
return httpClient.get<LinkedMovies>(
|
return httpClient.get<LinkedMovies>(
|
||||||
MovieTagRepository.QUERY_MOVIES,
|
MovieTagRepository.QUERY_MOVIES,
|
||||||
hashMapOf(MovieTagRepository.TAG_ID to tagId.toString()),
|
hashMapOf(MovieTagRepository.TAG_NAME to tag),
|
||||||
serverConfigurationProvider.getServerConfiguration()
|
serverConfigurationProvider.getServerConfiguration()
|
||||||
).ids
|
).ids
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun link(movieId: Long, tagId: Long) {
|
override suspend fun link(movieId: Long, tag: String) {
|
||||||
httpClient.post {
|
httpClient.post {
|
||||||
serverConfigurationProvider.getServerConfiguration()
|
serverConfigurationProvider.getServerConfiguration()
|
||||||
.apply(this, MovieTagRepository.LINK)
|
.apply(this, MovieTagRepository.LINK)
|
||||||
setJsonBody(LinkRequest(movieId, tagId))
|
setJsonBody(LinkRequest(movieId, tag))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unlink(movieId: Long, tagId: Long) {
|
override suspend fun unlink(movieId: Long, tag: String) {
|
||||||
httpClient.delete {
|
httpClient.delete {
|
||||||
serverConfigurationProvider.getServerConfiguration()
|
serverConfigurationProvider.getServerConfiguration()
|
||||||
.apply(this, MovieTagRepository.UNLINK)
|
.apply(this, MovieTagRepository.UNLINK)
|
||||||
setJsonBody(LinkRequest(movieId, tagId))
|
setJsonBody(LinkRequest(movieId, tag))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -57,19 +57,19 @@ class RemoteUserTagRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun edit(id: Long, newTag: String) {
|
override suspend fun edit(oldTag: String, newTag: String) {
|
||||||
httpClient.post {
|
httpClient.post {
|
||||||
serverConfigurationProvider.getServerConfiguration()
|
serverConfigurationProvider.getServerConfiguration()
|
||||||
.apply(this, UserTagRepository.EDIT)
|
.apply(this, UserTagRepository.EDIT)
|
||||||
setJsonBody(EditRequest(id, newTag))
|
setJsonBody(EditRequest(oldTag, newTag))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun delete(id: Long) {
|
override suspend fun delete(tag: String) {
|
||||||
httpClient.delete {
|
httpClient.delete {
|
||||||
serverConfigurationProvider.getServerConfiguration()
|
serverConfigurationProvider.getServerConfiguration()
|
||||||
.apply(this, UserTagRepository.DELETE)
|
.apply(this, UserTagRepository.DELETE)
|
||||||
setJsonBody(DeleteRequest(id))
|
setJsonBody(DeleteRequest(tag))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-6
@@ -6,10 +6,12 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.emitAll
|
import kotlinx.coroutines.flow.emitAll
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.koin.core.annotation.Factory
|
import org.koin.core.annotation.Factory
|
||||||
import org.koin.core.annotation.Named
|
import org.koin.core.annotation.Named
|
||||||
import ru.shadowsparky.domain.DispatcherProvider
|
import ru.shadowsparky.domain.DispatcherProvider
|
||||||
import ru.shadowsparky.vbox.domain.MovieTagQueries
|
import ru.shadowsparky.vbox.domain.MovieTagQueries
|
||||||
|
import ru.shadowsparky.vbox.domain.UserTagQueries
|
||||||
import ru.shadowsparky.vbox.shared.data.AppDatabaseProvider
|
import ru.shadowsparky.vbox.shared.data.AppDatabaseProvider
|
||||||
import ru.shadowsparky.vbox.shared.di.DbType
|
import ru.shadowsparky.vbox.shared.di.DbType
|
||||||
import ru.shadowsparky.vbox.shared.domain.tag.MovieTagRepository
|
import ru.shadowsparky.vbox.shared.domain.tag.MovieTagRepository
|
||||||
@@ -38,26 +40,29 @@ class SqlMovieTagRepository(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun queryMovies(tagId: Long): Flow<Set<Long>> = flow {
|
override fun queryMovies(tag: String): Flow<Set<Long>> = flow {
|
||||||
|
val id = queryId(tag)
|
||||||
|
if (id != null) {
|
||||||
emitAll(
|
emitAll(
|
||||||
getQueries().queryMovies(tagId)
|
getQueries().queryMovies(id)
|
||||||
.asFlow()
|
.asFlow()
|
||||||
.mapToList(dispatcherProvider.io)
|
.mapToList(dispatcherProvider.io)
|
||||||
.map { it.toSet() }
|
.map { it.toSet() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun link(movieId: Long, tagId: Long) {
|
override suspend fun link(movieId: Long, tag: String) {
|
||||||
getQueries().link(
|
getQueries().link(
|
||||||
movieId = movieId,
|
movieId = movieId,
|
||||||
tagId = tagId
|
tagId = queryId(tag) ?: return
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unlink(movieId: Long, tagId: Long) {
|
override suspend fun unlink(movieId: Long, tag: String) {
|
||||||
getQueries().unlink(
|
getQueries().unlink(
|
||||||
movieId = movieId,
|
movieId = movieId,
|
||||||
tagId = tagId
|
tagId = queryId(tag) ?: return
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +70,15 @@ class SqlMovieTagRepository(
|
|||||||
getQueries().clear()
|
getQueries().clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun queryId(tag: String): Long? = withContext(dispatcherProvider.io) {
|
||||||
|
getTagsQueries().find(tag).executeAsOneOrNull()?.id
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getQueries(): MovieTagQueries {
|
private suspend fun getQueries(): MovieTagQueries {
|
||||||
return dbProvider.get().movieTagQueries
|
return dbProvider.get().movieTagQueries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun getTagsQueries(): UserTagQueries {
|
||||||
|
return dbProvider.get().userTagQueries
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-7
@@ -42,15 +42,12 @@ class SqlUserTagRepository(
|
|||||||
runCatching { getQueries().insert(tag) }
|
runCatching { getQueries().insert(tag) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun edit(id: Long, newTag: String) {
|
override suspend fun edit(oldTag: String, newTag: String) {
|
||||||
getQueries().update(
|
getQueries().update(newTag, oldTag)
|
||||||
id = id,
|
|
||||||
newTag = newTag
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun delete(id: Long) {
|
override suspend fun delete(tag: String) {
|
||||||
runCatching { getQueries().delete(id) }
|
runCatching { getQueries().delete(tag) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun clear() {
|
override suspend fun clear() {
|
||||||
|
|||||||
+19
-25
@@ -48,69 +48,63 @@ class SyncMovieTagRepository(
|
|||||||
queryRemote = {
|
queryRemote = {
|
||||||
val allTags = userTagRemote.userTags.first()
|
val allTags = userTagRemote.userTags.first()
|
||||||
allTags.flatMap { tag ->
|
allTags.flatMap { tag ->
|
||||||
val tagId = tag.id ?: return@flatMap emptyList()
|
remote.queryMovies(tag.tag).first().map { movieId -> MovieTagLink(movieId, tag.tag) }
|
||||||
remote.queryMovies(tagId).first().map { movieId ->
|
|
||||||
MovieTagLink(movieId = movieId, tagId = tagId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
queryLocal = {
|
queryLocal = {
|
||||||
val allTags = userTagLocal.userTags.first()
|
val allTags = userTagLocal.userTags.first()
|
||||||
allTags.flatMap { tag ->
|
allTags.flatMap { tag ->
|
||||||
val tagId = tag.id ?: return@flatMap emptyList()
|
local.queryMovies(tag.tag).first().map { movieId -> MovieTagLink(movieId, tag.tag) }
|
||||||
local.queryMovies(tagId).first().map { movieId ->
|
|
||||||
MovieTagLink(movieId = movieId, tagId = tagId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
writeRemote = { list ->
|
writeRemote = { list ->
|
||||||
list.forEach { link -> remote.link(movieId = link.movieId, tagId = link.tagId) }
|
list.forEach { link -> remote.link(link.movieId, link.tag) }
|
||||||
},
|
},
|
||||||
writeLocal = { list ->
|
writeLocal = { list ->
|
||||||
list.forEach { link -> local.link(movieId = link.movieId, tagId = link.tagId) }
|
list.forEach { link -> local.link(link.movieId, link.tag) }
|
||||||
},
|
},
|
||||||
selectKey = { "${it.movieId}|${it.tagId}" }
|
selectKey = { "${it.movieId}|${it.tag}" }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class MovieTagLink(
|
data class MovieTagLink(
|
||||||
val movieId: Long,
|
val movieId: Long,
|
||||||
val tagId: Long
|
val tag: String
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun queryMovies(tagId: Long): Flow<Set<Long>> {
|
override fun queryMovies(tag: String): Flow<Set<Long>> {
|
||||||
return tokenStorage.queryFlow(remote.queryMovies(tagId), local.queryMovies(tagId))
|
return tokenStorage.queryFlow(remote.queryMovies(tag), local.queryMovies(tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun link(movieId: Long, tagId: Long) {
|
override suspend fun link(movieId: Long, tag: String) {
|
||||||
local.link(movieId, tagId)
|
local.link(movieId, tag)
|
||||||
if (tokenStorage.hasAccount()) {
|
if (tokenStorage.hasAccount()) {
|
||||||
try {
|
try {
|
||||||
remote.link(movieId, tagId)
|
remote.link(movieId, tag)
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
pendingRepo.insert(
|
pendingRepo.insert(
|
||||||
OperationInfo(
|
OperationInfo(
|
||||||
type = AggregateType.MOVIE_TAG,
|
type = AggregateType.MOVIE_TAG,
|
||||||
actionType = ACTION_LINK,
|
actionType = ACTION_LINK,
|
||||||
payload = buildPayload(movieId, tagId)
|
payload = buildPayload(movieId, tag)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unlink(movieId: Long, tagId: Long) {
|
override suspend fun unlink(movieId: Long, tag: String) {
|
||||||
local.unlink(movieId, tagId)
|
local.unlink(movieId, tag)
|
||||||
if (tokenStorage.hasAccount()) {
|
if (tokenStorage.hasAccount()) {
|
||||||
try {
|
try {
|
||||||
remote.unlink(movieId, tagId)
|
remote.unlink(movieId, tag)
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
pendingRepo.insert(
|
pendingRepo.insert(
|
||||||
OperationInfo(
|
OperationInfo(
|
||||||
type = AggregateType.MOVIE_TAG,
|
type = AggregateType.MOVIE_TAG,
|
||||||
actionType = ACTION_UNLINK,
|
actionType = ACTION_UNLINK,
|
||||||
payload = buildPayload(movieId, tagId)
|
payload = buildPayload(movieId, tag)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -127,11 +121,11 @@ class SyncMovieTagRepository(
|
|||||||
const val ACTION_UNLINK = "UNLINK"
|
const val ACTION_UNLINK = "UNLINK"
|
||||||
|
|
||||||
const val KEY_MOVIE_ID = "movieId"
|
const val KEY_MOVIE_ID = "movieId"
|
||||||
const val KEY_TAG_ID = "tagId"
|
const val KEY_TAG_NAME = "tagName"
|
||||||
|
|
||||||
private fun buildPayload(movieId: Long, tagId: Long) = buildJsonObject {
|
private fun buildPayload(movieId: Long, tagName: String) = buildJsonObject {
|
||||||
put(KEY_MOVIE_ID, movieId)
|
put(KEY_MOVIE_ID, movieId)
|
||||||
put(KEY_TAG_ID, tagId)
|
put(KEY_TAG_NAME, tagName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-9
@@ -72,14 +72,14 @@ class SyncUserTagRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun edit(id: Long, newTag: String) {
|
override suspend fun edit(oldTag: String, newTag: String) {
|
||||||
local.edit(id, newTag)
|
local.edit(oldTag, newTag)
|
||||||
if (tokenStorage.hasAccount()) {
|
if (tokenStorage.hasAccount()) {
|
||||||
try {
|
try {
|
||||||
remote.edit(id, newTag)
|
remote.edit(oldTag, newTag)
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
val payload = buildJsonObject {
|
val payload = buildJsonObject {
|
||||||
put(KEY_ID, id)
|
put(KEY_OLD_TAG, oldTag)
|
||||||
put(KEY_NEW_TAG, newTag)
|
put(KEY_NEW_TAG, newTag)
|
||||||
}
|
}
|
||||||
pendingRepo.insert(
|
pendingRepo.insert(
|
||||||
@@ -93,13 +93,13 @@ class SyncUserTagRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun delete(id: Long) {
|
override suspend fun delete(tag: String) {
|
||||||
local.delete(id)
|
local.delete(tag)
|
||||||
if (tokenStorage.hasAccount()) {
|
if (tokenStorage.hasAccount()) {
|
||||||
try {
|
try {
|
||||||
remote.delete(id)
|
remote.delete(tag)
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
val payload = buildJsonObject { put(KEY_ID, id) }
|
val payload = buildJsonObject { put(KEY_OLD_TAG, tag) }
|
||||||
pendingRepo.insert(
|
pendingRepo.insert(
|
||||||
OperationInfo(
|
OperationInfo(
|
||||||
type = AggregateType.USER_TAG,
|
type = AggregateType.USER_TAG,
|
||||||
@@ -121,7 +121,7 @@ class SyncUserTagRepository(
|
|||||||
const val ACTION_EDIT = "EDIT"
|
const val ACTION_EDIT = "EDIT"
|
||||||
const val ACTION_DELETE = "DELETE"
|
const val ACTION_DELETE = "DELETE"
|
||||||
|
|
||||||
const val KEY_ID = "id"
|
const val KEY_OLD_TAG = "old_tag"
|
||||||
const val KEY_TAG = "tag"
|
const val KEY_TAG = "tag"
|
||||||
const val KEY_NEW_TAG = "newTag"
|
const val KEY_NEW_TAG = "newTag"
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -72,12 +72,12 @@ class OverviewComponent(
|
|||||||
doOnStart { init() }
|
doOnStart { init() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateTag(tagId: Long, state: Boolean) {
|
fun updateTag(tag: String, state: Boolean) {
|
||||||
exec {
|
exec {
|
||||||
if (state) {
|
if (state) {
|
||||||
movieTagRepository.link(id, tagId)
|
movieTagRepository.link(id, tag)
|
||||||
} else {
|
} else {
|
||||||
movieTagRepository.unlink(id, tagId)
|
movieTagRepository.unlink(id, tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -81,7 +81,7 @@ fun SavedMovieContent(comp: SavedMovieScreenComponent, paddingValues: PaddingVal
|
|||||||
private fun FilterBottomSheet(
|
private fun FilterBottomSheet(
|
||||||
allTags: List<TagInfo>?,
|
allTags: List<TagInfo>?,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onFilterBy: (Long?) -> Unit
|
onFilterBy: (String?) -> Unit
|
||||||
) {
|
) {
|
||||||
allTags ?: return
|
allTags ?: return
|
||||||
ModalBottomSheet(onDismissRequest = onDismiss) {
|
ModalBottomSheet(onDismissRequest = onDismiss) {
|
||||||
@@ -113,7 +113,7 @@ private fun FilterBottomSheet(
|
|||||||
TextPreference(
|
TextPreference(
|
||||||
text = tag.tag,
|
text = tag.tag,
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
onFilterBy(tag.id)
|
onFilterBy(tag.tag)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-4
@@ -29,11 +29,11 @@ class SavedMovieScreenComponent(
|
|||||||
) : ComponentContext by context {
|
) : ComponentContext by context {
|
||||||
val userTags = userTagRepository.userTags
|
val userTags = userTagRepository.userTags
|
||||||
|
|
||||||
private val filterIdFlow = MutableStateFlow<Long?>(null)
|
private val filterTagFlow = MutableStateFlow<String?>(null)
|
||||||
|
|
||||||
private val allMovies = repo.getAll().map { it.map() }
|
private val allMovies = repo.getAll().map { it.map() }
|
||||||
|
|
||||||
private val filterMovieIds = filterIdFlow.flatMapLatest {
|
private val filterMovieIds = filterTagFlow.flatMapLatest {
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
flowOf(null)
|
flowOf(null)
|
||||||
} else {
|
} else {
|
||||||
@@ -49,8 +49,8 @@ class SavedMovieScreenComponent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun filterByTag(tagId: Long?) {
|
fun filterByTag(tagId: String?) {
|
||||||
filterIdFlow.tryEmit(tagId)
|
filterTagFlow.tryEmit(tagId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<VideoDetails>.map(): List<VideoItem> {
|
private fun List<VideoDetails>.map(): List<VideoItem> {
|
||||||
|
|||||||
+4
-4
@@ -33,7 +33,7 @@ fun LinkTagBottomSheet(
|
|||||||
linkedTags: List<TagInfo>?,
|
linkedTags: List<TagInfo>?,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
requestTagsScreen: () -> Unit,
|
requestTagsScreen: () -> Unit,
|
||||||
updateTag: (Long, Boolean) -> Unit
|
updateTag: (String, Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
ModalBottomSheet(onDismissRequest = onDismiss) {
|
ModalBottomSheet(onDismissRequest = onDismiss) {
|
||||||
Column(
|
Column(
|
||||||
@@ -70,7 +70,7 @@ fun LinkTagBottomSheet(
|
|||||||
private fun ShowTags(
|
private fun ShowTags(
|
||||||
allTags: List<TagInfo>,
|
allTags: List<TagInfo>,
|
||||||
linkedTags: List<TagInfo>,
|
linkedTags: List<TagInfo>,
|
||||||
updateTag: (Long, Boolean) -> Unit
|
updateTag: (String, Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
val linkedTagIds = remember(linkedTags) {
|
val linkedTagIds = remember(linkedTags) {
|
||||||
linkedTags.mapNotNull { it.id }.toSet()
|
linkedTags.mapNotNull { it.id }.toSet()
|
||||||
@@ -90,7 +90,7 @@ private fun ShowTags(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable { updateTag(tagId, !isChecked) }
|
.clickable { updateTag(tag.tag, !isChecked) }
|
||||||
.padding(vertical = 8.dp),
|
.padding(vertical = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -102,7 +102,7 @@ private fun ShowTags(
|
|||||||
)
|
)
|
||||||
Checkbox(
|
Checkbox(
|
||||||
checked = isChecked,
|
checked = isChecked,
|
||||||
onCheckedChange = { checked -> updateTag(tagId, checked) }
|
onCheckedChange = { checked -> updateTag(tag.tag, checked) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -27,15 +27,15 @@ class TagComponent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun edit(id: Long, newTag: String) {
|
fun edit(oldTag: String, newTag: String) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
tagRepository.edit(id, newTag)
|
tagRepository.edit(oldTag, newTag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(id: Long) {
|
fun delete(tag: String) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
tagRepository.delete(id)
|
tagRepository.delete(tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -89,8 +89,8 @@ fun TagScreen(component: TagComponent) {
|
|||||||
EditTagBottomSheet(
|
EditTagBottomSheet(
|
||||||
activeTag,
|
activeTag,
|
||||||
onDismiss = { activeTag = null },
|
onDismiss = { activeTag = null },
|
||||||
onEdit = { id, tag -> component.edit(id, tag.trim()) },
|
onEdit = { oldTag, tag -> component.edit(oldTag, tag.trim()) },
|
||||||
onDelete = { id -> component.delete(id) },
|
onDelete = { oldTag -> component.delete(oldTag) },
|
||||||
canSave = canSave
|
canSave = canSave
|
||||||
)
|
)
|
||||||
} else if (isAddSheetVisible) {
|
} else if (isAddSheetVisible) {
|
||||||
@@ -155,8 +155,8 @@ private fun AddTagBottomSheet(
|
|||||||
private fun EditTagBottomSheet(
|
private fun EditTagBottomSheet(
|
||||||
tag: TagInfo? = null,
|
tag: TagInfo? = null,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onEdit: (Long, String) -> Unit,
|
onEdit: (String, String) -> Unit,
|
||||||
onDelete: (Long) -> Unit,
|
onDelete: (String) -> Unit,
|
||||||
canSave: (String) -> Boolean
|
canSave: (String) -> Boolean
|
||||||
) {
|
) {
|
||||||
tag ?: return
|
tag ?: return
|
||||||
@@ -184,7 +184,7 @@ private fun EditTagBottomSheet(
|
|||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (tagId != null && newTag.isNotEmpty()) {
|
if (tagId != null && newTag.isNotEmpty()) {
|
||||||
onEdit(tagId, newTag)
|
onEdit(tag.tag, newTag)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -194,7 +194,7 @@ private fun EditTagBottomSheet(
|
|||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (tagId != null) {
|
if (tagId != null) {
|
||||||
onDelete(tagId)
|
onDelete(tag.tag)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+7
-3
@@ -5,8 +5,12 @@ CREATE TABLE UserTag (
|
|||||||
|
|
||||||
selectAll:
|
selectAll:
|
||||||
SELECT id, tag
|
SELECT id, tag
|
||||||
|
FROM UserTag;
|
||||||
|
|
||||||
|
find:
|
||||||
|
SELECT id, tag
|
||||||
FROM UserTag
|
FROM UserTag
|
||||||
ORDER BY id DESC;
|
WHERE tag = ?;
|
||||||
|
|
||||||
insert:
|
insert:
|
||||||
INSERT OR IGNORE INTO UserTag(tag)
|
INSERT OR IGNORE INTO UserTag(tag)
|
||||||
@@ -15,11 +19,11 @@ VALUES (?);
|
|||||||
update:
|
update:
|
||||||
UPDATE UserTag
|
UPDATE UserTag
|
||||||
SET tag = :newTag
|
SET tag = :newTag
|
||||||
WHERE id = :id;
|
WHERE tag = ?;
|
||||||
|
|
||||||
delete:
|
delete:
|
||||||
DELETE FROM UserTag
|
DELETE FROM UserTag
|
||||||
WHERE id = ?;
|
WHERE tag = ?;
|
||||||
|
|
||||||
clear:
|
clear:
|
||||||
DELETE FROM UserTag;
|
DELETE FROM UserTag;
|
||||||
|
|||||||
+5
-5
@@ -6,14 +6,14 @@ import ru.shadowsparky.vbox.shared.domain.DYNAMIC_PREFIX
|
|||||||
|
|
||||||
interface MovieTagRepository {
|
interface MovieTagRepository {
|
||||||
fun query(movieId: Long): Flow<List<TagInfo>>
|
fun query(movieId: Long): Flow<List<TagInfo>>
|
||||||
fun queryMovies(tagId: Long): Flow<Set<Long>>
|
fun queryMovies(tag: String): Flow<Set<Long>>
|
||||||
suspend fun link(movieId: Long, tagId: Long)
|
suspend fun link(movieId: Long, tag: String)
|
||||||
suspend fun unlink(movieId: Long, tagId: Long)
|
suspend fun unlink(movieId: Long, tag: String)
|
||||||
|
|
||||||
suspend fun clear() {}
|
suspend fun clear() {}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG_ID = "tagId"
|
const val TAG_NAME = "tagName"
|
||||||
const val MOVIE_ID = "movieId"
|
const val MOVIE_ID = "movieId"
|
||||||
|
|
||||||
const val PREFIX = "$DYNAMIC_PREFIX/movieTag"
|
const val PREFIX = "$DYNAMIC_PREFIX/movieTag"
|
||||||
@@ -25,7 +25,7 @@ interface MovieTagRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class LinkRequest(val movieId: Long, val tagId: Long)
|
data class LinkRequest(val movieId: Long, val tag: String)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class LinkedMovies(val ids: Set<Long>)
|
data class LinkedMovies(val ids: Set<Long>)
|
||||||
|
|||||||
+5
-4
@@ -8,8 +8,8 @@ interface UserTagRepository {
|
|||||||
val userTags: Flow<List<TagInfo>>
|
val userTags: Flow<List<TagInfo>>
|
||||||
|
|
||||||
suspend fun add(tag: String)
|
suspend fun add(tag: String)
|
||||||
suspend fun edit(id: Long, newTag: String)
|
suspend fun edit(oldTag: String, newTag: String)
|
||||||
suspend fun delete(id: Long)
|
suspend fun delete(tag: String)
|
||||||
|
|
||||||
suspend fun clear() {}
|
suspend fun clear() {}
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ interface UserTagRepository {
|
|||||||
|
|
||||||
const val LIST = "$PREFIX/list"
|
const val LIST = "$PREFIX/list"
|
||||||
const val ADD = "$PREFIX/add"
|
const val ADD = "$PREFIX/add"
|
||||||
|
|
||||||
const val EDIT = "$PREFIX/edit"
|
const val EDIT = "$PREFIX/edit"
|
||||||
const val DELETE = "$PREFIX/delete"
|
const val DELETE = "$PREFIX/delete"
|
||||||
}
|
}
|
||||||
@@ -30,10 +31,10 @@ data class TagResponse(val tags: List<TagInfo>)
|
|||||||
data class AddRequest(val tag: String)
|
data class AddRequest(val tag: String)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class EditRequest(val id: Long, val newTag: String)
|
data class EditRequest(val oldTag: String, val newTag: String)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class DeleteRequest(val id: Long)
|
data class DeleteRequest(val tag: String)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class TagInfo(
|
data class TagInfo(
|
||||||
|
|||||||
Reference in New Issue
Block a user