Skip to content

Commit

Permalink
Merge pull request #325 from privacy-scaling-explorations/refactoring…
Browse files Browse the repository at this point in the history
…/entities

Add Many-to-Many relationship between Group and Member
  • Loading branch information
vplasencia authored Nov 9, 2023
2 parents 2e36b6d + 20b1d6b commit 0343163
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 57 deletions.
8 changes: 7 additions & 1 deletion apps/api/src/app/credentials/credentials.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,15 @@ describe("CredentialsService", () => {
})

it("Should add a member to a credential group", async () => {
const _stateId = await credentialsService.setOAuthState({
groupId,
memberId: "125",
providerName: "twitter"
})

const clientRedirectUri = await credentialsService.addMember(
"code",
stateId
_stateId
)

expect(clientRedirectUri).toBeUndefined()
Expand Down
15 changes: 13 additions & 2 deletions apps/api/src/app/groups/entities/group.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
CreateDateColumn,
Entity,
Index,
JoinTable,
ManyToMany,
OneToMany,
PrimaryColumn,
UpdateDateColumn
Expand Down Expand Up @@ -31,8 +33,17 @@ export class Group {
@Column({ name: "fingerprint_duration" })
fingerprintDuration: number

@OneToMany(() => Member, (member) => member.group, {
cascade: ["insert"]
@ManyToMany(() => Member)
@JoinTable({
name: "memberships",
joinColumn: {
name: "group",
referencedColumnName: "id"
},
inverseJoinColumn: {
name: "member",
referencedColumnName: "id"
}
})
members: Member[]

Expand Down
27 changes: 3 additions & 24 deletions apps/api/src/app/groups/entities/member.entity.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,11 @@
import {
CreateDateColumn,
Entity,
ManyToOne,
Column,
Index,
Unique,
JoinColumn
} from "typeorm"

import { Group } from "./group.entity"
import { CreateDateColumn, Entity, Index, PrimaryColumn } from "typeorm"

@Entity("members")
@Unique(["id", "group"])
@Index(["id", "group"])
export class Member {
@Column({ primary: true, unique: false })
@PrimaryColumn()
@Index({ unique: true })
id: string

// In reality the relation group -> members is many-to-many.
// i.e we allow same member id to be part of many groups.
// But since this property is not used in any feature at the moment,
// it is treated as many-to-one in the code for simplicity.
@ManyToOne(() => Group, (group) => group.members, {
onDelete: "CASCADE"
})
@JoinColumn({ name: "group_id" })
group: Group

@CreateDateColumn({ name: "created_at" })
createdAt: Date
}
125 changes: 95 additions & 30 deletions apps/api/src/app/groups/groups.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,26 @@ export class GroupsService {
async addMember(groupId: string, memberId: string): Promise<Group> {
const group = await this.getGroup(groupId)

const member = new Member()
member.group = group
member.id = memberId
// Check if the member is already a group member.
const isMemberInGroup = group.members.some((m) => m.id === memberId)

if (isMemberInGroup) {
throw new Error(
`Member '${memberId}' is already in the group '${group.name}'`
)
}

// Check if the member is already a member of another group.
let member = await this.memberRepository.findOne({
where: { id: memberId }
})

if (!member) {
member = new Member()
member.id = memberId

await this.memberRepository.save(member)
}

group.members.push(member)

Expand Down Expand Up @@ -371,20 +388,37 @@ export class GroupsService {
async addMembers(groupId: string, memberIds: string[]): Promise<Group> {
const group = await this.getGroup(groupId)

// Prepare new members and attach them to the group
const newMembers = memberIds.map((memberId) => {
const member = new Member()
member.group = group
member.id = memberId
return member
})
await Promise.all(
memberIds.map(async (memberId) => {
const member = group.members.find((m) => m.id === memberId)

if (!member) {
let newMember: Member

// Check if the member is already a member of another group.
const anotherGroupMember =
await this.memberRepository.findOne({
where: { id: memberId }
})

if (!anotherGroupMember) {
newMember = new Member()
newMember.id = memberId

await this.memberRepository.save(newMember)
} else {
newMember = anotherGroupMember
}

group.members.push(newMember)
}
})
)

group.members.push(...newMembers)
await this.groupRepository.save(group)

const cachedGroup = this.cachedGroups.get(groupId)

// Add all the members to the cached group at once
memberIds.forEach((memberId) => {
cachedGroup.addMember(memberId)
Logger.log(
Expand Down Expand Up @@ -440,20 +474,35 @@ export class GroupsService {
`Member '${memberId}' is not a member of group '${groupId}'`
)
}
const member = group.members.find((m) => m.id === memberId)
const member: Member = group.members.find(
(m: Member) => m.id === memberId
)

if (member) membersToRemove.push(member)
}

await this.memberRepository.remove(membersToRemove)

const cachedGroup = this.cachedGroups.get(groupId)

for (const memberId of memberIds) {
cachedGroup.removeMember(cachedGroup.indexOf(BigInt(memberId)))
Logger.log(
`GroupsService: member '${memberId}' has been removed from the group '${group.name}'`
)
}
await Promise.all(
memberIds.map(async (memberId) => {
// Check if the member is a group member.
const memberIndex = group.members.findIndex(
(m) => m.id === memberId
)

if (memberIndex !== -1) {
// Remove the member from the group.
group.members.splice(memberIndex, 1)

await this.groupRepository.save(group)
}

cachedGroup.removeMember(cachedGroup.indexOf(BigInt(memberId)))
Logger.log(
`GroupsService: member '${memberId}' has been removed from the group '${group.name}'`
)
})
)

this._updateContractGroup(cachedGroup)

Expand Down Expand Up @@ -501,21 +550,37 @@ export class GroupsService {
`Member '${memberId}' is not a member of group '${groupId}'`
)
}
const member = group.members.find((m) => m.id === memberId)

// Check if the member is a group member.
const member: Member = group.members.find(
(m: Member) => m.id === memberId
)

if (member) membersToRemove.push(member)
}

await this.memberRepository.remove(membersToRemove)

const cachedGroup = this.cachedGroups.get(groupId)

for (const memberId of memberIds) {
cachedGroup.removeMember(cachedGroup.indexOf(BigInt(memberId)))
await Promise.all(
memberIds.map(async (memberId) => {
// Check if the member is a group member.
const memberIndex = group.members.findIndex(
(m) => m.id === memberId
)

Logger.log(
`GroupsService: member '${memberId}' has been removed from the group '${group.name}'`
)
}
if (memberIndex !== -1) {
// Remove the member from the group.
group.members.splice(memberIndex, 1)

await this.groupRepository.save(group)
}

cachedGroup.removeMember(cachedGroup.indexOf(BigInt(memberId)))
Logger.log(
`GroupsService: member '${memberId}' has been removed from the group '${group.name}'`
)
})
)

this._updateContractGroup(cachedGroup)

Expand Down

0 comments on commit 0343163

Please sign in to comment.