From 833760c67da45860351b0182ab4801bb1c946190 Mon Sep 17 00:00:00 2001 From: Nikolai Muhhin Date: Tue, 24 Jun 2025 15:24:22 +0300 Subject: [PATCH 1/4] Add abuse::reason text field --- .../src/modules/abuse/model/abuse.model.ts | 17 ++++++++++++++++- .../src/modules/abuse/spec/abuse.fixtures.ts | 4 ++++ .../1750766313641-AddReasonToAbuse.ts | 13 +++++++++++++ .../src/modules/abuse/abuse.controller.ts | 2 ++ .../server/src/modules/abuse/abuse.dto.ts | 14 +++++++++++++- .../server/src/modules/abuse/abuse.entity.ts | 3 +++ .../server/src/modules/abuse/abuse.service.ts | 1 + .../server/src/modules/abuse/fixtures/index.ts | 1 + .../server/src/modules/abuse/types.ts | 1 + 9 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 packages/apps/reputation-oracle/server/src/database/migrations/1750766313641-AddReasonToAbuse.ts diff --git a/packages/apps/human-app/server/src/modules/abuse/model/abuse.model.ts b/packages/apps/human-app/server/src/modules/abuse/model/abuse.model.ts index 37eec9942a..7372045f14 100644 --- a/packages/apps/human-app/server/src/modules/abuse/model/abuse.model.ts +++ b/packages/apps/human-app/server/src/modules/abuse/model/abuse.model.ts @@ -1,7 +1,12 @@ import { AutoMap } from '@automapper/classes'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; -import { IsEthereumAddress, IsNumber } from 'class-validator'; +import { + IsEthereumAddress, + IsNumber, + IsOptional, + IsString, +} from 'class-validator'; export class ReportAbuseDto { @AutoMap() @@ -13,6 +18,11 @@ export class ReportAbuseDto { @Type(() => Number) @ApiProperty() chain_id: number; + @AutoMap() + @IsOptional() + @IsString() + @ApiProperty({ required: false, description: 'Reason for the abuse report' }) + reason?: string; } export class ReportAbuseParams { @@ -20,6 +30,8 @@ export class ReportAbuseParams { chainId: number; @AutoMap() escrowAddress: string; + @AutoMap() + reason?: string; } export class ReportAbuseCommand { @AutoMap() @@ -33,6 +45,8 @@ export class ReportAbuseData { escrow_address: string; @AutoMap() chain_id: number; + @AutoMap() + reason?: string; } export class ReportedAbuseItem { @@ -40,6 +54,7 @@ export class ReportedAbuseItem { escrowAddress: string; chainId: number; status: string; + reason?: string; } export class ReportedAbuseResponse { diff --git a/packages/apps/human-app/server/src/modules/abuse/spec/abuse.fixtures.ts b/packages/apps/human-app/server/src/modules/abuse/spec/abuse.fixtures.ts index f0a0fe6439..84e9758301 100644 --- a/packages/apps/human-app/server/src/modules/abuse/spec/abuse.fixtures.ts +++ b/packages/apps/human-app/server/src/modules/abuse/spec/abuse.fixtures.ts @@ -10,16 +10,19 @@ const ESCROW_ADDRESS = 'test_address'; const CHAIN_ID = 1; const STATUS = 'reported'; const ABUSE_ID = 1; +const REASON = 'Test abuse reason'; export const TOKEN = 'test_user_token'; export const reportAbuseDtoFixture: ReportAbuseDto = { chain_id: CHAIN_ID, escrow_address: ESCROW_ADDRESS, + reason: REASON, }; export const reportAbuseParamsFixture: ReportAbuseParams = { chainId: CHAIN_ID, escrowAddress: ESCROW_ADDRESS, + reason: REASON, }; export const reportAbuseCommandFixture: ReportAbuseCommand = { @@ -32,6 +35,7 @@ export const reportedAbuseItemFixture: ReportedAbuseItem = { escrowAddress: ESCROW_ADDRESS, chainId: CHAIN_ID, status: STATUS, + reason: REASON, }; export const reportedAbuseResponseFixture: ReportedAbuseResponse = { diff --git a/packages/apps/reputation-oracle/server/src/database/migrations/1750766313641-AddReasonToAbuse.ts b/packages/apps/reputation-oracle/server/src/database/migrations/1750766313641-AddReasonToAbuse.ts new file mode 100644 index 0000000000..f7ce551ca4 --- /dev/null +++ b/packages/apps/reputation-oracle/server/src/database/migrations/1750766313641-AddReasonToAbuse.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddReasonToAbuse1750766313641 implements MigrationInterface { + name = 'AddReasonToAbuse1750766313641'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "hmt"."abuses" ADD "reason" text`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "hmt"."abuses" DROP COLUMN "reason"`); + } +} diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.controller.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.controller.ts index 35d4273953..ae1f146530 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.controller.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.controller.ts @@ -55,6 +55,7 @@ export class AbuseController { escrowAddress: data.escrowAddress, chainId: data.chainId, userId: request.user.id, + reason: data.reason, }); } @@ -82,6 +83,7 @@ export class AbuseController { escrowAddress: abuseEntity.escrowAddress, chainId: abuseEntity.chainId, status: abuseEntity.status, + reason: abuseEntity.reason, }; }); } diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts index b828c78490..3492812d72 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts @@ -1,6 +1,6 @@ import { ChainId } from '@human-protocol/sdk'; import { ApiProperty } from '@nestjs/swagger'; -import { IsEthereumAddress, IsString } from 'class-validator'; +import { IsEthereumAddress, IsString, IsOptional } from 'class-validator'; import { IsChainId } from '../../common/validators'; import { AbuseStatus } from './constants'; @@ -13,6 +13,15 @@ export class ReportAbuseDto { @ApiProperty({ name: 'escrow_address' }) @IsEthereumAddress() escrowAddress: string; + + @ApiProperty({ + name: 'reason', + required: false, + description: 'Reason for the abuse report', + }) + @IsOptional() + @IsString() + reason?: string; } export class AbuseResponseDto { @@ -27,6 +36,9 @@ export class AbuseResponseDto { @ApiProperty({ description: 'Current status of the abuse report' }) status: AbuseStatus; + + @ApiProperty({ description: 'Reason for the abuse report', required: false }) + reason?: string | null; } export class SlackInteractionDto { diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts index 1f5749b370..7facb26f51 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts @@ -31,6 +31,9 @@ export class AbuseEntity extends BaseEntity { @Column({ type: 'decimal', precision: 30, scale: 18, nullable: true }) amount: number | null; + @Column({ type: 'text', nullable: true }) + reason?: string | null; + @JoinColumn() @ManyToOne('UserEntity', { nullable: false, onDelete: 'CASCADE' }) user?: UserEntity; diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts index 80853720eb..1ac68c5f12 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.ts @@ -42,6 +42,7 @@ export class AbuseService { abuseEntity.escrowAddress = data.escrowAddress; abuseEntity.chainId = data.chainId; abuseEntity.userId = data.userId; + abuseEntity.reason = data.reason; abuseEntity.status = AbuseStatus.PENDING; abuseEntity.retriesCount = 0; abuseEntity.waitUntil = new Date(); diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts index e79e622b2b..08e5c69bdd 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts @@ -19,6 +19,7 @@ export function generateAbuseEntity( status: AbuseStatus.PENDING, decision: null, amount: null, + reason: null, waitUntil: faker.date.future(), createdAt: faker.date.recent(), updatedAt: new Date(), diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/types.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/types.ts index 9e733cea43..c2d0a5356b 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/types.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/types.ts @@ -4,6 +4,7 @@ export type ReportAbuseInput = { userId: number; chainId: ChainId; escrowAddress: string; + reason?: string; }; export type SlackInteractionBase = { From fcc6c803f96cc9af5dcd5148f94fd748d3172575 Mon Sep 17 00:00:00 2001 From: Nikolai Muhhin Date: Tue, 24 Jun 2025 15:31:13 +0300 Subject: [PATCH 2/4] Add max length validation --- .../server/src/modules/abuse/abuse.dto.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts index 3492812d72..76fca6608a 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts @@ -1,6 +1,11 @@ import { ChainId } from '@human-protocol/sdk'; import { ApiProperty } from '@nestjs/swagger'; -import { IsEthereumAddress, IsString, IsOptional } from 'class-validator'; +import { + IsEthereumAddress, + IsString, + IsOptional, + MaxLength, +} from 'class-validator'; import { IsChainId } from '../../common/validators'; import { AbuseStatus } from './constants'; @@ -21,6 +26,7 @@ export class ReportAbuseDto { }) @IsOptional() @IsString() + @MaxLength(1000) reason?: string; } From e9b8ecfe8e4afb2caefaf2471b98de3d3b089cf1 Mon Sep 17 00:00:00 2001 From: Nikolai Muhhin Date: Wed, 25 Jun 2025 18:46:27 +0300 Subject: [PATCH 3/4] Make reason property mandatory --- .../server/src/modules/abuse/model/abuse.model.ts | 15 ++++++++------- .../migrations/1750766313641-AddReasonToAbuse.ts | 2 +- .../server/src/modules/abuse/abuse.dto.ts | 9 ++++----- .../server/src/modules/abuse/abuse.entity.ts | 4 ++-- .../src/modules/abuse/abuse.service.spec.ts | 3 +++ .../server/src/modules/abuse/fixtures/index.ts | 2 +- .../server/src/modules/abuse/types.ts | 2 +- 7 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/apps/human-app/server/src/modules/abuse/model/abuse.model.ts b/packages/apps/human-app/server/src/modules/abuse/model/abuse.model.ts index 7372045f14..fda6f2e6b3 100644 --- a/packages/apps/human-app/server/src/modules/abuse/model/abuse.model.ts +++ b/packages/apps/human-app/server/src/modules/abuse/model/abuse.model.ts @@ -3,8 +3,8 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsEthereumAddress, + IsNotEmpty, IsNumber, - IsOptional, IsString, } from 'class-validator'; @@ -19,10 +19,10 @@ export class ReportAbuseDto { @ApiProperty() chain_id: number; @AutoMap() - @IsOptional() @IsString() - @ApiProperty({ required: false, description: 'Reason for the abuse report' }) - reason?: string; + @IsNotEmpty() + @ApiProperty({ required: true, description: 'Reason for the abuse report' }) + reason: string; } export class ReportAbuseParams { @@ -31,8 +31,9 @@ export class ReportAbuseParams { @AutoMap() escrowAddress: string; @AutoMap() - reason?: string; + reason: string; } + export class ReportAbuseCommand { @AutoMap() data: ReportAbuseParams; @@ -46,7 +47,7 @@ export class ReportAbuseData { @AutoMap() chain_id: number; @AutoMap() - reason?: string; + reason: string; } export class ReportedAbuseItem { @@ -54,7 +55,7 @@ export class ReportedAbuseItem { escrowAddress: string; chainId: number; status: string; - reason?: string; + reason: string; } export class ReportedAbuseResponse { diff --git a/packages/apps/reputation-oracle/server/src/database/migrations/1750766313641-AddReasonToAbuse.ts b/packages/apps/reputation-oracle/server/src/database/migrations/1750766313641-AddReasonToAbuse.ts index f7ce551ca4..da01de6e2f 100644 --- a/packages/apps/reputation-oracle/server/src/database/migrations/1750766313641-AddReasonToAbuse.ts +++ b/packages/apps/reputation-oracle/server/src/database/migrations/1750766313641-AddReasonToAbuse.ts @@ -4,7 +4,7 @@ export class AddReasonToAbuse1750766313641 implements MigrationInterface { name = 'AddReasonToAbuse1750766313641'; public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "hmt"."abuses" ADD "reason" text`); + await queryRunner.query(`ALTER TABLE "hmt"."abuses" ADD "reason" text NOT NULL`); } public async down(queryRunner: QueryRunner): Promise { diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts index 76fca6608a..0284dad293 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts @@ -21,13 +21,12 @@ export class ReportAbuseDto { @ApiProperty({ name: 'reason', - required: false, + required: true, description: 'Reason for the abuse report', }) - @IsOptional() @IsString() @MaxLength(1000) - reason?: string; + reason: string; } export class AbuseResponseDto { @@ -43,8 +42,8 @@ export class AbuseResponseDto { @ApiProperty({ description: 'Current status of the abuse report' }) status: AbuseStatus; - @ApiProperty({ description: 'Reason for the abuse report', required: false }) - reason?: string | null; + @ApiProperty({ description: 'Reason for the abuse report', required: true }) + reason: string; } export class SlackInteractionDto { diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts index 7facb26f51..2f436a1afb 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.entity.ts @@ -31,8 +31,8 @@ export class AbuseEntity extends BaseEntity { @Column({ type: 'decimal', precision: 30, scale: 18, nullable: true }) amount: number | null; - @Column({ type: 'text', nullable: true }) - reason?: string | null; + @Column({ type: 'text' }) + reason: string; @JoinColumn() @ManyToOne('UserEntity', { nullable: false, onDelete: 'CASCADE' }) diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.spec.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.spec.ts index e39275ea72..90c31e8dcb 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.spec.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.service.spec.ts @@ -73,17 +73,20 @@ describe('AbuseService', () => { describe('reportAbuse', () => { it('should create a new abuse entity', async () => { const userId = faker.number.int(); + const reason = faker.lorem.sentence(); await abuseService.reportAbuse({ escrowAddress, chainId, userId, + reason, }); expect(mockAbuseRepository.createUnique).toHaveBeenCalledWith({ escrowAddress: escrowAddress, chainId: chainId, userId: userId, + reason: reason, retriesCount: 0, status: AbuseStatus.PENDING, waitUntil: expect.any(Date), diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts index 08e5c69bdd..c75c6a8826 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/fixtures/index.ts @@ -19,7 +19,7 @@ export function generateAbuseEntity( status: AbuseStatus.PENDING, decision: null, amount: null, - reason: null, + reason: faker.lorem.sentence(), waitUntil: faker.date.future(), createdAt: faker.date.recent(), updatedAt: new Date(), diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/types.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/types.ts index c2d0a5356b..37dec9605c 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/types.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/types.ts @@ -4,7 +4,7 @@ export type ReportAbuseInput = { userId: number; chainId: ChainId; escrowAddress: string; - reason?: string; + reason: string; }; export type SlackInteractionBase = { From 914c383c23b7b309c5b6e88eb65ea2a436c1290a Mon Sep 17 00:00:00 2001 From: portuu3 Date: Tue, 1 Jul 2025 12:08:10 +0200 Subject: [PATCH 4/4] remove unused imports in abuse.dto.ts --- .../server/src/modules/abuse/abuse.dto.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts index 0284dad293..55afd67b15 100644 --- a/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts +++ b/packages/apps/reputation-oracle/server/src/modules/abuse/abuse.dto.ts @@ -1,11 +1,6 @@ import { ChainId } from '@human-protocol/sdk'; import { ApiProperty } from '@nestjs/swagger'; -import { - IsEthereumAddress, - IsString, - IsOptional, - MaxLength, -} from 'class-validator'; +import { IsEthereumAddress, IsString, MaxLength } from 'class-validator'; import { IsChainId } from '../../common/validators'; import { AbuseStatus } from './constants';