cc-devflow 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.md +83 -0
- package/.claude/agents/architecture-designer.md +443 -0
- package/.claude/agents/bug-analyzer.md +382 -0
- package/.claude/agents/checklist-agent.md +175 -0
- package/.claude/agents/clarify-analyst.md +50 -0
- package/.claude/agents/code-reviewer.md +71 -0
- package/.claude/agents/codex-analyzer.md +39 -0
- package/.claude/agents/compatibility-checker.md +580 -0
- package/.claude/agents/consistency-checker.md +532 -0
- package/.claude/agents/impact-analyzer.md +441 -0
- package/.claude/agents/planner.md +230 -0
- package/.claude/agents/prd-writer.md +320 -0
- package/.claude/agents/project-guidelines-generator.md +1329 -0
- package/.claude/agents/qa-tester.md +313 -0
- package/.claude/agents/release-manager.md +295 -0
- package/.claude/agents/security-reviewer.md +314 -0
- package/.claude/agents/style-guide-generator.md +458 -0
- package/.claude/agents/tech-architect.md +516 -0
- package/.claude/agents/ui-designer.md +485 -0
- package/.claude/commands/code-review-high.md +58 -0
- package/.claude/commands/core-architecture.md +429 -0
- package/.claude/commands/core-guidelines.md +486 -0
- package/.claude/commands/core-roadmap.md +439 -0
- package/.claude/commands/core-style.md +293 -0
- package/.claude/commands/flow-archive.md +245 -0
- package/.claude/commands/flow-checklist.md +260 -0
- package/.claude/commands/flow-clarify.md +136 -0
- package/.claude/commands/flow-constitution.md +82 -0
- package/.claude/commands/flow-dev.md +134 -0
- package/.claude/commands/flow-epic.md +150 -0
- package/.claude/commands/flow-fix.md +104 -0
- package/.claude/commands/flow-ideate.md +214 -0
- package/.claude/commands/flow-init.md +313 -0
- package/.claude/commands/flow-new.md +394 -0
- package/.claude/commands/flow-prd.md +131 -0
- package/.claude/commands/flow-qa.md +93 -0
- package/.claude/commands/flow-release.md +92 -0
- package/.claude/commands/flow-restart.md +98 -0
- package/.claude/commands/flow-status.md +64 -0
- package/.claude/commands/flow-tech.md +142 -0
- package/.claude/commands/flow-ui.md +189 -0
- package/.claude/commands/flow-update.md +111 -0
- package/.claude/commands/flow-upgrade.md +115 -0
- package/.claude/commands/flow-verify.md +96 -0
- package/.claude/commands/problem-analyzer.md +60 -0
- package/.claude/config/quality-rules.yml +161 -0
- package/.claude/docs/SPEC_KIT_CONSTITUTION_ANALYSIS.md +426 -0
- package/.claude/docs/design/consistency-conflict-detection-algorithms.md +658 -0
- package/.claude/docs/design/intent-driven-input-design.md +380 -0
- package/.claude/docs/design/prd-version-management-design.md +437 -0
- package/.claude/docs/guides/INIT_TROUBLESHOOTING.md +117 -0
- package/.claude/docs/guides/NEW_TROUBLESHOOTING.md +151 -0
- package/.claude/docs/guides/ROADMAP_TROUBLESHOOTING.md +188 -0
- package/.claude/docs/guides/TASK_COMPLETION_MARKING.md +338 -0
- package/.claude/docs/templates/ARCHITECTURE_TEMPLATE.md +633 -0
- package/.claude/docs/templates/BACKLOG_TEMPLATE.md +261 -0
- package/.claude/docs/templates/CHECKLIST_TEMPLATE.md +52 -0
- package/.claude/docs/templates/CLARIFICATION_REPORT_TEMPLATE.md +206 -0
- package/.claude/docs/templates/CODE_REVIEW_TEMPLATE.md +71 -0
- package/.claude/docs/templates/EPIC_TEMPLATE.md +805 -0
- package/.claude/docs/templates/INIT_FLOW_TEMPLATE.md +213 -0
- package/.claude/docs/templates/INTENT_CLARIFICATION_TEMPLATE.md +57 -0
- package/.claude/docs/templates/NEW_ORCHESTRATION_TEMPLATE.md +148 -0
- package/.claude/docs/templates/PRD_TEMPLATE.md +562 -0
- package/.claude/docs/templates/RESEARCH_TEMPLATE.md +276 -0
- package/.claude/docs/templates/REVIEW-HIGH.md +57 -0
- package/.claude/docs/templates/ROADMAP_DIALOGUE_TEMPLATE.md +198 -0
- package/.claude/docs/templates/ROADMAP_TEMPLATE.md +310 -0
- package/.claude/docs/templates/STYLE_TEMPLATE.md +1266 -0
- package/.claude/docs/templates/TASKS_TEMPLATE.md +523 -0
- package/.claude/docs/templates/TECH_DESIGN_TEMPLATE.md +1019 -0
- package/.claude/docs/templates/UI_PROTOTYPE_TEMPLATE.md +1436 -0
- package/.claude/guides/agent-guides/agent-coordination-guide.md +459 -0
- package/.claude/guides/project-guidelines-system.md +463 -0
- package/.claude/guides/technical-guides/datetime-handling-guide.md +563 -0
- package/.claude/guides/technical-guides/git-github-guide.md +642 -0
- package/.claude/guides/technical-guides/test-execution-guide.md +618 -0
- package/.claude/guides/workflow-guides/bug-fix-orchestrator.md +217 -0
- package/.claude/guides/workflow-guides/flow-orchestrator.md +282 -0
- package/.claude/hooks/checklist-gate.js +397 -0
- package/.claude/hooks/error-handling-reminder.sh +12 -0
- package/.claude/hooks/error-handling-reminder.ts +459 -0
- package/.claude/hooks/post-tool-use-tracker.sh +280 -0
- package/.claude/hooks/pre-tool-use-guardrail.sh +36 -0
- package/.claude/hooks/pre-tool-use-guardrail.ts +342 -0
- package/.claude/hooks/skill-activation-prompt.sh +36 -0
- package/.claude/hooks/skill-activation-prompt.ts +214 -0
- package/.claude/hooks/state/skills-used-test-guard.json +3 -0
- package/.claude/rules/devflow-conventions.md +305 -0
- package/.claude/rules/project-constitution.md +748 -0
- package/.claude/schemas/constitution.schema.json +43 -0
- package/.claude/scripts/analyze-upgrade-impact.sh +200 -0
- package/.claude/scripts/archive-requirement.sh +351 -0
- package/.claude/scripts/calculate-checklist-completion.sh +243 -0
- package/.claude/scripts/calculate-quarter.sh +206 -0
- package/.claude/scripts/check-dependencies.sh +409 -0
- package/.claude/scripts/check-prerequisites.sh +232 -0
- package/.claude/scripts/check-task-status.sh +264 -0
- package/.claude/scripts/checklist-errors.sh +131 -0
- package/.claude/scripts/common.sh +570 -0
- package/.claude/scripts/consolidate-research.sh +182 -0
- package/.claude/scripts/create-requirement.sh +426 -0
- package/.claude/scripts/export-contracts.sh +117 -0
- package/.claude/scripts/extract-data-model.sh +78 -0
- package/.claude/scripts/generate-clarification-questions.sh +377 -0
- package/.claude/scripts/generate-clarification-report.sh +463 -0
- package/.claude/scripts/generate-quickstart.sh +146 -0
- package/.claude/scripts/generate-research-tasks.sh +157 -0
- package/.claude/scripts/generate-status-report.sh +523 -0
- package/.claude/scripts/generate-tech-analysis.sh +46 -0
- package/.claude/scripts/locate-requirement-in-roadmap.sh +233 -0
- package/.claude/scripts/manage-constitution.sh +602 -0
- package/.claude/scripts/mark-task-complete.sh +198 -0
- package/.claude/scripts/populate-research-tasks.sh +259 -0
- package/.claude/scripts/recover-workflow.sh +460 -0
- package/.claude/scripts/run-clarify-scan.sh +601 -0
- package/.claude/scripts/run-high-review.sh +62 -0
- package/.claude/scripts/run-problem-analysis.sh +68 -0
- package/.claude/scripts/setup-epic.sh +173 -0
- package/.claude/scripts/sync-roadmap-progress.sh +300 -0
- package/.claude/scripts/sync-task-marks.sh +199 -0
- package/.claude/scripts/test-clarify-scan.sh +515 -0
- package/.claude/scripts/update-agent-context.sh +806 -0
- package/.claude/scripts/validate-constitution.sh +567 -0
- package/.claude/scripts/validate-hooks.sh +487 -0
- package/.claude/scripts/validate-research.sh +332 -0
- package/.claude/scripts/validate-scope-boundary.sh +493 -0
- package/.claude/scripts/verify-setup.sh +37 -0
- package/.claude/settings.json +76 -0
- package/.claude/skills/_reference-implementations/README.md +96 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/SKILL.md +302 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/architecture-overview.md +451 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/async-and-errors.md +307 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/complete-examples.md +638 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/configuration.md +275 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/database-patterns.md +224 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/middleware-guide.md +213 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/routing-and-controllers.md +756 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/sentry-and-monitoring.md +336 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/services-and-repositories.md +789 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/testing-guide.md +235 -0
- package/.claude/skills/_reference-implementations/backend-express-prisma/resources/validation-patterns.md +754 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/SKILL.md +399 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/common-patterns.md +331 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/complete-examples.md +872 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/component-patterns.md +502 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/data-fetching.md +767 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/file-organization.md +502 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/loading-and-error-states.md +501 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/performance.md +406 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/routing-guide.md +364 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/styling-guide.md +428 -0
- package/.claude/skills/_reference-implementations/frontend-react-mui/resources/typescript-standards.md +418 -0
- package/.claude/skills/cc-devflow-orchestrator/SKILL.md +229 -0
- package/.claude/skills/constitution-guardian/SKILL.md +306 -0
- package/.claude/skills/devflow-constitution-quick-ref/SKILL.md +374 -0
- package/.claude/skills/devflow-file-standards/SKILL.md +353 -0
- package/.claude/skills/devflow-tdd-enforcer/SKILL.md +192 -0
- package/.claude/skills/skill-developer/ADVANCED.md +197 -0
- package/.claude/skills/skill-developer/HOOK_MECHANISMS.md +306 -0
- package/.claude/skills/skill-developer/PATTERNS_LIBRARY.md +152 -0
- package/.claude/skills/skill-developer/SKILL.md +426 -0
- package/.claude/skills/skill-developer/SKILL_RULES_REFERENCE.md +315 -0
- package/.claude/skills/skill-developer/TRIGGER_TYPES.md +305 -0
- package/.claude/skills/skill-developer/TROUBLESHOOTING.md +514 -0
- package/.claude/skills/skill-rules.json +213 -0
- package/.claude/tests/README.md +300 -0
- package/.claude/tests/TODO.md +69 -0
- package/.claude/tests/__pycache__/test_analyze_upgrade_impact.cpython-311-pytest-7.2.2.pyc +0 -0
- package/.claude/tests/__pycache__/test_consolidate_research.cpython-311-pytest-7.2.2.pyc +0 -0
- package/.claude/tests/__pycache__/test_export_contracts.cpython-311-pytest-7.2.2.pyc +0 -0
- package/.claude/tests/__pycache__/test_extract_data_model.cpython-311-pytest-7.2.2.pyc +0 -0
- package/.claude/tests/__pycache__/test_generate_quickstart.cpython-311-pytest-7.2.2.pyc +0 -0
- package/.claude/tests/__pycache__/test_generate_research_tasks.cpython-311-pytest-7.2.2.pyc +0 -0
- package/.claude/tests/constitution/run_all_constitution_tests.sh +111 -0
- package/.claude/tests/constitution/test_agent_assignment.sh +207 -0
- package/.claude/tests/constitution/test_article_coverage.sh +201 -0
- package/.claude/tests/constitution/test_template_completeness.sh +150 -0
- package/.claude/tests/constitution/test_version_consistency.sh +120 -0
- package/.claude/tests/fixtures/spec_delta_full.md +16 -0
- package/.claude/tests/fixtures/tasks_progress_sample.md +5 -0
- package/.claude/tests/run-all-tests.sh +229 -0
- package/.claude/tests/scripts/run.sh +30 -0
- package/.claude/tests/scripts/test-framework.sh +128 -0
- package/.claude/tests/scripts/test_check_prerequisites.sh +511 -0
- package/.claude/tests/scripts/test_check_prerequisites.sh.bak +504 -0
- package/.claude/tests/scripts/test_check_prerequisites.sh.bak2 +505 -0
- package/.claude/tests/scripts/test_check_prerequisites.sh.bak3 +506 -0
- package/.claude/tests/scripts/test_check_prerequisites.sh.bak4 +507 -0
- package/.claude/tests/scripts/test_check_prerequisites.sh.bak5 +508 -0
- package/.claude/tests/scripts/test_check_task_status.sh +499 -0
- package/.claude/tests/scripts/test_common.sh +244 -0
- package/.claude/tests/scripts/test_generate_status_report.sh +71 -0
- package/.claude/tests/scripts/test_mark_task_complete.sh +441 -0
- package/.claude/tests/scripts/test_mark_task_complete.sh.backup +410 -0
- package/.claude/tests/scripts/test_recover_workflow.sh +304 -0
- package/.claude/tests/scripts/test_setup_epic.sh +437 -0
- package/.claude/tests/scripts/test_sync_task_marks.sh +196 -0
- package/.claude/tests/scripts/test_validate_constitution.sh +74 -0
- package/.claude/tests/scripts/test_validate_research.sh +462 -0
- package/.claude/tests/slugify.bats +82 -0
- package/.claude/tests/test-framework.sh +732 -0
- package/.claude/tests/test_analyze_upgrade_impact.py +34 -0
- package/.claude/tests/test_consolidate_research.py +48 -0
- package/.claude/tests/test_export_contracts.py +43 -0
- package/.claude/tests/test_extract_data_model.py +33 -0
- package/.claude/tests/test_generate_quickstart.py +50 -0
- package/.claude/tests/test_generate_research_tasks.py +52 -0
- package/.claude/tsc-cache/6e64f818-6398-49ca-8623-581a9af85c44/edited-files.log +1 -0
- package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/affected-repos.txt +1 -0
- package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/edited-files.log +1 -0
- package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/affected-repos.txt +1 -0
- package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/edited-files.log +1 -0
- package/CHANGELOG.md +507 -0
- package/LICENSE +21 -0
- package/README.md +534 -0
- package/README.zh-CN.md +530 -0
- package/bin/adapt.js +240 -0
- package/bin/cc-devflow-cli.js +185 -0
- package/bin/cc-devflow.js +78 -0
- package/config/adapters.yml +5 -0
- package/config/schema/adapters.schema.json +44 -0
- package/docs/CLAUDE.md +26 -0
- package/docs/commands/README.md +61 -0
- package/docs/commands/README.zh-CN.md +55 -0
- package/docs/commands/core-roadmap.md +106 -0
- package/docs/commands/core-roadmap.zh-CN.md +102 -0
- package/docs/commands/core-style.md +405 -0
- package/docs/commands/core-style.zh-CN.md +405 -0
- package/docs/commands/flow-init.md +134 -0
- package/docs/commands/flow-init.zh-CN.md +163 -0
- package/docs/commands/flow-new.md +274 -0
- package/docs/commands/flow-new.zh-CN.md +270 -0
- package/docs/guides/getting-started.md +204 -0
- package/docs/guides/getting-started.zh-CN.md +152 -0
- package/lib/adapters/adapter-interface.js +57 -0
- package/lib/adapters/claude-adapter.js +74 -0
- package/lib/adapters/codex-adapter.js +40 -0
- package/lib/adapters/config-validator.js +68 -0
- package/lib/adapters/logger.js +42 -0
- package/lib/adapters/registry.js +153 -0
- package/lib/compiler/CLAUDE.md +92 -0
- package/lib/compiler/__tests__/drift.test.js +215 -0
- package/lib/compiler/__tests__/errors.test.js +184 -0
- package/lib/compiler/__tests__/incremental.test.js +174 -0
- package/lib/compiler/__tests__/integration.test.js +174 -0
- package/lib/compiler/__tests__/manifest.test.js +233 -0
- package/lib/compiler/__tests__/parser.test.js +456 -0
- package/lib/compiler/__tests__/schemas.test.js +301 -0
- package/lib/compiler/__tests__/skills-registry.test.js +125 -0
- package/lib/compiler/__tests__/transformer.test.js +286 -0
- package/lib/compiler/emitters/antigravity-emitter.js +171 -0
- package/lib/compiler/emitters/base-emitter.js +73 -0
- package/lib/compiler/emitters/codex-emitter.js +52 -0
- package/lib/compiler/emitters/cursor-emitter.js +31 -0
- package/lib/compiler/emitters/index.js +50 -0
- package/lib/compiler/emitters/qwen-emitter.js +39 -0
- package/lib/compiler/errors.js +119 -0
- package/lib/compiler/index.js +256 -0
- package/lib/compiler/manifest.js +242 -0
- package/lib/compiler/parser.js +258 -0
- package/lib/compiler/platforms.js +113 -0
- package/lib/compiler/resource-copier.js +320 -0
- package/lib/compiler/rules-emitters/__tests__/antigravity-rules-emitter.test.js +191 -0
- package/lib/compiler/rules-emitters/__tests__/codex-rules-emitter.test.js +109 -0
- package/lib/compiler/rules-emitters/__tests__/cursor-rules-emitter.test.js +123 -0
- package/lib/compiler/rules-emitters/__tests__/qwen-rules-emitter.test.js +123 -0
- package/lib/compiler/rules-emitters/antigravity-rules-emitter.js +253 -0
- package/lib/compiler/rules-emitters/base-rules-emitter.js +83 -0
- package/lib/compiler/rules-emitters/codex-rules-emitter.js +116 -0
- package/lib/compiler/rules-emitters/cursor-rules-emitter.js +98 -0
- package/lib/compiler/rules-emitters/index.js +71 -0
- package/lib/compiler/rules-emitters/qwen-rules-emitter.js +70 -0
- package/lib/compiler/schemas.js +144 -0
- package/lib/compiler/skills-registry.js +225 -0
- package/lib/compiler/transformer.js +236 -0
- package/package.json +50 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
# Complete Examples - Full Working Code
|
|
2
|
+
|
|
3
|
+
Real-world examples showing complete implementation patterns.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Complete Controller Example](#complete-controller-example)
|
|
8
|
+
- [Complete Service with DI](#complete-service-with-di)
|
|
9
|
+
- [Complete Route File](#complete-route-file)
|
|
10
|
+
- [Complete Repository](#complete-repository)
|
|
11
|
+
- [Refactoring Example: Bad to Good](#refactoring-example-bad-to-good)
|
|
12
|
+
- [End-to-End Feature Example](#end-to-end-feature-example)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Complete Controller Example
|
|
17
|
+
|
|
18
|
+
### UserController (Following All Best Practices)
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// controllers/UserController.ts
|
|
22
|
+
import { Request, Response } from 'express';
|
|
23
|
+
import { BaseController } from './BaseController';
|
|
24
|
+
import { UserService } from '../services/userService';
|
|
25
|
+
import { createUserSchema, updateUserSchema } from '../validators/userSchemas';
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
|
|
28
|
+
export class UserController extends BaseController {
|
|
29
|
+
private userService: UserService;
|
|
30
|
+
|
|
31
|
+
constructor() {
|
|
32
|
+
super();
|
|
33
|
+
this.userService = new UserService();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async getUser(req: Request, res: Response): Promise<void> {
|
|
37
|
+
try {
|
|
38
|
+
this.addBreadcrumb('Fetching user', 'user_controller', {
|
|
39
|
+
userId: req.params.id,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const user = await this.withTransaction(
|
|
43
|
+
'user.get',
|
|
44
|
+
'db.query',
|
|
45
|
+
() => this.userService.findById(req.params.id)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (!user) {
|
|
49
|
+
return this.handleError(
|
|
50
|
+
new Error('User not found'),
|
|
51
|
+
res,
|
|
52
|
+
'getUser',
|
|
53
|
+
404
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.handleSuccess(res, user);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
this.handleError(error, res, 'getUser');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async listUsers(req: Request, res: Response): Promise<void> {
|
|
64
|
+
try {
|
|
65
|
+
const users = await this.userService.getAll();
|
|
66
|
+
this.handleSuccess(res, users);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
this.handleError(error, res, 'listUsers');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async createUser(req: Request, res: Response): Promise<void> {
|
|
73
|
+
try {
|
|
74
|
+
// Validate input with Zod
|
|
75
|
+
const validated = createUserSchema.parse(req.body);
|
|
76
|
+
|
|
77
|
+
// Track performance
|
|
78
|
+
const user = await this.withTransaction(
|
|
79
|
+
'user.create',
|
|
80
|
+
'db.mutation',
|
|
81
|
+
() => this.userService.create(validated)
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
this.handleSuccess(res, user, 'User created successfully', 201);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error instanceof z.ZodError) {
|
|
87
|
+
return this.handleError(error, res, 'createUser', 400);
|
|
88
|
+
}
|
|
89
|
+
this.handleError(error, res, 'createUser');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async updateUser(req: Request, res: Response): Promise<void> {
|
|
94
|
+
try {
|
|
95
|
+
const validated = updateUserSchema.parse(req.body);
|
|
96
|
+
|
|
97
|
+
const user = await this.userService.update(
|
|
98
|
+
req.params.id,
|
|
99
|
+
validated
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
this.handleSuccess(res, user, 'User updated');
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error instanceof z.ZodError) {
|
|
105
|
+
return this.handleError(error, res, 'updateUser', 400);
|
|
106
|
+
}
|
|
107
|
+
this.handleError(error, res, 'updateUser');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async deleteUser(req: Request, res: Response): Promise<void> {
|
|
112
|
+
try {
|
|
113
|
+
await this.userService.delete(req.params.id);
|
|
114
|
+
this.handleSuccess(res, null, 'User deleted', 204);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
this.handleError(error, res, 'deleteUser');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Complete Service with DI
|
|
125
|
+
|
|
126
|
+
### UserService
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// services/userService.ts
|
|
130
|
+
import { UserRepository } from '../repositories/UserRepository';
|
|
131
|
+
import { ConflictError, NotFoundError, ValidationError } from '../types/errors';
|
|
132
|
+
import type { CreateUserDTO, UpdateUserDTO, User } from '../types/user.types';
|
|
133
|
+
|
|
134
|
+
export class UserService {
|
|
135
|
+
private userRepository: UserRepository;
|
|
136
|
+
|
|
137
|
+
constructor(userRepository?: UserRepository) {
|
|
138
|
+
this.userRepository = userRepository || new UserRepository();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async findById(id: string): Promise<User | null> {
|
|
142
|
+
return await this.userRepository.findById(id);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async getAll(): Promise<User[]> {
|
|
146
|
+
return await this.userRepository.findActive();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async create(data: CreateUserDTO): Promise<User> {
|
|
150
|
+
// Business rule: validate age
|
|
151
|
+
if (data.age < 18) {
|
|
152
|
+
throw new ValidationError('User must be 18 or older');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Business rule: check email uniqueness
|
|
156
|
+
const existing = await this.userRepository.findByEmail(data.email);
|
|
157
|
+
if (existing) {
|
|
158
|
+
throw new ConflictError('Email already in use');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Create user with profile
|
|
162
|
+
return await this.userRepository.create({
|
|
163
|
+
email: data.email,
|
|
164
|
+
profile: {
|
|
165
|
+
create: {
|
|
166
|
+
firstName: data.firstName,
|
|
167
|
+
lastName: data.lastName,
|
|
168
|
+
age: data.age,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async update(id: string, data: UpdateUserDTO): Promise<User> {
|
|
175
|
+
// Check exists
|
|
176
|
+
const existing = await this.userRepository.findById(id);
|
|
177
|
+
if (!existing) {
|
|
178
|
+
throw new NotFoundError('User not found');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Business rule: email uniqueness if changing
|
|
182
|
+
if (data.email && data.email !== existing.email) {
|
|
183
|
+
const emailTaken = await this.userRepository.findByEmail(data.email);
|
|
184
|
+
if (emailTaken) {
|
|
185
|
+
throw new ConflictError('Email already in use');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return await this.userRepository.update(id, data);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async delete(id: string): Promise<void> {
|
|
193
|
+
const existing = await this.userRepository.findById(id);
|
|
194
|
+
if (!existing) {
|
|
195
|
+
throw new NotFoundError('User not found');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await this.userRepository.delete(id);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Complete Route File
|
|
206
|
+
|
|
207
|
+
### userRoutes.ts
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// routes/userRoutes.ts
|
|
211
|
+
import { Router } from 'express';
|
|
212
|
+
import { UserController } from '../controllers/UserController';
|
|
213
|
+
import { SSOMiddlewareClient } from '../middleware/SSOMiddleware';
|
|
214
|
+
import { auditMiddleware } from '../middleware/auditMiddleware';
|
|
215
|
+
|
|
216
|
+
const router = Router();
|
|
217
|
+
const controller = new UserController();
|
|
218
|
+
|
|
219
|
+
// GET /users - List all users
|
|
220
|
+
router.get('/',
|
|
221
|
+
SSOMiddlewareClient.verifyLoginStatus,
|
|
222
|
+
auditMiddleware,
|
|
223
|
+
async (req, res) => controller.listUsers(req, res)
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// GET /users/:id - Get single user
|
|
227
|
+
router.get('/:id',
|
|
228
|
+
SSOMiddlewareClient.verifyLoginStatus,
|
|
229
|
+
auditMiddleware,
|
|
230
|
+
async (req, res) => controller.getUser(req, res)
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// POST /users - Create user
|
|
234
|
+
router.post('/',
|
|
235
|
+
SSOMiddlewareClient.verifyLoginStatus,
|
|
236
|
+
auditMiddleware,
|
|
237
|
+
async (req, res) => controller.createUser(req, res)
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// PUT /users/:id - Update user
|
|
241
|
+
router.put('/:id',
|
|
242
|
+
SSOMiddlewareClient.verifyLoginStatus,
|
|
243
|
+
auditMiddleware,
|
|
244
|
+
async (req, res) => controller.updateUser(req, res)
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// DELETE /users/:id - Delete user
|
|
248
|
+
router.delete('/:id',
|
|
249
|
+
SSOMiddlewareClient.verifyLoginStatus,
|
|
250
|
+
auditMiddleware,
|
|
251
|
+
async (req, res) => controller.deleteUser(req, res)
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
export default router;
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Complete Repository
|
|
260
|
+
|
|
261
|
+
### UserRepository
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// repositories/UserRepository.ts
|
|
265
|
+
import { PrismaService } from '@project-lifecycle-portal/database';
|
|
266
|
+
import type { User, Prisma } from '@prisma/client';
|
|
267
|
+
|
|
268
|
+
export class UserRepository {
|
|
269
|
+
async findById(id: string): Promise<User | null> {
|
|
270
|
+
return PrismaService.main.user.findUnique({
|
|
271
|
+
where: { id },
|
|
272
|
+
include: { profile: true },
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
277
|
+
return PrismaService.main.user.findUnique({
|
|
278
|
+
where: { email },
|
|
279
|
+
include: { profile: true },
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async findActive(): Promise<User[]> {
|
|
284
|
+
return PrismaService.main.user.findMany({
|
|
285
|
+
where: { isActive: true },
|
|
286
|
+
include: { profile: true },
|
|
287
|
+
orderBy: { createdAt: 'desc' },
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async create(data: Prisma.UserCreateInput): Promise<User> {
|
|
292
|
+
return PrismaService.main.user.create({
|
|
293
|
+
data,
|
|
294
|
+
include: { profile: true },
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async update(id: string, data: Prisma.UserUpdateInput): Promise<User> {
|
|
299
|
+
return PrismaService.main.user.update({
|
|
300
|
+
where: { id },
|
|
301
|
+
data,
|
|
302
|
+
include: { profile: true },
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async delete(id: string): Promise<User> {
|
|
307
|
+
// Soft delete
|
|
308
|
+
return PrismaService.main.user.update({
|
|
309
|
+
where: { id },
|
|
310
|
+
data: {
|
|
311
|
+
isActive: false,
|
|
312
|
+
deletedAt: new Date(),
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Refactoring Example: Bad to Good
|
|
322
|
+
|
|
323
|
+
### BEFORE: Business Logic in Routes ❌
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
// routes/postRoutes.ts (BAD - 200+ lines)
|
|
327
|
+
router.post('/posts', async (req, res) => {
|
|
328
|
+
try {
|
|
329
|
+
const username = res.locals.claims.preferred_username;
|
|
330
|
+
const responses = req.body.responses;
|
|
331
|
+
const stepInstanceId = req.body.stepInstanceId;
|
|
332
|
+
|
|
333
|
+
// ❌ Permission check in route
|
|
334
|
+
const userId = await userProfileService.getProfileByEmail(username).then(p => p.id);
|
|
335
|
+
const canComplete = await permissionService.canCompleteStep(userId, stepInstanceId);
|
|
336
|
+
if (!canComplete) {
|
|
337
|
+
return res.status(403).json({ error: 'No permission' });
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ❌ Business logic in route
|
|
341
|
+
const post = await postRepository.create({
|
|
342
|
+
title: req.body.title,
|
|
343
|
+
content: req.body.content,
|
|
344
|
+
authorId: userId
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// ❌ More business logic...
|
|
348
|
+
if (res.locals.isImpersonating) {
|
|
349
|
+
impersonationContextStore.storeContext(...);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ... 100+ more lines
|
|
353
|
+
|
|
354
|
+
res.json({ success: true, data: result });
|
|
355
|
+
} catch (e) {
|
|
356
|
+
handler.handleException(res, e);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### AFTER: Clean Separation ✅
|
|
362
|
+
|
|
363
|
+
**1. Clean Route:**
|
|
364
|
+
```typescript
|
|
365
|
+
// routes/postRoutes.ts
|
|
366
|
+
import { PostController } from '../controllers/PostController';
|
|
367
|
+
|
|
368
|
+
const router = Router();
|
|
369
|
+
const controller = new PostController();
|
|
370
|
+
|
|
371
|
+
// ✅ CLEAN: 8 lines total!
|
|
372
|
+
router.post('/',
|
|
373
|
+
SSOMiddlewareClient.verifyLoginStatus,
|
|
374
|
+
auditMiddleware,
|
|
375
|
+
async (req, res) => controller.createPost(req, res)
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
export default router;
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**2. Controller:**
|
|
382
|
+
```typescript
|
|
383
|
+
// controllers/PostController.ts
|
|
384
|
+
export class PostController extends BaseController {
|
|
385
|
+
private postService: PostService;
|
|
386
|
+
|
|
387
|
+
constructor() {
|
|
388
|
+
super();
|
|
389
|
+
this.postService = new PostService();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async createPost(req: Request, res: Response): Promise<void> {
|
|
393
|
+
try {
|
|
394
|
+
const validated = createPostSchema.parse({
|
|
395
|
+
...req.body,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const result = await this.postService.createPost(
|
|
399
|
+
validated,
|
|
400
|
+
res.locals.userId
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
this.handleSuccess(res, result, 'Post created successfully');
|
|
404
|
+
} catch (error) {
|
|
405
|
+
this.handleError(error, res, 'createPost');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**3. Service:**
|
|
412
|
+
```typescript
|
|
413
|
+
// services/postService.ts
|
|
414
|
+
export class PostService {
|
|
415
|
+
async createPost(
|
|
416
|
+
data: CreatePostDTO,
|
|
417
|
+
userId: string
|
|
418
|
+
): Promise<SubmissionResult> {
|
|
419
|
+
// Permission check
|
|
420
|
+
const canComplete = await permissionService.canCompleteStep(
|
|
421
|
+
userId,
|
|
422
|
+
data.stepInstanceId
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
if (!canComplete) {
|
|
426
|
+
throw new ForbiddenError('No permission to complete step');
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Execute workflow
|
|
430
|
+
const engine = await createWorkflowEngine();
|
|
431
|
+
const command = new CompleteStepCommand(
|
|
432
|
+
data.stepInstanceId,
|
|
433
|
+
userId,
|
|
434
|
+
data.responses
|
|
435
|
+
);
|
|
436
|
+
const events = await engine.executeCommand(command);
|
|
437
|
+
|
|
438
|
+
// Handle impersonation
|
|
439
|
+
if (context.isImpersonating) {
|
|
440
|
+
await this.handleImpersonation(data.stepInstanceId, context);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return { events, success: true };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private async handleImpersonation(stepInstanceId: number, context: any) {
|
|
447
|
+
impersonationContextStore.storeContext(stepInstanceId, {
|
|
448
|
+
originalUserId: context.originalUserId,
|
|
449
|
+
effectiveUserId: context.effectiveUserId,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**Result:**
|
|
456
|
+
- Route: 8 lines (was 200+)
|
|
457
|
+
- Controller: 25 lines
|
|
458
|
+
- Service: 40 lines
|
|
459
|
+
- **Testable, maintainable, reusable!**
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## End-to-End Feature Example
|
|
464
|
+
|
|
465
|
+
### Complete User Management Feature
|
|
466
|
+
|
|
467
|
+
**1. Types:**
|
|
468
|
+
```typescript
|
|
469
|
+
// types/user.types.ts
|
|
470
|
+
export interface User {
|
|
471
|
+
id: string;
|
|
472
|
+
email: string;
|
|
473
|
+
isActive: boolean;
|
|
474
|
+
profile?: UserProfile;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export interface CreateUserDTO {
|
|
478
|
+
email: string;
|
|
479
|
+
firstName: string;
|
|
480
|
+
lastName: string;
|
|
481
|
+
age: number;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export interface UpdateUserDTO {
|
|
485
|
+
email?: string;
|
|
486
|
+
firstName?: string;
|
|
487
|
+
lastName?: string;
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**2. Validators:**
|
|
492
|
+
```typescript
|
|
493
|
+
// validators/userSchemas.ts
|
|
494
|
+
import { z } from 'zod';
|
|
495
|
+
|
|
496
|
+
export const createUserSchema = z.object({
|
|
497
|
+
email: z.string().email(),
|
|
498
|
+
firstName: z.string().min(1).max(100),
|
|
499
|
+
lastName: z.string().min(1).max(100),
|
|
500
|
+
age: z.number().int().min(18).max(120),
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
export const updateUserSchema = z.object({
|
|
504
|
+
email: z.string().email().optional(),
|
|
505
|
+
firstName: z.string().min(1).max(100).optional(),
|
|
506
|
+
lastName: z.string().min(1).max(100).optional(),
|
|
507
|
+
});
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**3. Repository:**
|
|
511
|
+
```typescript
|
|
512
|
+
// repositories/UserRepository.ts
|
|
513
|
+
export class UserRepository {
|
|
514
|
+
async findById(id: string): Promise<User | null> {
|
|
515
|
+
return PrismaService.main.user.findUnique({
|
|
516
|
+
where: { id },
|
|
517
|
+
include: { profile: true },
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async create(data: Prisma.UserCreateInput): Promise<User> {
|
|
522
|
+
return PrismaService.main.user.create({
|
|
523
|
+
data,
|
|
524
|
+
include: { profile: true },
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**4. Service:**
|
|
531
|
+
```typescript
|
|
532
|
+
// services/userService.ts
|
|
533
|
+
export class UserService {
|
|
534
|
+
private userRepository: UserRepository;
|
|
535
|
+
|
|
536
|
+
constructor() {
|
|
537
|
+
this.userRepository = new UserRepository();
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async create(data: CreateUserDTO): Promise<User> {
|
|
541
|
+
const existing = await this.userRepository.findByEmail(data.email);
|
|
542
|
+
if (existing) {
|
|
543
|
+
throw new ConflictError('Email already exists');
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return await this.userRepository.create({
|
|
547
|
+
email: data.email,
|
|
548
|
+
profile: {
|
|
549
|
+
create: {
|
|
550
|
+
firstName: data.firstName,
|
|
551
|
+
lastName: data.lastName,
|
|
552
|
+
age: data.age,
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
**5. Controller:**
|
|
561
|
+
```typescript
|
|
562
|
+
// controllers/UserController.ts
|
|
563
|
+
export class UserController extends BaseController {
|
|
564
|
+
private userService: UserService;
|
|
565
|
+
|
|
566
|
+
constructor() {
|
|
567
|
+
super();
|
|
568
|
+
this.userService = new UserService();
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async createUser(req: Request, res: Response): Promise<void> {
|
|
572
|
+
try {
|
|
573
|
+
const validated = createUserSchema.parse(req.body);
|
|
574
|
+
const user = await this.userService.create(validated);
|
|
575
|
+
this.handleSuccess(res, user, 'User created', 201);
|
|
576
|
+
} catch (error) {
|
|
577
|
+
this.handleError(error, res, 'createUser');
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
**6. Routes:**
|
|
584
|
+
```typescript
|
|
585
|
+
// routes/userRoutes.ts
|
|
586
|
+
const router = Router();
|
|
587
|
+
const controller = new UserController();
|
|
588
|
+
|
|
589
|
+
router.post('/',
|
|
590
|
+
SSOMiddlewareClient.verifyLoginStatus,
|
|
591
|
+
async (req, res) => controller.createUser(req, res)
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
export default router;
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**7. Register in app.ts:**
|
|
598
|
+
```typescript
|
|
599
|
+
// app.ts
|
|
600
|
+
import userRoutes from './routes/userRoutes';
|
|
601
|
+
|
|
602
|
+
app.use('/api/users', userRoutes);
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**Complete Request Flow:**
|
|
606
|
+
```
|
|
607
|
+
POST /api/users
|
|
608
|
+
↓
|
|
609
|
+
userRoutes matches /
|
|
610
|
+
↓
|
|
611
|
+
SSOMiddleware authenticates
|
|
612
|
+
↓
|
|
613
|
+
controller.createUser called
|
|
614
|
+
↓
|
|
615
|
+
Validates with Zod
|
|
616
|
+
↓
|
|
617
|
+
userService.create called
|
|
618
|
+
↓
|
|
619
|
+
Checks business rules
|
|
620
|
+
↓
|
|
621
|
+
userRepository.create called
|
|
622
|
+
↓
|
|
623
|
+
Prisma creates user
|
|
624
|
+
↓
|
|
625
|
+
Returns up the chain
|
|
626
|
+
↓
|
|
627
|
+
Controller formats response
|
|
628
|
+
↓
|
|
629
|
+
200/201 sent to client
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
**Related Files:**
|
|
635
|
+
- [SKILL.md](SKILL.md)
|
|
636
|
+
- [routing-and-controllers.md](routing-and-controllers.md)
|
|
637
|
+
- [services-and-repositories.md](services-and-repositories.md)
|
|
638
|
+
- [validation-patterns.md](validation-patterns.md)
|