@echovisionlab/geul-telemetry 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +6 -0
- package/README.md +38 -0
- package/fixtures/audit-records.json +316 -0
- package/fixtures/collaboration-checkpoint-entity-types.json +15 -0
- package/fixtures/domain-audit-actions.json +132 -0
- package/fixtures/domain-audit-wire-parity.json +1751 -0
- package/fixtures/http-request-results.json +21 -0
- package/fixtures/job-catalog.json +23 -0
- package/fixtures/locale-content-audit.json +126 -0
- package/fixtures/log-redaction.json +72 -0
- package/fixtures/request-record.json +14 -0
- package/fixtures/security-access-records.json +61 -0
- package/fixtures/service-identities.json +11 -0
- package/fixtures/system-catalog.json +28 -0
- package/fixtures/translation-job-terminal-catalog.json +45 -0
- package/fixtures/translation-settings-fields.json +1 -0
- package/package.json +61 -0
- package/policy/log-redaction.json +113 -0
- package/src/actor.ts +81 -0
- package/src/audit/account.ts +206 -0
- package/src/audit/actor-policy.ts +63 -0
- package/src/audit/artist-label.ts +312 -0
- package/src/audit/attributes.ts +107 -0
- package/src/audit/builder.ts +25 -0
- package/src/audit/catalog.ts +116 -0
- package/src/audit/content.ts +723 -0
- package/src/audit/email-authoring.ts +220 -0
- package/src/audit/file.ts +168 -0
- package/src/audit/form.ts +229 -0
- package/src/audit/integrations.ts +207 -0
- package/src/audit/locale-code.ts +6 -0
- package/src/audit/locale-content.ts +358 -0
- package/src/audit/member-account.ts +313 -0
- package/src/audit/member.ts +171 -0
- package/src/audit/program.ts +339 -0
- package/src/audit/reference-data.ts +230 -0
- package/src/audit/reference-entities.ts +332 -0
- package/src/audit/relation-download-policy.ts +292 -0
- package/src/audit/release-campaign.ts +422 -0
- package/src/audit/series-type.ts +298 -0
- package/src/audit/settings.ts +306 -0
- package/src/audit/source-locale.ts +337 -0
- package/src/audit/types.ts +9 -0
- package/src/audit.ts +21 -0
- package/src/authentication.ts +183 -0
- package/src/error-metadata.ts +11 -0
- package/src/index.ts +11 -0
- package/src/job.ts +56 -0
- package/src/log-attribute-policy.ts +21 -0
- package/src/otel.ts +64 -0
- package/src/records.ts +1244 -0
- package/src/redaction.ts +35 -0
- package/src/request-context.ts +73 -0
- package/src/request-id.ts +13 -0
- package/src/request-result.ts +73 -0
- package/src/request.ts +60 -0
- package/src/system.ts +355 -0
- package/src/trace.ts +13 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { isRequestId } from "../request-context.ts";
|
|
2
|
+
import type { AuditRecord, AuditState } from "../records.ts";
|
|
3
|
+
import { assertOnlyAuditAttributes } from "./attributes.ts";
|
|
4
|
+
import { requireCatalogTarget } from "./catalog.ts";
|
|
5
|
+
|
|
6
|
+
function isIdentifier(value: string): boolean {
|
|
7
|
+
return value.length > 0 && value.length <= 255 && value.trim() === value;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isEmail(value: string): boolean {
|
|
11
|
+
return (
|
|
12
|
+
value.length <= 320 &&
|
|
13
|
+
value.trim() === value &&
|
|
14
|
+
!/[\s]/.test(value) &&
|
|
15
|
+
value.split("@").length === 2
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isBoundedCode(value: string): boolean {
|
|
20
|
+
return /^[a-z0-9_]{1,64}$/.test(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function requireSortedUnique(
|
|
24
|
+
name: string,
|
|
25
|
+
values: readonly string[],
|
|
26
|
+
valid: (value: string) => boolean,
|
|
27
|
+
): void {
|
|
28
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
29
|
+
const value = values[index];
|
|
30
|
+
if (!valid(value)) throw new TypeError(`${name} contains an invalid value`);
|
|
31
|
+
if (index > 0 && values[index - 1] >= value)
|
|
32
|
+
throw new TypeError(`${name} must be sorted and unique`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function requireOnly(record: AuditRecord, ...allowed: readonly string[]): void {
|
|
37
|
+
assertOnlyAuditAttributes(record, allowed);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function requireTransition(
|
|
41
|
+
record: AuditRecord,
|
|
42
|
+
field: string,
|
|
43
|
+
allowed: readonly (readonly [AuditState, AuditState])[],
|
|
44
|
+
): void {
|
|
45
|
+
if (
|
|
46
|
+
record.previous_state === undefined ||
|
|
47
|
+
record.new_state === undefined ||
|
|
48
|
+
record.previous_state === record.new_state ||
|
|
49
|
+
!allowed.some(
|
|
50
|
+
([previous, next]) =>
|
|
51
|
+
previous === record.previous_state && next === record.new_state,
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
throw new TypeError(
|
|
55
|
+
`audit action ${record.action} field ${field} rejects transition ${String(record.previous_state)} to ${String(record.new_state)}`,
|
|
56
|
+
);
|
|
57
|
+
requireOnly(record, "changed_fields", "previous_state", "new_state");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validateMember(record: AuditRecord): void {
|
|
61
|
+
requireCatalogTarget(record.action, record.target_type, record.target_id);
|
|
62
|
+
if (
|
|
63
|
+
record.changed_fields?.length === 2 &&
|
|
64
|
+
record.changed_fields[0] === "nickname" &&
|
|
65
|
+
record.changed_fields[1] === "onboarded"
|
|
66
|
+
) {
|
|
67
|
+
if (
|
|
68
|
+
record.actor_kind !== "member" ||
|
|
69
|
+
record.actor_member_id !== record.target_id
|
|
70
|
+
)
|
|
71
|
+
throw new TypeError(
|
|
72
|
+
"member.updated onboarding requires the committed member as actor and target",
|
|
73
|
+
);
|
|
74
|
+
if (
|
|
75
|
+
record.nickname === undefined ||
|
|
76
|
+
record.nickname === "" ||
|
|
77
|
+
record.nickname.trim() !== record.nickname ||
|
|
78
|
+
[...record.nickname].length > 100
|
|
79
|
+
)
|
|
80
|
+
throw new TypeError(
|
|
81
|
+
"member.updated onboarding requires a trimmed nickname between 1 and 100 characters",
|
|
82
|
+
);
|
|
83
|
+
requireOnly(record, "changed_fields", "nickname");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const fields = record.changed_fields;
|
|
87
|
+
if (fields === undefined || fields.length === 0)
|
|
88
|
+
throw new TypeError(
|
|
89
|
+
"member.updated requires a catalog changed_fields variant",
|
|
90
|
+
);
|
|
91
|
+
requireSortedUnique("changed_fields", fields, isBoundedCode);
|
|
92
|
+
const field = fields[0];
|
|
93
|
+
if (field === "role") {
|
|
94
|
+
if (
|
|
95
|
+
!isBoundedCode(record.previous_role ?? "") ||
|
|
96
|
+
!isBoundedCode(record.new_role ?? "") ||
|
|
97
|
+
record.previous_role === record.new_role
|
|
98
|
+
)
|
|
99
|
+
throw new TypeError(
|
|
100
|
+
"member.updated role requires distinct previous_role and new_role",
|
|
101
|
+
);
|
|
102
|
+
requireOnly(record, "changed_fields", "previous_role", "new_role");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (field === "avatar") {
|
|
106
|
+
if (
|
|
107
|
+
(record.collection_operation !== "added" &&
|
|
108
|
+
record.collection_operation !== "removed") ||
|
|
109
|
+
!isIdentifier(record.asset_id ?? "")
|
|
110
|
+
)
|
|
111
|
+
throw new TypeError(
|
|
112
|
+
"member avatar requires collection operation and asset_id",
|
|
113
|
+
);
|
|
114
|
+
requireOnly(record, "changed_fields", "collection_operation", "asset_id");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (field === "tags") {
|
|
118
|
+
if (record.tag_ids === undefined)
|
|
119
|
+
throw new TypeError("member tags requires tag_ids");
|
|
120
|
+
requireSortedUnique("tag_ids", record.tag_ids, isIdentifier);
|
|
121
|
+
requireOnly(record, "changed_fields", "tag_ids");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const profile = new Set(["bio", "nickname", "social_links", "website"]);
|
|
125
|
+
if (fields.every((candidate) => profile.has(candidate))) {
|
|
126
|
+
if (fields.includes("nickname")) {
|
|
127
|
+
if (
|
|
128
|
+
record.nickname === undefined ||
|
|
129
|
+
record.nickname === "" ||
|
|
130
|
+
record.nickname.trim() !== record.nickname
|
|
131
|
+
)
|
|
132
|
+
throw new TypeError("member nickname requires a trimmed value");
|
|
133
|
+
} else if (record.nickname !== undefined)
|
|
134
|
+
throw new TypeError("member nickname requires changed_fields nickname");
|
|
135
|
+
requireOnly(record, "changed_fields", "nickname");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const preference = new Set(["cookie_consent", "preferred_locale"]);
|
|
139
|
+
if (fields.every((candidate) => preference.has(candidate))) {
|
|
140
|
+
if (fields.includes("preferred_locale")) {
|
|
141
|
+
if (!isBoundedCode(record.preferred_locale ?? ""))
|
|
142
|
+
throw new TypeError(
|
|
143
|
+
"member preferred_locale requires a bounded locale",
|
|
144
|
+
);
|
|
145
|
+
} else if (record.preferred_locale !== undefined)
|
|
146
|
+
throw new TypeError(
|
|
147
|
+
"member preferred_locale requires changed_fields preferred_locale",
|
|
148
|
+
);
|
|
149
|
+
if (fields.includes("cookie_consent")) {
|
|
150
|
+
if (!isIdentifier(record.consent_id ?? ""))
|
|
151
|
+
throw new TypeError("member cookie_consent requires consent_id");
|
|
152
|
+
} else if (record.consent_id !== undefined)
|
|
153
|
+
throw new TypeError(
|
|
154
|
+
"member consent_id requires changed_fields cookie_consent",
|
|
155
|
+
);
|
|
156
|
+
requireOnly(record, "changed_fields", "preferred_locale", "consent_id");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (fields.length !== 1 || field !== "status")
|
|
160
|
+
throw new TypeError(
|
|
161
|
+
"member.updated requires a catalog changed_fields variant",
|
|
162
|
+
);
|
|
163
|
+
requireTransition(record, "status", [
|
|
164
|
+
["active", "banned"],
|
|
165
|
+
["banned", "active"],
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function validateAccount(record: AuditRecord): void {
|
|
170
|
+
requireCatalogTarget(record.action, record.target_type, record.target_id);
|
|
171
|
+
if (record.changed_fields?.length !== 1)
|
|
172
|
+
throw new TypeError("account.updated requires one catalog changed_field");
|
|
173
|
+
const field = record.changed_fields[0];
|
|
174
|
+
const requireCollection = (): void => {
|
|
175
|
+
if (
|
|
176
|
+
record.collection_operation !== "added" &&
|
|
177
|
+
record.collection_operation !== "removed"
|
|
178
|
+
)
|
|
179
|
+
throw new TypeError(
|
|
180
|
+
`audit action ${record.action} requires a catalog collection_operation`,
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
switch (field) {
|
|
184
|
+
case "canonical_email":
|
|
185
|
+
if (
|
|
186
|
+
!isEmail(record.previous_email ?? "") ||
|
|
187
|
+
!isEmail(record.new_email ?? "") ||
|
|
188
|
+
record.previous_email === record.new_email
|
|
189
|
+
)
|
|
190
|
+
throw new TypeError(
|
|
191
|
+
"account.updated canonical_email requires distinct previous_email and new_email",
|
|
192
|
+
);
|
|
193
|
+
requireOnly(record, "changed_fields", "previous_email", "new_email");
|
|
194
|
+
return;
|
|
195
|
+
case "login_emails":
|
|
196
|
+
if (!isEmail(record.email ?? ""))
|
|
197
|
+
throw new TypeError("account.updated login_emails requires email");
|
|
198
|
+
requireCollection();
|
|
199
|
+
requireOnly(record, "changed_fields", "collection_operation", "email");
|
|
200
|
+
return;
|
|
201
|
+
case "social_logins":
|
|
202
|
+
if (
|
|
203
|
+
!isBoundedCode(record.provider ?? "") ||
|
|
204
|
+
!isIdentifier(record.provider_subject ?? "")
|
|
205
|
+
)
|
|
206
|
+
throw new TypeError(
|
|
207
|
+
"account.updated social_logins requires provider and provider_subject",
|
|
208
|
+
);
|
|
209
|
+
requireCollection();
|
|
210
|
+
requireOnly(
|
|
211
|
+
record,
|
|
212
|
+
"changed_fields",
|
|
213
|
+
"collection_operation",
|
|
214
|
+
"provider",
|
|
215
|
+
"provider_subject",
|
|
216
|
+
);
|
|
217
|
+
return;
|
|
218
|
+
case "passkeys":
|
|
219
|
+
if (record.passkey_ids === undefined || record.passkey_ids.length === 0)
|
|
220
|
+
throw new TypeError("account.updated passkeys requires passkey_ids");
|
|
221
|
+
requireSortedUnique("passkey_ids", record.passkey_ids, isIdentifier);
|
|
222
|
+
requireCollection();
|
|
223
|
+
requireOnly(
|
|
224
|
+
record,
|
|
225
|
+
"changed_fields",
|
|
226
|
+
"collection_operation",
|
|
227
|
+
"passkey_ids",
|
|
228
|
+
);
|
|
229
|
+
return;
|
|
230
|
+
case "sessions":
|
|
231
|
+
if (record.collection_operation !== "removed")
|
|
232
|
+
throw new TypeError(
|
|
233
|
+
"account.updated sessions requires collection_operation removed",
|
|
234
|
+
);
|
|
235
|
+
if (
|
|
236
|
+
record.session_scope !== "current" &&
|
|
237
|
+
record.session_scope !== "one" &&
|
|
238
|
+
record.session_scope !== "others"
|
|
239
|
+
)
|
|
240
|
+
throw new TypeError(
|
|
241
|
+
"account.updated sessions requires a catalog session_scope",
|
|
242
|
+
);
|
|
243
|
+
if (record.session_ids === undefined)
|
|
244
|
+
throw new TypeError("account.updated sessions requires session_ids");
|
|
245
|
+
requireSortedUnique("session_ids", record.session_ids, isRequestId);
|
|
246
|
+
if (
|
|
247
|
+
((record.session_scope === "current" ||
|
|
248
|
+
record.session_scope === "one") &&
|
|
249
|
+
record.session_ids.length !== 1) ||
|
|
250
|
+
(record.session_scope === "others" && record.session_ids.length === 0)
|
|
251
|
+
)
|
|
252
|
+
throw new TypeError(
|
|
253
|
+
`account.updated sessions scope ${record.session_scope} has invalid session_ids`,
|
|
254
|
+
);
|
|
255
|
+
requireOnly(
|
|
256
|
+
record,
|
|
257
|
+
"changed_fields",
|
|
258
|
+
"collection_operation",
|
|
259
|
+
"session_scope",
|
|
260
|
+
"session_ids",
|
|
261
|
+
);
|
|
262
|
+
return;
|
|
263
|
+
case "newsletter_subscription":
|
|
264
|
+
requireTransition(record, "newsletter_subscription", [
|
|
265
|
+
["subscribed", "unsubscribed"],
|
|
266
|
+
["unsubscribed", "subscribed"],
|
|
267
|
+
]);
|
|
268
|
+
return;
|
|
269
|
+
case "deletion_state":
|
|
270
|
+
requireTransition(record, "deletion_state", [
|
|
271
|
+
["none", "confirmation_pending"],
|
|
272
|
+
["cancelled", "confirmation_pending"],
|
|
273
|
+
["recovered", "confirmation_pending"],
|
|
274
|
+
["confirmation_pending", "scheduled"],
|
|
275
|
+
["none", "scheduled"],
|
|
276
|
+
["cancelled", "scheduled"],
|
|
277
|
+
["recovered", "scheduled"],
|
|
278
|
+
["scheduled", "cancelled"],
|
|
279
|
+
["recovery_confirmation_pending", "recovered"],
|
|
280
|
+
]);
|
|
281
|
+
return;
|
|
282
|
+
default:
|
|
283
|
+
throw new TypeError(`account.updated rejects changed_field ${field}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function validateMemberAccountAuditRecord(record: AuditRecord): boolean {
|
|
288
|
+
switch (record.action) {
|
|
289
|
+
case "member.updated":
|
|
290
|
+
validateMember(record);
|
|
291
|
+
return true;
|
|
292
|
+
case "member_tag.created":
|
|
293
|
+
case "member_tag.deleted":
|
|
294
|
+
requireCatalogTarget(record.action, record.target_type, record.target_id);
|
|
295
|
+
if (
|
|
296
|
+
record.tag_name === undefined ||
|
|
297
|
+
record.tag_name === "" ||
|
|
298
|
+
record.tag_name.trim() !== record.tag_name
|
|
299
|
+
)
|
|
300
|
+
throw new TypeError("member tag requires trimmed tag_name");
|
|
301
|
+
requireOnly(record, "tag_name");
|
|
302
|
+
return true;
|
|
303
|
+
case "account.updated":
|
|
304
|
+
validateAccount(record);
|
|
305
|
+
return true;
|
|
306
|
+
case "account.deleted":
|
|
307
|
+
requireCatalogTarget(record.action, record.target_type, record.target_id);
|
|
308
|
+
requireOnly(record);
|
|
309
|
+
return true;
|
|
310
|
+
default:
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { AuditRecord, AuditState } from "../records.ts";
|
|
2
|
+
import { canonicalAuditValues } from "./attributes.ts";
|
|
3
|
+
import { buildAuditRecord } from "./builder.ts";
|
|
4
|
+
import type { AuditMetadata } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
function buildMemberAuditRecord(
|
|
7
|
+
metadata: AuditMetadata,
|
|
8
|
+
memberId: string,
|
|
9
|
+
attributes: Parameters<typeof buildAuditRecord>[2],
|
|
10
|
+
): AuditRecord {
|
|
11
|
+
return buildAuditRecord(
|
|
12
|
+
metadata,
|
|
13
|
+
{ action: "member.updated", target_type: "member", target_id: memberId },
|
|
14
|
+
attributes,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildMemberOnboardingCompletedAuditRecord(
|
|
19
|
+
metadata: AuditMetadata,
|
|
20
|
+
memberId: string,
|
|
21
|
+
nickname: string,
|
|
22
|
+
): AuditRecord {
|
|
23
|
+
return buildMemberAuditRecord(metadata, memberId, {
|
|
24
|
+
changed_fields: ["nickname", "onboarded"],
|
|
25
|
+
nickname,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function buildMemberRoleUpdatedAuditRecord(
|
|
30
|
+
metadata: AuditMetadata,
|
|
31
|
+
memberId: string,
|
|
32
|
+
previousRole: string,
|
|
33
|
+
newRole: string,
|
|
34
|
+
): AuditRecord {
|
|
35
|
+
return buildMemberAuditRecord(metadata, memberId, {
|
|
36
|
+
changed_fields: ["role"],
|
|
37
|
+
previous_role: previousRole,
|
|
38
|
+
new_role: newRole,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function buildMemberProfileUpdatedAuditRecord(
|
|
43
|
+
metadata: AuditMetadata,
|
|
44
|
+
memberId: string,
|
|
45
|
+
fields: readonly string[],
|
|
46
|
+
nickname: string,
|
|
47
|
+
): AuditRecord {
|
|
48
|
+
return buildMemberAuditRecord(metadata, memberId, {
|
|
49
|
+
changed_fields: canonicalAuditValues(fields),
|
|
50
|
+
...(nickname === "" ? {} : { nickname }),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function buildMemberAvatarUpdatedAuditRecord(
|
|
55
|
+
metadata: AuditMetadata,
|
|
56
|
+
memberId: string,
|
|
57
|
+
operation: "added" | "removed",
|
|
58
|
+
assetId: string,
|
|
59
|
+
): AuditRecord {
|
|
60
|
+
return buildMemberAuditRecord(metadata, memberId, {
|
|
61
|
+
changed_fields: ["avatar"],
|
|
62
|
+
collection_operation: operation,
|
|
63
|
+
asset_id: assetId,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function buildMemberPreferencesUpdatedAuditRecord(
|
|
68
|
+
metadata: AuditMetadata,
|
|
69
|
+
memberId: string,
|
|
70
|
+
fields: readonly string[],
|
|
71
|
+
preferredLocale: string,
|
|
72
|
+
consentId: string,
|
|
73
|
+
): AuditRecord {
|
|
74
|
+
return buildMemberAuditRecord(metadata, memberId, {
|
|
75
|
+
changed_fields: canonicalAuditValues(fields),
|
|
76
|
+
...(preferredLocale === "" ? {} : { preferred_locale: preferredLocale }),
|
|
77
|
+
...(consentId === "" ? {} : { consent_id: consentId }),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function buildMemberTagsUpdatedAuditRecord(
|
|
82
|
+
metadata: AuditMetadata,
|
|
83
|
+
memberId: string,
|
|
84
|
+
tagIds: readonly string[],
|
|
85
|
+
): AuditRecord {
|
|
86
|
+
return buildMemberAuditRecord(metadata, memberId, {
|
|
87
|
+
changed_fields: ["tags"],
|
|
88
|
+
tag_ids: canonicalAuditValues(tagIds),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildMemberTagAuditRecord(
|
|
93
|
+
metadata: AuditMetadata,
|
|
94
|
+
action: "member_tag.created" | "member_tag.deleted",
|
|
95
|
+
tagId: string,
|
|
96
|
+
tagName: string,
|
|
97
|
+
): AuditRecord {
|
|
98
|
+
return buildAuditRecord(
|
|
99
|
+
metadata,
|
|
100
|
+
{
|
|
101
|
+
action,
|
|
102
|
+
target_type: "member_tag",
|
|
103
|
+
target_id: tagId,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
tag_name: tagName,
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function buildMemberTagCreatedAuditRecord(
|
|
112
|
+
metadata: AuditMetadata,
|
|
113
|
+
tagId: string,
|
|
114
|
+
tagName: string,
|
|
115
|
+
): AuditRecord {
|
|
116
|
+
return buildMemberTagAuditRecord(
|
|
117
|
+
metadata,
|
|
118
|
+
"member_tag.created",
|
|
119
|
+
tagId,
|
|
120
|
+
tagName,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function buildMemberTagDeletedAuditRecord(
|
|
125
|
+
metadata: AuditMetadata,
|
|
126
|
+
tagId: string,
|
|
127
|
+
tagName: string,
|
|
128
|
+
): AuditRecord {
|
|
129
|
+
return buildMemberTagAuditRecord(
|
|
130
|
+
metadata,
|
|
131
|
+
"member_tag.deleted",
|
|
132
|
+
tagId,
|
|
133
|
+
tagName,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function buildMemberStatusUpdatedAuditRecord(
|
|
138
|
+
metadata: AuditMetadata,
|
|
139
|
+
memberId: string,
|
|
140
|
+
previousState: AuditState,
|
|
141
|
+
newState: AuditState,
|
|
142
|
+
): AuditRecord {
|
|
143
|
+
return buildMemberAuditRecord(metadata, memberId, {
|
|
144
|
+
changed_fields: ["status"],
|
|
145
|
+
previous_state: previousState,
|
|
146
|
+
new_state: newState,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function buildMemberBannedAuditRecord(
|
|
151
|
+
metadata: AuditMetadata,
|
|
152
|
+
memberId: string,
|
|
153
|
+
): AuditRecord {
|
|
154
|
+
return buildMemberStatusUpdatedAuditRecord(
|
|
155
|
+
metadata,
|
|
156
|
+
memberId,
|
|
157
|
+
"active",
|
|
158
|
+
"banned",
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
export function buildMemberUnbannedAuditRecord(
|
|
162
|
+
metadata: AuditMetadata,
|
|
163
|
+
memberId: string,
|
|
164
|
+
): AuditRecord {
|
|
165
|
+
return buildMemberStatusUpdatedAuditRecord(
|
|
166
|
+
metadata,
|
|
167
|
+
memberId,
|
|
168
|
+
"banned",
|
|
169
|
+
"active",
|
|
170
|
+
);
|
|
171
|
+
}
|