Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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 .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @maxn990 @amywng
10 changes: 0 additions & 10 deletions apps/backend-e2e/src/apps/backend/apps/backend.spec.ts

This file was deleted.

9 changes: 7 additions & 2 deletions apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ import { TypeOrmModule } from '@nestjs/typeorm';

import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TaskModule } from './task/task.module';
import AppDataSource from './data-source';
import { TaskModule } from './task/task.module';
import { LabelModule } from './label/label.module';

@Module({
imports: [TypeOrmModule.forRoot(AppDataSource.options), TaskModule],
imports: [
TypeOrmModule.forRoot(AppDataSource.options),
TaskModule,
LabelModule,
],
controllers: [AppController],
providers: [AppService],
})
Expand Down
18 changes: 0 additions & 18 deletions apps/backend/src/auth/auth.controller.spec.ts

This file was deleted.

18 changes: 0 additions & 18 deletions apps/backend/src/auth/auth.service.spec.ts

This file was deleted.

3 changes: 2 additions & 1 deletion apps/backend/src/data-source.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DataSource } from 'typeorm';
import { PluralNamingStrategy } from './strategies/plural-naming.strategy';
import { Task } from './task/types/task.entity';
import { Label } from './label/types/label.entity';
import * as dotenv from 'dotenv';

dotenv.config();
Expand All @@ -12,7 +13,7 @@ const AppDataSource = new DataSource({
username: process.env.NX_DB_USERNAME,
password: process.env.NX_DB_PASSWORD,
database: process.env.NX_DB_DATABASE,
entities: [Task],
entities: [Task, Label],
migrations: ['apps/backend/src/migrations/*.js'],
// Setting synchronize: true shouldn't be used in production - otherwise you can lose production data
synchronize: false,
Expand Down
30 changes: 30 additions & 0 deletions apps/backend/src/label/label.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Test, TestingModule } from '@nestjs/testing';
import { LabelsController } from './label.controller';
import { LabelsService } from './label.service';

// Mock implementation for LabelsService
const mockLabelService = {};

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

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [LabelsController],
providers: [
{
provide: LabelsService,
useValue: mockLabelService,
},
],
}).compile();

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

it('should be defined', () => {
expect(controller).toBeDefined();
});

/* Test for create new label */
});
27 changes: 27 additions & 0 deletions apps/backend/src/label/label.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
Controller,
Get,

Check warning on line 3 in apps/backend/src/label/label.controller.ts

View workflow job for this annotation

GitHub Actions / pre-deploy

'Get' is defined but never used
Post,

Check warning on line 4 in apps/backend/src/label/label.controller.ts

View workflow job for this annotation

GitHub Actions / pre-deploy

'Post' is defined but never used
Body,

Check warning on line 5 in apps/backend/src/label/label.controller.ts

View workflow job for this annotation

GitHub Actions / pre-deploy

'Body' is defined but never used
Patch,

Check warning on line 6 in apps/backend/src/label/label.controller.ts

View workflow job for this annotation

GitHub Actions / pre-deploy

'Patch' is defined but never used
Param,

Check warning on line 7 in apps/backend/src/label/label.controller.ts

View workflow job for this annotation

GitHub Actions / pre-deploy

'Param' is defined but never used
Delete,

Check warning on line 8 in apps/backend/src/label/label.controller.ts

View workflow job for this annotation

GitHub Actions / pre-deploy

'Delete' is defined but never used
Query,

Check warning on line 9 in apps/backend/src/label/label.controller.ts

View workflow job for this annotation

GitHub Actions / pre-deploy

'Query' is defined but never used
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { LabelsService } from './label.service';
import { Label } from './types/label.entity';

@ApiTags('labels')
@Controller('labels')
export class LabelsController {
constructor(private readonly labelsService: LabelsService) {}

/** Creates a new label.
* @param LabelDto - The data transfer object containing label details.
* @returns The created label.
* @throws BadRequestException if the label name is not unique
* @throws BadRequestException if label name is not provided
* @throws BadRequestException if color is not provided or is not hexadecimal
*/
}
13 changes: 13 additions & 0 deletions apps/backend/src/label/label.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Label } from './types/label.entity';
import { LabelsService } from './label.service';
import { LabelsController } from './label.controller';

@Module({
imports: [TypeOrmModule.forFeature([Label])],
providers: [LabelsService],
controllers: [LabelsController],
exports: [LabelsService],
})
export class LabelModule {}
32 changes: 32 additions & 0 deletions apps/backend/src/label/label.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Test, TestingModule } from '@nestjs/testing';
import { mock } from 'jest-mock-extended';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Label } from './types/label.entity';
import { LabelsService } from './label.service';

const mockLabelsRepository = mock<Repository<Label>>();

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

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
LabelsService,
{
provide: getRepositoryToken(Label),
useValue: mockLabelsRepository,
},
],
}).compile();

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

it('should be defined', () => {
expect(service).toBeDefined();
});

/* Tests for create new label */
});
14 changes: 14 additions & 0 deletions apps/backend/src/label/label.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Label } from './types/label.entity';

@Injectable()
export class LabelsService {
constructor(
@InjectRepository(Label)
private labelRepository: Repository<Label>,
) {}

// Creates a new label
}
20 changes: 20 additions & 0 deletions apps/backend/src/label/types/label.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Column, Entity, PrimaryGeneratedColumn, ManyToMany } from 'typeorm';
import { Task } from '../../task/types/task.entity';

