feat(m2): DB foundation tests + multi-statement migration fix

- Add SqliteDatabase.exec() using better-sqlite3 exec() so the migrator
  can run multi-statement SQL files (prepare() rejected 001_initial.sql)
- Migrator now uses exec() for migration file content
- Fix tests/helpers/db.ts: replace CJS require() with ESM import
- Add unit tests for SqliteDatabase (query/get/execute/transaction/exec,
  WAL & foreign_keys pragmas) and Migrator (ordering, idempotency,
  rollback, real 001_initial.sql applies)
- Restore .gitignore steering exclusion; remove stale steering files
This commit is contained in:
Ken Yasue
2026-06-25 00:21:26 +02:00
parent 07c7d424e5
commit d7c4cd5e58
13 changed files with 451 additions and 462 deletions

View File

@ -1,189 +0,0 @@
# Design Document
## Architecture Overview
Layered architecture (UI → Service → Repository → Data) per `docs/architecture.md`. Next.js 15 App Router with Node.js Runtime only (no Edge Runtime). SQLite via better-sqlite3 accessed exclusively through a custom SQL wrapper. No Prisma.
```
app/ (UI: Route Handlers / Server Components / SSE)
services/ (business logic, permission checks, transactions)
repositories/ (SQL, parameter binding, logical-delete filter)
lib/db/ (SQLite connection, SQL wrapper, Migration)
```
## Component Design
### 1. SQL Wrapper: `lib/db/sqlite.ts`
**Responsibilities**:
- SQLite connection management (singleton getDb(), WAL, foreign_keys ON)
- query<T> (multi-row), get<T> (single-row), execute (insert/update/delete), transaction<T>, close
- Error handling
**Implementation highlights**:
- dbPath = process.env.SQLITE_PATH ?? "./data/app.db"
- SqlParams = Record<string, unknown> | unknown[]
- All repositories use this wrapper; direct better-sqlite3 access forbidden
### 2. Migrator: `lib/db/migrator.ts`
**Responsibilities**:
- Create schema_migrations table
- Read .sql files from migrations dir in filename order
- Skip already-applied files (tracked in schema_migrations)
- Execute each file in a transaction; rollback on failure
### 3. Initial Schema: `lib/db/migrations/001_initial.sql`
16 tables: users, projects, project_members, board_threads, board_comments, chat_messages, todo_columns, todo_items, file_assets, project_notes, milestones, calendar_events, meetings, meeting_members, notifications, activity_logs + indexes.
### 4. Entity Types: `lib/types/`
PascalCase type files for each entity + union types (UserRole, UserStatus, ProjectStatus, ProjectMemberRole, etc.).
### 5. UserRepository
findById, findByEmail, create (with passwordHash), update (name/avatarUrl/role/status).
### 6. AuthService
register (hash password, create user), login (verify hash, issue session), logout, getCurrentUser, updateProfile. Uses bcrypt.
### 7. Session: `lib/auth/session.ts`, `lib/auth/getCurrentUser.ts`
Cookie-based session. getCurrentUser resolves the user from the request cookie.
### 8. ProjectRepository / ProjectMemberRepository
Project: findById, findByOwner, findByUser (via join), create, update, delete.
ProjectMember: findByProject, findByUser, add, remove, isMember, getRole.
### 9. ProjectService
createProject (owner becomes admin member), updateProject, addMember (permission check + notification hook later), removeMember, archiveProject, getDashboard skeleton.
## Data Flow
### Login
1. POST /api/auth/login → AuthService.login(email, password)
2. AuthService → UserRepository.findByEmail → verify bcrypt hash
3. Issue session cookie → return user
### Project creation
1. POST /api/projects → ProjectService.createProject(actorId, input)
2. ProjectService → permission (any authenticated user) → ProjectRepository.create
3. ProjectMemberRepository.add(projectId, ownerId, 'admin')
4. Return project
## Error Handling Strategy
### Custom Error Classes
- ValidationError (400) - field, value
- ForbiddenError (403)
- NotFoundError (404) - resource, id
- (409 for unique constraint - handled via SQLite error code)
### Error Handling Patterns
Route Handlers catch errors and map to HTTP status. Expected errors use custom classes; unexpected errors propagate + log.
## Test Strategy
### Unit Tests (Vitest)
- lib/db/sqlite.test.ts (query/get/execute/transaction, WAL, foreign keys)
- lib/db/migrator.test.ts (order, skip applied, rollback)
- repositories/UserRepository.test.ts
- services/AuthService.test.ts
- repositories/ProjectRepository.test.ts
- repositories/ProjectMemberRepository.test.ts
- services/ProjectService.test.ts
Tests use a temp SQLite file (in-memory or tmpdir) to avoid touching real data/.
### Integration Tests (Vitest)
- project-member-permission.test.ts (non-member cannot access project data)
### E2E Tests (Playwright)
- auth.spec.ts (login, logout, protected screen)
- project-management.spec.ts (create, edit, member add/remove, archive)
## Dependent Libraries
```json
{
"dependencies": {
"next": "15",
"react": "19",
"react-dom": "19",
"better-sqlite3": "^11",
"bcrypt": "^5",
"react-markdown": "^9",
"remark-gfm": "^4",
"rehype-sanitize": "^6"
},
"devDependencies": {
"tailwindcss": "^3",
"@types/better-sqlite3": "^7",
"@types/bcrypt": "^5",
"@playwright/test": "^1",
"tsx": "^4"
}
}
```
## Directory Structure
```
app/
api/auth/{register,login,logout,me}/route.ts
api/users/me/route.ts
api/projects/route.ts
api/projects/[projectId]/route.ts
api/projects/[projectId]/members/route.ts
api/projects/[projectId]/members/[userId]/route.ts
api/admin/migrations/route.ts
login/page.tsx
profile/page.tsx
dashboard/page.tsx
projects/[projectId]/{page,members,settings}.tsx
layout.tsx, globals.css
lib/
db/{sqlite.ts, migrator.ts, run-migrations.ts, migrations/001_initial.sql}
auth/{session.ts, getCurrentUser.ts}
types/*.ts
validators/{userValidator.ts, projectValidator.ts}
repositories/{UserRepository,ProjectRepository,ProjectMemberRepository}.ts
services/{AuthService,ProjectService}.ts
components/layout/{Header,Sidebar,ProjectNav}.tsx
tests/unit/..., tests/integration/..., tests/e2e/...
```
## Implementation Order
1. M1 (branch feature/m1-setup): Next.js setup, deps, config, dir structure, remove boilerplate → merge to main
2. M2 (branch feature/m2-db-foundation): SQL wrapper, migrator, schema, types, migration API, unit tests → merge to main
3. M3 (branch feature/m3-auth-user): UserRepository, AuthService, session, validators, auth API, login/profile pages, unit + e2e → merge to main
4. M4 (branch feature/m4-project-member): Project/Member repos, ProjectService, project API, dashboard/members/settings pages, unit + integration + e2e → merge to main
## Security Considerations
- Passwords hashed with bcrypt (never plaintext)
- All SQL parameter-bound (no string concatenation)
- Auth required on all protected routes (except /api/auth/register, /api/auth/login)
- Project access requires isMember check
- Admin features require role='system_admin'
- .env not committed; secrets via environment
## Performance Considerations
- SQLite WAL mode for read/write concurrency
- Pagination on list endpoints
- Singleton DB connection
## Future Extensibility
- Repository pattern allows adding tables without changing wrapper
- SseEvent type extensible (M8)
- NotificationService/ActivityLogService hooks prepared for M5 (ProjectService.addMember will call notification in M5)

