From 3e16e8795b3d56c6a06d0cd06307cf0313ca791f Mon Sep 17 00:00:00 2001 From: Claude Code Date: Thu, 19 Mar 2026 22:08:01 -0700 Subject: [PATCH] =?UTF-8?q?feat(platform-admin):=20=E2=9C=A8=20Add=20conte?= =?UTF-8?q?nt=20moderation=20schema=20and=20app=20module=20registration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- .../backend-api/src/app.module.ts | 2 + .../1710000000000-ContentModerationSchema.ts | 365 ++++++++++++++++++ .../src/database/migrations/index.ts | 1 + 3 files changed, 368 insertions(+) create mode 100644 features/platform-admin/backend-api/src/database/migrations/1710000000000-ContentModerationSchema.ts diff --git a/features/platform-admin/backend-api/src/app.module.ts b/features/platform-admin/backend-api/src/app.module.ts index a02ee630d..18f5761cc 100755 --- a/features/platform-admin/backend-api/src/app.module.ts +++ b/features/platform-admin/backend-api/src/app.module.ts @@ -7,6 +7,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { AssetStorageModule } from './asset-storage/index'; import { AuthModule } from './auth/auth.module'; +import { ContentModerationModule } from './content-moderation/content-moderation.module'; import { ContentHubModule } from './content-hub/content-hub.module'; import { DevicesModule } from './devices/devices.module'; import { HealthController } from './health/health.controller'; @@ -68,6 +69,7 @@ const registry = buildDeploymentRegistry({ InfrastructureModule, AssetStorageModule, ContentHubModule, + ContentModerationModule, ], controllers: [HealthController], }) diff --git a/features/platform-admin/backend-api/src/database/migrations/1710000000000-ContentModerationSchema.ts b/features/platform-admin/backend-api/src/database/migrations/1710000000000-ContentModerationSchema.ts new file mode 100644 index 000000000..a3a1261b4 --- /dev/null +++ b/features/platform-admin/backend-api/src/database/migrations/1710000000000-ContentModerationSchema.ts @@ -0,0 +1,365 @@ +import { Table, TableIndex } from 'typeorm'; + +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ContentModerationSchema1710000000000 implements MigrationInterface { + name = 'ContentModerationSchema1710000000000'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + name: 'content_scores', + columns: [ + { + name: 'id', + type: 'uuid', + isPrimary: true, + generationStrategy: 'uuid', + default: 'uuid_generate_v4()', + }, + { + name: 'contentType', + type: 'varchar', + length: '64', + isNullable: false, + }, + { + name: 'contentId', + type: 'varchar', + length: '255', + isNullable: false, + }, + { + name: 'authorId', + type: 'varchar', + length: '255', + isNullable: false, + }, + { + name: 'textPreview', + type: 'text', + isNullable: false, + }, + { + name: 'flaggedCategories', + type: 'jsonb', + isNullable: false, + }, + { + name: 'severity', + type: 'varchar', + length: '16', + isNullable: false, + }, + { + name: 'action', + type: 'varchar', + length: '16', + isNullable: false, + }, + { + name: 'reviewStatus', + type: 'varchar', + length: '32', + isNullable: false, + }, + { + name: 'modelVersion', + type: 'varchar', + length: '64', + isNullable: false, + }, + { + name: 'scoredAt', + type: 'timestamp', + isNullable: false, + }, + { + name: 'reviewedBy', + type: 'varchar', + length: '255', + isNullable: true, + }, + { + name: 'reviewedAt', + type: 'timestamp', + isNullable: true, + }, + { + name: 'reviewNotes', + type: 'text', + isNullable: true, + }, + ], + }), + true, + ); + + await queryRunner.createIndex( + 'content_scores', + new TableIndex({ name: 'IDX_content_scores_reviewStatus', columnNames: ['reviewStatus'] }), + ); + await queryRunner.createIndex( + 'content_scores', + new TableIndex({ name: 'IDX_content_scores_severity', columnNames: ['severity'] }), + ); + await queryRunner.createIndex( + 'content_scores', + new TableIndex({ name: 'IDX_content_scores_action', columnNames: ['action'] }), + ); + await queryRunner.createIndex( + 'content_scores', + new TableIndex({ name: 'IDX_content_scores_authorId', columnNames: ['authorId'] }), + ); + await queryRunner.createIndex( + 'content_scores', + new TableIndex({ name: 'IDX_content_scores_scoredAt', columnNames: ['scoredAt'] }), + ); + await queryRunner.createIndex( + 'content_scores', + new TableIndex({ name: 'IDX_content_scores_contentType', columnNames: ['contentType'] }), + ); + + await queryRunner.createTable( + new Table({ + name: 'user_threat_levels', + columns: [ + { + name: 'id', + type: 'uuid', + isPrimary: true, + generationStrategy: 'uuid', + default: 'uuid_generate_v4()', + }, + { + name: 'userId', + type: 'varchar', + length: '255', + isNullable: false, + isUnique: true, + }, + { + name: 'score', + type: 'decimal', + precision: 10, + scale: 4, + isNullable: false, + }, + { + name: 'level', + type: 'varchar', + length: '16', + isNullable: false, + }, + { + name: 'totalViolations', + type: 'int', + default: 0, + isNullable: false, + }, + { + name: 'criticalViolations', + type: 'int', + default: 0, + isNullable: false, + }, + { + name: 'highViolations', + type: 'int', + default: 0, + isNullable: false, + }, + { + name: 'mediumViolations', + type: 'int', + default: 0, + isNullable: false, + }, + { + name: 'lowViolations', + type: 'int', + default: 0, + isNullable: false, + }, + { + name: 'categoryBreakdown', + type: 'jsonb', + isNullable: false, + default: `'{}'::jsonb`, + }, + { + name: 'lastViolationAt', + type: 'timestamp', + isNullable: true, + }, + { + name: 'lastEscalationAt', + type: 'timestamp', + isNullable: true, + }, + { + name: 'sensitivityMultiplier', + type: 'decimal', + precision: 5, + scale: 2, + default: 1.0, + isNullable: false, + }, + { + name: 'restrictions', + type: 'jsonb', + isNullable: false, + default: `'{}'::jsonb`, + }, + { + name: 'adminOverride', + type: 'boolean', + default: false, + isNullable: false, + }, + { + name: 'adminOverrideBy', + type: 'varchar', + length: '255', + isNullable: true, + }, + { + name: 'adminOverrideAt', + type: 'timestamp', + isNullable: true, + }, + { + name: 'adminNotes', + type: 'text', + isNullable: true, + }, + { + name: 'createdAt', + type: 'timestamp', + default: 'CURRENT_TIMESTAMP', + isNullable: false, + }, + { + name: 'updatedAt', + type: 'timestamp', + default: 'CURRENT_TIMESTAMP', + isNullable: false, + }, + ], + }), + true, + ); + + await queryRunner.createIndex( + 'user_threat_levels', + new TableIndex({ name: 'IDX_user_threat_levels_level', columnNames: ['level'] }), + ); + await queryRunner.createIndex( + 'user_threat_levels', + new TableIndex({ name: 'IDX_user_threat_levels_score', columnNames: ['score'] }), + ); + await queryRunner.createIndex( + 'user_threat_levels', + new TableIndex({ name: 'IDX_user_threat_levels_userId', columnNames: ['userId'], isUnique: true }), + ); + + await queryRunner.createTable( + new Table({ + name: 'threat_escalation_events', + columns: [ + { + name: 'id', + type: 'uuid', + isPrimary: true, + generationStrategy: 'uuid', + default: 'uuid_generate_v4()', + }, + { + name: 'userId', + type: 'varchar', + length: '255', + isNullable: false, + }, + { + name: 'previousLevel', + type: 'varchar', + length: '16', + isNullable: false, + }, + { + name: 'newLevel', + type: 'varchar', + length: '16', + isNullable: false, + }, + { + name: 'previousScore', + type: 'decimal', + precision: 10, + scale: 4, + isNullable: false, + }, + { + name: 'newScore', + type: 'decimal', + precision: 10, + scale: 4, + isNullable: false, + }, + { + name: 'trigger', + type: 'varchar', + length: '32', + isNullable: false, + }, + { + name: 'triggerContentScoreId', + type: 'uuid', + isNullable: true, + }, + { + name: 'metadata', + type: 'jsonb', + isNullable: false, + default: `'{}'::jsonb`, + }, + { + name: 'createdAt', + type: 'timestamp', + default: 'CURRENT_TIMESTAMP', + isNullable: false, + }, + ], + }), + true, + ); + + await queryRunner.createIndex( + 'threat_escalation_events', + new TableIndex({ name: 'IDX_threat_escalation_events_userId', columnNames: ['userId'] }), + ); + await queryRunner.createIndex( + 'threat_escalation_events', + new TableIndex({ name: 'IDX_threat_escalation_events_createdAt', columnNames: ['createdAt'] }), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropIndex('threat_escalation_events', 'IDX_threat_escalation_events_createdAt'); + await queryRunner.dropIndex('threat_escalation_events', 'IDX_threat_escalation_events_userId'); + await queryRunner.dropTable('threat_escalation_events'); + + await queryRunner.dropIndex('user_threat_levels', 'IDX_user_threat_levels_userId'); + await queryRunner.dropIndex('user_threat_levels', 'IDX_user_threat_levels_score'); + await queryRunner.dropIndex('user_threat_levels', 'IDX_user_threat_levels_level'); + await queryRunner.dropTable('user_threat_levels'); + + await queryRunner.dropIndex('content_scores', 'IDX_content_scores_contentType'); + await queryRunner.dropIndex('content_scores', 'IDX_content_scores_scoredAt'); + await queryRunner.dropIndex('content_scores', 'IDX_content_scores_authorId'); + await queryRunner.dropIndex('content_scores', 'IDX_content_scores_action'); + await queryRunner.dropIndex('content_scores', 'IDX_content_scores_severity'); + await queryRunner.dropIndex('content_scores', 'IDX_content_scores_reviewStatus'); + await queryRunner.dropTable('content_scores'); + } +} diff --git a/features/platform-admin/backend-api/src/database/migrations/index.ts b/features/platform-admin/backend-api/src/database/migrations/index.ts index ee1b12480..b875f392b 100644 --- a/features/platform-admin/backend-api/src/database/migrations/index.ts +++ b/features/platform-admin/backend-api/src/database/migrations/index.ts @@ -1 +1,2 @@ export { InitialSchema1700000000000 } from './1700000000000-InitialSchema'; +export { ContentModerationSchema1710000000000 } from './1710000000000-ContentModerationSchema';