Skip to content

Commit 2f9c807

Browse files
committed
feat: move user flags to table
1 parent 8db9f14 commit 2f9c807

9 files changed

Lines changed: 338 additions & 16 deletions

File tree

apps/rpc/src/modules/event/attendance-repository.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export function getAttendanceRepository(): AttendanceRepository {
102102
user: {
103103
include: {
104104
memberships: true,
105+
flags: true,
105106
},
106107
},
107108
},
@@ -129,6 +130,7 @@ export function getAttendanceRepository(): AttendanceRepository {
129130
user: {
130131
include: {
131132
memberships: true,
133+
flags: true,
132134
},
133135
},
134136
},
@@ -165,6 +167,7 @@ export function getAttendanceRepository(): AttendanceRepository {
165167
user: {
166168
include: {
167169
memberships: true,
170+
flags: true,
168171
},
169172
},
170173
},
@@ -208,6 +211,7 @@ export function getAttendanceRepository(): AttendanceRepository {
208211
user: {
209212
include: {
210213
memberships: true,
214+
flags: true,
211215
},
212216
},
213217
},
@@ -237,6 +241,7 @@ export function getAttendanceRepository(): AttendanceRepository {
237241
user: {
238242
include: {
239243
memberships: true,
244+
flags: true,
240245
},
241246
},
242247
},
@@ -266,6 +271,7 @@ export function getAttendanceRepository(): AttendanceRepository {
266271
user: {
267272
include: {
268273
memberships: true,
274+
flags: true,
269275
},
270276
},
271277
},
@@ -295,6 +301,7 @@ export function getAttendanceRepository(): AttendanceRepository {
295301
user: {
296302
include: {
297303
memberships: true,
304+
flags: true,
298305
},
299306
},
300307
},
@@ -375,6 +382,7 @@ export function getAttendanceRepository(): AttendanceRepository {
375382
user: {
376383
include: {
377384
memberships: true,
385+
flags: true,
378386
},
379387
},
380388
},
@@ -400,6 +408,7 @@ export function getAttendanceRepository(): AttendanceRepository {
400408
user: {
401409
include: {
402410
memberships: true,
411+
flags: true,
403412
},
404413
},
405414
},
@@ -418,6 +427,7 @@ export function getAttendanceRepository(): AttendanceRepository {
418427
user: {
419428
include: {
420429
memberships: true,
430+
flags: true,
421431
},
422432
},
423433
},
@@ -436,6 +446,7 @@ export function getAttendanceRepository(): AttendanceRepository {
436446
user: {
437447
include: {
438448
memberships: true,
449+
flags: true,
439450
},
440451
},
441452
},

apps/rpc/src/modules/user/__test__/user-merging.spec.ts

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { randomUUID } from "node:crypto"
22
import type { DBHandle } from "@dotkomonline/db"
3-
import { GenderSchema, type Membership, type User } from "@dotkomonline/types"
3+
import { GenderSchema, type Membership, type User, type UserFlag } from "@dotkomonline/types"
44
import { beforeEach, describe, expect, it, type vi } from "vitest"
55
import { mockDeep } from "vitest-mock-extended"
66
import type { GroupRepository } from "../../group/group-repository"
@@ -42,6 +42,18 @@ function makeMembership(userId: string, overrides: Partial<Membership> = {}): Me
4242
}
4343
}
4444

45+
function makeUserFlag(overrides: Partial<UserFlag> = {}): UserFlag {
46+
return {
47+
id: randomUUID(),
48+
name: randomUUID(),
49+
createdAt: new Date(),
50+
updatedAt: new Date(),
51+
description: null,
52+
imageUrl: null,
53+
...overrides,
54+
}
55+
}
56+
4557
describe("mergeUsers", () => {
4658
let handle: ReturnType<typeof mockDeep<DBHandle>>
4759
let groupRepository: ReturnType<typeof mockDeep<GroupRepository>>
@@ -113,15 +125,60 @@ describe("mergeUsers", () => {
113125
})
114126
})
115127

