mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
refactor(api): emit moderation events and apply account actions (#3031)
This commit is contained in:
1 parent
c488906131
commit
364084c819
385 files changed
+8994
-22221
No files matched your search
@@ -10,24 +10,13 @@ import {
|
||||
BanUrlDomainRequest,
|
||||
BanUrlRequest,
|
||||
CheckAvatarHashRequest,
|
||||
SuspiciousEmailDomainRequest,
|
||||
} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
||||
import {createQueryIntegerType} from '@fluxer/schema/src/primitives/QueryValidators';
|
||||
import {createStringType, Int32Type, SnowflakeStringType} from '@fluxer/schema/src/primitives/SchemaPrimitives';
|
||||
import {z} from 'zod';
|
||||
|
||||
export const AdminBlocklistListType = z
|
||||
.enum([
|
||||
'ip',
|
||||
'email',
|
||||
'email-domain-suspicious',
|
||||
'phrase',
|
||||
'url',
|
||||
'url-domain',
|
||||
'file-sha',
|
||||
'avatar-hash',
|
||||
'profile-substring',
|
||||
])
|
||||
.enum(['ip', 'email', 'phrase', 'url', 'url-domain', 'file-sha', 'avatar-hash', 'profile-substring'])
|
||||
.describe('The blocklist an entry belongs to');
|
||||
|
||||
export type AdminBlocklistListType = z.infer<typeof AdminBlocklistListType>;
|
||||
@@ -119,7 +108,6 @@ export const AdminBlocklistEntryCreateRequest = z
|
||||
.union([
|
||||
BanIpRequest,
|
||||
BanEmailRequest,
|
||||
SuspiciousEmailDomainRequest,
|
||||
BanPhraseRequest,
|
||||
BanUrlRequest,
|
||||
BanUrlDomainRequest,
|
||||
|
||||
@@ -309,19 +309,6 @@ export const BanEmailRequest = z.object({
|
||||
|
||||
export type BanEmailRequest = z.infer<typeof BanEmailRequest>;
|
||||
|
||||
export const SuspiciousEmailDomainRequest = z.object({
|
||||
domain: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(253)
|
||||
.regex(/^[a-zA-Z0-9][a-zA-Z0-9\-.]*\.[a-zA-Z]{2,}$/, 'Must be a valid domain name (e.g. example.com)')
|
||||
.describe(
|
||||
'Email domain to flag as suspicious (e.g. mail.ru). Registrants from this domain will be required to verify a phone number.',
|
||||
),
|
||||
});
|
||||
|
||||
export type SuspiciousEmailDomainRequest = z.infer<typeof SuspiciousEmailDomainRequest>;
|
||||
|
||||
export const BanPhraseRequest = z.object({
|
||||
phrase: createStringType(1, 500).describe(
|
||||
'Phrase to ban. Matching is case-insensitive and also normalizes common bypass tricks such as inserted whitespace, punctuation, invisible characters, and compatibility glyphs.',
|
||||
@@ -585,11 +572,6 @@ const InstancePolicyResponse = z.object({
|
||||
youtube: z.boolean(),
|
||||
bluesky: z.boolean(),
|
||||
}),
|
||||
deferred_phone_gate: z.object({
|
||||
enabled: z.boolean(),
|
||||
window_hours: z.number(),
|
||||
member_threshold: z.number(),
|
||||
}),
|
||||
});
|
||||
|
||||
const EmailProviderSchema = z.enum(['smtp', 'none']);
|
||||
@@ -700,13 +682,6 @@ const InstancePolicyUpdateSchema = z.object({
|
||||
bluesky_enabled: z.boolean().nullish(),
|
||||
})
|
||||
.nullish(),
|
||||
deferred_phone_gate: z
|
||||
.object({
|
||||
enabled: z.boolean().optional(),
|
||||
window_hours: z.number().positive().max(8760).optional(),
|
||||
member_threshold: z.number().int().positive().max(1_000_000).optional(),
|
||||
})
|
||||
.nullish(),
|
||||
});
|
||||
|
||||
export const InstanceConfigUpdateRequest = z.object({
|
||||
|
||||
@@ -63,14 +63,20 @@ export const UserAdminResponseSchema = z.object({
|
||||
'Suspicious activity indicators',
|
||||
'SuspiciousActivityFlags',
|
||||
),
|
||||
phone_verification_deferred: z
|
||||
.boolean()
|
||||
.describe('Whether a stored phone requirement is deferred until the user joins a discoverable or large community'),
|
||||
phone_verification_deferred: z.boolean().describe('Whether a stored phone requirement is deferred and not enforced'),
|
||||
temp_banned_until: z.string().nullable(),
|
||||
pending_deletion_at: z.string().nullable(),
|
||||
pending_bulk_message_deletion_at: z.string().nullable(),
|
||||
deletion_reason_code: Int32Type.nullable(),
|
||||
deletion_public_reason: z.string().nullable(),
|
||||
deletion_audit_log_reason: z
|
||||
.string()
|
||||
.nullable()
|
||||
.describe('Private reason recorded with the pending deletion, null without the audit log view permission'),
|
||||
deletion_scheduled_by: SnowflakeStringType.nullable().describe(
|
||||
'ID of the account that scheduled the pending deletion, null when it was not recorded',
|
||||
),
|
||||
deletion_scheduled_at: z.string().nullable().describe('ISO 8601 timestamp when the pending deletion was scheduled'),
|
||||
acls: z.array(z.string()).max(ADMIN_ACL_COUNT),
|
||||
traits: z.array(z.string()).max(100),
|
||||
has_totp: z.boolean(),
|
||||
@@ -334,6 +340,12 @@ export const ScheduleAccountDeletionRequest = z.object({
|
||||
.max(365)
|
||||
.default(60)
|
||||
.describe('Number of days until the account is deleted'),
|
||||
replace_pending_deletion_at: z.iso
|
||||
.datetime()
|
||||
.optional()
|
||||
.describe(
|
||||
'pending_deletion_at of the deletion this request replaces. Required when a deletion is already scheduled for the account',
|
||||
),
|
||||
});
|
||||
|
||||
export type ScheduleAccountDeletionRequest = z.infer<typeof ScheduleAccountDeletionRequest>;
|
||||
@@ -420,7 +432,10 @@ export const BulkUpdateUserFlagsRequest = z.object({
|
||||
|
||||
export type BulkUpdateUserFlagsRequest = z.infer<typeof BulkUpdateUserFlagsRequest>;
|
||||
|
||||
export const BulkScheduleUserDeletionRequest = ScheduleAccountDeletionRequest.omit({user_id: true}).extend({
|
||||
export const BulkScheduleUserDeletionRequest = ScheduleAccountDeletionRequest.omit({
|
||||
user_id: true,
|
||||
replace_pending_deletion_at: true,
|
||||
}).extend({
|
||||
user_ids: z.array(SnowflakeType).max(1000).describe('List of user IDs to schedule deletion for'),
|
||||
days_until_deletion: ScheduleAccountDeletionRequest.shape.days_until_deletion.describe(
|
||||
'Number of days until the accounts are deleted',
|
||||
@@ -629,6 +644,22 @@ export const AdminUserDeletionScheduleRequest = ScheduleAccountDeletionRequest.o
|
||||
|
||||
export type AdminUserDeletionScheduleRequest = z.infer<typeof AdminUserDeletionScheduleRequest>;
|
||||
|
||||
export const AdminUserDeletionCancelRequest = z.object({
|
||||
expected_pending_deletion_at: z.iso
|
||||
.datetime()
|
||||
.describe('pending_deletion_at of the deletion being cancelled, as shown on the account'),
|
||||
notify_user: z.boolean().default(false).describe('Whether to email the user that the deletion was cancelled'),
|
||||
});
|
||||
|
||||
export type AdminUserDeletionCancelRequest = z.infer<typeof AdminUserDeletionCancelRequest>;
|
||||
|
||||
export const AdminUserBanNoteRequest = z.object({
|
||||
ban_audit_log_id: SnowflakeType.describe('Audit log entry of the current ban that the note refers to'),
|
||||
note: createStringType(1, 512).describe('Note to append to the ban. Recorded as the reason of a new audit log entry'),
|
||||
});
|
||||
|
||||
export type AdminUserBanNoteRequest = z.infer<typeof AdminUserBanNoteRequest>;
|
||||
|
||||
export const AdminUserAclsRequest = SetUserAclsRequest.omit({user_id: true});
|
||||
|
||||
export type AdminUserAclsRequest = z.infer<typeof AdminUserAclsRequest>;
|
||||
|
||||
@@ -441,9 +441,7 @@ const PhoneSendVerificationInboundChallengeResponse = z.object({
|
||||
challenge_code: createStringType(4, 12).describe('The numeric code the user must text to our number'),
|
||||
our_number: createStringType(4, 32).describe('The Twilio number the user must text the code to (E.164)'),
|
||||
expires_at: z.iso.datetime().describe('ISO 8601 timestamp when this inbound challenge expires'),
|
||||
reason: z
|
||||
.enum(['voip', 'canadian', 'unknown_line_type', 'expensive_destination', 'account_forced', 'behavioural_risk'])
|
||||
.describe('Why inbound verification is required'),
|
||||
reason: z.enum(['verification_required']).describe('Always verification_required'),
|
||||
});
|
||||
|
||||
export const PhoneSendVerificationResponse = z.union([
|
||||
|
||||
@@ -137,9 +137,7 @@ export const UserPrivateResponse = UserPartialResponse.extend({
|
||||
force_inbound_phone_verification: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Whether this account is forced through the inbound (expensive-destination) phone verification flow regardless of prefix, for debugging',
|
||||
),
|
||||
.describe('Whether this account is forced through the inbound phone verification flow, for debugging'),
|
||||
password_last_changed_at: z.string().nullable().describe('ISO8601 timestamp of the last password change'),
|
||||
last_voice_activity_sharing_change_at: z
|
||||
.string()
|
||||
@@ -653,13 +651,9 @@ const PhoneGateEscapeGuildResponse = z.object({
|
||||
});
|
||||
|
||||
export const PhoneGateEscapePreviewResponse = z.object({
|
||||
available: z.boolean().describe('Whether this account can set the deferred phone verification check aside right now'),
|
||||
guilds: z
|
||||
.array(PhoneGateEscapeGuildResponse)
|
||||
.describe('Communities that trigger the phone check and will be left when the escape runs'),
|
||||
owned_guilds: z
|
||||
.array(PhoneGateEscapeGuildResponse)
|
||||
.describe('Communities that trigger the phone check but are owned by this user, so they are kept'),
|
||||
available: z.boolean().describe('Whether this account can set a due phone verification requirement aside right now'),
|
||||
guilds: z.array(PhoneGateEscapeGuildResponse).describe('Always empty, the escape leaves no community'),
|
||||
owned_guilds: z.array(PhoneGateEscapeGuildResponse).describe('Always empty, the escape leaves no community'),
|
||||
});
|
||||
|
||||
export type PhoneGateEscapePreviewResponse = z.infer<typeof PhoneGateEscapePreviewResponse>;
|
||||
|
||||
Reference in new issue
Block a user