Skip to content

Commit 165a334

Browse files
refactor: query optimization, removing DUPLICATE_ROLE error
1 parent 52bfce5 commit 165a334

File tree

2 files changed

+54
-47
lines changed

2 files changed

+54
-47
lines changed

backend/src/db/schema/tg/permissions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { bigint, index, primaryKey, text, varchar } from "drizzle-orm/pg-core"
1+
import { bigint, index, primaryKey, text } from "drizzle-orm/pg-core"
22
import { timeColumns } from "@/db/columns"
33
import { createTable } from "../create-table"
44

backend/src/routers/tg/permissions.ts

Lines changed: 53 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { and, arrayContains, eq, inArray } from "drizzle-orm"
1+
import { and, arrayContains, eq, inArray, ne as neq, sql } from "drizzle-orm"
22
import { z } from "zod"
33
import { DB, SCHEMA } from "@/db"
44
import { ARRAY_USER_ROLE, type TUserRole, USER_ROLE } from "@/db/schema/tg/permissions"
@@ -53,9 +53,9 @@ export default createTRPCRouter({
5353
try {
5454
const res = await DB.select({ userId: s.permissions.userId, roles: s.permissions.roles })
5555
.from(s.permissions)
56-
.where(arrayContains(s.permissions.roles, ["direttivo"]))
56+
.where(arrayContains(s.permissions.roles, [USER_ROLE.DIRETTIVO]))
5757

58-
const members = res.map((r) => ({ userId: r.userId, isPresident: r.roles.includes("president") }))
58+
const members = res.map((r) => ({ userId: r.userId, isPresident: r.roles.includes(USER_ROLE.PRESIDENT) }))
5959

6060
if (res.length === 0) return { error: "EMPTY", members }
6161
if (res.length < 3) return { error: "NOT_ENOUGH_MEMBERS", members }
@@ -77,12 +77,16 @@ export default createTRPCRouter({
7777
})
7878
)
7979
.output(
80-
z.object({
81-
error: z.union([
82-
z.null(),
83-
z.enum(["UNAUTHORIZED", "UNAUTHORIZED_SELF_ASSIGN", "DUPLICATE_ROLE", "INTERNAL_SERVER_ERROR"]),
84-
]),
85-
})
80+
z.union([
81+
z.object({
82+
roles: z.array(z.string<TUserRole>()),
83+
error: z.null(),
84+
}),
85+
z.object({
86+
roles: z.null().optional(),
87+
error: z.union([z.null(), z.enum(["UNAUTHORIZED", "UNAUTHORIZED_SELF_ASSIGN", "INTERNAL_SERVER_ERROR"])]),
88+
}),
89+
])
8690
)
8791
.mutation(async ({ input }) => {
8892
try {
@@ -92,7 +96,6 @@ export default createTRPCRouter({
9296
.where(inArray(s.permissions.userId, [input.userId, input.adderId]))
9397

9498
const adder = q.find((e) => e.userId === input.adderId)
95-
const existing = q.find((e) => e.userId === input.userId)
9699

97100
// check if adder is not in permission table or doesn't have permissions
98101
if (!adder || !adder.roles.some((a) => CAN_ASSIGN.includes(a))) return { error: "UNAUTHORIZED" }
@@ -103,52 +106,56 @@ export default createTRPCRouter({
103106

104107
// president and owner are special role
105108
// only owners can perform this role update
106-
if ((input.role === "president" || input.role === "owner") && !adder.roles.includes("owner"))
109+
if (
110+
(input.role === USER_ROLE.PRESIDENT || input.role === USER_ROLE.OWNER) &&
111+
!adder.roles.includes(USER_ROLE.OWNER)
112+
)
107113
return { error: "UNAUTHORIZED" }
108114

109-
// check if it's the first time the target is added to permissions table
110-
if (!existing) {
111-
await DB.insert(s.permissions).values({
112-
userId: input.userId,
113-
roles: input.role === "president" ? [input.role, "direttivo"] : [input.role],
114-
addedBy: input.adderId,
115-
})
116-
return { error: null }
117-
}
115+
const existingRoles = q.find((e) => e.userId === input.userId)?.roles ?? []
118116

119-
// if target already has this role, skip
120-
if (existing.roles.includes(input.role)) return { error: "DUPLICATE_ROLE" }
117+
// concat is the superpowered push
118+
const roles = Array.from(
119+
new Set(
120+
existingRoles.concat(
121+
input.role === USER_ROLE.PRESIDENT ? [USER_ROLE.PRESIDENT, USER_ROLE.DIRETTIVO] : input.role
122+
)
123+
)
124+
)
121125

122126
// we must check if there's already a president and change it
123-
if (input.role === "president") {
124-
const qOldPres = await DB.select()
125-
.from(s.permissions)
126-
.where(arrayContains(s.permissions.roles, ["president"]))
127-
128-
if (qOldPres.length === 1) {
129-
logger.warn("Role: an owner is changing the current president of PoliNetwork")
127+
if (input.role === USER_ROLE.PRESIDENT) {
128+
const updated = await DB.update(s.permissions)
129+
.set({ roles: sql`array_remove(${s.permissions.roles}, ${USER_ROLE.PRESIDENT})` })
130+
.where(
131+
and(arrayContains(s.permissions.roles, [USER_ROLE.PRESIDENT]), neq(s.permissions.userId, input.userId))
132+
)
133+
.returning({ userId: s.permissions.userId })
134+
135+
if (updated.length > 0) {
130136
// TODO: send email warning to adminorg email
131-
await DB.update(s.permissions)
132-
.set({ roles: qOldPres[0].roles.filter((r) => r !== "president") })
133-
.where(eq(s.permissions.userId, qOldPres[0].userId))
134-
}
135-
136-
// to avoid another mutation, we directly add "direttivo"
137-
if (!existing.roles.includes("direttivo")) {
138-
existing.roles.push("direttivo")
137+
logger.warn(
138+
{ adderId: input.adderId, olds: updated.map((e) => e.userId), new: input.userId },
139+
"Role: an owner is changing the current president of PoliNetwork"
140+
)
139141
}
140142
}
141143

142-
// here we finally make the update
143-
// first we push the new role to the roles array
144-
existing.roles.push(input.role)
145-
// then we update the DB entry with the updated roles array
146-
await DB.update(s.permissions)
147-
.set({
148-
roles: existing.roles,
144+
// then we upsert the DB entry with the updated roles array
145+
await DB.insert(s.permissions)
146+
.values({
147+
userId: input.userId,
148+
roles,
149+
addedBy: input.adderId,
149150
})
150-
.where(eq(s.permissions.userId, input.userId))
151-
return { error: null }
151+
.onConflictDoUpdate({
152+
target: s.permissions.userId,
153+
set: {
154+
roles,
155+
},
156+
})
157+
158+
return { roles, error: null }
152159
} catch (error) {
153160
logger.error({ error }, "Error while executing addRole in tg.permissions router")
154161
return { error: "INTERNAL_SERVER_ERROR" }

0 commit comments

Comments
 (0)