Skip to content

Commit 7b9c68a

Browse files
committed
feat: move user flags to table
1 parent 0bc2a66 commit 7b9c68a

File tree

6 files changed

+235
-4
lines changed

6 files changed

+235
-4
lines changed

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

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import {
99
type UserProfileSlug,
1010
UserSchema,
1111
type UserWrite,
12+
type UserFlagWithUsers,
13+
UserFlagWithUsersSchema,
14+
type UserFlagWrite,
1215
} from "@dotkomonline/types"
1316
import invariant from "tiny-invariant"
1417
import { parseOrReport } from "../../invariant"
@@ -33,6 +36,14 @@ export interface UserRepository {
3336
createMembership(handle: DBHandle, userId: UserId, membership: MembershipWrite): Promise<User>
3437
updateMembership(handle: DBHandle, membershipId: MembershipId, membership: Partial<MembershipWrite>): Promise<User>
3538
deleteMembership(handle: DBHandle, membershipId: MembershipId): Promise<User>
39+
40+
createFlag(handle: DBHandle, data: UserFlagWrite): Promise<void>
41+
updateFlag(handle: DBHandle, name: string, data: Partial<UserFlagWrite>): Promise<void>
42+
deleteFlag(handle: DBHandle, name: string): Promise<void>
43+
findFlagByName(handle: DBHandle, name: string): Promise<UserFlagWithUsers | null>
44+
findFlagsByUserId(handle: DBHandle, userId: UserId): Promise<UserFlagWithUsers[]>
45+
assignFlagToUser(handle: DBHandle, userId: UserId, flagName: string): Promise<void>
46+
removeFlagFromUser(handle: DBHandle, userId: UserId, flagName: string): Promise<void>
3647
}
3748

3849
export function getUserRepository(): UserRepository {
@@ -51,6 +62,7 @@ export function getUserRepository(): UserRepository {
5162
},
5263
include: {
5364
memberships: true,
65+
flags: true,
5466
},
5567
})
5668

@@ -80,6 +92,7 @@ export function getUserRepository(): UserRepository {
8092
},
8193
include: {
8294
memberships: true,
95+
flags: true,
8396
},
8497
})
8598

@@ -93,6 +106,7 @@ export function getUserRepository(): UserRepository {
93106
},
94107
include: {
95108
memberships: true,
109+
flags: true,
96110
},
97111
})
98112

@@ -110,6 +124,7 @@ export function getUserRepository(): UserRepository {
110124
},
111125
include: {
112126
memberships: true,
127+
flags: true,
113128
},
114129
})
115130

@@ -129,6 +144,7 @@ export function getUserRepository(): UserRepository {
129144
where,
130145
include: {
131146
memberships: true,
147+
flags: true,
132148
},
133149
})
134150

