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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ When you're in this mode, Intania Auth is bypassed. Real JWT are generated, but

Logging out have not been implemented yet for mock auth.

### API Doc

API docs are autogenerated using Orpc wrapping Trpc. You may view openapi (swagger) UI at `${BACKENDURL}/reference`.

There, you may export JSON or YAML OpenAPI specs and compile them to HTML by running `npx redocly build-docs ${PATH_TO_YOUR_EXPORTED_YAML}.yaml --output=${YOUR_OUTPUT_FILE_NAME}.html`

A [Redocly Output Example](./resources/redocly_example/redoc-static-esc-project-tracker-api.html) is also included in this repo.

## Test

1. Run this command at /api for backend, /web for frontend, or root for both at the same time
Expand Down
19 changes: 13 additions & 6 deletions apps/api/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"name": "api",
"version": "1.0.0",
"type": "module",
"private": true,
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"dev": "node --import @swc-node/register/esm-register --watch src/main.ts",
"start:debug": "node --import @swc-node/register/esm-register --inspect --watch src/main.ts",
"start:prod": "node dist/main.js",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
Expand All @@ -17,7 +18,6 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@repo/shared": "workspace:*",
"@aws-sdk/client-s3": "^3.670.0",
"@aws-sdk/s3-request-presigner": "^3.651.1",
"@nestjs/axios": "^3.0.3",
Expand All @@ -28,7 +28,12 @@
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.4.2",
"@nestjs/typeorm": "^10.0.2",
"@trpc/server": "^10.45.2",
"@orpc/openapi": "^1.10.2",
"@orpc/trpc": "^1.10.2",
"@orpc/zod": "^1.10.2",
"@repo/shared": "workspace:*",
"@scalar/nestjs-api-reference": "^1.0.5",
"@trpc/server": "^11.7.0",
"argon2": "^0.40.3",
"axios": "^1.7.7",
"bcryptjs": "^2.4.3",
Expand All @@ -39,7 +44,7 @@
"rxjs": "^7.8.1",
"typeorm": "^0.3.20",
"uuid": "^9.0.1",
"zod": "^3.23.8"
"zod": "^3.25.0"
},
"devDependencies": {
"@nestjs/cli": "^10.4.5",
Expand All @@ -48,6 +53,7 @@
"@repo/eslint-config": "workspace:*",
"@repo/tailwind-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"@swc-node/register": "^1.11.1",
"@swc/cli": "^0.3.14",
"@swc/core": "^1.7.26",
"@types/cookie-parser": "^1.4.7",
Expand All @@ -70,6 +76,7 @@
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.20.6",
"typescript": "^5.6.2"
},
"jest": {
Expand Down
6 changes: 5 additions & 1 deletion apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { AppService } from './app.service';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TrpcModule } from './trpc/trpc.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { join } from 'path';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

