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 main 161 lines 4.5 kB view raw
1import { type Kysely } from 'kysely'; 2 3import { 4 CoopError, 5 ErrorType, 6 type ErrorInstanceData, 7} from '../../../utils/errors.js'; 8import { isUniqueViolationError } from '../../../utils/kysely.js'; 9import { removeUndefinedKeys } from '../../../utils/misc.js'; 10import { type ModerationConfigServicePg } from '../dbTypes.js'; 11 12const userStrikeThresholdSelection = [ 13 'id', 14 'org_id as orgId', 15 'threshold', 16 'actions', 17] as const; 18 19export default class UserStrikeOperations { 20 constructor( 21 private readonly pgQuery: Kysely<ModerationConfigServicePg>, 22 private readonly pgQueryReplica: Kysely<ModerationConfigServicePg>, 23 ) {} 24 25 async getUserStrikeThresholds(opts: { 26 orgId: string; 27 readFromReplica?: boolean; 28 }) { 29 const { orgId, readFromReplica } = opts; 30 const pgQuery = this.#getPgQuery(readFromReplica); 31 const query = pgQuery 32 .selectFrom('public.user_strike_thresholds') 33 .select(userStrikeThresholdSelection) 34 .where('org_id', '=', orgId); 35 return query.execute(); 36 } 37 38 async createUserStrikeThreshold(opts: { 39 orgId: string; 40 thresholdSettings: { 41 threshold: number; 42 actions: string[]; 43 }; 44 }) { 45 const { orgId: org_id, thresholdSettings } = opts; 46 47 try { 48 return await this.pgQuery 49 .insertInto('public.user_strike_thresholds') 50 .values({ 51 org_id, 52 threshold: thresholdSettings.threshold, 53 actions: thresholdSettings.actions, 54 }) 55 .returning(userStrikeThresholdSelection) 56 .executeTakeFirstOrThrow(); 57 } catch (e: unknown) { 58 throw isUniqueViolationError(e) 59 ? makeThresholdAlreadyExistsError({ shouldErrorSpan: true }) 60 : e; 61 } 62 } 63 64 async updateUserStrikeThreshold(opts: { 65 orgId: string; 66 thresholdSettings: { 67 id: string; 68 threshold?: number; 69 actions?: string[]; 70 }; 71 }) { 72 const { orgId, thresholdSettings } = opts; 73 74 try { 75 return await this.pgQuery 76 .updateTable('public.user_strike_thresholds') 77 .set( 78 removeUndefinedKeys({ 79 threshold: thresholdSettings.threshold, 80 actions: thresholdSettings.actions, 81 }), 82 ) 83 .where('id', '=', thresholdSettings.id) 84 .where('org_id', '=', orgId) 85 .returning(userStrikeThresholdSelection) 86 .executeTakeFirstOrThrow(); 87 } catch (e: unknown) { 88 throw isUniqueViolationError(e) 89 ? makeThresholdAlreadyExistsError({ shouldErrorSpan: true }) 90 : e; 91 } 92 } 93 /** 94 * Set all user strike thresholds for an organization. 95 * this replaces all existing thresholds with the new set 96 */ 97 async setAllUserStrikeThresholds(opts: { 98 orgId: string; 99 thresholds: readonly { 100 threshold: number; 101 actions: readonly string[]; 102 }[]; 103 }) { 104 await this.pgQuery.transaction().execute(async (trx) => { 105 await trx 106 .deleteFrom('public.user_strike_thresholds') 107 .where('org_id', '=', opts.orgId) 108 .execute(); 109 for (const threshold of opts.thresholds) { 110 await trx 111 .insertInto('public.user_strike_thresholds') 112 .values({ 113 org_id: opts.orgId, 114 threshold: threshold.threshold, 115 actions: [...threshold.actions], 116 }) 117 .onConflict((oc) => 118 oc.columns(['org_id', 'threshold']).doUpdateSet({ 119 actions: [...threshold.actions], 120 }), 121 ) 122 .execute(); 123 } 124 }); 125 } 126 127 async deleteUserStrikeThreshold(opts: { 128 orgId: string; 129 threshold: number; 130 id: string; 131 }) { 132 const { orgId, id, threshold } = opts; 133 134 const rowsDeleted = await this.pgQuery 135 .deleteFrom('public.user_strike_thresholds') 136 .where('org_id', '=', orgId) 137 .where('id', '=', id) 138 .where('threshold', '=', threshold) 139 .execute(); 140 141 return rowsDeleted.length === 1; 142 } 143 144 #getPgQuery(readFromReplica: boolean = false) { 145 return readFromReplica ? this.pgQueryReplica : this.pgQuery; 146 } 147} 148 149export type UserStrikeThresholdErrorType = 150 'UserStrikeThresholdAlreadyExistsError'; 151 152// TODO: throw this error on failed policy creation/update when appropriate. 153export const makeThresholdAlreadyExistsError = (data: ErrorInstanceData) => 154 new CoopError({ 155 status: 409, 156 type: [ErrorType.UniqueViolation], 157 title: 158 'A rule with that threshold value already exists in this organization.', 159 name: 'UserStrikeThresholdAlreadyExistsError', 160 ...data, 161 });