Skip to content

Commit 41bd030

Browse files
committed
feat(dao): Apply a hierarchy filter when listing organizations
Extend the `OrganizationRepository.list()` function to support a `HierarchyFiler`. This allows to query a specific set of organizations; for instance, all those a user can access. Signed-off-by: Oliver Heger <[email protected]>
1 parent ce357da commit 41bd030

File tree

3 files changed

+82
-12
lines changed

3 files changed

+82
-12
lines changed

dao/src/main/kotlin/repositories/organization/DaoOrganizationRepository.kt

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,20 @@ package org.eclipse.apoapsis.ortserver.dao.repositories.organization
2121

2222
import org.eclipse.apoapsis.ortserver.dao.blockingQuery
2323
import org.eclipse.apoapsis.ortserver.dao.entityQuery
24+
import org.eclipse.apoapsis.ortserver.dao.utils.apply
2425
import org.eclipse.apoapsis.ortserver.dao.utils.applyRegex
26+
import org.eclipse.apoapsis.ortserver.dao.utils.extractIds
2527
import org.eclipse.apoapsis.ortserver.dao.utils.listQuery
28+
import org.eclipse.apoapsis.ortserver.model.CompoundHierarchyId
2629
import org.eclipse.apoapsis.ortserver.model.repositories.OrganizationRepository
2730
import org.eclipse.apoapsis.ortserver.model.util.FilterParameter
31+
import org.eclipse.apoapsis.ortserver.model.util.HierarchyFilter
2832
import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters
2933
import org.eclipse.apoapsis.ortserver.model.util.OptionalValue
3034

3135
import org.jetbrains.exposed.sql.Database
3236
import org.jetbrains.exposed.sql.Op
33-
import org.jetbrains.exposed.sql.and
37+
import org.jetbrains.exposed.sql.SqlExpressionBuilder
3438

3539
/**
3640
* An implementation of [OrganizationRepository] that stores organizations in [OrganizationsTable].
@@ -45,17 +49,17 @@ class DaoOrganizationRepository(private val db: Database) : OrganizationReposito
4549

4650
override fun get(id: Long) = db.entityQuery { OrganizationDao[id].mapToModel() }
4751

48-
override fun list(parameters: ListQueryParameters, filter: FilterParameter?) =
52+
override fun list(parameters: ListQueryParameters, nameFilter: FilterParameter?, hierarchyFilter: HierarchyFilter) =
4953
db.blockingQuery {
50-
OrganizationDao.listQuery(parameters, OrganizationDao::mapToModel) {
51-
var condition: Op<Boolean> = Op.TRUE
52-
filter?.let {
53-
condition = condition and OrganizationsTable.name.applyRegex(
54-
it.value
55-
)
56-
}
57-
condition
54+
val nameCondition = nameFilter?.let {
55+
OrganizationsTable.name.applyRegex(it.value)
56+
} ?: Op.TRUE
57+
58+
val builder = hierarchyFilter.apply(nameCondition) { level, ids, filter ->
59+
generateHierarchyCondition(level, ids, filter)
5860
}
61+
62+
OrganizationDao.listQuery(parameters, OrganizationDao::mapToModel, builder)
5963
}
6064

6165
override fun update(id: Long, name: OptionalValue<String>, description: OptionalValue<String?>) = db.blockingQuery {
@@ -69,3 +73,21 @@ class DaoOrganizationRepository(private val db: Database) : OrganizationReposito
6973

7074
override fun delete(id: Long) = db.blockingQuery { OrganizationDao[id].delete() }
7175
}
76+
77+
/**
78+
* Generate a condition defined by a [filter] for the given [level] and [ids].
79+
*/
80+
private fun SqlExpressionBuilder.generateHierarchyCondition(
81+
level: Int,
82+
ids: List<CompoundHierarchyId>,
83+
filter: HierarchyFilter
84+
): Op<Boolean> =
85+
when (level) {
86+
CompoundHierarchyId.ORGANIZATION_LEVEL ->
87+
OrganizationsTable.id inList (
88+
ids.extractIds(CompoundHierarchyId.ORGANIZATION_LEVEL) +
89+
filter.nonTransitiveIncludes[CompoundHierarchyId.ORGANIZATION_LEVEL].orEmpty()
90+
.extractIds(CompoundHierarchyId.ORGANIZATION_LEVEL)
91+
)
92+
else -> Op.FALSE
93+
}

dao/src/test/kotlin/repositories/organization/DaoOrganizationRepositoryTest.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ import io.kotest.matchers.nulls.shouldNotBeNull
2525
import io.kotest.matchers.shouldBe
2626

