Skip to content

Commit 0e9307b

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 6a96261 commit 0e9307b

File tree

3 files changed

+80
-12
lines changed

3 files changed

+80
-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: 43 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,46 @@ 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+
)
164+
val result = organizationRepository.list(hierarchyFilter = hierarchyFilter)
165+
166+
result shouldBe ListQueryResult(
167+
data = listOf(createdOrg1, createdOrg2),
168+
params = ListQueryParameters.DEFAULT,
169+
totalCount = 2
170+
)
171+
}
172+
173+
"list should apply a hierarchy filter with non-transitive includes" {
174+
val createdOrg1 = organizationRepository.create("org1", "description1")
175+
val org1Id = CompoundHierarchyId.forOrganization(OrganizationId(createdOrg1.id))
176+
val createdOrg2 = organizationRepository.create("org2", "description2")
177+
val org2Id = CompoundHierarchyId.forOrganization(OrganizationId(createdOrg2.id))
178+
organizationRepository.create("org3", "description3")
179+
180+
val hierarchyFilter = HierarchyFilter(
181+
transitiveIncludes = mapOf(CompoundHierarchyId.ORGANIZATION_LEVEL to listOf(org1Id)),
182+
nonTransitiveIncludes = mapOf(CompoundHierarchyId.ORGANIZATION_LEVEL to listOf(org2Id)),
183+
)
184+
val result = organizationRepository.list(hierarchyFilter = hierarchyFilter)
185+
186+
result shouldBe ListQueryResult(
187+
data = listOf(createdOrg1, createdOrg2),
188+
params = ListQueryParameters.DEFAULT,
189+
totalCount = 2
190+
)
191+
}
192+
150193
"update should update an entity in the database" {
151194
val createdOrg = organizationRepository.create("name", "description")
152195

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)