import { UserModule } from './user_/user.module';
import { DocumentModule } from './document_/document.module';
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/entities/document.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class Document {
@Column({ nullable: true })
name: string;

@Column({ default: DocumentActivity.CREATE })
@Column({ type: 'enum', enum: DocumentActivity, default: DocumentActivity.CREATE })
activity: DocumentActivity;

@ManyToOne(() => User, { onDelete: 'CASCADE' })
Expand All @@ -33,7 +33,7 @@ export class Document {
@Column()
userId: string;

@Column({ default: DocumentStatus.DRAFT })
@Column({ type: 'enum', enum: DocumentStatus, default: DocumentStatus.DRAFT })
status: DocumentStatus;

@Column({ nullable: true })
Expand Down
4 changes: 4 additions & 0 deletions apps/api/src/trpc/routers/auth.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class AuthRouter {

appRouter = this.trpcService.router({
signin: this.trpcService.trpc.procedure
.meta({ route: { tags: ['Authentication'], summary: 'Sign in with Intania Auth token' } })
.input(
z.object({
token: z.string(),
Expand All @@ -21,6 +22,7 @@ export class AuthRouter {
return this.authService.signIn(input.token);
}),
validateToken: this.trpcService.trpc.procedure
.meta({ route: { tags: ['Authentication'], summary: 'Validate JWT access token' } })
.input(
z.object({
accessToken: z.string(),
Expand All @@ -30,6 +32,7 @@ export class AuthRouter {
return this.authService.validateJWT(input.accessToken);
}),
refreshToken: this.trpcService.trpc.procedure
.meta({ route: { tags: ['Authentication'], summary: 'Refresh access token' } })
.input(
z.object({
userId: z.string(),
Expand All @@ -40,6 +43,7 @@ export class AuthRouter {
return this.authService.refreshToken(input.userId, input.refreshToken);
}),
signOut: this.trpcService.trpc.procedure
.meta({ route: { tags: ['Authentication'], summary: 'Sign out user' } })
.input(
z.object({
accessToken: z.string(),
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/trpc/routers/aws.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class AwsRouter {

appRouter = this.trpcService.router({
uploadFileToS3: this.trpcService.protectedProcedure
.meta({ route: { tags: ['AWS'], summary: 'Upload a file to AWS S3' } })
.input(
z.object({
file: z.instanceof(Buffer),
Expand All @@ -28,6 +29,7 @@ export class AwsRouter {
}),

getUrlToFile: this.trpcService.protectedProcedure
.meta({ route: { tags: ['AWS'], summary: 'Get presigned URL to access a file in S3' } })
.input(
z.object({
fileName: z.string(),
Expand Down
9 changes: 9 additions & 0 deletions apps/api/src/trpc/routers/document.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ export class DocumentRouter {
appRouter = this.trpcService.router({
//get Documents by filingId
findDocumentsByFilingId: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Documents'], summary: 'Get documents by filing ID' } })
.input(z.object({ filingId: z.string() }))
.query(({ input }) => {
return this.documentService.findDocumentsByFilingId(input.filingId);
}),
findLatestDocumentByFilingId: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Documents'], summary: 'Get latest document by filing ID' } })
.input(z.object({ filingId: z.string().uuid() }))
.query(({ input }) => {
return this.documentService.findLatestDocumentByFilingId(
Expand All @@ -28,6 +30,7 @@ export class DocumentRouter {
}),
// Used in admin section Only for now
findLatestReplyByFilingId: this.trpcService.adminProcedure
.meta({ route: { tags: ['Documents'], summary: 'Get latest reply document by filing ID (admin only)' } })
.input(z.object({ filingId: z.string() }))
.query(({ input }) => {
return this.documentService.findLatestReplyDocumentByFilingId(
Expand All @@ -36,6 +39,7 @@ export class DocumentRouter {
}),
// Used in admin section Only for now
findLatestPendingByFilingId: this.trpcService.adminProcedure
.meta({ route: { tags: ['Documents'], summary: 'Get latest pending document by filing ID (admin only)' } })
.input(z.object({ filingId: z.string() }))
.query(({ input }) => {
return this.documentService.findLatestPendingDocumentByFilingId(
Expand All @@ -45,6 +49,7 @@ export class DocumentRouter {

// Create Document -> Document
createDocument: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Documents'], summary: 'Create a new document (project members or admin)' } })
.input(
z.object({
filingId: z.string().uuid(),
Expand Down Expand Up @@ -84,6 +89,7 @@ export class DocumentRouter {

//edit document
updateDocument: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Documents'], summary: 'Update document details (project members or admin)' } })
.input(
z.object({
docId: z.string(),
Expand Down Expand Up @@ -114,6 +120,7 @@ export class DocumentRouter {
}),
// Delete Document -> Document
deleteDocument: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Documents'], summary: 'Delete a document (project members only)' } })
.input(z.object({ id: z.string() }))
.mutation(async ({ input, ctx }) => {
const { isMember } = await this.trpcService.isProjectMember(
Expand All @@ -131,6 +138,7 @@ export class DocumentRouter {

// Used in admin section Only for now
reviewSubmission: this.trpcService.adminProcedure
.meta({ route: { tags: ['Documents'], summary: 'Review document submission (admin only)' } })
.input(z.object({ id: z.string().uuid(), updatedStatus: z.boolean() }))
.mutation(({ input }) => {
return this.documentService.reviewDocumentSubmission(
Expand All @@ -141,6 +149,7 @@ export class DocumentRouter {

// Update Last Open Date of File
updateFileLastOpen: this.trpcService.adminProcedure
.meta({ route: { tags: ['Documents'], summary: 'Update file last open timestamp (admin only)' } })
.input(z.object({ id: z.string().uuid(), fileType: z.string() }))
.mutation(({ input }) => {
return this.documentService.updateFileLastOpen(
Expand Down
23 changes: 17 additions & 6 deletions apps/api/src/trpc/routers/filing.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,28 @@ export class FilingRouter {

appRouter = this.trpcService.router({
//Get All Filing
findAllFiling: this.trpcService.protectedProcedure.query(() => {
return this.filingService.findAllFiling();
}),
findAllFiling: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Filings'], summary: 'Get all filings' } })
.query(() => {
return this.filingService.findAllFiling();
}),
// Get Filings By UserID -> Filing[]
findFilingsByUserId: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Filings', 'Users'], summary: 'Get filings by user ID' } })
.input(z.object({ userId: z.string() }))
.query(({ input }) => {
return this.filingService.findByUserID(input.userId);
}),
// Get Filings By ProjectID -> Filing[]
findFilingsByProjectId: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Filings', 'Projects'], summary: 'Get filings by project ID' } })
.input(z.object({ projectId: z.string() }))
.query(({ input }) => {
return this.filingService.findByProjectID(input.projectId);
}),
// Create a new Filing
createFiling: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Filings'], summary: 'Create a new filing (project members only)' } })
.input(
z.object({
projectId: z.string(),
Expand Down Expand Up @@ -67,6 +72,7 @@ export class FilingRouter {
//Update filing name

updateFilingName: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Filings'], summary: 'Update filing name and status (members or admin)' } })
.input(
z.object({
filingId: z.string(),
Expand All @@ -92,6 +98,7 @@ export class FilingRouter {
}),
//Delete filing
deleteFiling: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Filings'], summary: 'Delete a filing (project owner only)' } })
.input(z.object({ filingId: z.string() }))
.mutation(async ({ input, ctx }) => {
const filingRaw = await this.filingService.findByFilingID(
Expand All @@ -110,13 +117,15 @@ export class FilingRouter {
}),
// Get Filing By FilingID -> Filing
getFilingByFilingId: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Filings'], summary: 'Get filing by ID' } })
.input(z.object({ filingId: z.string() }))
.query(({ input }) => {
return this.filingService.findByFilingID(input.filingId);
}),

//findFilingWithFilter
findFilingsWithFilter: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Filings'], summary: 'Find filings with filters (status, type, department)' } })
.input(
z.object({
status: z.string(),
Expand All @@ -134,8 +143,10 @@ export class FilingRouter {
});
}),

findLatestFilings: this.trpcService.adminProcedure.query(() => {
return this.filingService.findLatestFilings();
}),
findLatestFilings: this.trpcService.adminProcedure
.meta({ route: { tags: ['Filings'], summary: 'Get latest filings (admin only)' } })
.query(() => {
return this.filingService.findLatestFilings();
}),
});
}
13 changes: 10 additions & 3 deletions apps/api/src/trpc/routers/gendoc.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@ export class GendocRouter {

appRouter = this.trpcService.router({
//Get All Gendoc
findAllGendoc: this.trpcService.protectedProcedure.query(() => {
return this.gendocService.findAllGendoc();
}),
findAllGendoc: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Document Generation'], summary: 'Get all generated documents' } })
.query(() => {
return this.gendocService.findAllGendoc();
}),
// Get Gendocs By ProjectID -> Gendoc[]
findGendocsByProjectId: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Document Generation'], summary: 'Get generated documents by project ID' } })
.input(z.object({ projectId: z.string() }))
.query(({ input }) => {
return this.gendocService.findByProjectID(input.projectId);
}),
// Create a new Gendoc
createGendoc: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Document Generation'], summary: 'Generate a new document from template' } })
.input(
z.object({
customProjectName: z.string(),
Expand Down Expand Up @@ -55,6 +59,7 @@ export class GendocRouter {
//Update gendoc

updateGendocName: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Document Generation'], summary: 'Update generated document name (members or admin)' } })
.input(
z.object({
gendocId: z.string(),
Expand All @@ -79,6 +84,7 @@ export class GendocRouter {

//Delete Gendoc
deleteGendoc: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Document Generation'], summary: 'Delete a generated document (project owner only)' } })
.input(z.object({ gendocId: z.string() }))
.mutation(async ({ input, ctx }) => {
const gendocRaw = await this.gendocService.findByGendocID(
Expand All @@ -97,6 +103,7 @@ export class GendocRouter {
}),
// Get Gendoc By GendocID -> Gendoc
getGendocByGendocId: this.trpcService.protectedProcedure
.meta({ route: { tags: ['Document Generation'], summary: 'Get generated document by ID' } })
.input(z.object({ gendocId: z.string() }))
.query(({ input }) => {
return this.gendocService.findByGendocID(input.gendocId);
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/trpc/routers/notification.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class NotificationRouter {
) {}
appRouter = this.trpcService.router({
findNotificationsByUserId: this.trpcService.trpc.procedure
.meta({ route: { tags: ['Notifications'], summary: 'Get notifications for a user' } })
.input(z.object({ userId: z.string() }))
.query(({ input }) => {
this.notificationService.findNotificationsByUserId(input.userId);
Expand Down
Loading