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
1 change: 1 addition & 0 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.1",
"@nestjs/mapped-types": "^2.1.0",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/typeorm": "^11.0.0",
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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';

@Module({
imports: [
Expand All @@ -27,6 +28,7 @@ import { AuthModule } from '@/auth/auth.module';
}),
AuthModule,
UsersModule,
ProjectsModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
12 changes: 12 additions & 0 deletions apps/backend/src/projects/dto/create-project.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { IsNotEmpty, IsOptional, IsString, Matches } from 'class-validator';

export class CreateProjectDto {
@IsString()
@IsNotEmpty()
@Matches(/\S/, { message: 'name must contain non-whitespace characters' })
name: string;

@IsString()
@IsOptional()
description?: string;
}
4 changes: 4 additions & 0 deletions apps/backend/src/projects/dto/update-project.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateProjectDto } from '@/projects/dto/create-project.dto';

export class UpdateProjectDto extends PartialType(CreateProjectDto) {}
35 changes: 35 additions & 0 deletions apps/backend/src/projects/entities/project.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { User } from '@/users/user.entity';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';

@Entity({ name: 'projects' })
export class Project {
@PrimaryGeneratedColumn()
id: number;

@Column()
name: string;

@Column({ nullable: true })
description: string;

@Column({ name: 'user_id' })
userId: number;

@ManyToOne(() => User, (user) => user.projects, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'user_id' })
user: User;

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

@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}
20 changes: 20 additions & 0 deletions apps/backend/src/projects/projects.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ProjectsController } from '@/projects/projects.controller';
import { ProjectsService } from '@/projects/projects.service';

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

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

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

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

@UseGuards(JwtAuthGuard)
@Controller('projects')
export class ProjectsController {
constructor(private readonly projectsService: ProjectsService) {}

@Post()
create(
@Body() createProjectDto: CreateProjectDto,
@Request() req: { user: { userId: number; email: string } },
) {
return this.projectsService.create(createProjectDto, req.user.userId);
}

@Get()
findAll(@Request() req: { user: { userId: number; email: string } }) {
return this.projectsService.findAll(req.user.userId);
}

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

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

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

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

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

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

service = module.get<ProjectsService>(ProjectsService);
});

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

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

async create(createProjectDto: CreateProjectDto, userId: number) {
const project = this.projectsRepository.create({
...createProjectDto,
userId,
});

return await this.projectsRepository.save(project);
}

async findAll(userId: number) {
return await this.projectsRepository.find({
where: { userId },
order: { createdAt: 'DESC' },
});
}

async findOne(id: number, userId: number) {
const project = await this.projectsRepository.findOne({
where: { id, userId },
});

if (!project) {
throw new NotFoundException(
`Project with ID "${id}" not found or you don't have access.`,
);
}

return project;
}

async update(id: number, updateProjectDto: UpdateProjectDto, userId: number) {
const project = await this.findOne(id, userId);

const updatedProject = this.projectsRepository.merge(
project,
updateProjectDto,
);

return await this.projectsRepository.save(updatedProject);
}

async remove(id: number, userId: number) {
const project = await this.findOne(id, userId);

return await this.projectsRepository.remove(project);
}
}
5 changes: 5 additions & 0 deletions apps/backend/src/users/user.entity.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Project } from '@/projects/entities/project.entity';
import {
BeforeInsert,
BeforeUpdate,
Column,
CreateDateColumn,
Entity,
OneToMany,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
Expand Down Expand Up @@ -35,4 +37,7 @@ export class User {
this.email = this.email.trim().toLowerCase();
}
}

@OneToMany(() => Project, (project) => project.user)
projects: Project[];
}
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.