Skip to content

Commit

Permalink
p2-inc#287 Add attributes to an OrganizationMembership
Browse files Browse the repository at this point in the history
  • Loading branch information
rtufisi committed Dec 31, 2024
1 parent 44e5432 commit c5dfeb2
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.phasetwo.service.model;

public interface OrganizationMembershipModel extends WithAttributes {

String getId();

String getUserId();

OrganizationModel getOrganization();
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ public interface OrganizationModel extends WithAttributes {

Stream<UserModel> searchForMembersStream(String search, Integer firstResult, Integer maxResults);

Stream<OrganizationMembershipModel> getOrganizationMembersStream();

Stream<OrganizationMembershipModel> searchForOrganizationMembersStream(String search, Integer firstResult, Integer maxResults);

boolean hasMembership(UserModel user);

void grantMembership(UserModel user);
Expand Down Expand Up @@ -74,6 +78,8 @@ default OrganizationRoleModel getRoleByName(String name) {
.orElse(null);
}

OrganizationMembershipModel getMembershipDetails(UserModel user);

void removeRole(String name);

OrganizationRoleModel addRole(String name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import com.google.common.base.Strings;
import io.phasetwo.service.model.DomainModel;
import io.phasetwo.service.model.InvitationModel;
import io.phasetwo.service.model.OrganizationMembershipModel;
import io.phasetwo.service.model.OrganizationModel;
import io.phasetwo.service.model.OrganizationRoleModel;
import io.phasetwo.service.model.jpa.entity.DomainEntity;
import io.phasetwo.service.model.jpa.entity.ExtOrganizationEntity;
import io.phasetwo.service.model.jpa.entity.InvitationEntity;
import io.phasetwo.service.model.jpa.entity.OrganizationAttributeEntity;
import io.phasetwo.service.model.jpa.entity.OrganizationMemberEntity;
import io.phasetwo.service.model.jpa.entity.OrganizationMembershipAttributeEntity;
import io.phasetwo.service.model.jpa.entity.OrganizationRoleEntity;
import io.phasetwo.service.model.jpa.entity.UserOrganizationRoleMappingEntity;
import io.phasetwo.service.util.IdentityProviders;
Expand Down Expand Up @@ -188,6 +190,20 @@ public Stream<UserModel> searchForMembersStream(
.limit(maxResults);
}

@Override
public Stream<OrganizationMembershipModel> getOrganizationMembersStream() {
TypedQuery<OrganizationMemberEntity> query = em.createNamedQuery("getOrganizationMembers", OrganizationMemberEntity.class);
query.setParameter("organization", org);

return query.getResultStream()
.map(organizationMemberEntity -> new OrganizationMembershipAdapter(session, realm, em, organizationMemberEntity));
}

@Override
public Stream<OrganizationMembershipModel> searchForOrganizationMembersStream(String search, Integer firstResult, Integer maxResults) {
return Stream.empty();
}

@Override
public Long getMembersCount() {
TypedQuery<Long> query = em.createNamedQuery("getOrganizationMembersCount", Long.class);
Expand Down Expand Up @@ -301,6 +317,14 @@ public Stream<OrganizationRoleModel> getRolesByUserStream(UserModel user) {
}
}

@Override
public OrganizationMembershipModel getMembershipDetails(UserModel user) {
TypedQuery<OrganizationMembershipModel> query = em.createNamedQuery("getOrganizationMemberByUserId", OrganizationMembershipModel.class);
query.setParameter("organization", org);
query.setParameter("id", user.getId());
return query.getSingleResult();
}

@Override
public void removeRole(String name) {
org.getRoles().removeIf(r -> r.getName().equals(name));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package io.phasetwo.service.model.jpa;

import io.phasetwo.service.model.OrganizationMembershipModel;
import io.phasetwo.service.model.OrganizationModel;
import io.phasetwo.service.model.OrganizationProvider;
import io.phasetwo.service.model.jpa.entity.OrganizationMemberEntity;
import io.phasetwo.service.model.jpa.entity.OrganizationMembershipAttributeEntity;
import jakarta.persistence.EntityManager;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.jpa.JpaModel;
import org.keycloak.models.utils.KeycloakModelUtils;

import java.util.List;
import java.util.Map;

public class OrganizationMembershipAdapter implements OrganizationMembershipModel, JpaModel<OrganizationMemberEntity> {

protected final KeycloakSession session;
protected final OrganizationMemberEntity organizationMemberEntity;
protected final EntityManager em;
protected final RealmModel realm;

public OrganizationMembershipAdapter(
KeycloakSession session, RealmModel realm, EntityManager em, OrganizationMemberEntity organizationMemberEntity) {
this.session = session;
this.em = em;
this.organizationMemberEntity = organizationMemberEntity;
this.realm = realm;
}

@Override
public OrganizationMemberEntity getEntity() {
return organizationMemberEntity;
}

@Override
public String getId() {
return organizationMemberEntity.getId();
}

@Override
public String getUserId() {
return organizationMemberEntity.getUserId();
}

@Override
public OrganizationModel getOrganization() {
return session
.getProvider(OrganizationProvider.class)
.getOrganizationById(realm, organizationMemberEntity.getOrganization().getId());
}

@Override
public Map<String, List<String>> getAttributes() {
MultivaluedHashMap<String, String> result = new MultivaluedHashMap<>();
for (OrganizationMembershipAttributeEntity attr : organizationMemberEntity.getAttributes()) {
result.add(attr.getName(), attr.getValue());
}
return result;
}

@Override
public void removeAttribute(String name) {
organizationMemberEntity.getAttributes().removeIf(attribute -> attribute.getName().equals(name));
}

@Override
public void removeAttributes() {
organizationMemberEntity.getAttributes().clear();
}

@Override
public void setAttribute(String name, List<String> values) {
removeAttribute(name);
for (String value : values) {
OrganizationMembershipAttributeEntity a = new OrganizationMembershipAttributeEntity();
a.setId(KeycloakModelUtils.generateId());
a.setName(name);
a.setValue(value);
a.setOrganizationMember(organizationMemberEntity);
em.persist(a);
organizationMemberEntity.getAttributes().add(a);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
Expand All @@ -10,11 +11,15 @@
import jakarta.persistence.ManyToOne;
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.OneToMany;
import jakarta.persistence.PrePersist;
import jakarta.persistence.Table;
import jakarta.persistence.Temporal;
import jakarta.persistence.TemporalType;
import jakarta.persistence.UniqueConstraint;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Objects;

Expand Down Expand Up @@ -60,6 +65,9 @@ public class OrganizationMemberEntity {
@Column(name = "CREATED_AT")
protected Date createdAt;

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "organizationMember")
protected Collection<OrganizationMembershipAttributeEntity> attributes = new ArrayList<>();

@PrePersist
protected void onCreate() {
if (createdAt == null) createdAt = new Date();
Expand Down Expand Up @@ -97,6 +105,14 @@ public void setCreatedAt(Date at) {
createdAt = at;
}

public Collection<OrganizationMembershipAttributeEntity> getAttributes() {
return attributes;
}

public void setAttributes(Collection<OrganizationMembershipAttributeEntity> attributes) {
this.attributes = attributes;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package io.phasetwo.service.model.jpa.entity;

import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import org.hibernate.annotations.Nationalized;

import java.util.Objects;

@Table(
name = "ORGANIZATION_MEMBER_ATTRIBUTE",
uniqueConstraints = {@UniqueConstraint(columnNames = {"ORGANIZATION_MEMBER_ID", "NAME"})})
@Entity
public class OrganizationMembershipAttributeEntity {

@Id
@Column(name = "ID", length = 36)
@Access(
AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This
// avoids an extra SQL
protected String id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ORGANIZATION_MEMBER_ID")
protected OrganizationMemberEntity organizationMember;

@Column(name = "NAME")
protected String name;

@Nationalized
@Column(name = "VALUE")
protected String value;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public OrganizationMemberEntity getOrganizationMember() {
return organizationMember;
}

public void setOrganizationMember(OrganizationMemberEntity organizationMember) {
this.organizationMember = organizationMember;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
OrganizationMembershipAttributeEntity that = (OrganizationMembershipAttributeEntity) o;
return Objects.equals(id, that.id) && Objects.equals(organizationMember, that.organizationMember);
}

@Override
public int hashCode() {
return Objects.hash(id, organizationMember);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ public Stream<UserRepresentation> getMembers(
maxResults = maxResults != null ? maxResults : Constants.DEFAULT_MAX_RESULTS;
return organization
.searchForMembersStream(searchQuery, firstResult, maxResults)
.map(m -> toRepresentation(session, realm, m));
.map(m ->{
var membership = organization.getOrganizationMembersStream();
return toRepresentation(session, realm, m);
});
}

@GET
Expand Down
17 changes: 17 additions & 0 deletions src/main/resources/META-INF/jpa-changelog-phasetwo-20241228.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet author="rtuf" id="organization-member-attr-1">
<createTable tableName="ORGANIZATION_MEMBER_ATTRIBUTE">
<column name="ID" type="VARCHAR(36)">
<constraints primaryKey="true" primaryKeyName="ORGANIZATION_MEMBER_ATTRIBUTEPK" nullable="false"/>
</column>
<column name="NAME" type="VARCHAR(255)"/>
<column name="VALUE" type="NVARCHAR(255)"/>
<column name="ORGANIZATION_MEMBER_ID" type="VARCHAR(36)"/>
</createTable>
<addForeignKeyConstraint baseColumnNames="ORGANIZATION_MEMBER_ID" baseTableName="ORGANIZATION_MEMBER_ATTRIBUTE" constraintName="FK_RA9TUF8D9JRH6MMR9PY7UFL7NBJP0R" deferrable="false" initiallyDeferred="false" referencedColumnNames="ID" referencedTableName="ORGANIZATION_MEMBER" onDelete="CASCADE"/>
<createIndex indexName="IDX_ORGANIZATION_MEMBER_ATTRIBUTE" tableName="ORGANIZATION_MEMBER_ATTRIBUTE">
<column name="ORGANIZATION_MEMBER_ID" type="VARCHAR(36)"/>
</createIndex>
</changeSet>
</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@
<include file="META-INF/jpa-changelog-phasetwo-20240308.xml"/>
<include file="META-INF/jpa-changelog-phasetwo-20240610.xml"/>
<include file="META-INF/jpa-changelog-phasetwo-20240611.xml"/>
<include file="META-INF/jpa-changelog-phasetwo-20241228.xml"/>

</databaseChangeLog>

0 comments on commit c5dfeb2

Please sign in to comment.