Skip to content
Open
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
6 changes: 5 additions & 1 deletion apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { UsersModule } from '@/users/users.module';
import { AuthModule } from '@/auth/auth.module';
import { ProjectsModule } from './projects/projects.module';
import { ProjectsModule } from '@/projects/projects.module';
import { TasksModule } from '@/tasks/tasks.module';
import { BoardColumnsModule } from '@/board-columns/board-columns.module';

@Module({
imports: [
Expand All @@ -29,6 +31,8 @@ import { ProjectsModule } from './projects/projects.module';
AuthModule,
UsersModule,
ProjectsModule,
TasksModule,
BoardColumnsModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
20 changes: 20 additions & 0 deletions apps/backend/src/board-columns/board-columns.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { BoardColumnsController } from '@/board-columns/board-columns.controller';
import { BoardColumnsService } from '@/board-columns/board-columns.service';

describe('BoardColumnsController', () => {
let controller: BoardColumnsController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [BoardColumnsController],
providers: [BoardColumnsService],
}).compile();
Comment on lines +9 to +12
Copy link

@coderabbitai coderabbitai bot Mar 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether BoardColumnsService has injected constructor dependencies
rg -n -C2 'class BoardColumnsService|constructor\(|InjectRepository|Repository<' apps/backend/src/board-columns/board-columns.service.ts
rg -n -C2 'createTestingModule|providers:|BoardColumnsService|useValue' apps/backend/src/board-columns/board-columns.controller.spec.ts

Repository: Zafar7645/syncup

Length of output: 1143


Mock BoardColumnsService in this controller unit test.

BoardColumnsService has constructor dependencies on two TypeORM repositories (BoardColumn and Project). Registering the concrete service as a provider will cause module compilation to fail due to missing repository providers. Use a useValue mock to keep this unit test isolated from the database layer.

🔧 Proposed fix
   beforeEach(async () => {
     const module: TestingModule = await Test.createTestingModule({
       controllers: [BoardColumnsController],
-      providers: [BoardColumnsService],
+      providers: [
+        {
+          provide: BoardColumnsService,
+          useValue: {
+            create: jest.fn(),
+            findAll: jest.fn(),
+            findOne: jest.fn(),
+            update: jest.fn(),
+            remove: jest.fn(),
+          },
+        },
+      ],
     }).compile();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const module: TestingModule = await Test.createTestingModule({
controllers: [BoardColumnsController],
providers: [BoardColumnsService],
}).compile();
const module: TestingModule = await Test.createTestingModule({
controllers: [BoardColumnsController],
providers: [
{
provide: BoardColumnsService,
useValue: {
create: jest.fn(),
findAll: jest.fn(),
findOne: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
},
},
],
}).compile();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/board-columns/board-columns.controller.spec.ts` around lines
9 - 12, The test registers the concrete BoardColumnsService which depends on
TypeORM repositories and causes module compilation to fail; instead, in the
Test.createTestingModule call provide a mocked service using { provide:
BoardColumnsService, useValue: mockBoardColumnsService } (replace the providers:
[BoardColumnsService] entry), where mockBoardColumnsService is an object with
jest.fn() stubs for the methods the BoardColumnsController tests exercise (e.g.,
the controller's create/find/update/delete handlers); this keeps the test
isolated from repositories and allows the module to compile when instantiating
BoardColumnsController.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All tests will be handled in a separate issue for tests.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Zafar7645, understood! Would you like me to open a follow-up GitHub issue to track the test improvements (mocking BoardColumnsService and TasksService in their respective controller specs, and any other test coverage work) so it doesn't get lost?


🧠 Learnings used
Learnt from: Zafar7645
Repo: Zafar7645/syncup PR: 12
File: apps/backend/src/users/users.service.spec.ts:15-17
Timestamp: 2025-10-19T16:44:42.665Z
Learning: In the syncup repository (apps/backend/src/users/users.service.ts), the UsersService is scaffolding for future implementation and currently contains no methods or business logic. Basic "should be defined" smoke tests are sufficient for empty scaffolding services.

Learnt from: Zafar7645
Repo: Zafar7645/syncup PR: 16
File: apps/backend/src/auth/auth.service.spec.ts:6-6
Timestamp: 2026-02-07T16:08:28.564Z
Learning: In the syncup monorepo, use bcryptjs instead of native bcrypt in the backend (apps/backend) because Webpack bundling with shared libs breaks with node-gyp. Import from 'bcryptjs' where password hashing is used and ensure existing bcrypt usage is replaced accordingly. Expect roughly a 30% performance trade-off; if performance becomes critical, profile and consider alternatives, but default to bcryptjs to avoid bundling issues.


controller = module.get<BoardColumnsController>(BoardColumnsController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
68 changes: 68 additions & 0 deletions apps/backend/src/board-columns/board-columns.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
UseGuards,
Request,
Query,
ParseIntPipe,
} from '@nestjs/common';
import { BoardColumnsService } from '@/board-columns/board-columns.service';
import { CreateBoardColumnDto } from '@/board-columns/dto/create-board-column.dto';
import { UpdateBoardColumnDto } from '@/board-columns/dto/update-board-column.dto';
import { JwtAuthGuard } from '@/auth/guards/jwt-auth.guard';

@UseGuards(JwtAuthGuard)
@Controller('board-columns')
export class BoardColumnsController {
constructor(private readonly boardColumnsService: BoardColumnsService) {}

@Post()
create(
@Body() createBoardColumnDto: CreateBoardColumnDto,
@Request() req: { user: { userId: number; email: string } },
) {
return this.boardColumnsService.create(
createBoardColumnDto,
req.user.userId,
);
}

@Get()
findAll(
@Query('projectId', ParseIntPipe) projectId: number,
@Request() req: { user: { userId: number; email: string } },
) {
return this.boardColumnsService.findAll(projectId, req.user.userId);
}

@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.boardColumnsService.findOne(id);
}
Comment on lines +43 to +46
Copy link

@coderabbitai coderabbitai bot Mar 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add user context to findOne for ownership checks.

This handler should pass req.user.userId to the service, otherwise per-user authorization cannot be enforced consistently on single-column reads.

Suggested change
 `@Get`(':id')
- findOne(`@Param`('id', ParseIntPipe) id: number) {
-   return this.boardColumnsService.findOne(id);
+ findOne(
+   `@Param`('id', ParseIntPipe) id: number,
+   `@Request`() req: { user: { userId: number; email: string } },
+ ) {
+   return this.boardColumnsService.findOne(id, req.user.userId);
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/board-columns/board-columns.controller.ts` around lines 43 -
46, The findOne controller method currently calls
boardColumnsService.findOne(id) without user context so ownership checks can't
run; update the BoardColumnsController.findOne to accept the request user (e.g.,
add `@Req`() req: Request or `@User`() user param) and pass req.user.userId into the
service call (change to boardColumnsService.findOne(id, userId)); ensure the
service method signature (boardColumnsService.findOne) is updated to accept and
enforce the userId for authorization if not already implemented.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check findOne in controller
rg -n "findOne" apps/backend/src/board-columns/board-columns.controller.ts -A5 -B2

# Check findOne in service
rg -n "findOne" apps/backend/src/board-columns/board-columns.service.ts -A10 -B2

Length of output: 1996


@Zafar7645, it looks like the fix hasn't landed in the branch yet. The controller's findOne on Line 44 still lacks the @Request() parameter and still calls boardColumnsService.findOne(id) without userId. Likewise, the service's findOne on Line 77 is still a stub with no ownership check:

// board-columns.controller.ts — still unfixed
findOne(`@Param`('id', ParseIntPipe) id: number) {
  return this.boardColumnsService.findOne(id);
}

// board-columns.service.ts — still a stub
findOne(id: number) {
  return `This action returns a #${id} boardColumn`;
}

Here's what both files need:

Controller (board-columns.controller.ts):

`@Get`(':id')
findOne(
  `@Param`('id', ParseIntPipe) id: number,
  `@Request`() req: { user: { userId: number; email: string } },
) {
  return this.boardColumnsService.findOne(id, req.user.userId);
}

Service (board-columns.service.ts):

async findOne(id: number, userId: number): Promise<BoardColumn> {
  return this.verifyColumnAccess(id, userId);
}

verifyColumnAccess already performs the existence and ownership checks, so the service implementation is straightforward. Could you push the changes?


🧠 Learnings used
Learnt from: Zafar7645
Repo: Zafar7645/syncup PR: 16
File: apps/backend/src/auth/auth.service.spec.ts:6-6
Timestamp: 2026-02-07T16:08:28.564Z
Learning: In the syncup monorepo, use bcryptjs instead of native bcrypt in the backend (apps/backend) because Webpack bundling with shared libs breaks with node-gyp. Import from 'bcryptjs' where password hashing is used and ensure existing bcrypt usage is replaced accordingly. Expect roughly a 30% performance trade-off; if performance becomes critical, profile and consider alternatives, but default to bcryptjs to avoid bundling issues.


@Patch(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateBoardColumnDto: UpdateBoardColumnDto,
@Request() req: { user: { userId: number; email: string } },
) {
return this.boardColumnsService.update(
id,
updateBoardColumnDto,
req.user.userId,
);
}

@Delete(':id')
remove(
@Param('id', ParseIntPipe) id: number,
@Request() req: { user: { userId: number; email: string } },
) {
return this.boardColumnsService.remove(id, req.user.userId);
}
}
13 changes: 13 additions & 0 deletions apps/backend/src/board-columns/board-columns.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { BoardColumnsService } from '@/board-columns/board-columns.service';
import { BoardColumnsController } from '@/board-columns/board-columns.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BoardColumn } from '@/board-columns/entities/board-column.entity';
import { Project } from '@/projects/entities/project.entity';

@Module({
imports: [TypeOrmModule.forFeature([BoardColumn, Project])],
controllers: [BoardColumnsController],
providers: [BoardColumnsService],
})
export class BoardColumnsModule {}
18 changes: 18 additions & 0 deletions apps/backend/src/board-columns/board-columns.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { BoardColumnsService } from '@/board-columns/board-columns.service';

describe('BoardColumnsService', () => {
let service: BoardColumnsService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [BoardColumnsService],
}).compile();

service = module.get<BoardColumnsService>(BoardColumnsService);
Comment on lines +7 to +12
Copy link

@coderabbitai coderabbitai bot Mar 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Test will fail: Missing repository mocks for injected dependencies.

BoardColumnsService requires @InjectRepository(BoardColumn) and @InjectRepository(Project) (see board-columns.service.ts:15-19). Without providing mocks, module.compile() will throw a dependency resolution error.

🐛 Proposed fix to add repository mocks
 import { Test, TestingModule } from '@nestjs/testing';
 import { BoardColumnsService } from '@/board-columns/board-columns.service';
+import { getRepositoryToken } from '@nestjs/typeorm';
+import { BoardColumn } from '@/board-columns/entities/board-column.entity';
+import { Project } from '@/projects/entities/project.entity';

 describe('BoardColumnsService', () => {
   let service: BoardColumnsService;

   beforeEach(async () => {
     const module: TestingModule = await Test.createTestingModule({
-      providers: [BoardColumnsService],
+      providers: [
+        BoardColumnsService,
+        {
+          provide: getRepositoryToken(BoardColumn),
+          useValue: {
+            findOne: jest.fn(),
+            find: jest.fn(),
+            create: jest.fn(),
+            save: jest.fn(),
+            merge: jest.fn(),
+            remove: jest.fn(),
+          },
+        },
+        {
+          provide: getRepositoryToken(Project),
+          useValue: {
+            findOne: jest.fn(),
+          },
+        },
+      ],
     }).compile();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [BoardColumnsService],
}).compile();
service = module.get<BoardColumnsService>(BoardColumnsService);
import { Test, TestingModule } from '@nestjs/testing';
import { BoardColumnsService } from '@/board-columns/board-columns.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { BoardColumn } from '@/board-columns/entities/board-column.entity';
import { Project } from '@/projects/entities/project.entity';
describe('BoardColumnsService', () => {
let service: BoardColumnsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
BoardColumnsService,
{
provide: getRepositoryToken(BoardColumn),
useValue: {
findOne: jest.fn(),
find: jest.fn(),
create: jest.fn(),
save: jest.fn(),
merge: jest.fn(),
remove: jest.fn(),
},
},
{
provide: getRepositoryToken(Project),
useValue: {
findOne: jest.fn(),
},
},
],
}).compile();
service = module.get<BoardColumnsService>(BoardColumnsService);
});
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/board-columns/board-columns.service.spec.ts` around lines 7
- 12, The test fails because BoardColumnsService depends on two injected
repositories (BoardColumn and Project); update the Test.createTestingModule
setup to provide mocks for those repositories (use the Nest testing pattern with
getRepositoryToken(BoardColumn) and getRepositoryToken(Project) or equivalent
providers) so dependency injection can resolve them, then retrieve
BoardColumnsService as before; ensure the mock providers are included alongside
BoardColumnsService in the module's providers array to prevent compile-time
resolution errors.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All tests will be handled in a separate issue for tests.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Zafar7645, understood! Would you like me to open a follow-up GitHub issue to track the test fixes (adding proper repository mocks for BoardColumnsService, TasksService, and related spec files) so it doesn't get lost?


🧠 Learnings used
Learnt from: Zafar7645
Repo: Zafar7645/syncup PR: 12
File: apps/backend/src/users/users.service.spec.ts:15-17
Timestamp: 2025-10-19T16:44:42.665Z
Learning: In the syncup repository (apps/backend/src/users/users.service.ts), the UsersService is scaffolding for future implementation and currently contains no methods or business logic. Basic "should be defined" smoke tests are sufficient for empty scaffolding services.

Learnt from: Zafar7645
Repo: Zafar7645/syncup PR: 16
File: apps/backend/src/auth/auth.service.spec.ts:6-6
Timestamp: 2026-02-07T16:08:28.564Z
Learning: In the syncup monorepo, use bcryptjs instead of native bcrypt in the backend (apps/backend) because Webpack bundling with shared libs breaks with node-gyp. Import from 'bcryptjs' where password hashing is used and ensure existing bcrypt usage is replaced accordingly. Expect roughly a 30% performance trade-off; if performance becomes critical, profile and consider alternatives, but default to bcryptjs to avoid bundling issues.

});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
91 changes: 91 additions & 0 deletions apps/backend/src/board-columns/board-columns.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {
ForbiddenException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { CreateBoardColumnDto } from '@/board-columns/dto/create-board-column.dto';
import { UpdateBoardColumnDto } from '@/board-columns/dto/update-board-column.dto';
import { Project } from '@/projects/entities/project.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { BoardColumn } from '@/board-columns/entities/board-column.entity';

@Injectable()
export class BoardColumnsService {
constructor(
@InjectRepository(BoardColumn)
private columnsRepository: Repository<BoardColumn>,
@InjectRepository(Project) private projectsRepository: Repository<Project>,
) {}

private async verifyProjectAccess(
projectId: number,
userId: number,
): Promise<void> {
const project = await this.projectsRepository.findOne({
where: { id: projectId },
});
if (!project) throw new NotFoundException('Project not found');
if (project.userId !== userId) {
throw new ForbiddenException(
'You do not have permission to modify this project board',
);
}
}

private async verifyColumnAccess(
columnId: number,
userId: number,
): Promise<BoardColumn> {
const column = await this.columnsRepository.findOne({
where: { id: columnId },
relations: ['project'],
});
if (!column) throw new NotFoundException('Column not found');
if (column.project.userId !== userId) {
throw new ForbiddenException(
'You do not have permission to modify this column',
);
}
return column;
}

async create(createDto: CreateBoardColumnDto, userId: number) {
await this.verifyProjectAccess(createDto.projectId, userId);

let order = createDto.order;
if (order === undefined) {
const lastColumn = await this.columnsRepository.findOne({
where: { projectId: createDto.projectId },
order: { order: 'DESC' },
});
order = lastColumn ? lastColumn.order + 1 : 0;
}
Comment on lines +56 to +63
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Order assignment is race-prone under concurrent creates.

Two concurrent requests can read the same last order and insert duplicate positions. Consider a transaction + lock (or a unique (project_id, order) constraint with retry) to keep ordering deterministic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/board-columns/board-columns.service.ts` around lines 56 -
63, The current order assignment in the create flow (reading createDto.order and
using columnsRepository.findOne to fetch the lastColumn) is race-prone under
concurrent creates; fix by making order allocation atomic — either wrap the
create logic in a DB transaction and acquire a lock on the project's columns
(e.g., select the project's columns with FOR UPDATE before computing
lastColumn.order and inserting) in the service method that handles creation (the
method that reads createDto and calls columnsRepository.findOne), or add a
unique constraint on (project_id, order) and implement a small retry loop that
catches unique-constraint violations and recomputes the next order before
retrying the insert; update the board-columns service create flow (where
createDto and columnsRepository are used) to use one of these approaches.


const column = this.columnsRepository.create({ ...createDto, order });
return await this.columnsRepository.save(column);
}

async findAll(projectId: number, userId: number) {
await this.verifyProjectAccess(projectId, userId);
return await this.columnsRepository.find({
where: { projectId },
order: { order: 'ASC' },
});
}

findOne(id: number) {
return `This action returns a #${id} boardColumn`;
}
Comment on lines +77 to +79
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

findOne is still a scaffold stub and breaks endpoint correctness.

This currently returns a string instead of the actual column and does not enforce ownership. It should use the same access check path as update/delete.

Suggested change
- findOne(id: number) {
-   return `This action returns a #${id} boardColumn`;
+ async findOne(id: number, userId: number) {
+   return await this.verifyColumnAccess(id, userId);
  }

Downstream impact: apps/backend/src/board-columns/board-columns.controller.ts findOne must pass req.user.userId.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/board-columns/board-columns.service.ts` around lines 77 -
79, The findOne method in BoardColumnsService is still a scaffold that returns a
string; replace it with the real lookup that fetches the BoardColumn by id,
enforces ownership using the same access-check path as update/delete (reuse the
existing check/permission logic used in update() and remove()), and
throw/not-found or forbidden errors as appropriate; ensure it returns the actual
entity (or DTO) instead of a string and update the controller call to pass
req.user.userId into findOne so ownership can be validated.


async update(id: number, updateDto: UpdateBoardColumnDto, userId: number) {
const column = await this.verifyColumnAccess(id, userId);
const updatedColumn = this.columnsRepository.merge(column, updateDto);
return await this.columnsRepository.save(updatedColumn);
}

async remove(id: number, userId: number) {
const column = await this.verifyColumnAccess(id, userId);
return await this.columnsRepository.remove(column);
}
}
15 changes: 15 additions & 0 deletions apps/backend/src/board-columns/dto/create-board-column.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IsNotEmpty, IsNumber, IsString, IsOptional } from 'class-validator';

export class CreateBoardColumnDto {
@IsString()
@IsNotEmpty()
name: string;

@IsNumber()
@IsOptional()
order?: number;

@IsNumber()
@IsNotEmpty()
projectId: number;
Comment on lines +8 to +14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify current validators and integer schema expectations
rg -n -C2 'class CreateBoardColumnDto|@IsNumber|@IsInt|order|projectId' apps/backend/src/board-columns/dto/create-board-column.dto.ts
rg -n -C2 'CREATE TABLE board_columns|project_id|\"order\"| INT' database/init.sql

Repository: Zafar7645/syncup

Length of output: 533


🏁 Script executed:

#!/bin/bash
# Search for the board_columns table schema definition
rg -n 'CREATE TABLE board_columns' database/init.sql -A 20

Repository: Zafar7645/syncup

Length of output: 799


🏁 Script executed:

#!/bin/bash
# Check for global validation pipes or transformers
rg -n 'ValidationPipe|ClassSerializerInterceptor|TransformOptions|transform:' apps/backend/src/main.ts apps/backend/src/app.module.ts --max-count=20
# Also check if there's a common DTO base class or validation config
fd -t f name '*validation*' 'apps/backend/src/' | head -20

Repository: Zafar7645/syncup

Length of output: 315


🏁 Script executed:

#!/bin/bash
# Read the full ValidationPipe configuration
cat -n apps/backend/src/main.ts | head -20
# Check if CreateBoardColumnDto uses any `@Type` or `@Transform` decorators
rg -n '@Type|@Transform' apps/backend/src/board-columns/dto/create-board-column.dto.ts

Repository: Zafar7645/syncup

Length of output: 818


🏁 Script executed:

#!/bin/bash
# Check if `@Min` constraints make sense
# order: 0 is typical starting value for ordering; projectId: 1 is typical for auto-increment IDs
# Search for other DTOs with similar number validation patterns
rg -n '@IsNumber\(\)' apps/backend/src --type ts | head -30

Repository: Zafar7645/syncup

Length of output: 342


Replace @IsNumber() with @IsInt() to enforce integer validation for ID and order fields.

@IsNumber() accepts decimal values (e.g., 3.14), but the database schema requires INT. This mismatch causes validation to pass but persistence to fail. Use @IsInt() with appropriate @Min() bounds:

🔧 Proposed fix
-import { IsNotEmpty, IsNumber, IsString, IsOptional } from 'class-validator';
+import { IsInt, IsNotEmpty, IsOptional, IsString, Min } from 'class-validator';
@@
-  `@IsNumber`()
+  `@IsInt`()
+  `@Min`(0)
   `@IsOptional`()
   order?: number;
@@
-  `@IsNumber`()
+  `@IsInt`()
+  `@Min`(1)
   `@IsNotEmpty`()
   projectId: number;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/board-columns/dto/create-board-column.dto.ts` around lines 8
- 14, Replace the loose numeric validators on the DTO with integer-specific
validators: change the decorators on the order and projectId properties in
CreateBoardColumnDto from `@IsNumber`() to `@IsInt`(), import `@IsInt` and `@Min` from
class-validator, add `@Min`(0) on order (since it’s optional) and `@Min`(1) on
projectId to enforce positive IDs, and update the imports to remove `@IsNumber`
and include `@IsInt` and `@Min` so runtime validation matches the INT DB schema.

}
4 changes: 4 additions & 0 deletions apps/backend/src/board-columns/dto/update-board-column.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateBoardColumnDto } from '@/board-columns/dto/create-board-column.dto';

export class UpdateBoardColumnDto extends PartialType(CreateBoardColumnDto) {}
42 changes: 42 additions & 0 deletions apps/backend/src/board-columns/entities/board-column.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Project } from '@/projects/entities/project.entity';
import { Task } from '@/tasks/entities/task.entity';
import {
Column,
CreateDateColumn,
UpdateDateColumn,
Entity,
ManyToOne,
JoinColumn,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';

@Entity({ name: 'board_columns' })
export class BoardColumn {
@PrimaryGeneratedColumn()
id: number;

@Column()
name: string;

@Column()
order: number;

@Column({ name: 'project_id' })
projectId: number;

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

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

@ManyToOne(() => Project, (project) => project.boardColumns, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'project_id' })
project: Project;

@OneToMany(() => Task, (task) => task.column)
tasks: Task[];
}
5 changes: 5 additions & 0 deletions apps/backend/src/projects/entities/project.entity.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { BoardColumn } from '@/board-columns/entities/board-column.entity';
import { User } from '@/users/user.entity';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
Expand Down Expand Up @@ -32,4 +34,7 @@ export class Project {

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

@OneToMany(() => BoardColumn, (boardColumn) => boardColumn.project)
boardColumns: BoardColumn[];
}
13 changes: 7 additions & 6 deletions apps/backend/src/projects/projects.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Delete,
UseGuards,
Request,
ParseIntPipe,
} from '@nestjs/common';
import { ProjectsService } from '@/projects/projects.service';
import { CreateProjectDto } from '@/projects/dto/create-project.dto';
Expand All @@ -34,26 +35,26 @@ export class ProjectsController {

@Get(':id')
findOne(
@Param('id') id: string,
@Param('id', ParseIntPipe) id: number,
@Request() req: { user: { userId: number; email: string } },
) {
return this.projectsService.findOne(+id, req.user.userId);
return this.projectsService.findOne(id, req.user.userId);
}

@Patch(':id')
update(
@Param('id') id: string,
@Param('id', ParseIntPipe) id: number,
@Body() updateProjectDto: UpdateProjectDto,
@Request() req: { user: { userId: number; email: string } },
) {
return this.projectsService.update(+id, updateProjectDto, req.user.userId);
return this.projectsService.update(id, updateProjectDto, req.user.userId);
}

@Delete(':id')
remove(
@Param('id') id: string,
@Param('id', ParseIntPipe) id: number,
@Request() req: { user: { userId: number; email: string } },
) {
return this.projectsService.remove(+id, req.user.userId);
return this.projectsService.remove(id, req.user.userId);
}
}
19 changes: 19 additions & 0 deletions apps/backend/src/tasks/dto/create-task.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { IsNotEmpty, IsOptional, IsString, IsNumber } from 'class-validator';

export class CreateTaskDto {
@IsString()
@IsNotEmpty()
title: string;

@IsString()
@IsOptional()
description?: string;

@IsNumber()
@IsOptional()
order?: number;

@IsNumber()
@IsNotEmpty()
columnId: number;
}
4 changes: 4 additions & 0 deletions apps/backend/src/tasks/dto/update-task.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateTaskDto } from '@/tasks/dto/create-task.dto';

export class UpdateTaskDto extends PartialType(CreateTaskDto) {}
Loading
Loading