diff --git a/src/app.module.ts b/src/app.module.ts index d49fd1a..4d1e673 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,8 +1,9 @@ import { Module } from '@nestjs/common'; import { PrismaModule } from './common/prisma/prisma.module'; +import { CasesModule } from './cases/cases.module'; @Module({ - imports: [PrismaModule], + imports: [PrismaModule, CasesModule], controllers: [], providers: [], }) diff --git a/src/cases/cases.module.ts b/src/cases/cases.module.ts new file mode 100644 index 0000000..b583b81 --- /dev/null +++ b/src/cases/cases.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { CasesService } from './cases.service'; +import { CasesResolver } from './cases.resolver'; + +@Module({ + providers: [CasesService, CasesResolver] +}) +export class CasesModule {} diff --git a/src/cases/cases.resolver.ts b/src/cases/cases.resolver.ts new file mode 100644 index 0000000..e637da8 --- /dev/null +++ b/src/cases/cases.resolver.ts @@ -0,0 +1,36 @@ +import { Resolver, Query, Mutation, Args, ID, Int, ResolveField, Parent } from '@nestjs/graphql'; +import { GraphQLUpload } from 'graphql-upload-ts'; +import type { FileUpload } from 'graphql-upload-ts'; +import { CasesService } from './cases.service'; +import { CaseLaw } from './entities/case-law.entity'; + +@Resolver(() => CaseLaw) +export class CasesResolver { + constructor( + private readonly casesService: CasesService, + ) {} + + + @Query(() => CaseLaw, { name: 'caseLaw', nullable: true }) + async findOne( + @Args('id', { type: () => String, nullable: true }) id?: string, + @Args('caseNumber', { type: () => String, nullable: true }) caseNumber?: string, + ) { + return this.casesService.findOne(id, caseNumber); + } + + @Mutation(() => CaseLaw) + async uploadCase( + @Args({ name: 'file', type: () => GraphQLUpload }) + { createReadStream, filename, mimetype }: FileUpload, + ) { + const chunks: any[] = []; + for await (const chunk of createReadStream()) { + chunks.push(chunk); + } + + const buffer = Buffer.concat(chunks); + return this.casesService.processAndSave(buffer, mimetype, filename); + } + +} diff --git a/src/cases/cases.service.ts b/src/cases/cases.service.ts new file mode 100644 index 0000000..0d12ea7 --- /dev/null +++ b/src/cases/cases.service.ts @@ -0,0 +1,47 @@ +import { Injectable, NotFoundException, BadRequestException, Logger, Inject } from '@nestjs/common'; +import { PRISMA_CLIENT, type PrismaClientInstance } from '../common/prisma/prisma.service'; + +@Injectable() +export class CasesService { + private readonly logger = new Logger(CasesService.name); + + constructor( + @Inject(PRISMA_CLIENT) private prisma: PrismaClientInstance, + ) {} + + async processAndSave(buffer: Buffer, mimetype: string, filename: string) { + this.logger.log(`Upload received: ${filename} (${mimetype}, ${(buffer.length / 1024).toFixed(1)} KB)`); + const fileType = mimetype === 'application/pdf' ? 'PDF' : 'HTML'; + + const caseLaw = await this.prisma.caseLaw.create({ + data: { + title: `Processing: ${filename}`, + fileType, + }, + }); + + return caseLaw; + } + + + async findOne(id?: string, caseNumber?: string) { + if (!id && !caseNumber) throw new BadRequestException('Provide ID or Case Number'); + + const caseLaw = await this.prisma.caseLaw.findFirst({ + where: { + OR: [ + id && isUuid(id) ? { id } : undefined, + caseNumber ? { caseNumber } : undefined, + ].filter(Boolean) as any, + }, + }); + + if (!caseLaw) throw new NotFoundException('Case not found'); + return caseLaw; + } +} + +export const isUuid = (value: string): boolean => { + return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value); +}; + diff --git a/src/cases/entities/case-law.entity.ts b/src/cases/entities/case-law.entity.ts new file mode 100644 index 0000000..46015ed --- /dev/null +++ b/src/cases/entities/case-law.entity.ts @@ -0,0 +1,37 @@ +import { ObjectType, Field, ID } from '@nestjs/graphql'; + +@ObjectType() +export class CaseLaw { + @Field(() => ID) + id: string; + + @Field() + title: string; + + @Field({ nullable: true }) + decisionType?: string; + + @Field({ nullable: true }) + decisionDate?: Date; + + @Field({ nullable: true }) + office?: string; + + @Field({ nullable: true }) + court?: string; + + @Field({ nullable: true }) + caseNumber?: string; + + @Field({ nullable: true }) + summary?: string; + + @Field() + fileType: string; + + @Field() + createdAt: Date; + + @Field() + updatedAt: Date; +}