diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index b52087e..04fcba8 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,3 +1,3 @@ FROM node:24-alpine -RUN apk add --no-cache bash git curl python3 make g++ pkgconf sqlite sqlite-dev +RUN apk add --no-cache bash git curl python3 make g++ pkgconf postgresql-client WORKDIR /workspaces/${localWorkspaceFolderBasename} \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 98b39fb..d58ce33 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,26 +1,36 @@ { "name": "Taskflow", - "build": { - "dockerfile": "Dockerfile" - }, + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "postCreateCommand": "npm ci", "postStartCommand": "npm run db:setup", "postAttachCommand": "npm run dev", - "forwardPorts": [3000], + "forwardPorts": [ + 3000, + 5432 + ], "portsAttributes": { "3000": { - "label": "App", + "label": "Next.js App", "onAutoForward": "openPreview" + }, + "5432": { + "label": "PostgreSQL", + "onAutoForward": "silent" } }, "containerEnv": { - "NODE_ENV": "development" + "NODE_ENV": "development", + "DATABASE_URL": "postgresql://taskflow:taskflow@db:5432/taskflow" }, "customizations": { "vscode": { "extensions": [ "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode" + "esbenp.prettier-vscode", + "bradlc.vscode-tailwindcss", + "Prisma.prisma" ] } } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..31b0f6d --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,34 @@ +version: '3.8' +services: + app: + build: + context: .. + dockerfile: .devcontainer/Dockerfile + volumes: + - ..:/workspaces/${localWorkspaceFolderBasename}:cached + command: sleep infinity + environment: + DATABASE_URL: postgresql://taskflow:taskflow@db:5432/taskflow + depends_on: + db: + condition: service_healthy + + db: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: taskflow + POSTGRES_PASSWORD: taskflow + POSTGRES_DB: taskflow + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U taskflow" ] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + postgres_data: diff --git a/.env b/.env new file mode 100644 index 0000000..bbde5a9 --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +# THIS FILE WILL NOT BE IGNORED BY GIT, USE FOR DEMO PURPOSES ONLY +# You can store environment variables here for local development +# IMPORTANT: DO NOT STORE SENSITIVE INFORMATION IN THIS FILE!! + +# Database Configuration +DATABASE_URL="postgresql://taskflow:taskflow@localhost:5432/taskflow" diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..05c6d5d --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1 @@ +See `package.json` for the available project scripts and dependencies. \ No newline at end of file diff --git a/.github/prompts/debug-with-logs.prompt.md b/.github/prompts/debug-with-logs.prompt.md new file mode 100644 index 0000000..364e9fe --- /dev/null +++ b/.github/prompts/debug-with-logs.prompt.md @@ -0,0 +1,23 @@ +You are a senior developer helping to debug issues in a codebase. A user has reported a bug and you need to help them identify and fix the issue by examining the bug report outlined in `BUG_REPORT.md`. + + +Follow these steps to debug the issue: +1. **Understand the Bug Report**: Carefully read the bug report to understand the issue, including the steps to reproduce, current behavior, expected behavior, and environment details. +2. **Identify Relevant Code Areas**: Based on the bug report, identify which parts of the codebase are likely involved in the issue (e.g., task editing logic, database update functions, UI components). +3. **Check the database**: Access the PostgreSQL MCP server to inspect the current state of the database. Ensure that all data is correct and consistent with expectations. +4. **Add logging**: Add logs to both the front end and back end code paths involved. In every log, include which file you're in as well as the function and any other relevant information. This will help trace the flow of data and identify where the update is failing. +5. **Run the tests**: Run the unit and E2E tests to see if the bug can be reproduced in the test environment. Note any failures or unexpected behaviors. + - Unit tests can be run with `npm run test` + - E2E tests can be run with `npm run test:e2e:debug` (use :debug here to see logs from both front end and back end). + - E2E tests can be filtered using the `-g` option to focus on tests related to the bug report. For example, if the bug report is about editing a task via a modal form, you can run: + ``` + npm run test:e2e:debug -- -g "edit task via modal form" + ``` +6. **(OPTIONAL) Write a new test**: If no existing test covers the bug scenario, write a new unit or E2E test that reproduces the bug based on the steps provided in the bug report. This will help ensure that the bug is captured and can be verified as fixed later. Run the new test to confirm it fails as expected. +7. **Fix the Bug**: Investigate the logs and code to identify the root cause of the issue. Implement a fix to ensure that the task assignee updates correctly and persists after a page refresh. +8. **Verify the Fix**: Re-run the unit and E2E tests to confirm that the bug has been resolved and that no other functionality is broken. +9. **Clean Up**: Remove any logging added during the debugging process to keep the codebase clean. + +--- + +**IMPORTANT**: Whenever you run the e2e tests use the `npm run test:e2e:debug` command to get detailed logs from both the front end and back end. \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2e0c0eb..0b81043 100644 --- a/.gitignore +++ b/.gitignore @@ -30,8 +30,9 @@ yarn-debug.log* yarn-error.log* .pnpm-debug.log* -# env files (can opt-in for committing if needed) -.env* +# # env files (can opt-in for committing if needed) +# .env* +# !.env.example # vercel .vercel @@ -44,4 +45,6 @@ next-env.d.ts # database -/prisma/app.db \ No newline at end of file +/prisma/app.db +/prisma/*.db +/prisma/*.db-journal \ No newline at end of file diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 0000000..1a0b55f --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,12 @@ +{ + "servers": { + "postgres": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-postgres", + "postgresql://taskflow:taskflow@localhost:5432/taskflow" + ] + } + } +} \ No newline at end of file diff --git a/BUG_REPORT.md b/BUG_REPORT.md new file mode 100644 index 0000000..6f74e29 --- /dev/null +++ b/BUG_REPORT.md @@ -0,0 +1,48 @@ +# Bug Report: Missing Tasks on Kanban Board + +## 📝 Description + +Tasks are disappearing from the Kanban board view. Users report that when they navigate to `/board`, they only see a fraction of their tasks, even though the complete list is visible on the `/tasks` page. + +## 🔴 Severity + +**High** - This significantly impacts user experience and makes the board view nearly unusable. + +## 📍 Location + +- **Page:** `/board` (Kanban Board) +- **Component:** `components/kanban-board.tsx` +- **Related:** `app/(dashboard)/board/page.tsx` + +## 🪜 Steps to Reproduce + +1. Navigate to the Tasks page at `/tasks` +2. Note the total number of tasks displayed (should be 30) +3. Navigate to the Board page at `/board` +4. Observe that only a small subset of tasks appear on the board +5. Count the tasks on each column - the total is much less than expected + +## ✅ Expected Behavior + +All 30 tasks should be distributed across the four Kanban columns: +- **To Do** - Tasks with status "todo" +- **In Progress** - Tasks with status "in_progress" +- **Review** - Tasks with status "review" +- **Done** - Tasks with status "done" + +The total count across all columns should match the total tasks in the database. + +## ❌ Actual Behavior + +Only approximately 7 tasks appear on the Kanban board, while 23 tasks are missing. The task counts on the board columns are significantly lower than expected. + +## 🔍 Initial Observations + +1. The tasks DO exist in the database (visible on `/tasks` page) +2. The issue seems to be with how tasks are being filtered or queried for the board +3. No errors are shown in the console or logs +4. The issue is consistent across all users + +## IMPORTANT Considerations + +- Any modifications to data in the database must use the PostgreSQL MCP server. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 12e1d56..be75dc8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,18 +4,15 @@ WORKDIR /app COPY package*.json ./ RUN npm ci -# Install SQLite -RUN apk add --no-cache sqlite sqlite-dev && \ - npm install better-sqlite3 - COPY . . -# Setup the Database -RUN npm run db:setup +# Generate Prisma Client +RUN npx prisma generate RUN npm run build ENV NODE_ENV=production EXPOSE 3000 -CMD ["npm", "run", "start"] +# Run migrations and start the app +CMD ["sh", "-c", "npx prisma migrate deploy && npm run start"] diff --git a/POSTGRESQL_SETUP.md b/POSTGRESQL_SETUP.md new file mode 100644 index 0000000..5e9ea64 --- /dev/null +++ b/POSTGRESQL_SETUP.md @@ -0,0 +1,218 @@ +# PostgreSQL Setup Guide + +This project uses PostgreSQL as its database, running in Docker for easy local development and deployment. + +## Quick Start + +### Local Development (Recommended) + +1. **Start PostgreSQL in Docker:** + ```bash + docker-compose up -d db + ``` + +2. **Install dependencies and setup database:** + ```bash + npm install + npm run db:setup + ``` + +3. **Run the development server:** + ```bash + npm run dev + ``` + +The app will be available at http://localhost:3000 and PostgreSQL at localhost:5432. + +### GitHub Codespaces + +When you open this project in a Codespace, the PostgreSQL database will automatically start. Just run: + +```bash +npm install +npm run db:setup +npm run dev +``` + +### Full Docker Deployment + +To run both the database and app in Docker: + +```bash +docker-compose up --build +``` + +## Database Configuration + +### Environment Variables + +The project uses a `.env` file for database configuration: + +```env +DATABASE_URL="postgresql://taskflow:taskflow@localhost:5432/taskflow" +``` + +- For **local development** with Next.js running outside Docker: use `localhost:5432` +- For **containerized app** (when app runs in Docker): use `db:5432` + +### Database Credentials + +Default PostgreSQL credentials (defined in `docker-compose.yml`): +- **User:** taskflow +- **Password:** taskflow +- **Database:** taskflow +- **Port:** 5432 + +**⚠️ Change these for production deployments!** + +## Database Commands + +```bash +# Run migrations +npm run db:migrate + +# Clear all data +npm run db:clear + +# Seed the database with sample data +npm run db:seed + +# Reset database (clear + seed) +npm run db:reset + +# Full setup (migrate + reset) +npm run db:setup +``` + +## Prisma Commands + +```bash +# Generate Prisma Client +npx prisma generate + +# Create a new migration +npx prisma migrate dev --name migration_name + +# Apply migrations (production) +npx prisma migrate deploy + +# Open Prisma Studio (database GUI) +npx prisma studio +``` + +## Docker Commands + +```bash +# Start only PostgreSQL +docker-compose up -d db + +# Start all services +docker-compose up -d + +# Stop all services +docker-compose down + +# Stop and remove volumes (deletes all data) +docker-compose down -v + +# View logs +docker-compose logs -f + +# Access PostgreSQL CLI +docker-compose exec db psql -U taskflow -d taskflow +``` + +## Connecting with PostgreSQL MCP + +To use the PostgreSQL MCP server with GitHub Copilot for debugging: + +1. Ensure PostgreSQL is running: `docker-compose up -d db` +2. Configure the MCP server to connect to `localhost:5432` +3. Use credentials: `taskflow` / `taskflow` + +The MCP server can then query the database, inspect schema, and help debug issues. + +## Troubleshooting + +### Connection Issues + +If you can't connect to PostgreSQL: + +1. **Check if container is running:** + ```bash + docker-compose ps + ``` + +2. **Check logs:** + ```bash + docker-compose logs db + ``` + +3. **Verify DATABASE_URL:** + - Local dev: `postgresql://taskflow:taskflow@localhost:5432/taskflow` + - In Docker: `postgresql://taskflow:taskflow@db:5432/taskflow` + +### Migration Issues + +If migrations fail: + +1. **Reset the database:** + ```bash + docker-compose down -v + docker-compose up -d db + npm run db:setup + ``` + +2. **Check migration status:** + ```bash + npx prisma migrate status + ``` + +### Port Already in Use + +If port 5432 is already in use: + +1. **Stop existing PostgreSQL:** + ```bash + # macOS/Linux + sudo systemctl stop postgresql + # or + sudo service postgresql stop + ``` + +2. **Or change the port in docker-compose.yml:** + ```yaml + ports: + - "5433:5432" # Use 5433 on host + ``` + +## Project Structure + +``` +prisma/ + schema.prisma # Database schema + migrations/ # Database migrations + seed.js # Sample data seeder + clear.js # Database cleanup script + +.env # Environment variables (not in git) +.env.example # Example environment config +docker-compose.yml # Docker services configuration +.devcontainer/ # Codespaces/Dev Container config +``` + +## Production Deployment + +For production, use a managed PostgreSQL service like: +- AWS RDS +- Heroku Postgres +- Supabase +- DigitalOcean Managed Databases + +Update `DATABASE_URL` environment variable with your production connection string and run: + +```bash +npx prisma migrate deploy +npm run build +npm start +``` diff --git a/app/(dashboard)/board/page.tsx b/app/(dashboard)/board/page.tsx index 85885e5..1b18038 100644 --- a/app/(dashboard)/board/page.tsx +++ b/app/(dashboard)/board/page.tsx @@ -8,7 +8,7 @@ export default async function BoardPage() { const { tasks, error } = await getAllTasks() if (error) { - console.error("Error fetching tasks:", error) + console.error("[board/page.tsx - BoardPage] Error fetching tasks:", error) return
Could not load data. Please try again later.
} @@ -19,10 +19,20 @@ export default async function BoardPage() { done: { id: "done", title: "Done", tasks: [] }, } + let matchedCount = 0; + let unmatchedCount = 0; + const unmatchedTasks: any[] = []; + tasks?.forEach((task) => { - // Ensure task status is a valid key for initialColumns - if (task.status && task.status in initialColumns) { - initialColumns[task.status as keyof KanbanData].tasks.push(task) + // Normalize the status to lowercase and replace spaces with underscores + const normalizedStatus = task.status?.toLowerCase().replace(/\s+/g, '_'); + // Ensure task status is a valid key for initialColumns after normalization + if (normalizedStatus && normalizedStatus in initialColumns) { + initialColumns[normalizedStatus as keyof KanbanData].tasks.push(task) + matchedCount++; + } else { + unmatchedCount++; + unmatchedTasks.push({ id: task.id, name: task.name, status: task.status, normalized: normalizedStatus }); } }) diff --git a/app/(dashboard)/tasks/actions.ts b/app/(dashboard)/tasks/actions.ts index ec6522d..28f8a33 100644 --- a/app/(dashboard)/tasks/actions.ts +++ b/app/(dashboard)/tasks/actions.ts @@ -53,6 +53,7 @@ export async function getAllTasks() { }); return { tasks, error: null }; } catch (e) { + console.error("[actions.ts - getAllTasks] Error fetching tasks:", e); return { tasks: [], error: "Failed to fetch tasks." }; } } @@ -71,10 +72,10 @@ export async function deleteTask(taskId: number) { // Update a task's status by ID export async function updateTaskStatus(taskId: number, status: string) { try { - await prisma.task.update({ where: { id: taskId }, data: { status } }); - const afterUpdate = Date.now(); + const updateData: { status: string; priority?: string } = { status }; + + await prisma.task.update({ where: { id: taskId }, data: updateData }); revalidatePath("/tasks"); - const afterRevalidate = Date.now(); return { error: null }; } catch (e) { return { error: "Failed to update task status." }; diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..06d95f7 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,23 @@ +version: '3.8' +services: + devcontainer: + image: mcr.microsoft.com/devcontainers/javascript-node:24 + volumes: + - ..:/workspace:cached + command: sleep infinity + network_mode: service:db + + db: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: taskflow + POSTGRES_PASSWORD: taskflow + POSTGRES_DB: taskflow + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: diff --git a/docker-compose.yml b/docker-compose.yml index b498ecc..44e7c27 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,22 @@ version: '3.8' services: + db: + image: postgres:16-alpine + container_name: taskflow-db + environment: + POSTGRES_USER: taskflow + POSTGRES_PASSWORD: taskflow + POSTGRES_DB: taskflow + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U taskflow" ] + interval: 5s + timeout: 5s + retries: 5 + app: build: context: . @@ -7,4 +24,11 @@ services: ports: - "3000:3000" environment: - - NODE_ENV=production \ No newline at end of file + - NODE_ENV=production + - DATABASE_URL=postgresql://taskflow:taskflow@db:5432/taskflow + depends_on: + db: + condition: service_healthy + +volumes: + postgres_data: diff --git a/package.json b/package.json index cc480d2..aa6ac56 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,11 @@ "test:watch": "jest --watch", "test:e2e": "playwright test", "test:e2e:headed": "playwright test --headed", + "test:e2e:debug": "node scripts/test-e2e-with-server.js", "lint": "next lint", "setup": "npm i && npm run db:setup", - "db:setup": "npx prisma db push && npm run db:reset", + "db:setup": "npx prisma migrate dev --name init && npm run db:reset", + "db:migrate": "npx prisma migrate dev", "db:clear": "node prisma/clear.js", "db:seed": "node prisma/seed.js", "db:reset": "npm run db:clear && npm run db:seed" @@ -53,7 +55,7 @@ "@testing-library/user-event": "14.6.1", "@testing-library/jest-dom": "6.9.1", "@playwright/test": "^1", - "prisma": "^6.13.0", + "prisma": "6.13.0", "tailwindcss": "^4", "typescript": "^5" } diff --git a/prisma/migrations/20250807161730_init/migration.sql b/prisma/migrations/20250807161730_init/migration.sql deleted file mode 100644 index 6d3418a..0000000 --- a/prisma/migrations/20250807161730_init/migration.sql +++ /dev/null @@ -1,10 +0,0 @@ --- CreateTable -CREATE TABLE "User" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "email" TEXT NOT NULL, - "password" TEXT NOT NULL, - "name" TEXT NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/prisma/migrations/20250807174134_add_session/migration.sql b/prisma/migrations/20250807174134_add_session/migration.sql deleted file mode 100644 index bd9b89f..0000000 --- a/prisma/migrations/20250807174134_add_session/migration.sql +++ /dev/null @@ -1,11 +0,0 @@ --- CreateTable -CREATE TABLE "Session" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "token" TEXT NOT NULL, - "userId" INTEGER NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateIndex -CREATE UNIQUE INDEX "Session_token_key" ON "Session"("token"); diff --git a/prisma/migrations/20250807190813_add_task_entity/migration.sql b/prisma/migrations/20250807190813_add_task_entity/migration.sql deleted file mode 100644 index 9e766e6..0000000 --- a/prisma/migrations/20250807190813_add_task_entity/migration.sql +++ /dev/null @@ -1,15 +0,0 @@ --- CreateTable -CREATE TABLE "Task" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "name" TEXT NOT NULL, - "description" TEXT NOT NULL, - "priority" TEXT NOT NULL, - "status" TEXT NOT NULL, - "dueDate" DATETIME, - "assigneeId" INTEGER, - "creatorId" INTEGER NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "Task_assigneeId_fkey" FOREIGN KEY ("assigneeId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT "Task_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); diff --git a/prisma/migrations/20251126214802_init/migration.sql b/prisma/migrations/20251126214802_init/migration.sql new file mode 100644 index 0000000..ce8a153 --- /dev/null +++ b/prisma/migrations/20251126214802_init/migration.sql @@ -0,0 +1,50 @@ +-- CreateTable +CREATE TABLE "public"."User" ( + "id" SERIAL NOT NULL, + "email" TEXT NOT NULL, + "password" TEXT NOT NULL, + "name" TEXT NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "public"."Session" ( + "id" SERIAL NOT NULL, + "token" TEXT NOT NULL, + "userId" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Session_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "public"."Task" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT NOT NULL, + "priority" TEXT NOT NULL, + "status" TEXT NOT NULL, + "dueDate" TIMESTAMP(3), + "assigneeId" INTEGER, + "creatorId" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Task_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "public"."User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Session_token_key" ON "public"."Session"("token"); + +-- AddForeignKey +ALTER TABLE "public"."Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "public"."Task" ADD CONSTRAINT "Task_assigneeId_fkey" FOREIGN KEY ("assigneeId") REFERENCES "public"."User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "public"."Task" ADD CONSTRAINT "Task_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml index 2a5a444..044d57c 100644 --- a/prisma/migrations/migration_lock.toml +++ b/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually # It should be added in your version-control system (e.g., Git) -provider = "sqlite" +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b819a96..54bbf32 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,8 +8,8 @@ generator client { } datasource db { - provider = "sqlite" - url = "file:app.db" + provider = "postgresql" + url = env("DATABASE_URL") } model User { diff --git a/prisma/seed.js b/prisma/seed.js index af2d579..b74d133 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -1,3 +1,5 @@ +// This is a seed script to populate the database with initial data for testing and development purposes. Do not use in production or to seed real user data. + const { PrismaClient } = require('../app/generated/prisma'); const bcrypt = require('bcryptjs'); @@ -47,7 +49,12 @@ const taskTemplates = [ { name: 'Create automated workflow system', description: 'Build automation rules for task assignments and updates', priority: 'high' }, ]; -const statuses = ['todo', 'in_progress', 'done', 'review']; +const statuses = [ + 'todo', 'Todo', 'TODO', + 'in_progress', 'In Progress', 'IN PROGRESS', + 'done', 'Done', 'DONE', + 'review', 'Review', 'REVIEW' +]; const priorities = ['low', 'medium', 'high']; function getRandomElement(arr) { diff --git a/scripts/test-e2e-with-server.js b/scripts/test-e2e-with-server.js new file mode 100644 index 0000000..ae84603 --- /dev/null +++ b/scripts/test-e2e-with-server.js @@ -0,0 +1,164 @@ +/** + * Orchestration script for running E2E tests with dev server + * + * This script: + * 1. Starts the Next.js dev server + * 2. Waits for it to be ready + * 3. Runs Playwright tests with browser console logging + * 4. Cleans up the dev server on exit + * + * All logs (server and browser) are piped to the terminal with prefixes for clarity. + */ + +const { spawn } = require('child_process'); +const http = require('http'); + +const SERVER_URL = 'http://localhost:3000'; +const MAX_RETRIES = 30; +const RETRY_INTERVAL = 1000; + +let devServer = null; + +// Get additional args to pass to playwright (e.g., test file, --headed) +const playwrightArgs = process.argv.slice(2); + +function log(message) { + console.log(`[Orchestrator] ${message}`); +} + +function separator() { + console.log('[Orchestrator] ----------------------------------------\n'); +} + +async function waitForServer(retries = MAX_RETRIES) { + for (let i = 0; i < retries; i++) { + try { + await new Promise((resolve, reject) => { + const req = http.get(SERVER_URL, (res) => { + resolve(); + }); + req.on('error', reject); + req.setTimeout(2000, () => { + req.destroy(); + reject(new Error('Timeout')); + }); + }); + return true; + } catch (error) { + if (i < retries - 1) { + process.stdout.write('.'); + await new Promise(resolve => setTimeout(resolve, RETRY_INTERVAL)); + } + } + } + return false; +} + +function cleanup() { + if (devServer) { + log('Shutting down dev server...'); + devServer.kill('SIGTERM'); + + // Force kill after 5 seconds if still running + setTimeout(() => { + if (devServer && !devServer.killed) { + devServer.kill('SIGKILL'); + } + }, 5000); + } +} + +async function main() { + log('Starting dev server...'); + separator(); + + // Start dev server + devServer = spawn('npm', ['run', 'dev'], { + stdio: ['ignore', 'pipe', 'pipe'], + shell: true + }); + + // Pipe server output with prefix + devServer.stdout.on('data', (data) => { + const lines = data.toString().split('\n'); + lines.forEach(line => { + if (line.trim()) { + console.log(`[Server] ${line}`); + } + }); + }); + + devServer.stderr.on('data', (data) => { + const lines = data.toString().split('\n'); + lines.forEach(line => { + if (line.trim()) { + console.error(`[Server] ${line}`); + } + }); + }); + + devServer.on('exit', (code) => { + if (code !== null && code !== 0) { + console.error(`[Server] Dev server exited with code ${code}`); + } + }); + + // Wait for server to be ready + log('Waiting for server to be ready...'); + separator(); + const isReady = await waitForServer(); + + if (!isReady) { + console.error('\n[Orchestrator] Server failed to start'); + cleanup(); + process.exit(1); + } + + console.log('\n'); + log('Server is ready!'); + separator(); + + // Run Playwright tests with browser console logging + log('Running Playwright tests in debug mode...'); + separator(); + + const playwrightProcess = spawn( + 'npx', + ['playwright', 'test', ...playwrightArgs], + { + stdio: 'inherit', + shell: true, + env: { + ...process.env, + NODE_OPTIONS: '--require ./tests/e2e/console-log-proxy.js' + } + } + ); + + playwrightProcess.on('exit', (code) => { + separator(); + log(`Tests completed with exit code: ${code}`); + separator(); + cleanup(); + process.exit(code); + }); + + // Handle script termination + process.on('SIGINT', () => { + log('Received SIGINT, cleaning up...'); + cleanup(); + process.exit(130); + }); + + process.on('SIGTERM', () => { + log('Received SIGTERM, cleaning up...'); + cleanup(); + process.exit(143); + }); +} + +main().catch(error => { + console.error('[Orchestrator] Error:', error); + cleanup(); + process.exit(1); +}); diff --git a/tests/e2e/console-log-proxy.js b/tests/e2e/console-log-proxy.js new file mode 100644 index 0000000..b49b16d --- /dev/null +++ b/tests/e2e/console-log-proxy.js @@ -0,0 +1,51 @@ +/** + * Playwright Bootstrap - Pipes browser console logs to terminal + * + * This file patches the @playwright/test module to automatically attach + * console and error listeners to all pages during test execution. + * + * Load this via NODE_OPTIONS: NODE_OPTIONS='--require ./tests/playwright-bootstrap.js' + */ + +const Module = require('module'); +const originalRequire = Module.prototype.require; + +Module.prototype.require = function (id) { + const module = originalRequire.apply(this, arguments); + + if (id === '@playwright/test') { + const originalTest = module.test; + + // Extend the test.extend functionality + const patchedTest = originalTest.extend({ + page: async ({ page }, use) => { + // Attach console listener + page.on('console', msg => { + const type = msg.type().toUpperCase(); + const text = msg.text(); + console.log(`[Browser ${type}] ${text}`); + }); + + // Attach page error listener + page.on('pageerror', error => { + console.error(`[Browser ERROR] ${error.message}`); + console.error(error.stack); + }); + + await use(page); + } + }); + + // Copy all properties from original test to patched test + Object.keys(originalTest).forEach(key => { + if (!(key in patchedTest)) { + patchedTest[key] = originalTest[key]; + } + }); + + // Replace test with patched version + module.test = patchedTest; + } + + return module; +}; diff --git a/tests/e2e/tasks.spec.ts b/tests/e2e/tasks.spec.ts index db215d4..667749e 100644 --- a/tests/e2e/tasks.spec.ts +++ b/tests/e2e/tasks.spec.ts @@ -63,6 +63,10 @@ test.describe('Task CRUD flows', () => { // Verify the updated title is visible await expect(page.locator('h3', { hasText: newTitle })).toBeVisible({ timeout: 5000 }); + + // Verify priority is still medium + const updatedCard = page.locator(`[data-testid^="task-card-"]`).filter({ has: page.locator('h3', { hasText: newTitle }) }).first(); + await expect(updatedCard.locator('text=medium')).toBeVisible({ timeout: 5000 }); }); test('delete task', async ({ page }) => {