Skip to content

Commit

Permalink
feat(backend): adapt endpoints /group to accept additional group info…
Browse files Browse the repository at this point in the history
…rmation
  • Loading branch information
TobiasKampmann committed Feb 29, 2024
1 parent 68afa9c commit a8ecdb9
Show file tree
Hide file tree
Showing 18 changed files with 267 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
package org.loculus.backend.api

import io.swagger.v3.oas.annotations.media.Schema

data class Address(
@Schema(description = "The first line of the address.", example = "1234 Loculus Street")
val line1: String,
@Schema(description = "The second line of the address.", example = "Apt 1")
val line2: String,
@Schema(description = "The city of the address.", example = "Dortmund")
val city: String,
@Schema(description = "The state of the address.", example = "NRW")
val state: String,
@Schema(description = "The postal code of the address.", example = "12345")
val postalCode: String,
@Schema(description = "The country of the address.", example = "Germany")
val country: String,
)

data class Group(
@Schema(description = "The name of the group.", example = "Group 1")
val groupName: String,
@Schema(description = "The name of the institution.", example = "University of Loculus")
val institution: String,
@Schema(description = "The address of the institution.")
val address: Address,
@Schema(description = "The contact email for the group.", example = "something@loculus.org")
val contactEmail: String,
)

data class User(
val name: String,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ class GroupManagementController(
fun createNewGroup(
@UsernameFromJwt username: String,
@Parameter(
description = "The name of the newly created group",
) @RequestBody group: GroupName,
) = groupManagementDatabaseService.createNewGroup(group.groupName, username)
description = "Information about the newly created group",
) @RequestBody group: Group,
) = groupManagementDatabaseService.createNewGroup(group, username)

@Operation(description = "Get details of a group that the user is a member of.")
@ResponseStatus(HttpStatus.OK)
Expand Down Expand Up @@ -84,8 +84,4 @@ class GroupManagementController(
description = "The user name that should be removed from the group.",
) @PathVariable usernameToRemove: String,
) = groupManagementDatabaseService.removeUserFromGroup(groupMember, groupName, usernameToRemove)

data class GroupName(
val groupName: String,
)
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package org.loculus.backend.service.groupmanagement

import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.jetbrains.exposed.sql.JoinType
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.selectAll
import org.loculus.backend.api.Address
import org.loculus.backend.api.Group
import org.loculus.backend.api.GroupDetails
import org.loculus.backend.controller.ConflictException
Expand All @@ -29,10 +31,18 @@ class GroupManagementDatabaseService(
return GroupDetails(groupName, users)
}