116-
describe("flags custom merger", () => {
117-
it("concatenates and deduplicates flags from both users", async () => {
118-
const survivor = makeUser({ flags: ["a", "b"] })
119-
const consumed = makeUser({ flags: ["b", "c"] })
128+
describe("flags custom relation merger", () => {
129+
it("connects consumed flags that are not already attached to survivor", async () => {
130+
const sharedFlag = makeUserFlag({ name: "shared" })
131+
const survivorFlag = makeUserFlag({ name: "survivor" })
132+
const consumedFlag = makeUserFlag({ name: "consumed" })
133+
const survivor = makeUser({ flags: [survivorFlag, sharedFlag] })
134+
const consumed = makeUser({ flags: [sharedFlag, consumedFlag] })
120135

121136
await mergeUsers(handle, groupRepository, survivor, consumed)
122137

123-
const [[updateArgs]] = (handle.user.update as ReturnType<typeof vi.fn>).mock.calls
124-
expect(new Set(updateArgs.data.flags)).toEqual(new Set(["a", "b", "c"]))
138+
expect(handle.user.update).toHaveBeenCalledWith({
139+
where: {
140+
id: survivor.id,
141+
},
142+
data: {
143+
flags: {
144+
connect: [{ id: consumedFlag.id }],
145+
},
146+
},
147+
})
148+
})
149+
150+
it("does not connect flags when consumed only has duplicates", async () => {
151+
const sharedFlag = makeUserFlag({ name: "shared" })
152+
const survivor = makeUser({ flags: [sharedFlag] })
153+
const consumed = makeUser({ flags: [sharedFlag] })
154+
155+
await mergeUsers(handle, groupRepository, survivor, consumed)
156+
157+
const updateCalls = (handle.user.update as ReturnType<typeof vi.fn>).mock.calls
158+
expect(updateCalls).not.toEqual(
159+
expect.arrayContaining([
160+
[
161+
expect.objectContaining({
162+
data: expect.objectContaining({
163+
flags: expect.objectContaining({
164+
connect: expect.any(Array),
165+
}),
166+
}),
167+
}),
168+
],
169+
])
170+
)
171+
})
172+
173+
it("does not write flags as a scalar array", async () => {
174+
const survivor = makeUser({ flags: [makeUserFlag({ name: "survivor" })] })
175+
const consumed = makeUser({ flags: [makeUserFlag({ name: "consumed" })] })
176+
177+
await mergeUsers(handle, groupRepository, survivor, consumed)
178+
179+
for (const [updateArgs] of (handle.user.update as ReturnType<typeof vi.fn>).mock.calls) {
180+
expect(Array.isArray(updateArgs.data.flags)).toBe(false)
181+
}
125182
})
126183
})
127184

apps/rpc/src/modules/user/user-merging.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { simplifyGroupMemberships } from "../group/group-service"
2828
// 1. Add a { fkField, relationName, deleteOrphan } entry.
2929
//
3030
// CUSTOM_SCALAR_MERGERS
31-
// - Scalars that need special logic (e.g. username, flags).
31+
// - Scalars that need special logic (e.g. username).
3232
// - What you need to do:
3333
// 1. Add the field to the object.
3434
// 2. Add a function value.
@@ -42,7 +42,7 @@ import { simplifyGroupMemberships } from "../group/group-service"
4242
// User relation name as the key.
4343
//
4444
// CUSTOM_RELATION_MERGERS
45-
// - Relations needing deduplication logic (memberships, group memberships).
45+
// - Relations needing deduplication logic (flags, memberships, group memberships).
4646
// - What you need to do:
4747
// 1. Add the field to the object.
4848
// 2. Add a handler function.
@@ -120,9 +120,6 @@ const CUSTOM_SCALAR_MERGERS = {
120120
// it's a custom username).
121121
username: (survivor: User, consumed: User): string =>
122122
isUuid(survivor.username) && !isUuid(consumed.username) ? consumed.username : survivor.username,
123-
124-
// Concatenate and deduplicate
125-
flags: (survivor: User, consumed: User): string[] => [...new Set([...survivor.flags, ...consumed.flags])],
126123
} satisfies Partial<Record<AllUserKeys, (survivor: User, consumed: User) => unknown>>
127124

128125
/**
@@ -192,6 +189,26 @@ const REASSIGN_RELATION_HANDLERS = {
192189
* Relations with custom merge logic (deduplication).
193190
*/
194191
const CUSTOM_RELATION_MERGERS = {
192+
flags: async (handle: DBHandle, _groupRepository: GroupRepository, survivor: User, consumed: User) => {
193+
const survivorFlagIds = new Set(survivor.flags.map((flag) => flag.id))
194+
const consumedFlagsToConnect = consumed.flags
195+
.filter((flag) => !survivorFlagIds.has(flag.id))
196+
.map((flag) => ({ id: flag.id }))
197+
198+
if (consumedFlagsToConnect.length > 0) {
199+
await handle.user.update({
200+
where: {
201+
id: survivor.id,
202+
},
203+
data: {
204+
flags: {
205+
connect: consumedFlagsToConnect,
206+
},
207+
},
208+
})
209+
}
210+
},
211+
195212
memberships: async (handle: DBHandle, _groupRepository: GroupRepository, survivor: User, consumed: User) => {
196213
const survivorMembershipKeys = new Set(survivor.memberships.map(buildMembershipDeduplicationKey))
197214

0 commit comments

Comments
 (0)