2727
import org.eclipse.apoapsis.ortserver.dao.test.DatabaseTestExtension
28+
import org.eclipse.apoapsis.ortserver.model.CompoundHierarchyId
2829
import org.eclipse.apoapsis.ortserver.model.Organization
30+
import org.eclipse.apoapsis.ortserver.model.OrganizationId
2931
import org.eclipse.apoapsis.ortserver.model.util.FilterParameter
32+
import org.eclipse.apoapsis.ortserver.model.util.HierarchyFilter
3033
import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters
3134
import org.eclipse.apoapsis.ortserver.model.util.ListQueryResult
3235
import org.eclipse.apoapsis.ortserver.model.util.OptionalValue
@@ -147,6 +150,48 @@ class DaoOrganizationRepositoryTest : StringSpec({
147150
)
148151
}
149152

153+
"list should apply a hierarchy filter" {
154+
val createdOrg1 = organizationRepository.create("org1", "description1")
155+
val org1Id = CompoundHierarchyId.forOrganization(OrganizationId(createdOrg1.id))
156+
val createdOrg2 = organizationRepository.create("org2", "description2")
157+
val org2Id = CompoundHierarchyId.forOrganization(OrganizationId(createdOrg2.id))
158+
organizationRepository.create("org3", "description3")
159+
160+
val hierarchyFilter = HierarchyFilter(
161+
transitiveIncludes = mapOf(CompoundHierarchyId.ORGANIZATION_LEVEL to listOf(org1Id, org2Id)),
162+
nonTransitiveIncludes = emptyMap(),
163+
excludes = emptyMap()
164+
)
165+
val result = organizationRepository.list(hierarchyFilter = hierarchyFilter)
166+
167+
result shouldBe ListQueryResult(
168+
data = listOf(createdOrg1, createdOrg2),
169+
params = ListQueryParameters.DEFAULT,
170+
totalCount = 2
171+
)
172+
}
173+
174+
"list should apply a hierarchy filter with non-transitive includes" {
175+
val createdOrg1 = organizationRepository.create("org1", "description1")
176+
val org1Id = CompoundHierarchyId.forOrganization(OrganizationId(createdOrg1.id))
177+
val createdOrg2 = organizationRepository.create("org2", "description2")
178+
val org2Id = CompoundHierarchyId.forOrganization(OrganizationId(createdOrg2.id))
179+
organizationRepository.create("org3", "description3")
180+
181+
val hierarchyFilter = HierarchyFilter(
182+
transitiveIncludes = mapOf(CompoundHierarchyId.ORGANIZATION_LEVEL to listOf(org1Id)),
183+
nonTransitiveIncludes = mapOf(CompoundHierarchyId.ORGANIZATION_LEVEL to listOf(org2Id)),
184+
excludes = emptyMap()
185+
)
186+
val result = organizationRepository.list(hierarchyFilter = hierarchyFilter)
187+
188+
result shouldBe ListQueryResult(
189+
data = listOf(createdOrg1, createdOrg2),
190+
params = ListQueryParameters.DEFAULT,
191+
totalCount = 2
192+
)
193+
}
194+
150195
"update should update an entity in the database" {
151196
val createdOrg = organizationRepository.create("name", "description")
152197

model/src/commonMain/kotlin/repositories/OrganizationRepository.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package org.eclipse.apoapsis.ortserver.model.repositories
2121

2222
import org.eclipse.apoapsis.ortserver.model.Organization
2323
import org.eclipse.apoapsis.ortserver.model.util.FilterParameter
24+
import org.eclipse.apoapsis.ortserver.model.util.HierarchyFilter
2425
import org.eclipse.apoapsis.ortserver.model.util.ListQueryParameters
2526
import org.eclipse.apoapsis.ortserver.model.util.ListQueryResult
2627
import org.eclipse.apoapsis.ortserver.model.util.OptionalValue
@@ -40,11 +41,13 @@ interface OrganizationRepository {
4041
fun get(id: Long): Organization?
4142

4243
/**
43-
* List all organizations according to the given [parameters].
44+
* List all organizations according to the given [parameters]. Optionally, a [nameFilter] on the product name and a
45+
* [hierarchyFilter] can be provided.
4446
*/
4547
fun list(
4648
parameters: ListQueryParameters = ListQueryParameters.DEFAULT,
47-
filter: FilterParameter? = null
49+
nameFilter: FilterParameter? = null,
50+
hierarchyFilter: HierarchyFilter = HierarchyFilter.WILDCARD
4851
): ListQueryResult<Organization>
4952

5053
/**

0 commit comments

Comments
 (0)