fun createNewGroup(groupName: String, username: String) {
fun createNewGroup(group: Group, username: String) {
try {
GroupsTable.insert {
it[groupNameColumn] = groupName
it[groupNameColumn] = group.groupName
it[institutionColumn] = group.institution
it[addressLine1] = group.address.line1
it[addressLine2] = group.address.line2
it[addressPostalCode] = group.address.postalCode
it[addressState] = group.address.state
it[addressCity] = group.address.city
it[addressCountry] = group.address.country
it[contactEmailColumn] = group.contactEmail
}
} catch (e: ExposedSQLException) {
if (e.sqlState == UNIQUE_CONSTRAINT_VIOLATION_SQL_STATE) {
Expand All @@ -45,14 +55,34 @@ class GroupManagementDatabaseService(

UserGroupsTable.insert {
it[userNameColumn] = username
it[groupNameColumn] = groupName
it[groupNameColumn] = group.groupName
}
}

fun getGroupsOfUser(username: String): List<Group> {
return UserGroupsTable
return UserGroupsTable.join(
GroupsTable,
JoinType.LEFT,
additionalConstraint = {
(UserGroupsTable.groupNameColumn eq GroupsTable.groupNameColumn)
},
)
.select { UserGroupsTable.userNameColumn eq username }
.map { Group(it[UserGroupsTable.groupNameColumn]) }
.map {
Group(
groupName = it[GroupsTable.groupNameColumn],
institution = it[GroupsTable.institutionColumn],
address = Address(
line1 = it[GroupsTable.addressLine1],
line2 = it[GroupsTable.addressLine2],
postalCode = it[GroupsTable.addressPostalCode],
city = it[GroupsTable.addressCity],
state = it[GroupsTable.addressState],
country = it[GroupsTable.addressCountry],
),
contactEmail = it[GroupsTable.contactEmailColumn],
)
}
}

fun addUserToGroup(groupMember: String, groupName: String, usernameToAdd: String) {
Expand Down Expand Up @@ -87,6 +117,20 @@ class GroupManagementDatabaseService(
fun getAllGroups(): List<Group> {
return GroupsTable
.selectAll()
.map { Group(it[GroupsTable.groupNameColumn]) }
.map {
Group(
groupName = it[GroupsTable.groupNameColumn],
institution = it[GroupsTable.institutionColumn],
address = Address(
line1 = it[GroupsTable.addressLine1],
line2 = it[GroupsTable.addressLine2],
postalCode = it[GroupsTable.addressPostalCode],
city = it[GroupsTable.addressCity],
state = it[GroupsTable.addressState],
country = it[GroupsTable.addressCountry],
),
contactEmail = it[GroupsTable.contactEmailColumn],
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ const val USER_GROUPS_TABLE_NAME = "user_groups_table"

object GroupsTable : Table("groups_table") {
val groupNameColumn = text("group_name")
val institutionColumn = text("institution")
val addressLine1 = text("address_line_1")
val addressLine2 = text("address_line_2")
val addressPostalCode = text("address_postal_code")
val addressCity = text("address_city")
val addressState = text("address_state")
val addressCountry = text("address_country")
val contactEmailColumn = text("contact_email")

override val primaryKey = PrimaryKey(groupNameColumn)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ class SequenceEntriesDataTable(

fun groupIsOneOf(groups: List<Group>) = groupNameColumn inList groups.map { it.groupName }

fun groupNameIsOneOf(groupNames: List<String>) = groupNameColumn inList groupNames

private val warningWhenNoOrganismWhenSerializing = "Organism is null when de-serializing data. " +
"This should not happen. " +
"Please check your code. " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import org.loculus.backend.api.DataUseTerms
import org.loculus.backend.api.DataUseTermsType
import org.loculus.backend.api.DeleteSequenceScope
import org.loculus.backend.api.GetSequenceResponse
import org.loculus.backend.api.Group
import org.loculus.backend.api.Organism
import org.loculus.backend.api.ProcessedData
import org.loculus.backend.api.SequenceEntryStatus
Expand Down Expand Up @@ -418,10 +417,10 @@ class SubmissionDatabaseService(
}

val validatedGroupNames = if (groupsFilter == null) {
groupManagementDatabaseService.getGroupsOfUser(username)
groupManagementDatabaseService.getGroupsOfUser(username).map { it.groupName }
} else {
groupManagementPreconditionValidator.validateUserInExistingGroups(groupsFilter, username)
groupsFilter.map { Group(it) }
groupsFilter
}

val listOfStatuses = statusesFilter ?: Status.entries
Expand Down Expand Up @@ -451,7 +450,7 @@ class SubmissionDatabaseService(
.select(
where = {
table.statusIsOneOf(listOfStatuses) and
table.groupIsOneOf(validatedGroupNames)
table.groupNameIsOneOf(validatedGroupNames)
},
)
.orderBy(table.accessionColumn)
Expand Down
10 changes: 9 additions & 1 deletion backend/src/main/resources/db/migration/V1__init.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
create sequence accession_sequence start with 1;

create table groups_table (
group_name varchar(255) primary key
group_name varchar(255) primary key,
institution varchar(255) not null,
address_line_1 varchar(255) not null,
address_line_2 varchar(255),
address_postal_code varchar(255) not null,
address_city varchar(255) not null,
address_state varchar(255),
address_country varchar(255) not null,
contact_email varchar(255) not null
);

create table sequence_entries (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import org.junit.platform.engine.support.descriptor.ClassSource
import org.junit.platform.engine.support.descriptor.MethodSource
import org.junit.platform.launcher.TestExecutionListener
import org.junit.platform.launcher.TestPlan
import org.loculus.backend.api.Address
import org.loculus.backend.api.Group
import org.loculus.backend.controller.datasetcitations.DatasetCitationsControllerClient
import org.loculus.backend.controller.datauseterms.DataUseTermsControllerClient
import org.loculus.backend.controller.groupmanagement.GroupManagementControllerClient
Expand Down Expand Up @@ -53,8 +55,34 @@ private const val SPRING_DATASOURCE_PASSWORD = "spring.datasource.password"

const val ACCESSION_SEQUENCE_NAME = "accession_sequence"
const val DEFAULT_GROUP_NAME = "testGroup"
val DEFAULT_GROUP = Group(
groupName = DEFAULT_GROUP_NAME,
institution = "testInstitution",
address = Address(
line1 = "testAddressLine1",
line2 = "testAddressLine2",
postalCode = "testPostalCode",
city = "testCity",
state = "testState",
country = "testCountry",
),
contactEmail = "testEmail",
)
const val ALTERNATIVE_DEFAULT_GROUP_NAME = "testGroup2"
const val ALTERNATIVE_DEFAULT_USER_NAME = "testUser2"
val ALTERNATIVE_DEFAULT_GROUP = Group(
groupName = ALTERNATIVE_DEFAULT_GROUP_NAME,
institution = "alternativeTestInstitution",
address = Address(
line1 = "alternativeTestAddressLine1",
line2 = "alternativeTestAddressLine2",
postalCode = "alternativeTestPostalCode",
city = "alternativeTestCity",
state = "alternativeTestState",
country = "alternativeTestCountry",
),
contactEmail = "alternativeTestEmail",
)

private val log = KotlinLogging.logger { }

Expand Down Expand Up @@ -115,7 +143,7 @@ class EndpointTestExtension : BeforeEachCallback, TestExecutionListener {
postgres.databaseName,
"-c",
clearDatabaseStatement() +
createGroupsStatement(listOf(DEFAULT_GROUP_NAME, ALTERNATIVE_DEFAULT_GROUP_NAME)) +
createGroupsStatement(listOf(DEFAULT_GROUP, ALTERNATIVE_DEFAULT_GROUP)) +
addUsersToGroupStatement(
DEFAULT_GROUP_NAME,
listOf(DEFAULT_USER_NAME, ALTERNATIVE_DEFAULT_USER_NAME),
Expand All @@ -133,9 +161,19 @@ class EndpointTestExtension : BeforeEachCallback, TestExecutionListener {
}
}

private fun createGroupsStatement(groupNames: List<String>): String {
private fun createGroupsStatement(groupNames: List<Group>): String {
return groupNames.joinToString("\n") {
"insert into $GROUPS_TABLE_NAME (group_name) values ('$it');"
"insert into $GROUPS_TABLE_NAME (group_name, institution, address_line_1, " +
"address_line_2, address_city, address_postal_code, address_state, address_country, contact_email) values" +
"('${it.groupName}','" +
"${it.institution}','" +
"${it.address.line1}','" +
"${it.address.line2}','" +
"${it.address.city}','" +
"${it.address.postalCode}','" +
"${it.address.state}','" +
"${it.address.country}','" +
"${it.contactEmail}');"
} + "\n"
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.loculus.backend.controller.groupmanagement

import com.fasterxml.jackson.databind.ObjectMapper
import org.loculus.backend.api.Group
import org.loculus.backend.controller.jwtForDefaultUser
import org.loculus.backend.controller.withAuth
import org.springframework.http.MediaType
Expand All @@ -10,20 +12,25 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put

class GroupManagementControllerClient(private val mockMvc: MockMvc) {
fun createNewGroup(groupName: String = NEW_GROUP, jwt: String? = jwtForDefaultUser): ResultActions =
mockMvc.perform(
post("/groups")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content("""{"groupName":"$groupName"}""")
.withAuth(jwt),
)
class GroupManagementControllerClient(private val mockMvc: MockMvc, private val objectMapper: ObjectMapper) {
fun createNewGroup(group: Group = NEW_GROUP, jwt: String? = jwtForDefaultUser): ResultActions = mockMvc.perform(
post("/groups")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(group))
.withAuth(jwt),
)

fun getDetailsOfGroup(groupName: String = NEW_GROUP, jwt: String? = jwtForDefaultUser): ResultActions =
mockMvc.perform(
get("/groups/$groupName")
.withAuth(jwt),
)
fun createNewGroupWithBody(body: String, jwt: String? = jwtForDefaultUser): ResultActions = mockMvc.perform(
post("/groups")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(body)
.withAuth(jwt),
)

fun getDetailsOfGroup(group: Group = NEW_GROUP, jwt: String? = jwtForDefaultUser): ResultActions = mockMvc.perform(
get("/groups/${group.groupName}")
.withAuth(jwt),
)

fun getGroupsOfUser(jwt: String? = jwtForDefaultUser): ResultActions = mockMvc.perform(
get("/groups")
Expand All @@ -32,19 +39,19 @@ class GroupManagementControllerClient(private val mockMvc: MockMvc) {

fun addUserToGroup(
usernameToAdd: String,
groupName: String = NEW_GROUP,
group: Group = NEW_GROUP,
jwt: String? = jwtForDefaultUser,
): ResultActions = mockMvc.perform(
put("/groups/$groupName/users/$usernameToAdd")
put("/groups/${group.groupName}/users/$usernameToAdd")
.withAuth(jwt),
)

fun removeUserFromGroup(
userToRemove: String,
groupName: String = NEW_GROUP,
group: Group = NEW_GROUP,
jwt: String? = jwtForDefaultUser,
): ResultActions = mockMvc.perform(
delete("/groups/$groupName/users/$userToRemove")
delete("/groups/${group.groupName}/users/$userToRemove")
.withAuth(jwt),
)
}
Loading

0 comments on commit a8ecdb9

Please sign in to comment.