View File

@ -1,102 +0,0 @@
# Requirements
## Overview
Implement milestones M1-M4 of the Simple Groupware project: project foundation setup (Next.js 15), DB foundation (SQL wrapper + Migration), authentication & user management, and project & member management. Each milestone is developed on a separate branch and merged to main. Unit tests and E2E tests are mandatory.
## Background
The repository is currently a spec-driven-development boilerplate (vitest/eslint/prettier/husky configured, but no Next.js, no react, no SQLite). It must be transformed into the foundation of the Simple Groupware: a Next.js 15 + TypeScript + SQLite(better-sqlite3) application following the layered architecture (UI → Service → Repository → Data) defined in `docs/architecture.md`. M1-M4 establish the base that all subsequent milestones (M5+) depend on.
## Features to Implement
### 1. M1: Project Foundation Setup
- Next.js 15 (App Router, TypeScript) project configuration
- Tailwind CSS setup
- tsconfig with `@/*` path alias, strict mode
- next.config, .env.example
- Directory structure: app/, lib/, repositories/, services/, components/, tests/, data/, backups/
- ESLint (with Next.js plugin), Prettier, Husky, lint-staged, Vitest, Playwright setup
- CI config (GitHub Actions)
- package.json scripts: lint, format, typecheck, test, test:e2e, migrate, dev, build
- Dependencies: next, react, react-dom, better-sqlite3, bcrypt, react-markdown, remark-gfm, rehype-sanitize, tailwindcss, @types/better-sqlite3
- Remove boilerplate src/example.ts and src/example.test.ts
### 2. M2: DB Foundation (SQL Wrapper + Migration)
- `lib/db/sqlite.ts`: SqliteDatabase class (query/get/execute/transaction/close) + getDb() singleton (WAL, foreign_keys ON)
- `lib/db/migrator.ts`: Migrator class (filename-order execution, skip applied, 1-file-1-transaction, rollback on failure)
- `lib/db/migrations/001_initial.sql`: all 16 tables + indexes
- `lib/types/`: all Entity type definitions + enums
- `lib/db/run-migrations.ts`: migration runner script
- API: `GET /api/admin/migrations` (admin-only migration status)
- Unit tests: sqlite.test.ts, migrator.test.ts
### 3. M3: Authentication & User Management
- UserRepository (findById/findByEmail/create/update)
- AuthService (register/login/logout/getCurrentUser/updateProfile) with bcrypt password hashing
- Role management (system_admin/project_admin/member/guest), account enable/disable
- lib/auth/ (session.ts, getCurrentUser.ts)
- lib/validators/userValidator.ts
- API: register, login, logout, me, users/me
- Login page, profile page, root layout
- Auth middleware (redirect unauthenticated to login)
- Unit tests: UserRepository, AuthService; E2E test: auth.spec.ts
### 4. M4: Project & Member Management
- ProjectRepository, ProjectMemberRepository
- ProjectService (createProject/updateProject/addMember/removeMember/archiveProject/getDashboard) with permission checks
- projectValidator.ts
- API: projects CRUD, members CRUD
- Dashboard (project list), project overview, members page, settings page
- Layout components (Header, Sidebar, ProjectNav)
- Unit tests: ProjectRepository, ProjectMemberRepository, ProjectService; Integration test: project-member-permission; E2E test: project-management.spec.ts
## Acceptance Criteria
### M1
- [ ] `npm run dev` starts the Next.js dev server
- [ ] `npm run lint`, `npm run typecheck`, `npm run build` succeed
- [ ] Directory structure matches repository-structure.md
### M2
- [ ] `npm run migrate` creates the initial schema
- [ ] Migrations run in filename order and skip applied ones
- [ ] Failed migration rolls back
- [ ] Unit tests pass
### M3
- [ ] User registration, login, logout work
- [ ] Password is hashed with bcrypt
- [ ] Profile & avatar editable
- [ ] Inactive accounts cannot log in
- [ ] Unauthenticated access to protected screens is blocked
- [ ] Unit & E2E tests pass
### M4
- [ ] Project create/edit/delete/archive work
- [ ] Member add/remove/role work
- [ ] Non-members get 403
- [ ] Unit, Integration & E2E tests pass
## Success Metrics
- All Unit tests pass (`npm test`)
- All E2E tests pass (`npm run test:e2e`)
- Lint and typecheck clean
- Each milestone merged to main on its own branch
## Out of Scope
The following will not be implemented in this phase:
- M5-M15 (notifications, board, chat, SSE, todo, files, calendar, milestones, meetings, search, dashboard completion, backup)
- E2E tests for features beyond M3/M4 scope (board, chat, etc.)
- Full project dashboard content (only skeleton in M4; full content in M13)
## Reference Documents
- `docs/product-requirements.md` - Product Requirements Document
- `docs/functional-design.md` - Functional design document
- `docs/architecture.md` - Architecture design document
- `docs/repository-structure.md` - Repository structure document
- `docs/development-guidelines.md` - Development guidelines
- `docs/milestones.md` - Milestone & task definitions

