Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1import _ from 'lodash';
2import sequelize, {
3 type BelongsToGetAssociationMixin,
4 type HasManyAddAssociationsMixin,
5 type HasManyGetAssociationsMixin,
6 type HasManySetAssociationsMixin,
7 type InferAttributes,
8 type InferCreationAttributes,
9 type Sequelize,
10} from 'sequelize';
11
12import {
13 PolicyType,
14 UserPenaltySeverity,
15} from '../services/moderationConfigService/index.js';
16import { type DataTypes } from './index.js';
17import { type Rule } from './rules/RuleModel.js';
18
19const { groupBy, mapValues } = _;
20const { Model } = sequelize;
21
22export type Policy = InstanceType<ReturnType<typeof makePolicy>>;
23
24/**
25 * Data Model for Policies. These policies can represent overall
26 * policy areas (crime, safety) or more granular policy areas (e.g.
27 * selling dangerous goods, child safety). Policies are constructed as
28 * a tree - or rather, a set of trees, all of which can be thought of
29 * as child trees under an abstract root node.
30 *
31 * ROOT
32 * | | |
33 * Crime Safety Hate
34 * | | | | | |
35 * Weapons Drugs Children Self-harm Dehumanization Threats
36 *
37 * Each node (except the root) is a policy.
38 */
39const makePolicy = (sequelize: Sequelize, DataTypes: DataTypes) => {
40 class Policy extends Model<
41 InferAttributes<Policy>,
42 InferCreationAttributes<Policy>
43 > {
44 public declare id: string;
45 public declare name: string;
46 public declare policyText?: string | undefined;
47
48 public declare orgId: string;
49
50 public declare parentId: string | undefined;
51 public declare parent?: Policy;
52 public declare getParent: BelongsToGetAssociationMixin<Policy>;
53
54 public declare getChildren: HasManyGetAssociationsMixin<Policy>;
55 public declare addChildren: HasManyAddAssociationsMixin<Policy, string>;
56 public declare setChildren: HasManySetAssociationsMixin<Policy, string>;
57
58 public declare penalty: UserPenaltySeverity;
59 public declare userStrikeCount: number;
60 public declare applyUserStrikeCountConfigToChildren: boolean;
61 public declare semanticVersion: number;
62 public declare policyType: PolicyType | undefined;
63
64 public declare rules?: Rule[];
65
66 // eslint-disable-next-line @typescript-eslint/no-explicit-any
67 static associate(models: { [key: string]: any }) {
68 Policy.belongsTo(models.Org, { as: 'org' });
69 Policy.hasMany(models.Policy, {
70 as: 'children',
71 foreignKey: 'parentId',
72 });
73 Policy.belongsTo(models.Policy, {
74 as: 'parent',
75 foreignKey: 'parentId',
76 });
77 Policy.belongsToMany(models.Rule, {
78 through: 'rules_and_policies',
79 as: 'rules',
80 });
81 }
82
83 static async getPoliciesForRuleIds(ruleIds: readonly string[]) {
84 const results = await Policy.findAll({
85 where: { '$rules.id$': ruleIds },
86 include: [{ association: 'rules', attributes: ['id'] }],
87 });
88
89 const ruleIdPolicyPairs = results.flatMap((policy) =>
90 policy.rules!.map((rule) => [rule.id, policy] as const),
91 );
92
93 return mapValues(
94 groupBy(ruleIdPolicyPairs, ([ruleId]) => ruleId),
95 (pairs) => pairs.map(([, policy]) => policy),
96 ) as { [ruleId: string]: Policy[] | undefined };
97 }
98 }
99
100 /* Fields */
101 Policy.init(
102 {
103 id: {
104 type: DataTypes.STRING,
105 primaryKey: true,
106 },
107 orgId: {
108 type: DataTypes.STRING,
109 allowNull: false,
110 },
111 name: {
112 type: DataTypes.STRING,
113 allowNull: true,
114 },
115 policyText: {
116 type: DataTypes.STRING,
117 allowNull: true,
118 },
119 parentId: {
120 allowNull: true,
121 type: DataTypes.STRING,
122 },
123 policyType: {
124 type: DataTypes.ENUM(...Object.values(PolicyType)),
125 allowNull: true,
126 },
127 penalty: {
128 type: DataTypes.STRING,
129 defaultValue: UserPenaltySeverity.NONE,
130 allowNull: false,
131 validate: {
132 notNull: true,
133 isIn: [Object.values(UserPenaltySeverity)],
134 },
135 },
136 userStrikeCount: {
137 allowNull: false,
138 type: DataTypes.INTEGER,
139 defaultValue: 1,
140 },
141 applyUserStrikeCountConfigToChildren: {
142 allowNull: false,
143 type: DataTypes.BOOLEAN,
144 defaultValue: false,
145 },
146 semanticVersion: {
147 allowNull: false,
148 type: DataTypes.INTEGER,
149 defaultValue: 1,
150 },
151 },
152 {
153 sequelize,
154 modelName: 'policy',
155 underscored: true,
156 tableName: 'policies',
157 },
158 );
159
160 return Policy;
161};
162
163export default makePolicy;