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
57 changes: 57 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: ProjectLens CI/CD

# Trigger the workflow on push or pull request to the main branch
on:
pull_request:
branches: [ main ]

jobs:
# Job 1: Build and Test the Backend (NestJS)
backend-ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: backend/package-lock.json

- name: Install Dependencies
run: npm install
working-directory: backend

- name: Lint
run: npm run lint
working-directory: backend

- name: Run Tests
run: npm run test
working-directory: backend

- name: Build
run: npm run build
working-directory: backend

# Job 2: Build and Test the Frontend (Next.js)
frontend-ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

- name: Install Dependencies
run: npm install
working-directory: frontend

- name: Build
run: npm run build
working-directory: frontend
env:
NEXT_PUBLIC_API_URL: http://localhost:4000
Binary file modified README.md
Binary file not shown.
38 changes: 38 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# --- Build Stage ---
# Use Node.js 22 Alpine for a lightweight build environment
FROM node:22-alpine AS builder

WORKDIR /app

# Copy configuration files for dependency installation
COPY package*.json ./
RUN npm install

# Copy source code and build the application
COPY . .
RUN npm run build

# --- Runtime Stage ---
# Use a fresh slim image for the production environment
FROM node:22-alpine

WORKDIR /app

# Copy only the necessary files from the builder stage
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
# Include src for the seed script (in a real prod app, we might bundle this differently)
COPY --from=builder /app/src/seed.ts ./src/seed.ts

# Documentation: Port 4000 is the default for our NestJS API
EXPOSE 4000

# Documentation: Port 4000 is the default for our NestJS API
EXPOSE 4000

# Documentation: Environment variables for MongoDB
ENV MONGODB_URI=mongodb://mongodb:27017/projectlens

# Start the application
CMD ["node", "dist/main"]
55 changes: 55 additions & 0 deletions backend/package-lock.json

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

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"dependencies": {
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.3",
"@nestjs/core": "^11.0.1",
"@nestjs/mongoose": "^11.0.4",
"@nestjs/platform-express": "^11.0.1",
Expand Down
14 changes: 13 additions & 1 deletion backend/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProjectsModule } from './projects/projects.module';
import { TasksModule } from './tasks/tasks.module';

@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost:27017/projectlens'),
ConfigModule.forRoot({
isGlobal: true,
}),
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
uri:
configService.get<string>('MONGODB_URI') ||
'mongodb://localhost:27017/projectlens',
}),
inject: [ConfigService],
}),
ProjectsModule,
TasksModule,
],
Expand Down
17 changes: 11 additions & 6 deletions backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ async function bootstrap() {

app.enableCors(); // Enable CORS for development

app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}));
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);

const config = new DocumentBuilder()
.setTitle('ProjectLens API')
Expand All @@ -26,4 +28,7 @@ async function bootstrap() {

await app.listen(process.env.PORT ?? 4000);
}
bootstrap();
void bootstrap().catch((err) => {
console.error('Error starting application:', err);
process.exit(1);
});
5 changes: 4 additions & 1 deletion backend/src/projects/dto/create-project.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ export class CreateProjectDto {
@IsNotEmpty()
name: string;

@ApiProperty({ description: 'The description of the project', required: false })
@ApiProperty({
description: 'The description of the project',
required: false,
})
@IsString()
@IsOptional()
description?: string;
Expand Down
2 changes: 1 addition & 1 deletion backend/src/projects/projects.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { ApiTags } from '@nestjs/swagger';
import { ProjectService } from './projects.service';
import { CreateProjectDto } from './dto/create-project.dto';

Expand Down
2 changes: 1 addition & 1 deletion backend/src/projects/projects.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument */
import { Test, TestingModule } from '@nestjs/testing';
import { ProjectService } from './projects.service';
import { ProjectRepository } from './repositories/project.repository';
import { TaskRepository } from '../tasks/repositories/task.repository';
import { TaskStatus } from '../tasks/schemas/task.schema';
import { NotFoundException } from '@nestjs/common';
import { describe, beforeEach, it, expect, jest } from '@jest/globals';

describe('ProjectService', () => {
Expand Down
4 changes: 3 additions & 1 deletion backend/src/projects/projects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export class ProjectService {
};
}

const completedTasks = tasks.filter((t) => t.status === TaskStatus.COMPLETED);
const completedTasks = tasks.filter(
(t) => t.status === TaskStatus.COMPLETED,
);
const totalTasksCount = tasks.length;
const completedTasksCount = completedTasks.length;

Expand Down
4 changes: 3 additions & 1 deletion backend/src/projects/repositories/project.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { Project } from '../schemas/project.schema';

@Injectable()
export class ProjectRepository {
constructor(@InjectModel(Project.name) private projectModel: Model<Project>) {}
constructor(
@InjectModel(Project.name) private projectModel: Model<Project>,
) {}

async create(projectData: any): Promise<Project> {
const newProject = new this.projectModel(projectData);
Expand Down
Loading
Loading