@@ -5,7 +5,7 @@ import { z } from "zod";
55import { ISSUE } from "@forge/consts" ;
66import { and , eq , exists , inArray , sql } from "@forge/db" ;
77import { db } from "@forge/db/client" ;
8- import { Permissions } from "@forge/db/schemas/auth" ;
8+ import { Permissions , User } from "@forge/db/schemas/auth" ;
99import {
1010 InsertTemplateSchema ,
1111 Issue ,
@@ -15,6 +15,7 @@ import {
1515 Template ,
1616} from "@forge/db/schemas/knight-hacks" ;
1717import { permissions } from "@forge/utils" ;
18+ import * as permissionsServer from "@forge/utils/permissions.server" ;
1819
1920import { permProcedure } from "../trpc" ;
2021
@@ -112,6 +113,48 @@ const issueTemplateSchema: z.ZodType<IssueTemplate> =
112113 } ) ;
113114
114115export const issuesRouter = {
116+ getUsersOnTeam : permProcedure
117+ . input (
118+ z . object ( {
119+ teamId : z . string ( ) . uuid ( ) ,
120+ } ) ,
121+ )
122+ . query ( async ( { ctx, input } ) => {
123+ permissions . controlPerms . or ( [ "EDIT_ISSUES" ] , ctx ) ;
124+
125+ const rows = await db
126+ . select ( {
127+ id : User . id ,
128+ name : User . name ,
129+ email : User . email ,
130+ discordUserId : User . discordUserId ,
131+ } )
132+ . from ( User )
133+ . innerJoin ( Permissions , eq ( User . id , Permissions . userId ) )
134+ . where ( eq ( Permissions . roleId , input . teamId ) ) ;
135+
136+ const userById = new Map <
137+ string ,
138+ {
139+ id : string ;
140+ name : string ;
141+ email : string | null ;
142+ }
143+ > ( ) ;
144+
145+ for ( const row of rows ) {
146+ userById . set ( row . id , {
147+ id : row . id ,
148+ name : row . name ?? row . email ?? row . discordUserId ,
149+ email : row . email ,
150+ } ) ;
151+ }
152+
153+ return [ ...userById . values ( ) ] . sort ( ( a , b ) =>
154+ a . name . localeCompare ( b . name ) ,
155+ ) ;
156+ } ) ,
157+
115158 createIssue : permProcedure
116159 . input (
117160 CreateIssueInputSchema . omit ( { creator : true } ) . extend ( {
@@ -124,6 +167,15 @@ export const issuesRouter = {
124167 return await db . transaction ( async ( tx ) => {
125168 const { teamVisibilityIds, assigneeIds, children, ...rest } = input ;
126169
170+ await permissionsServer . validateAssigneesBelongToTeam (
171+ tx ,
172+ input . team ,
173+ assigneeIds ,
174+ ) ;
175+ if ( children ?. length ) {
176+ await permissionsServer . validateIssueNodeAssignees ( tx , children ) ;
177+ }
178+
127179 const [ issue ] = await tx
128180 . insert ( Issue )
129181 . values ( {
@@ -316,59 +368,89 @@ export const issuesRouter = {
316368 )
317369 . mutation ( async ( { ctx, input } ) => {
318370 permissions . controlPerms . or ( [ "EDIT_ISSUES" ] , ctx ) ;
319- await requireIssue ( input . id ) ;
371+ return await db . transaction ( async ( tx ) => {
372+ const existingIssue = await tx . query . Issue . findFirst ( {
373+ where : ( t , { eq } ) => eq ( t . id , input . id ) ,
374+ with : {
375+ userAssignments : true ,
376+ } ,
377+ } ) ;
320378
321- const { id , assigneeIds , teamVisibilityIds , ... fields } = input ;
322- const updateData = Object . fromEntries (
323- ( Object . entries ( fields ) as [ string , unknown ] [ ] ) . filter (
324- ( [ , v ] ) => v !== undefined ,
325- ) ,
326- ) ;
379+ if ( ! existingIssue ) {
380+ throw new TRPCError ( {
381+ message : "Issue not found." ,
382+ code : "NOT_FOUND" ,
383+ } ) ;
384+ }
327385
328- if ( Object . keys ( updateData ) . length > 0 ) {
329- await db . update ( Issue ) . set ( updateData ) . where ( eq ( Issue . id , id ) ) ;
330- }
386+ const assignmentTeamId = input . team ?? existingIssue . team ;
387+ const existingAssigneeIds = existingIssue . userAssignments . map (
388+ ( assignment ) => assignment . userId ,
389+ ) ;
390+ const assigneeIdsToValidate =
391+ input . assigneeIds ??
392+ ( input . team !== undefined && input . team !== existingIssue . team
393+ ? existingAssigneeIds
394+ : undefined ) ;
395+
396+ await permissionsServer . validateAssigneesBelongToTeam (
397+ tx ,
398+ assignmentTeamId ,
399+ assigneeIdsToValidate ,
400+ ) ;
331401
332- if ( teamVisibilityIds !== undefined ) {
333- await db
334- . delete ( IssuesToTeamsVisibility )
335- . where ( eq ( IssuesToTeamsVisibility . issueId , id ) ) ;
336- if ( teamVisibilityIds . length > 0 ) {
337- await db
338- . insert ( IssuesToTeamsVisibility )
339- . values (
340- teamVisibilityIds . map ( ( teamId ) => ( { issueId : id , teamId } ) ) ,
341- ) ;
402+ const { id, assigneeIds, teamVisibilityIds, ...fields } = input ;
403+ const updateData = Object . fromEntries (
404+ ( Object . entries ( fields ) as [ string , unknown ] [ ] ) . filter (
405+ ( [ , v ] ) => v !== undefined ,
406+ ) ,
407+ ) ;
408+
409+ if ( Object . keys ( updateData ) . length > 0 ) {
410+ await tx . update ( Issue ) . set ( updateData ) . where ( eq ( Issue . id , id ) ) ;
342411 }
343- }
344412
345- if ( assigneeIds !== undefined ) {
346- await db
347- . delete ( IssuesToUsersAssignment )
348- . where ( eq ( IssuesToUsersAssignment . issueId , id ) ) ;
349- if ( assigneeIds . length > 0 ) {
350- await db
351- . insert ( IssuesToUsersAssignment )
352- . values ( assigneeIds . map ( ( userId ) => ( { issueId : id , userId } ) ) ) ;
413+ if ( teamVisibilityIds !== undefined ) {
414+ await tx
415+ . delete ( IssuesToTeamsVisibility )
416+ . where ( eq ( IssuesToTeamsVisibility . issueId , id ) ) ;
417+ if ( teamVisibilityIds . length > 0 ) {
418+ await tx
419+ . insert ( IssuesToTeamsVisibility )
420+ . values (
421+ teamVisibilityIds . map ( ( teamId ) => ( { issueId : id , teamId } ) ) ,
422+ ) ;
423+ }
353424 }
354- }
355425
356- if (
357- Object . keys ( updateData ) . length === 0 &&
358- ( teamVisibilityIds !== undefined || assigneeIds !== undefined )
359- ) {
360- await db
361- . update ( Issue )
362- . set ( { updatedAt : new Date ( ) } )
363- . where ( eq ( Issue . id , id ) ) ;
364- }
426+ if ( assigneeIds !== undefined ) {
427+ await tx
428+ . delete ( IssuesToUsersAssignment )
429+ . where ( eq ( IssuesToUsersAssignment . issueId , id ) ) ;
430+ if ( assigneeIds . length > 0 ) {
431+ await tx
432+ . insert ( IssuesToUsersAssignment )
433+ . values ( assigneeIds . map ( ( userId ) => ( { issueId : id , userId } ) ) ) ;
434+ }
435+ }
365436
366- return db . query . Issue . findFirst ( {
367- where : ( t , { eq } ) => eq ( t . id , id ) ,
368- with : {
369- teamVisibility : { with : { team : true } } ,
370- userAssignments : { with : { user : true } } ,
371- } ,
437+ if (
438+ Object . keys ( updateData ) . length === 0 &&
439+ ( teamVisibilityIds !== undefined || assigneeIds !== undefined )
440+ ) {
441+ await tx
442+ . update ( Issue )
443+ . set ( { updatedAt : new Date ( ) } )
444+ . where ( eq ( Issue . id , id ) ) ;
445+ }
446+
447+ return tx . query . Issue . findFirst ( {
448+ where : ( t , { eq } ) => eq ( t . id , id ) ,
449+ with : {
450+ teamVisibility : { with : { team : true } } ,
451+ userAssignments : { with : { user : true } } ,
452+ } ,
453+ } ) ;
372454 } ) ;
373455 } ) ,
374456 deleteIssue : permProcedure
0 commit comments