View File

@ -1,150 +0,0 @@
# Task List
## 🚨 Principle of Fully Completing Tasks
**Keep working until all tasks in this file are complete**
### Mandatory Rules
- **Make every task `[x]`**
- "Planned as a separate task due to time constraints" is forbidden
- "Postponed because the implementation is too complex" is forbidden
- Do not finish work while leaving incomplete tasks (`[ ]`)
### The Only Case Where Skipping a Task Is Permitted
Skipping is possible only when one of the following technical reasons applies:
- A change in the implementation approach made the feature itself unnecessary
- An architecture change replaced it with a different implementation method
- A change in dependencies made the task impossible to execute
When skipping, always state the reason clearly:
```markdown
- [x] ~~task name~~ (unnecessary due to a change in approach: specific technical reason)
```
### If a Task Is Too Large
- Split the task into smaller subtasks
- Add the split subtasks to this file
- Complete the subtasks one by one
---
## Phase 1: M1 - Project Foundation Setup (branch: feature/m1-setup)
- [x] Create branch `feature/m1-setup` from main
- [x] Update package.json: name, scripts (dev/build/lint/format/typecheck/test/test:e2e/migrate), dependencies (next, react, react-dom, better-sqlite3, bcrypt, react-markdown, remark-gfm, rehype-sanitize), devDependencies (tailwindcss, @types/better-sqlite3, @types/bcrypt, @playwright/test, tsx, eslint-config-next)
- [x] Run `npm install` and confirm dependencies install successfully
- [x] Configure Next.js: create next.config.mjs (Node.js runtime), update tsconfig.json for Next.js (@/* path alias, jsx preserve, next plugin types), create app/globals.css (Tailwind directives), create tailwind.config.ts, postcss.config.mjs
- [x] Create Next.js app structure: app/layout.tsx (root layout), app/page.tsx (home redirect), app/globals.css
- [x] Create directory structure: lib/, repositories/, services/, components/, tests/unit, tests/integration, tests/e2e, data/.gitkeep, backups/.gitkeep
- [x] Remove boilerplate: delete src/example.ts, src/example.test.ts
- [x] Update vitest.config.ts (include tests/**, exclude .next), create playwright.config.ts
- [x] Update eslint.config.js (add Next.js plugin/ignores for .next), update .prettierrc if needed
- [x] Create .env.example (SQLITE_PATH, SESSION_SECRET)
- [x] Create CI config .github/workflows/ci.yml (lint, typecheck, test, build)
- [x] Commit M1, merge feature/m1-setup to main, push
## Phase 2: M2 - DB Foundation (branch: feature/m2-db-foundation)
- [ ] Create branch `feature/m2-db-foundation` from main
- [ ] Implement `lib/db/sqlite.ts`: SqliteDatabase class (query/get/execute/transaction/close) + getDb() singleton (WAL, foreign_keys ON)
- [ ] Implement `lib/db/migrator.ts`: Migrator class (schema_migrations table, filename-order, skip applied, 1-file-1-tx, rollback)
- [ ] Create `lib/db/migrations/001_initial.sql`: all 16 tables + indexes
- [ ] Create `lib/types/`: all Entity types + enums (User, Project, ProjectMember, BoardThread, BoardComment, ChatMessage, TodoColumn, TodoItem, FileAsset, ProjectNote, Milestone, CalendarEvent, Meeting, MeetingMember, Notification, ActivityLog, SchemaMigration + union types)
- [ ] Create `lib/db/run-migrations.ts`: migration runner script
- [ ] Implement API `GET /api/admin/migrations` (migration status, admin-only)
- [ ] Create test helper `tests/helpers/db.ts` (temp SQLite DB factory)
- [ ] Write Unit test `tests/unit/lib/db/sqlite.test.ts`
- [ ] Write Unit test `tests/unit/lib/db/migrator.test.ts`
- [ ] Run `npm run migrate` to verify schema creation
- [ ] Commit M2, merge feature/m2-db-foundation to main, push
## Phase 3: M3 - Auth & User Management (branch: feature/m3-auth-user)
- [ ] Create branch `feature/m3-auth-user` from main
- [ ] Create custom error classes `lib/errors.ts` (ValidationError, ForbiddenError, NotFoundError)
- [ ] Implement `lib/auth/session.ts` (session cookie read/write) and `lib/auth/getCurrentUser.ts`
- [ ] Implement `lib/validators/userValidator.ts`
- [ ] Implement `repositories/UserRepository.ts`
- [ ] Implement `services/AuthService.ts` (register/login/logout/getCurrentUser/updateProfile, bcrypt)
- [ ] Implement API routes: register, login, logout, me, users/me
- [ ] Implement auth middleware/guard (protected routes redirect to login)
- [ ] Implement `app/login/page.tsx`, `app/profile/page.tsx`, update `app/layout.tsx`
- [ ] Write Unit test `tests/unit/repositories/UserRepository.test.ts`
- [ ] Write Unit test `tests/unit/services/AuthService.test.ts`
- [ ] Write E2E test `tests/e2e/auth.spec.ts`
- [ ] Commit M3, merge feature/m3-auth-user to main, push
## Phase 4: M4 - Project & Member Management (branch: feature/m4-project-member)
- [ ] Create branch `feature/m4-project-member` from main
- [ ] Implement `lib/validators/projectValidator.ts`
- [ ] Implement `repositories/ProjectRepository.ts`
- [ ] Implement `repositories/ProjectMemberRepository.ts`
- [ ] Implement `services/ProjectService.ts` (createProject/updateProject/addMember/removeMember/archiveProject/getDashboard skeleton, permission checks)
- [ ] Implement API routes: projects (list/create), projects/[projectId] (detail/edit/delete), members (list/add), members/[userId] (remove)
- [ ] Implement `app/dashboard/page.tsx` (project list skeleton)
- [ ] Implement `app/projects/[projectId]/page.tsx` (overview skeleton)
- [ ] Implement `app/projects/[projectId]/members/page.tsx`
- [ ] Implement `app/projects/[projectId]/settings/page.tsx`
- [ ] Implement layout components: components/layout/Header.tsx, Sidebar.tsx, ProjectNav.tsx
- [ ] Write Unit test `tests/unit/repositories/ProjectRepository.test.ts`
- [ ] Write Unit test `tests/unit/repositories/ProjectMemberRepository.test.ts`
- [ ] Write Unit test `tests/unit/services/ProjectService.test.ts`
- [ ] Write Integration test `tests/integration/project-member-permission.test.ts`
- [ ] Write E2E test `tests/e2e/project-management.spec.ts`
- [ ] Commit M4, merge feature/m4-project-member to main, push
## Phase 5: Quality Check and Fixes
- [ ] Confirm that all tests pass
- [ ] `npm test`
- [ ] Confirm that there are no lint errors
- [ ] `npm run lint`
- [ ] Confirm that there are no type errors
- [ ] `npm run typecheck`
- [ ] Confirm that the build succeeds
- [ ] `npm run build`
## Phase 6: Documentation Updates
- [ ] Update README.md (setup instructions, scripts)
- [ ] Post-implementation retrospective (record at the bottom of this file)
---
## Post-Implementation Retrospective
### Implementation Completion Date
{YYYY-MM-DD}
### Differences Between Plan and Actual
**Points that differed from the plan**:
- {Technical changes not anticipated at planning time}
- {Changes in the implementation approach and the reasons}
**Tasks that became newly necessary**:
- {Tasks added during implementation}
- {Why the addition was necessary}
**Tasks skipped for technical reasons** (only when applicable):
- {Task name}
- Reason for skipping: {specific technical reason}
- Alternative implementation: {what it was replaced with}
**⚠️ Note**: Do not list tasks skipped for reasons such as "time constraints" or "difficulty" here. Completing all tasks is the principle.
### Lessons Learned
**Technical insights**:
- {Technical knowledge gained through implementation}
- {New technologies or patterns used}
**Process improvements**:
- {What went well in task management}
- {How the steering files were leveraged}
### Improvement Suggestions for Next Time
- {Things to watch out for in the next feature addition}
- {More efficient implementation methods}
- {Improvements to task planning}