diff --git a/apps/backend/prisma/schema.prisma b/apps/backend/prisma/schema.prisma index 2462ec1..5a064ab 100644 --- a/apps/backend/prisma/schema.prisma +++ b/apps/backend/prisma/schema.prisma @@ -140,6 +140,7 @@ model Task { history TaskHistory[] comments TaskComment[] + children Task[] @relation("TaskHierarchy") status TaskStatus @relation(fields: [statusId], references: [id], onDelete: Restrict) statusId String @@ -155,7 +156,6 @@ model Task { parentId String? parent Task? @relation("TaskHierarchy", fields: [parentId], references: [id], onDelete: SetNull) - children Task[] @relation("TaskHierarchy") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/apps/backend/src/prisma/seed/seed-constants.ts b/apps/backend/src/prisma/seed/seed-constants.ts deleted file mode 100644 index b629f53..0000000 --- a/apps/backend/src/prisma/seed/seed-constants.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { UserRole } from '@prisma/client'; - -export const usersForSeed = [ - { - name: 'testUser', - email: 'testUser@gmail.com', - password: 'testUser', - // role: USER - default role - }, - { - name: 'testAdmin', - email: 'testAdmin@gmail.com', - password: 'testAdmin', - role: UserRole.ADMIN, - }, - { - name: 'testModerator', - email: 'testModeartor@gmail.com', - password: 'testModerator', - role: UserRole.MODERATOR, - }, -]; diff --git a/apps/backend/src/prisma/seed/seed.ts b/apps/backend/src/prisma/seed/seed.ts index 7777263..b53b536 100644 --- a/apps/backend/src/prisma/seed/seed.ts +++ b/apps/backend/src/prisma/seed/seed.ts @@ -1,24 +1,58 @@ import { PrismaClient } from '@prisma/client'; -import { usersForSeed } from './seed-constants'; -import { hashPassword } from '../../modules/auth/utils/hashPassword/hashPassword'; +import { seedTeamMembers } from './seedTeamMembers'; +import { seedTeam } from './seedTeam'; +import { seedUsers } from './seedUsers'; +import { seedProjects } from './seedTeamProjects'; +import { seedBoards } from './seedBoards'; +import { seedBoardStatusesAndColumns } from './seedBoardColumns'; +import { seedTasks } from './seedTasks'; +import { seedTaskComments } from './seedTaskComments'; +import { seedTaskHistory } from './seedTaskHistory'; const prisma = new PrismaClient(); async function up() { - // create users with different roles - for (const user of usersForSeed) { - const hashedPassword = await hashPassword(user.password); - await prisma.user.create({ - data: { - ...user, - password: hashedPassword, - }, - }); + const users = await seedUsers(prisma); + + const team = await seedTeam(prisma, users.userTest1.id); + + const { teamMemberAdmin, teamMember } = await seedTeamMembers( + prisma, + team.id, + users.userTest2.id, + users.userTest3.id + ); + + const { project1, project2 } = await seedProjects(prisma, team.id); + + const boards = await seedBoards(prisma, project1.id, project2.id); + + for (const board of boards) { + const statuses = await seedBoardStatusesAndColumns(prisma, board.id); + + const tasks = await seedTasks(prisma, board.id, statuses, teamMemberAdmin.id, teamMember.id); + + await seedTaskComments(prisma, tasks, teamMember.id); + + await seedTaskHistory(prisma, tasks, teamMemberAdmin.id); } } async function down() { - await prisma.$executeRaw`TRUNCATE TABLE "User" RESTART IDENTITY CASCADE`; + await prisma.$executeRawUnsafe(` + TRUNCATE TABLE + "TaskComment", + "TaskHistory", + "Task", + "BoardColumn", + "TaskStatus", + "Board", + "Project", + "TeamMember", + "Team", + "User" + RESTART IDENTITY CASCADE + `); } async function main() { diff --git a/apps/backend/src/prisma/seed/seedBoardColumns.ts b/apps/backend/src/prisma/seed/seedBoardColumns.ts new file mode 100644 index 0000000..f567d6b --- /dev/null +++ b/apps/backend/src/prisma/seed/seedBoardColumns.ts @@ -0,0 +1,35 @@ +import type { PrismaClient } from '@prisma/client'; + +export async function seedBoardStatusesAndColumns(prisma: PrismaClient, boardId: string) { + return prisma.$transaction(async (tx) => { + const statuses = [ + { name: 'Todo', position: 1 }, + { name: 'In Progress', position: 2 }, + { name: 'Done', position: 3 }, + ]; + + const createdStatuses = []; + + for (const s of statuses) { + const status = await tx.taskStatus.create({ + data: { + name: s.name, + boardId, + }, + }); + + await tx.boardColumn.create({ + data: { + title: s.name, + position: s.position, + boardId, + statusId: status.id, + }, + }); + + createdStatuses.push(status); + } + + return createdStatuses; + }); +} diff --git a/apps/backend/src/prisma/seed/seedBoards.ts b/apps/backend/src/prisma/seed/seedBoards.ts new file mode 100644 index 0000000..7134508 --- /dev/null +++ b/apps/backend/src/prisma/seed/seedBoards.ts @@ -0,0 +1,33 @@ +import type { PrismaClient } from '@prisma/client'; + +export async function seedBoards(prisma: PrismaClient, project1Id: string, project2Id: string) { + const board1 = await prisma.board.create({ + data: { + name: 'Frontend React', + projectId: project1Id, + }, + }); + + const board2 = await prisma.board.create({ + data: { + name: 'Backend Express', + projectId: project1Id, + }, + }); + + const board3 = await prisma.board.create({ + data: { + name: 'Frontend React Native', + projectId: project2Id, + }, + }); + + const board4 = await prisma.board.create({ + data: { + name: 'Backend NestJS', + projectId: project2Id, + }, + }); + + return [board1, board2, board3, board4]; +} diff --git a/apps/backend/src/prisma/seed/seedTaskComments.ts b/apps/backend/src/prisma/seed/seedTaskComments.ts new file mode 100644 index 0000000..b9aae54 --- /dev/null +++ b/apps/backend/src/prisma/seed/seedTaskComments.ts @@ -0,0 +1,23 @@ +import { type PrismaClient } from '@prisma/client'; + +export async function seedTaskComments( + prisma: PrismaClient, + tasks: { id: string }[], + teamMemberId: string +) { + const comments = []; + + for (const task of tasks) { + const comment = await prisma.taskComment.create({ + data: { + text: `Comment for task ${task.id}`, + taskId: task.id, + teamMemberId, + }, + }); + + comments.push(comment); + } + + return comments; +} diff --git a/apps/backend/src/prisma/seed/seedTaskHistory.ts b/apps/backend/src/prisma/seed/seedTaskHistory.ts new file mode 100644 index 0000000..9da066a --- /dev/null +++ b/apps/backend/src/prisma/seed/seedTaskHistory.ts @@ -0,0 +1,19 @@ +import { type PrismaClient } from '@prisma/client'; + +export async function seedTaskHistory( + prisma: PrismaClient, + tasks: { id: string }[], + teamMemberId: string +) { + for (const task of tasks) { + await prisma.taskHistory.create({ + data: { + field: 'title', + oldValue: { status: 'todo' }, + newValue: { status: 'To do' }, + taskId: task.id, + teamMemberId, + }, + }); + } +} diff --git a/apps/backend/src/prisma/seed/seedTasks.ts b/apps/backend/src/prisma/seed/seedTasks.ts new file mode 100644 index 0000000..8ccdf53 --- /dev/null +++ b/apps/backend/src/prisma/seed/seedTasks.ts @@ -0,0 +1,64 @@ +import { type PrismaClient, TaskPriority, TaskType } from '@prisma/client'; + +export async function seedTasks( + prisma: PrismaClient, + boardId: string, + statuses: { id: string }[], + creatorId: string, + assigneeId: string +) { + const tasks = []; + const priorities = Object.values(TaskPriority); + const types = Object.values(TaskType); + + for (let i = 1; i <= 10; i++) { + const status = statuses[i % statuses.length]; + const randomPriority = priorities[Math.floor(Math.random() * priorities.length)]; + const randomType = types[Math.floor(Math.random() * types.length)]; + + const task = await prisma.task.create({ + data: { + title: `Task ${i}`, + description: `Description for task ${i}`, + type: randomType, + priority: randomPriority, + boardId, + statusId: status.id, + createdById: creatorId, + assignedToId: assigneeId, + }, + }); + + tasks.push(task); + + const shouldCreateSubtasks = Math.random() < 0.3; + + if (shouldCreateSubtasks) { + const subtasksCount = Math.random() < 0.5 ? 1 : 2; + + for (let j = 1; j <= subtasksCount; j++) { + const subPriority = priorities[Math.floor(Math.random() * priorities.length)]; + const subType = types[Math.floor(Math.random() * types.length)]; + const subStatus = statuses[Math.floor(Math.random() * statuses.length)]; + + const subtask = await prisma.task.create({ + data: { + title: `Task ${i}.${j}`, + description: `Subtask ${j} for task ${i}`, + type: subType, + priority: subPriority, + boardId, + statusId: subStatus.id, + createdById: creatorId, + assignedToId: assigneeId, + parentId: task.id, + }, + }); + + tasks.push(subtask); + } + } + } + + return tasks; +} diff --git a/apps/backend/src/prisma/seed/seedTeam.ts b/apps/backend/src/prisma/seed/seedTeam.ts new file mode 100644 index 0000000..1567bfe --- /dev/null +++ b/apps/backend/src/prisma/seed/seedTeam.ts @@ -0,0 +1,22 @@ +import { type PrismaClient, TeamMemberRole } from '@prisma/client'; + +export async function seedTeam(prisma: PrismaClient, ownerId: string) { + return prisma.$transaction(async (tx) => { + const team = await tx.team.create({ + data: { + name: 'Test Team', + ownerId, + }, + }); + + await tx.teamMember.create({ + data: { + userId: ownerId, + teamId: team.id, + role: TeamMemberRole.OWNER, + }, + }); + + return team; + }); +} diff --git a/apps/backend/src/prisma/seed/seedTeamMembers.ts b/apps/backend/src/prisma/seed/seedTeamMembers.ts new file mode 100644 index 0000000..aeb3ceb --- /dev/null +++ b/apps/backend/src/prisma/seed/seedTeamMembers.ts @@ -0,0 +1,26 @@ +import { type PrismaClient, TeamMemberRole } from '@prisma/client'; + +export async function seedTeamMembers( + prisma: PrismaClient, + teamId: string, + userAdminId: string, + userMemberId: string +) { + const teamMemberAdmin = await prisma.teamMember.create({ + data: { + userId: userAdminId, + teamId, + role: TeamMemberRole.ADMIN, + }, + }); + + const teamMember = await prisma.teamMember.create({ + data: { + userId: userMemberId, + teamId, + role: TeamMemberRole.MEMBER, + }, + }); + + return { teamMemberAdmin, teamMember }; +} diff --git a/apps/backend/src/prisma/seed/seedTeamProjects.ts b/apps/backend/src/prisma/seed/seedTeamProjects.ts new file mode 100644 index 0000000..ae29d7f --- /dev/null +++ b/apps/backend/src/prisma/seed/seedTeamProjects.ts @@ -0,0 +1,19 @@ +import type { PrismaClient } from '@prisma/client'; + +export async function seedProjects(prisma: PrismaClient, teamId: string) { + const project1 = await prisma.project.create({ + data: { + name: 'Desktop App', + teamId, + }, + }); + + const project2 = await prisma.project.create({ + data: { + name: 'Mobile App', + teamId, + }, + }); + + return { project1, project2 }; +} diff --git a/apps/backend/src/prisma/seed/seedUsers.ts b/apps/backend/src/prisma/seed/seedUsers.ts new file mode 100644 index 0000000..af4e9a1 --- /dev/null +++ b/apps/backend/src/prisma/seed/seedUsers.ts @@ -0,0 +1,48 @@ +import { type PrismaClient, UserRole } from '@prisma/client'; +import { hashPassword } from '../../modules/auth/utils/hashPassword/hashPassword'; + +export async function seedUsers(prisma: PrismaClient) { + const userAdmin = await prisma.user.create({ + data: { + name: 'testAdmin', + email: 'testAdmin@gmail.com', + password: await hashPassword('testAdmin'), + role: UserRole.ADMIN, + }, + }); + + const userModerator = await prisma.user.create({ + data: { + name: 'testModerator', + email: 'testModerator@gmail.com', + password: await hashPassword('testModerator'), + role: UserRole.MODERATOR, + }, + }); + + const userTest1 = await prisma.user.create({ + data: { + name: 'userTest1', + email: 'userTest1@gmail.com', + password: await hashPassword('userTest1'), + }, + }); + + const userTest2 = await prisma.user.create({ + data: { + name: 'userTest2', + email: 'userTest2@gmail.com', + password: await hashPassword('userTest2'), + }, + }); + + const userTest3 = await prisma.user.create({ + data: { + name: 'userTest3', + email: 'userTest3@gmail.com', + password: await hashPassword('userTest3'), + }, + }); + + return { userAdmin, userModerator, userTest1, userTest2, userTest3 }; +} diff --git a/package.json b/package.json index 3328815..9900c8b 100644 --- a/package.json +++ b/package.json @@ -5,41 +5,39 @@ "description": "", "main": "index.js", "scripts": { - "dev": "pnpm run --parallel dev", - "build": "pnpm run --parallel build", - "start": "pnpm run --parallel start", - "lint": "pnpm run --recursive lint", - "lint:shared": "pnpm --filter @task-tracker/shared-types lint", - "lint:fix": "pnpm run --recursive lint:fix", - "format": "pnpm run --recursive format", - "format:check": "pnpm run --recursive format:check", - - "lint:frontend": "pnpm --filter @task-tracker/frontend lint", - "format:check:frontend": "pnpm --filter @task-tracker/frontend format:check", - "lint:fsd:frontend": "pnpm --filter @task-tracker/frontend lint:fsd", - "typecheck:frontend": "pnpm --filter @task-tracker/frontend typecheck", - "build:frontend": "pnpm --filter @task-tracker/frontend build", - - "dev:backend": "pnpm --filter @task-tracker/backend start:dev", - "start:backend": "pnpm --filter @task-tracker/backend start:prod", - "build:backend": "pnpm --filter @task-tracker/backend build", - "lint:backend": "pnpm --filter @task-tracker/backend lint", - "format:check:backend": "pnpm --filter @task-tracker/backend format:check", - "test:backend:ci": "pnpm --filter @task-tracker/backend test:ci", - "typecheck:backend": "pnpm --filter @task-tracker/backend typecheck", - "prisma:generate:backend": "pnpm --filter @task-tracker/backend prisma:generate", - "prisma:migrate:deploy:backend": "pnpm --filter @task-tracker/backend prisma:migrate:deploy", - "prepare": "husky" - }, + "dev": "pnpm run --parallel dev", + "build": "pnpm run --parallel build", + "start": "pnpm run --parallel start", + "lint": "pnpm run --recursive lint", + "lint:shared": "pnpm --filter @task-tracker/shared-types lint", + "lint:fix": "pnpm run --recursive lint:fix", + "format": "pnpm run --recursive format", + "format:check": "pnpm run --recursive format:check", + "lint:frontend": "pnpm --filter @task-tracker/frontend lint", + "format:check:frontend": "pnpm --filter @task-tracker/frontend format:check", + "lint:fsd:frontend": "pnpm --filter @task-tracker/frontend lint:fsd", + "typecheck:frontend": "pnpm --filter @task-tracker/frontend typecheck", + "build:frontend": "pnpm --filter @task-tracker/frontend build", + "dev:backend": "pnpm --filter @task-tracker/backend start:dev", + "start:backend": "pnpm --filter @task-tracker/backend start:prod", + "build:backend": "pnpm --filter @task-tracker/backend build", + "lint:backend": "pnpm --filter @task-tracker/backend lint", + "format:check:backend": "pnpm --filter @task-tracker/backend format:check", + "test:backend:ci": "pnpm --filter @task-tracker/backend test:ci", + "typecheck:backend": "pnpm --filter @task-tracker/backend typecheck", + "prisma:generate:backend": "pnpm --filter @task-tracker/backend prisma:generate", + "prisma:migrate:deploy:backend": "pnpm --filter @task-tracker/backend prisma:migrate:deploy", + "prepare": "husky" + }, "lint-staged": { - ".{ts,tsx,js,,mjs}": [ - "eslint --fix --no-warn-ignored", - "prettier --write" - ], - ".{json,css,md}": [ - "prettier --write" - ] -}, + "*.{ts,tsx,js,mjs}": [ + "eslint --fix --no-warn-ignored", + "prettier --write" + ], + "*.{json,css,md}": [ + "prettier --write" + ] + }, "devDependencies": { "@eslint/js": "9.39.2", "@types/node": "^25.0.0",