1- import { and , arrayContains , eq , inArray } from "drizzle-orm"
1+ import { and , arrayContains , eq , inArray , ne as neq , sql } from "drizzle-orm"
22import { z } from "zod"
33import { DB , SCHEMA } from "@/db"
44import { 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