Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions backend/src/dispute/dispute.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
Body,
Controller,
Get,
NotFoundException,
Param,
Patch,
Post,
Req,
UseGuards,
} from '@nestjs/common';
import { Request } from 'express';
import { DisputeService } from './dispute.service';
import { CreateDisputeDto } from './dto/create-dispute.dto';
import { ResolveDisputeDto } from './dto/resolve-dispute.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { User } from '../users/entities/user.entity';

@Controller('disputes')
export class DisputeController {
constructor(private readonly disputeService: DisputeService) {}

@Post()
@UseGuards(JwtAuthGuard)
async create(
@Body() dto: CreateDisputeDto,
@Req() req: Request & { user?: User },
) {
const user = req.user!;
return this.disputeService.create(user.id, dto);
}

@Get(':id')
@UseGuards(JwtAuthGuard)
async getById(@Param('id') id: string) {
const dispute = await this.disputeService.findById(id);
if (!dispute) throw new NotFoundException('Dispute not found');
return dispute;
}

@Get('document/:documentId')
@UseGuards(JwtAuthGuard)
async getByDocument(@Param('documentId') documentId: string) {
return this.disputeService.findByDocument(documentId);
}

@Get()
@UseGuards(JwtAuthGuard)
async getMyDisputes(@Req() req: Request & { user?: User }) {
const user = req.user!;
return this.disputeService.findByUser(user.id);
}

@Patch(':id/resolve')
@UseGuards(JwtAuthGuard)
async resolve(
@Param('id') id: string,
@Body() dto: ResolveDisputeDto,
@Req() req: Request & { user?: User },
) {
const user = req.user!;
return this.disputeService.resolve(id, dto, user.id);
}
}
14 changes: 14 additions & 0 deletions backend/src/dispute/dispute.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Dispute } from './entities/dispute.entity';
import { DisputeReason } from './entities/dispute-reason.entity';
import { DisputeController } from './dispute.controller';
import { DisputeService } from './dispute.service';

@Module({
imports: [TypeOrmModule.forFeature([Dispute, DisputeReason])],
controllers: [DisputeController],
providers: [DisputeService],
exports: [DisputeService],
})
export class DisputeModule {}
65 changes: 65 additions & 0 deletions backend/src/dispute/dispute.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Dispute, DisputeStatus } from './entities/dispute.entity';

Check failure on line 4 in backend/src/dispute/dispute.service.ts

View workflow job for this annotation

GitHub Actions / Backend (NestJS)

'ForbiddenException' is defined but never used
import { CreateDisputeDto } from './dto/create-dispute.dto';
import { ResolveDisputeDto } from './dto/resolve-dispute.dto';

@Injectable()

Check failure on line 8 in backend/src/dispute/dispute.service.ts

View workflow job for this annotation

GitHub Actions / Backend (NestJS)

'DisputeStatus' is defined but never used
export class DisputeService {
constructor(
@InjectRepository(Dispute)
private readonly disputeRepository: Repository<Dispute>,
) {}

create(userId: string, dto: CreateDisputeDto): Promise<Dispute> {
const dispute = this.disputeRepository.create({
raisedById: userId,
documentId: dto.documentId,
reason: dto.reason,
description: dto.description,
});
return this.disputeRepository.save(dispute);
}

findById(id: string): Promise<Dispute | null> {
return this.disputeRepository.findOne({
where: { id },
relations: ['raisedBy', 'document'],
});
}

findByDocument(documentId: string): Promise<Dispute[]> {
return this.disputeRepository.find({
where: { documentId },
relations: ['raisedBy'],
order: { createdAt: 'DESC' },
});
}

findByUser(userId: string): Promise<Dispute[]> {
return this.disputeRepository.find({
where: { raisedById: userId },
relations: ['document'],
order: { createdAt: 'DESC' },
});
}

async resolve(
id: string,
dto: ResolveDisputeDto,
resolvedById: string,
): Promise<Dispute> {
const dispute = await this.findById(id);
if (!dispute) throw new NotFoundException('Dispute not found');

await this.disputeRepository.update(id, {
status: dto.status,
resolutionAction: dto.action,
resolutionNote: dto.note,
resolvedById,
});

return this.findById(id) as Promise<Dispute>;
}
}
13 changes: 13 additions & 0 deletions backend/src/dispute/dto/create-dispute.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IsEnum, IsOptional, IsString, IsUUID } from 'class-validator';

Check failure on line 1 in backend/src/dispute/dto/create-dispute.dto.ts

View workflow job for this annotation

GitHub Actions / Backend (NestJS)

'IsEnum' is defined but never used

export class CreateDisputeDto {
@IsUUID()
documentId: string;

@IsString()
reason: string;

@IsOptional()
@IsString()
description?: string;
}
14 changes: 14 additions & 0 deletions backend/src/dispute/dto/resolve-dispute.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { IsEnum, IsOptional, IsString } from 'class-validator';
import { DisputeStatus, ResolutionAction } from '../entities/dispute.entity';

export class ResolveDisputeDto {
@IsEnum(DisputeStatus)
status: DisputeStatus.RESOLVED | DisputeStatus.DISMISSED;

@IsEnum(ResolutionAction)
action: ResolutionAction;

@IsOptional()
@IsString()
note?: string;
}
74 changes: 74 additions & 0 deletions backend/src/dispute/entities/dispute.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
Column,
CreateDateColumn,
Entity,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { User } from '../../users/entities/user.entity';
import { Document } from '../../documents/entities/document.entity';

export enum DisputeStatus {
OPEN = 'open',
UNDER_REVIEW = 'under_review',
RESOLVED = 'resolved',
DISMISSED = 'dismissed',
}

export enum ResolutionAction {
NONE = 'none',
DOCUMENT_UPDATED = 'document_updated',
OWNERSHIP_TRANSFERRED = 'ownership_transferred',
COMPENSATION_ISSUED = 'compensation_issued',
}

@Entity('disputes')
export class Dispute {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ name: 'raised_by_id' })
raisedById: string;

@ManyToOne(() => User, { onDelete: 'CASCADE' })
raisedBy: User;

@Column({ name: 'document_id' })
documentId: string;

@ManyToOne(() => Document, { onDelete: 'CASCADE' })
document: Document;

@Column()
reason: string;

@Column({ type: 'text', nullable: true })
description?: string;

@Column({
type: 'enum',
enum: DisputeStatus,
default: DisputeStatus.OPEN,
})
status: DisputeStatus;

@Column({
type: 'enum',
enum: ResolutionAction,
default: ResolutionAction.NONE,
})
resolutionAction: ResolutionAction;

@Column({ type: 'text', nullable: true })
resolutionNote?: string;

@Column({ name: 'resolved_by_id', nullable: true })
resolvedById?: string;

@CreateDateColumn({ name: 'created_at' })
createdAt: Date;

@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}
Loading