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 220 lines 6.5 kB view raw
1import sequelize, { 2 type HasManyAddAssociationsMixin, 3 type HasManyGetAssociationsMixin, 4 type HasManyGetAssociationsMixinOptions, 5 type HasManySetAssociationsMixin, 6 type InferAttributes, 7 type InferCreationAttributes, 8 type Sequelize, 9} from 'sequelize'; 10import { type JsonObject } from 'type-fest'; 11 12import { 13 ActionType, 14 ItemTypeKind, 15 UserPenaltySeverity, 16} from '../../services/moderationConfigService/index.js'; 17import { validateUrlOrNull } from '../../utils/url.js'; 18import { type DataTypes } from '../index.js'; 19import { type ItemType as TContentType } from './ItemTypeModel.js'; 20 21const { Model } = sequelize; 22 23// The default type an Action sequelize model instance. 24// This type is vague, w/ more optional fields than we'll have at runtime, and 25// not accounting for the rules that we've set up in pg for how different action 26// type values constrain the values in other columns. 27export type CollapsedSequelizeAction = InstanceType< 28 ReturnType<typeof makeActionModel> 29>; 30 31// These types handle the different constraints per action type, mirroring pg. 32export type EnqueueToMrtAction = CollapsedSequelizeAction & { 33 actionType: (typeof ActionType)['ENQUEUE_TO_MRT']; 34 callbackUrl: null; 35}; 36 37export type EnqueueToNcmecAction = CollapsedSequelizeAction & { 38 actionType: (typeof ActionType)['ENQUEUE_TO_NCMEC']; 39 callbackUrl: null; 40}; 41 42export type CustomAction = CollapsedSequelizeAction & { 43 actionType: (typeof ActionType)['CUSTOM_ACTION']; 44 callbackUrl: string; 45}; 46 47export type EnqueueAuthorToMrtAction = CollapsedSequelizeAction & { 48 actionType: (typeof ActionType)['ENQUEUE_AUTHOR_TO_MRT']; 49 callbackUrl: string; 50}; 51 52// And this is the more precise replacement for UntypedAction, which we 53// use outside this file. 54export type SequelizeAction = 55 | EnqueueToMrtAction 56 | EnqueueToNcmecAction 57 | EnqueueAuthorToMrtAction 58 | CustomAction; 59 60/** 61 * Data Model for Actions. Actions are components 62 * of Rules that get executed if all Conditions are met. 63 * Examples of Actions are Delete, Enqueue, Log, etc. 64 */ 65const makeActionModel = (sequelize: Sequelize, DataTypes: DataTypes) => { 66 class Action extends Model< 67 InferAttributes<Action>, 68 InferCreationAttributes<Action> 69 > { 70 public declare id: string; 71 public declare name: string; 72 public declare orgId: string; 73 public declare description: string | null; 74 public declare callbackUrl: string | null; 75 public declare callbackUrlHeaders: JsonObject | null; 76 public declare callbackUrlBody: JsonObject | null; 77 public declare customMrtApiParams: JsonObject | null; 78 79 public declare penalty: UserPenaltySeverity; 80 public declare actionType: ActionType; 81 public declare appliesToAllItemsOfKind: ItemTypeKind[]; 82 public declare applyUserStrikes: boolean; 83 84 public declare addContentTypes: HasManyAddAssociationsMixin< 85 unknown, 86 string 87 >; 88 public declare setContentTypes: HasManySetAssociationsMixin< 89 unknown, 90 string 91 >; 92 private declare getContentTypesSequelizeImpl: HasManyGetAssociationsMixin<unknown>; 93 94 // eslint-disable-next-line @typescript-eslint/no-explicit-any 95 static associate(models: { [key: string]: any }) { 96 Action.belongsTo(models.Org, { as: 'org' }); 97 Action.belongsToMany(models.Rule, { 98 through: 'rules_and_actions', 99 as: 'rules', 100 }); 101 102 // Assign the default sequelize getContentTypes function to another 103 // name so that we can use it in the actual implemented function. 104 // 105 const contentTypeAssoc = Action.belongsToMany(models.ItemType, { 106 through: 'actions_and_item_types', 107 as: 'ContentTypes', 108 otherKey: 'item_type_id', 109 }); 110 Object.defineProperty( 111 models.Action.prototype, 112 'getContentTypesSequelizeImpl', 113 { 114 enumerable: false, 115 value(...params: unknown[]) { 116 // eslint-disable-next-line @typescript-eslint/no-explicit-any 117 return (contentTypeAssoc as any)['get'](this, ...params); 118 }, 119 }, 120 ); 121 } 122 123 async getContentTypes( 124 options?: HasManyGetAssociationsMixinOptions, 125 ): Promise<TContentType[]> { 126 const contentTypes = 127 this.appliesToAllItemsOfKind.length > 0 128 ? await this.sequelize.model('content_type').findAll({ 129 ...options, 130 where: { 131 ...options?.where, 132 orgId: this.orgId, 133 kind: this.appliesToAllItemsOfKind, 134 }, 135 }) 136 : await this.getContentTypesSequelizeImpl(options); 137 return contentTypes as TContentType[]; 138 } 139 } 140 141 /* Fields */ 142 Action.init( 143 { 144 id: { 145 type: DataTypes.STRING, 146 primaryKey: true, 147 }, 148 // Name of the action -- this must be unique for each Org (i.e. an Org can't 149 // have two actions with the same name) 150 name: { 151 type: DataTypes.STRING, 152 allowNull: false, 153 validate: { notEmpty: true }, 154 }, 155 orgId: { 156 type: DataTypes.STRING, 157 allowNull: false, 158 }, 159 description: { 160 type: DataTypes.STRING, 161 allowNull: true, 162 }, 163 callbackUrl: { 164 type: DataTypes.STRING, 165 allowNull: true, 166 validate: { 167 isValidUrl: validateUrlOrNull, 168 }, 169 }, 170 callbackUrlHeaders: { 171 type: DataTypes.JSONB, 172 allowNull: true, 173 }, 174 callbackUrlBody: { 175 type: DataTypes.JSONB, 176 allowNull: true, 177 }, 178 customMrtApiParams: { 179 type: DataTypes.ARRAY(DataTypes.JSONB), 180 allowNull: true, 181 }, 182 penalty: { 183 type: DataTypes.STRING, 184 defaultValue: UserPenaltySeverity.NONE, 185 allowNull: false, 186 validate: { 187 isIn: [Object.values(UserPenaltySeverity)], 188 }, 189 }, 190 actionType: { 191 type: DataTypes.STRING, 192 defaultValue: ActionType.CUSTOM_ACTION, 193 allowNull: false, 194 validate: { 195 notNull: true, 196 isIn: [Object.values(ActionType)], 197 }, 198 }, 199 appliesToAllItemsOfKind: { 200 field: 'applies_to_all_items_of_kind', 201 type: DataTypes.ARRAY(DataTypes.ENUM(...Object.values(ItemTypeKind))), 202 defaultValue: [], 203 }, 204 applyUserStrikes: { 205 type: DataTypes.BOOLEAN, 206 defaultValue: false, 207 allowNull: false, 208 }, 209 }, 210 { 211 sequelize, 212 modelName: 'action', 213 underscored: true, 214 }, 215 ); 216 217 return Action; 218}; 219 220export default makeActionModel;