Mirror of https://github.com/roostorg/coop github.com/roostorg/coop
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 557ff54b2b435e5f1e789c6a8a4e1bebf2d7deb6 305 lines 8.0 kB view raw
1import { AuthenticationError } from 'apollo-server-express'; 2 3import { 4 hasPermission, 5 UserPermission, 6} from '../../models/types/permissioning.js'; 7import { isCoopErrorOfType } from '../../utils/errors.js'; 8import { 9 isNonEmptyArray, 10 isNonEmptyString, 11 type NonEmptyArray, 12 type NonEmptyString, 13} from '../../utils/typescript-types.js'; 14import { transformConditionForDB } from '../datasources/RuleApi.js'; 15import { 16 type GQLMutationResolvers, 17 type GQLQueryResolvers, 18 type GQLRoutingRuleResolvers, 19} from '../generated.js'; 20import { gqlErrorResult, gqlSuccessResult } from '../utils/gqlResult.js'; 21 22const typeDefs = /* GraphQL */ ` 23 type RoutingRule { 24 id: ID! 25 name: String! 26 creatorId: String! 27 description: String 28 itemTypes: [ItemType!]! 29 status: RoutingRuleStatus! 30 conditionSet: ConditionSet! 31 destinationQueue: ManualReviewQueue! 32 } 33 34 enum RoutingRuleStatus { 35 LIVE 36 } 37 38 input CreateRoutingRuleInput { 39 name: String! 40 description: String 41 status: RoutingRuleStatus! 42 itemTypeIds: [ID!]! 43 conditionSet: ConditionSetInput! 44 destinationQueueId: ID! 45 sequenceNumber: Int 46 isAppealsRule: Boolean 47 } 48 49 input UpdateRoutingRuleInput { 50 id: ID! 51 name: String 52 description: String 53 status: RoutingRuleStatus 54 itemTypeIds: [ID!] 55 conditionSet: ConditionSetInput 56 destinationQueueId: ID 57 sequenceNumber: Int 58 isAppealsRule: Boolean 59 } 60 61 input ReorderRoutingRulesInput { 62 order: [ID!]! 63 isAppealsRule: Boolean 64 } 65 66 input DeleteRoutingRuleInput { 67 id: ID! 68 isAppealsRule: Boolean 69 } 70 71 type RoutingRuleNameExistsError implements Error { 72 title: String! 73 status: Int! 74 type: [String!]! 75 pointer: String 76 detail: String 77 requestId: String 78 } 79 80 type QueueDoesNotExistError implements Error { 81 title: String! 82 status: Int! 83 type: [String!]! 84 pointer: String 85 detail: String 86 requestId: String 87 } 88 89 type MutateRoutingRuleSuccessResponse { 90 data: RoutingRule! 91 } 92 93 type MutateRoutingRulesOrderSuccessResponse { 94 data: [RoutingRule!]! 95 } 96 97 union CreateRoutingRuleResponse = 98 MutateRoutingRuleSuccessResponse 99 | RoutingRuleNameExistsError 100 | QueueDoesNotExistError 101 102 union UpdateRoutingRuleResponse = 103 MutateRoutingRuleSuccessResponse 104 | RoutingRuleNameExistsError 105 | NotFoundError 106 | QueueDoesNotExistError 107 108 union ReorderRoutingRulesResponse = MutateRoutingRulesOrderSuccessResponse 109 110 type Mutation { 111 createRoutingRule( 112 input: CreateRoutingRuleInput! 113 ): CreateRoutingRuleResponse! 114 updateRoutingRule( 115 input: UpdateRoutingRuleInput! 116 ): UpdateRoutingRuleResponse! 117 deleteRoutingRule(input: DeleteRoutingRuleInput!): Boolean! 118 reorderRoutingRules( 119 input: ReorderRoutingRulesInput! 120 ): ReorderRoutingRulesResponse! 121 } 122`; 123 124const RoutingRule: GQLRoutingRuleResolvers = { 125 async destinationQueue(routingRule, _, context) { 126 const user = context.getUser(); 127 if (!user || user.orgId !== routingRule.orgId) { 128 throw new AuthenticationError('User required'); 129 } 130 131 const userCanEditMRTQueues = hasPermission( 132 UserPermission.EDIT_MRT_QUEUES, 133 user.role, 134 ); 135 136 const queueSelector = { 137 orgId: user.orgId, 138 queueId: routingRule.destinationQueueId, 139 }; 140 141 const queue = userCanEditMRTQueues 142 ? await context.services.ManualReviewToolService.getQueueForOrgAndDangerouslyBypassPermissioning( 143 queueSelector, 144 ) 145 : await context.services.ManualReviewToolService.getQueueForOrg({ 146 userId: user.id, 147 ...queueSelector, 148 }); 149 150 // Assume the queue won't be missing, as the db requires routing rules to 151 // point to existing queues -- although technically the queue could've been 152 // deleted between loading the rule and querying for the queue. 153 return queue!; 154 }, 155 async itemTypes(routingRule, _, context) { 156 const user = context.getUser(); 157 if (!user || user.orgId !== routingRule.orgId) { 158 throw new AuthenticationError('User required'); 159 } 160 161 const itemTypes = 162 await context.services.ModerationConfigService.getItemTypes({ 163 orgId: user.orgId, 164 }); 165 166 return itemTypes.filter((itemType) => 167 routingRule.itemTypeIds.includes(itemType.id), 168 ); 169 }, 170}; 171 172const Query: GQLQueryResolvers = {}; 173 174const Mutation: GQLMutationResolvers = { 175 async createRoutingRule(_, params, context) { 176 const user = context.getUser(); 177 const { itemTypeIds } = params.input; 178 179 if (user == null) { 180 throw new AuthenticationError('User required.'); 181 } 182 183 if (!itemTypeIdsAreValid(itemTypeIds)) { 184 throw new Error('itemTypeIds must be a non-empty array'); 185 } 186 187 try { 188 const routingRule = 189 await context.services.ManualReviewToolService.createRoutingRule({ 190 ...params.input, 191 itemTypeIds, 192 orgId: user.orgId, 193 creatorId: user.id, 194 conditionSet: transformConditionForDB(params.input.conditionSet), 195 isAppealsRule: params.input.isAppealsRule ?? false, 196 }); 197 198 return gqlSuccessResult( 199 { data: routingRule }, 200 'MutateRoutingRuleSuccessResponse', 201 ); 202 } catch (e: unknown) { 203 if ( 204 isCoopErrorOfType(e, [ 205 'RoutingRuleNameExistsError', 206 'QueueDoesNotExistError', 207 ]) 208 ) { 209 return gqlErrorResult(e); 210 } 211 212 throw e; 213 } 214 }, 215 async updateRoutingRule(_, params, context) { 216 const user = context.getUser(); 217 const { itemTypeIds } = params.input; 218 if (user == null) { 219 throw new AuthenticationError('User required.'); 220 } 221 222 if (itemTypeIds && !itemTypeIdsAreValid(itemTypeIds)) { 223 throw new Error('itemTypeIds must be a non-empty array'); 224 } 225 226 try { 227 const routingRule = 228 await context.services.ManualReviewToolService.updateRoutingRule({ 229 id: params.input.id, 230 orgId: user.orgId, 231 name: params.input.name ?? undefined, 232 description: params.input.description ?? undefined, 233 status: params.input.status ?? undefined, 234 itemTypeIds: itemTypeIds ?? undefined, 235 destinationQueueId: params.input.destinationQueueId ?? undefined, 236 conditionSet: params.input.conditionSet 237 ? transformConditionForDB(params.input.conditionSet) 238 : undefined, 239 sequenceNumber: params.input.sequenceNumber ?? undefined, 240 isAppealsRule: params.input.isAppealsRule ?? false, 241 }); 242 243 return gqlSuccessResult( 244 { data: routingRule }, 245 'MutateRoutingRuleSuccessResponse', 246 ); 247 } catch (e: unknown) { 248 if ( 249 isCoopErrorOfType(e, [ 250 'RoutingRuleNameExistsError', 251 'NotFoundError', 252 'QueueDoesNotExistError', 253 ]) 254 ) { 255 return gqlErrorResult(e); 256 } 257 258 throw e; 259 } 260 }, 261 async deleteRoutingRule(_, params, context) { 262 const user = context.getUser(); 263 if (user == null) { 264 throw new AuthenticationError('User required.'); 265 } 266 267 return context.services.ManualReviewToolService.deleteRoutingRule({ 268 id: params.input.id, 269 isAppealsRule: params.input.isAppealsRule ?? false, 270 }); 271 }, 272 async reorderRoutingRules(_, params, context) { 273 const user = context.getUser(); 274 if (user == null) { 275 throw new AuthenticationError('User required.'); 276 } 277 278 const { order } = params.input; 279 const reorderedRules = 280 await context.services.ManualReviewToolService.reorderRoutingRules({ 281 orgId: user.orgId, 282 order, 283 isAppealsRule: params.input.isAppealsRule ?? false, 284 }); 285 286 return gqlSuccessResult( 287 { data: reorderedRules }, 288 'MutateRoutingRulesOrderSuccessResponse', 289 ); 290 }, 291}; 292 293const resolvers = { 294 RoutingRule, 295 Query, 296 Mutation, 297}; 298 299export { typeDefs, resolvers }; 300 301function itemTypeIdsAreValid( 302 arr: readonly string[], 303): arr is NonEmptyArray<NonEmptyString> { 304 return isNonEmptyArray(arr) && arr.every(isNonEmptyString); 305}