@@ -183,5 +199,110 @@ export function getUserRepository(): UserRepository {
183199
invariant(user !== null, `User with id ${row.userId} not found after deleting membership`)
184200
return user
185201
},
202+
203+
async createFlag(handle, data) {
204+
await handle.userFlag.create({
205+
data: {
206+
name: data.name,
207+
description: data.description,
208+
imageUrl: data.imageUrl,
209+
},
210+
})
211+
},
212+
213+
async updateFlag(handle, name, data) {
214+
await handle.userFlag.update({
215+
where: {
216+
name,
217+
},
218+
data: {
219+
name: data.name,
220+
description: data.description,
221+
imageUrl: data.imageUrl,
222+
},
223+
})
224+
},
225+
226+
async deleteFlag(handle, name) {
227+
await handle.userFlag.delete({
228+
where: {
229+
name,
230+
},
231+
})
232+
},
233+
234+
async findFlagByName(handle, name) {
235+
const flag = await handle.userFlag.findUnique({
236+
where: {
237+
name,
238+
},
239+
include: {
240+
users: {
241+
select: {
242+
id: true,
243+
name: true,
244+
profileSlug: true,
245+
imageUrl: true,
246+
},
247+
},
248+
},
249+
})
250+
251+
return parseOrReport(UserFlagWithUsersSchema.nullable(), flag)
252+
},
253+
254+
async findFlagsByUserId(handle, userId) {
255+
const flags = await handle.userFlag.findMany({
256+
where: {
257+
users: {
258+
some: {
259+
id: userId,
260+
},
261+
},
262+
},
263+
include: {
264+
users: {
265+
select: {
266+
id: true,
267+
name: true,
268+
profileSlug: true,
269+
imageUrl: true,
270+
},
271+
},
272+
},
273+
})
274+
275+
return parseOrReport(UserFlagWithUsersSchema.array(), flags)
276+
},
277+
278+
async assignFlagToUser(handle, userId, flagName) {
279+
await handle.user.update({
280+
where: {
281+
id: userId,
282+
},
283+
data: {
284+
flags: {
285+
connect: {
286+
name: flagName,
287+
},
288+
},
289+
},
290+
})
291+
},
292+
293+
async removeFlagFromUser(handle, userId, flagName) {
294+
await handle.user.update({
295+
where: {
296+
id: userId,
297+
},
298+
data: {
299+
flags: {
300+
disconnect: {
301+
name: flagName,
302+
},
303+
},
304+
},
305+
})
306+
},
186307
}
187308
}

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
type UserWrite,
1717
UserWriteSchema,
1818
findActiveMembership,
19+
type UserFlagWithUsers,
20+
type UserFlagWrite,
1921
} from "@dotkomonline/types"
2022
import { createS3PresignedPost, slugify, getNextSemesterStart, getCurrentSemesterStart } from "@dotkomonline/utils"
2123
import { trace } from "@opentelemetry/api"
@@ -80,6 +82,14 @@ export interface UserService {
8082
updateMembership(handle: DBHandle, membershipId: MembershipId, membership: Partial<MembershipWrite>): Promise<User>
8183
deleteMembership(handle: DBHandle, membershipId: MembershipId): Promise<User>
8284

85+
createFlag(handle: DBHandle, data: UserFlagWrite): Promise<void>
86+
updateFlag(handle: DBHandle, flagName: string, data: Partial<UserFlagWrite>): Promise<void>
87+
deleteFlag(handle: DBHandle, flagName: string): Promise<void>
88+
findFlagByName(handle: DBHandle, flagName: string): Promise<UserFlagWithUsers | null>
89+
findFlagsByUserId(handle: DBHandle, userId: UserId): Promise<UserFlagWithUsers[]>
90+
assignFlagToUser(handle: DBHandle, userId: UserId, flagName: string): Promise<void>
91+
removeFlagFromUser(handle: DBHandle, userId: UserId, flagName: string): Promise<void>
92+
8393
/**
8494
* Find the Feide federated access token for a user, if it exists.
8595
*
@@ -493,6 +503,34 @@ export function getUserService(
493503
return identity?.access_token ?? null
494504
},
495505

506+
async createFlag(handle, data) {
507+
return userRepository.createFlag(handle, data)
508+
},
509+
510+
async updateFlag(handle, flagName, data) {
511+
return userRepository.updateFlag(handle, flagName, data)
512+
},
513+
514+
async deleteFlag(handle, flagName) {
515+
return userRepository.deleteFlag(handle, flagName)
516+
},
517+
518+
async findFlagByName(handle, flagName) {
519+
return userRepository.findFlagByName(handle, flagName)
520+
},
521+
522+
async findFlagsByUserId(handle, userId) {
523+
return userRepository.findFlagsByUserId(handle, userId)
524+
},
525+
526+
async assignFlagToUser(handle, userId, flagName) {
527+
return userRepository.assignFlagToUser(handle, userId, flagName)
528+
},
529+
530+
async removeFlagFromUser(handle, userId, flagName) {
531+
return userRepository.removeFlagFromUser(handle, userId, flagName)
532+
},
533+
496534
async createFileUpload(handle, filename, contentType, userId, createdByUserId) {
497535
const user = await this.getById(handle, userId)
498536

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to drop the column `flags` on the `ow_user` table. All the data in the column will be lost.
5+
6+
*/
7+
-- AlterTable
8+
ALTER TABLE "ow_user" DROP COLUMN "flags";
9+
10+
-- CreateTable
11+
CREATE TABLE "user_flag" (
12+
"id" TEXT NOT NULL,
13+
"name" TEXT NOT NULL,
14+
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
15+
"updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
16+
"description" TEXT,
17+
"image_url" TEXT,
18+
19+
CONSTRAINT "user_flag_pkey" PRIMARY KEY ("id")
20+
);
21+
22+
-- CreateTable
23+
CREATE TABLE "_UserFlags" (
24+
"A" TEXT NOT NULL,
25+
"B" TEXT NOT NULL,
26+
27+
CONSTRAINT "_UserFlags_AB_pkey" PRIMARY KEY ("A","B")
28+
);
29+
30+
-- CreateIndex
31+
CREATE UNIQUE INDEX "user_flag_name_key" ON "user_flag"("name");
32+
33+
-- CreateIndex
34+
CREATE INDEX "_UserFlags_B_index" ON "_UserFlags"("B");
35+
36+
-- AddForeignKey
37+
ALTER TABLE "_UserFlags" ADD CONSTRAINT "_UserFlags_A_fkey" FOREIGN KEY ("A") REFERENCES "ow_user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
38+
39+
-- AddForeignKey
40+
ALTER TABLE "_UserFlags" ADD CONSTRAINT "_UserFlags_B_fkey" FOREIGN KEY ("B") REFERENCES "user_flag"("id") ON DELETE CASCADE ON UPDATE CASCADE;

packages/db/prisma/schema.prisma

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,19 @@ model Membership {
7373
@@map("membership")
7474
}
7575

76+
model UserFlag {
77+
id String @id @default(uuid())
78+
name String @unique
79+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
80+
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(3)
81+
description String?
82+
imageUrl String? @map("image_url")
83+
84+
users User[] @relation("UserFlags")
85+
86+
@@map("user_flag")
87+
}
88+
7689
model User {
7790
/// OpenID Connect Subject claim - for this reason there is no @default(uuid()) here.
7891
id String @id
@@ -85,7 +98,6 @@ model User {
8598
gender String?
8699
dietaryRestrictions String? @map("dietary_restrictions")
87100
ntnuUsername String? @map("ntnu_username")
88-
flags String[]
89101
/// Used for identifying the user in Google Workspace (my.name@online.ntnu.no)
90102
workspaceUserId String? @unique @map("workspace_user_id")
91103
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
@@ -107,6 +119,7 @@ model User {
107119
notificationsReceived NotificationRecipient[]
108120
notificationsCreated Notification[] @relation("created_by")
109121
notificationsUpdated Notification[] @relation("last_updated_by")
122+
flags UserFlag[] @relation("UserFlags")
110123
111124
@@map("ow_user")
112125
}

packages/db/src/fixtures/user.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export const getUserFixtures = () =>
1313
gender: "Mann",
1414
dietaryRestrictions: "Vegetarianer",
1515
ntnuUsername: null,
16-
flags: [],
1716
privacyPermissionsId: null,
1817
notificationPermissionsId: null,
1918
},
@@ -28,7 +27,6 @@ export const getUserFixtures = () =>
2827
gender: "Kvinne",
2928
dietaryRestrictions: null,
3029
ntnuUsername: null,
31-
flags: [],
3230
privacyPermissionsId: null,
3331
notificationPermissionsId: null,
3432
},
@@ -42,7 +40,6 @@ export const getUserFixtures = () =>
4240
phone: null,
4341
gender: null,
4442
dietaryRestrictions: "Har dårlig reaksjon mot gluten",
45-
flags: [],
4643
privacyPermissionsId: null,
4744
notificationPermissionsId: null,
4845
},

