diff --git a/komga/src/flyway/resources/db/migration/sqlite/V20220225164240__user_restrictions.sql b/komga/src/flyway/resources/db/migration/sqlite/V20220225164240__user_restrictions.sql new file mode 100644 index 000000000..28219035d --- /dev/null +++ b/komga/src/flyway/resources/db/migration/sqlite/V20220225164240__user_restrictions.sql @@ -0,0 +1,13 @@ +CREATE TABLE USER_SHARING +( + LABEL varchar NOT NULL, + ALLOW boolean NOT NULL, + USER_ID varchar NOT NULL, + PRIMARY KEY (LABEL, ALLOW, USER_ID), + FOREIGN KEY (USER_ID) REFERENCES USER (ID) +); + +ALTER TABLE USER + add column AGE_RESTRICTION integer NULL; +ALTER TABLE USER + add column AGE_RESTRICTION_ALLOW_ONLY boolean NULL; diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/model/ContentRestriction.kt b/komga/src/main/kotlin/org/gotson/komga/domain/model/ContentRestriction.kt index e75aa3fce..352c74df1 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/model/ContentRestriction.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/model/ContentRestriction.kt @@ -17,7 +17,7 @@ sealed class ContentRestriction { class ExcludeOver(age: Int) : AgeRestriction(age) } - sealed class LabelsRestriction(val labels: Collection) : ContentRestriction() { + sealed class LabelsRestriction(val labels: Set) : ContentRestriction() { /** * Allow only content that has at least one of the provided sharing [labels] * diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDao.kt index 982fc5cee..840531e76 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDao.kt @@ -1,5 +1,7 @@ package org.gotson.komga.infrastructure.jooq +import org.gotson.komga.domain.model.ContentRestriction +import org.gotson.komga.domain.model.ContentRestrictions import org.gotson.komga.domain.model.KomgaUser import org.gotson.komga.domain.persistence.KomgaUserRepository import org.gotson.komga.jooq.Tables @@ -18,6 +20,7 @@ class KomgaUserDao( private val u = Tables.USER private val ul = Tables.USER_LIBRARY_SHARING + private val us = Tables.USER_SHARING override fun count(): Long = dsl.fetchCount(u).toLong() @@ -41,6 +44,9 @@ class KomgaUserDao( private fun ResultQuery.fetchAndMap() = this.fetchGroups({ it.into(u) }, { it.into(ul) }) .map { (ur, ulr) -> + val usr = dsl.selectFrom(us) + .where(us.USER_ID.eq(ur.id)) + .toList() KomgaUser( email = ur.email, password = ur.password, @@ -49,6 +55,14 @@ class KomgaUserDao( rolePageStreaming = ur.rolePageStreaming, sharedLibrariesIds = ulr.mapNotNull { it.libraryId }.toSet(), sharedAllLibraries = ur.sharedAllLibraries, + restrictions = ContentRestrictions( + ageRestriction = if (ur.ageRestriction != null && ur.ageRestrictionAllowOnly != null) + if (ur.ageRestrictionAllowOnly) ContentRestriction.AgeRestriction.AllowOnlyUnder(ur.ageRestriction) + else ContentRestriction.AgeRestriction.ExcludeOver(ur.ageRestriction) + else null, + labelsAllow = usr.filter { it.allow }.map { it.label }.toSet(), + labelsExclude = usr.filterNot { it.allow }.map { it.label }.toSet(), + ), id = ur.id, createdDate = ur.createdDate.toCurrentTimeZone(), lastModifiedDate = ur.lastModifiedDate.toCurrentTimeZone(), @@ -65,14 +79,19 @@ class KomgaUserDao( .set(u.ROLE_FILE_DOWNLOAD, user.roleFileDownload) .set(u.ROLE_PAGE_STREAMING, user.rolePageStreaming) .set(u.SHARED_ALL_LIBRARIES, user.sharedAllLibraries) + .set(u.AGE_RESTRICTION, user.restrictions.ageRestriction?.age) + .set( + u.AGE_RESTRICTION_ALLOW_ONLY, + when (user.restrictions.ageRestriction) { + is ContentRestriction.AgeRestriction.AllowOnlyUnder -> true + is ContentRestriction.AgeRestriction.ExcludeOver -> false + null -> null + }, + ) .execute() - user.sharedLibrariesIds.forEach { - dsl.insertInto(ul) - .columns(ul.USER_ID, ul.LIBRARY_ID) - .values(user.id, it) - .execute() - } + insertSharedLibraries(user) + insertSharingRestrictions(user) } @Transactional @@ -84,6 +103,15 @@ class KomgaUserDao( .set(u.ROLE_FILE_DOWNLOAD, user.roleFileDownload) .set(u.ROLE_PAGE_STREAMING, user.rolePageStreaming) .set(u.SHARED_ALL_LIBRARIES, user.sharedAllLibraries) + .set(u.AGE_RESTRICTION, user.restrictions.ageRestriction?.age) + .set( + u.AGE_RESTRICTION_ALLOW_ONLY, + when (user.restrictions.ageRestriction) { + is ContentRestriction.AgeRestriction.AllowOnlyUnder -> true + is ContentRestriction.AgeRestriction.ExcludeOver -> false + null -> null + }, + ) .set(u.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z"))) .where(u.ID.eq(user.id)) .execute() @@ -92,6 +120,15 @@ class KomgaUserDao( .where(ul.USER_ID.eq(user.id)) .execute() + dsl.deleteFrom(us) + .where(us.USER_ID.eq(user.id)) + .execute() + + insertSharedLibraries(user) + insertSharingRestrictions(user) + } + + private fun insertSharedLibraries(user: KomgaUser) { user.sharedLibrariesIds.forEach { dsl.insertInto(ul) .columns(ul.USER_ID, ul.LIBRARY_ID) @@ -100,14 +137,32 @@ class KomgaUserDao( } } + private fun insertSharingRestrictions(user: KomgaUser) { + user.restrictions.labelsAllowRestriction?.labels?.forEach { label -> + dsl.insertInto(us) + .columns(us.USER_ID, us.ALLOW, us.LABEL) + .values(user.id, true, label) + .execute() + } + + user.restrictions.labelsExcludeRestriction?.labels?.forEach { label -> + dsl.insertInto(us) + .columns(us.USER_ID, us.ALLOW, us.LABEL) + .values(user.id, false, label) + .execute() + } + } + @Transactional override fun delete(userId: String) { + dsl.deleteFrom(us).where(us.USER_ID.equal(userId)).execute() dsl.deleteFrom(ul).where(ul.USER_ID.equal(userId)).execute() dsl.deleteFrom(u).where(u.ID.equal(userId)).execute() } @Transactional override fun deleteAll() { + dsl.deleteFrom(us).execute() dsl.deleteFrom(ul).execute() dsl.deleteFrom(u).execute() } diff --git a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDaoTest.kt b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDaoTest.kt index 2f0c44c26..16fbb4dc1 100644 --- a/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDaoTest.kt +++ b/komga/src/test/kotlin/org/gotson/komga/infrastructure/jooq/KomgaUserDaoTest.kt @@ -1,6 +1,8 @@ package org.gotson.komga.infrastructure.jooq import org.assertj.core.api.Assertions.assertThat +import org.gotson.komga.domain.model.ContentRestriction +import org.gotson.komga.domain.model.ContentRestrictions import org.gotson.komga.domain.model.KomgaUser import org.gotson.komga.domain.model.makeLibrary import org.gotson.komga.domain.persistence.LibraryRepository @@ -59,9 +61,12 @@ class KomgaUserDaoTest( assertThat(lastModifiedDate).isCloseTo(now, offset) assertThat(email).isEqualTo("user@example.org") assertThat(password).isEqualTo("password") - assertThat(roleAdmin).isFalse() + assertThat(roleAdmin).isFalse assertThat(sharedLibrariesIds).containsExactly(library.id) - assertThat(sharedAllLibraries).isFalse() + assertThat(sharedAllLibraries).isFalse + assertThat(restrictions.ageRestriction).isNull() + assertThat(restrictions.labelsAllowRestriction).isNull() + assertThat(restrictions.labelsExcludeRestriction).isNull() } } @@ -73,10 +78,29 @@ class KomgaUserDaoTest( roleAdmin = false, sharedLibrariesIds = setOf(library.id), sharedAllLibraries = false, + restrictions = ContentRestrictions( + ageRestriction = ContentRestriction.AgeRestriction.AllowOnlyUnder(10), + labelsAllow = setOf("allow"), + labelsExclude = setOf("exclude"), + ) ) komgaUserDao.insert(user) val created = komgaUserDao.findByIdOrNull(user.id)!! + with(created) { + assertThat(restrictions.ageRestriction) + .isNotNull + .isExactlyInstanceOf(ContentRestriction.AgeRestriction.AllowOnlyUnder::class.java) + assertThat(restrictions.ageRestriction!!.age).isEqualTo(10) + assertThat(restrictions.labelsAllowRestriction) + .isNotNull + .isExactlyInstanceOf(ContentRestriction.LabelsRestriction.AllowOnly::class.java) + assertThat(restrictions.labelsAllowRestriction!!.labels).containsExactly("allow") + assertThat(restrictions.labelsExcludeRestriction) + .isNotNull + .isExactlyInstanceOf(ContentRestriction.LabelsRestriction.Exclude::class.java) + assertThat(restrictions.labelsExcludeRestriction!!.labels).containsExactly("exclude") + } val modified = created.copy( email = "user2@example.org", @@ -84,6 +108,11 @@ class KomgaUserDaoTest( roleAdmin = true, sharedLibrariesIds = emptySet(), sharedAllLibraries = true, + restrictions = ContentRestrictions( + ageRestriction = ContentRestriction.AgeRestriction.ExcludeOver(16), + labelsAllow = setOf("allow2"), + labelsExclude = setOf("exclude2"), + ), ) val modifiedDate = LocalDateTime.now() komgaUserDao.update(modified) @@ -97,9 +126,28 @@ class KomgaUserDaoTest( .isNotEqualTo(modified.createdDate) assertThat(email).isEqualTo("user2@example.org") assertThat(password).isEqualTo("password2") - assertThat(roleAdmin).isTrue() + assertThat(roleAdmin).isTrue assertThat(sharedLibrariesIds).isEmpty() - assertThat(sharedAllLibraries).isTrue() + assertThat(sharedAllLibraries).isTrue + assertThat(restrictions.ageRestriction) + .isNotNull + .isExactlyInstanceOf(ContentRestriction.AgeRestriction.ExcludeOver::class.java) + assertThat(restrictions.ageRestriction!!.age).isEqualTo(16) + assertThat(restrictions.labelsAllowRestriction) + .isNotNull + .isExactlyInstanceOf(ContentRestriction.LabelsRestriction.AllowOnly::class.java) + assertThat(restrictions.labelsAllowRestriction!!.labels).containsExactly("allow2") + assertThat(restrictions.labelsExcludeRestriction) + .isNotNull + .isExactlyInstanceOf(ContentRestriction.LabelsRestriction.Exclude::class.java) + assertThat(restrictions.labelsExcludeRestriction!!.labels).containsExactly("exclude2") + } + + komgaUserDao.update(modifiedSaved.copy(restrictions = ContentRestrictions())) + with(komgaUserDao.findByIdOrNull(modified.id)!!) { + assertThat(restrictions.ageRestriction).isNull() + assertThat(restrictions.labelsAllowRestriction).isNull() + assertThat(restrictions.labelsExcludeRestriction).isNull() } }