@Entity()
export class Label {
@PrimaryGeneratedColumn()
id: number;

@Column({ unique: true })
name: string;

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

@Column()
color: string;

@ManyToMany(() => Task, (task) => task.labels)
tasks: Task[];
}
19 changes: 0 additions & 19 deletions apps/backend/src/migrations/1754254886189-add_task.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddLabelRelationToTask1754510950040 implements MigrationInterface {
name = 'AddLabelRelationToTask1754510950040';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "public"."tasks_category_enum" AS ENUM('Draft', 'To Do', 'In Progress', 'Completed')`,
);
await queryRunner.query(
`CREATE TABLE "labels" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "description" character varying, "color" character varying NOT NULL, CONSTRAINT "UQ_543605929e5ebe08eeeab493f60" UNIQUE ("name"), CONSTRAINT "PK_c0c4e97f76f1f3a268c7a70b925" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "tasks" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "description" character varying, "dateCreated" TIMESTAMP NOT NULL DEFAULT now(), "dueDate" TIMESTAMP, "category" "public"."tasks_category_enum" NOT NULL DEFAULT 'Draft', CONSTRAINT "PK_8d12ff38fcc62aaba2cab748772" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "tasks_labels_labels" ("tasksId" integer NOT NULL, "labelsId" integer NOT NULL, CONSTRAINT "PK_f85aea2ec53934cf2a19c64d5ac" PRIMARY KEY ("tasksId", "labelsId"))`,
);
await queryRunner.query(
`CREATE INDEX "IDX_f20c9d0af5f9650c92f0b95001" ON "tasks_labels_labels" ("tasksId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_9eb372639eba51a1539303cca9" ON "tasks_labels_labels" ("labelsId") `,
);
await queryRunner.query(
`ALTER TABLE "tasks_labels_labels" ADD CONSTRAINT "FK_f20c9d0af5f9650c92f0b95001c" FOREIGN KEY ("tasksId") REFERENCES "tasks"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE "tasks_labels_labels" ADD CONSTRAINT "FK_9eb372639eba51a1539303cca97" FOREIGN KEY ("labelsId") REFERENCES "labels"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "tasks_labels_labels" DROP CONSTRAINT "FK_9eb372639eba51a1539303cca97"`,
);
await queryRunner.query(
`ALTER TABLE "tasks_labels_labels" DROP CONSTRAINT "FK_f20c9d0af5f9650c92f0b95001c"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_9eb372639eba51a1539303cca9"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_f20c9d0af5f9650c92f0b95001"`,
);
await queryRunner.query(`DROP TABLE "tasks_labels_labels"`);
await queryRunner.query(`DROP TABLE "tasks"`);
await queryRunner.query(`DROP TABLE "labels"`);
await queryRunner.query(`DROP TYPE "public"."tasks_category_enum"`);
}
}
95 changes: 95 additions & 0 deletions apps/backend/src/seeds/seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import dataSource from '../data-source'; // adjust path as needed
import { Task } from '../task/types/task.entity';
import { Label } from '../label/types/label.entity';
import { TaskCategory } from '../task/types/category';

async function seed() {
try {
console.log('🌱 Starting database seed...');

// Initialize the data source
await dataSource.initialize();
console.log('✅ Database connection established');

// Clear existing data (drop and recreate schema)
console.log('🧹 Clearing existing data...');
await dataSource.query('DROP SCHEMA public CASCADE; CREATE SCHEMA public;');

// Recreate tables
await dataSource.synchronize();
console.log('✅ Database schema synchronized');

// Create labels
console.log('🏷️ Creating labels...');
const labels = await dataSource.getRepository(Label).save([
{ name: 'High Priority', color: '#FF4444' },
{ name: 'Bug Fix', color: '#FF8800' },
{ name: 'Feature', color: '#00AA00' },
{ name: 'Documentation', color: '#4488FF' },
]);
console.log(`✅ Created ${labels.length} labels`);

// Create tasks
console.log('📝 Creating tasks...');
const tasks = await dataSource.getRepository(Task).save([
{
title: 'Complete project proposal',
description: 'Draft and finalize the Q4 project proposal document',
dueDate: new Date('2025-08-15'),
category: TaskCategory.IN_PROGRESS,
},
{
title: 'Review code changes',
description: 'Review pull requests for the authentication module',
dueDate: new Date('2025-08-10'),
category: TaskCategory.TODO,
},
{
title: 'Team meeting preparation',
description: 'Prepare agenda and materials for weekly team sync',
dueDate: new Date('2025-08-09'),
category: TaskCategory.COMPLETED,
},
{
title: 'Update documentation',
description: 'Update API documentation with recent endpoint changes',
category: TaskCategory.DRAFT,
},
]);
console.log(`✅ Created ${tasks.length} tasks`);

// Optionally assign some labels to tasks
console.log('🔗 Assigning labels to tasks...');

// Assign "High Priority" and "Feature" to first task
tasks[0].labels = [labels[0], labels[2]];
await dataSource.getRepository(Task).save(tasks[0]);

// Assign "Bug Fix" to second task
tasks[1].labels = [labels[1]];
await dataSource.getRepository(Task).save(tasks[1]);

// Assign "Documentation" to last task
tasks[3].labels = [labels[3]];
await dataSource.getRepository(Task).save(tasks[3]);

console.log('✅ Labels assigned to tasks');

console.log('🎉 Database seed completed successfully!');
} catch (error) {
console.error('❌ Seed failed:', error);
throw error;
} finally {
// Close the database connection
if (dataSource.isInitialized) {
await dataSource.destroy();
console.log('✅ Database connection closed');
}
}
}

// Run the seed function
seed().catch((error) => {
console.error('❌ Fatal error during seed:', error);
process.exit(1);
});
Loading
Loading