packages/types/src/user.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,35 @@ export const MembershipWriteSchema = MembershipSchema.pick({
2424
})
2525
export type MembershipWrite = z.infer<typeof MembershipWriteSchema>
2626

27+
export const UserFlagSchema = schemas.UserFlagSchema.extend({})
28+
export type UserFlag = z.infer<typeof UserFlagSchema>
29+
export type UserFlagId = UserFlag["id"]
30+
31+
export const UserFlagWriteSchema = UserFlagSchema.pick({
32+
name: true,
33+
description: true,
34+
imageUrl: true,
35+
})
36+
export type UserFlagWrite = z.infer<typeof UserFlagWriteSchema>
37+
2738
export const UserSchema = schemas.UserSchema.extend({
2839
memberships: z.array(MembershipSchema),
40+
flags: z.array(UserFlagSchema),
2941
})
3042
export type User = z.infer<typeof UserSchema>
3143
export type UserId = User["id"]
3244
export type UserProfileSlug = User["profileSlug"]
3345

46+
export const UserFlagWithUsersSchema = schemas.UserFlagSchema.extend({
47+
users: UserSchema.pick({
48+
id: true,
49+
name: true,
50+
profileSlug: true,
51+
imageUrl: true,
52+
}).array(),
53+
})
54+
export type UserFlagWithUsers = z.infer<typeof UserFlagWithUsersSchema>
55+
3456
export const NAME_REGEX = /^[\p{L}\p{M}\s'-]+$/u
3557
export const PHONE_REGEX = /^[0-9-+\s]*$/
3658
export const PROFILE_SLUG_REGEX = /^[a-z0-9-]+$/

0 commit comments

Comments
 (0)