@c0x12c/ai-toolkit 1.15.0
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-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +12 -0
- package/README.md +439 -0
- package/VERSION +1 -0
- package/agents/design-critic.md +127 -0
- package/agents/idea-killer.md +72 -0
- package/agents/infrastructure-expert.md +49 -0
- package/agents/micronaut-backend-expert.md +45 -0
- package/agents/phase-reviewer.md +150 -0
- package/agents/research-planner.md +70 -0
- package/agents/solution-architect-cto.md +49 -0
- package/agents/sre-architect.md +49 -0
- package/agents/team-coordinator.md +111 -0
- package/bin/cli.js +780 -0
- package/claude-md/00-header.md +39 -0
- package/claude-md/01-core.md +105 -0
- package/claude-md/05-database.md +20 -0
- package/claude-md/11-backend-micronaut.md +19 -0
- package/claude-md/20-frontend-react.md +44 -0
- package/claude-md/25-ux-design.md +56 -0
- package/claude-md/30-infrastructure.md +24 -0
- package/claude-md/30-project-mgmt.md +119 -0
- package/claude-md/40-product.md +39 -0
- package/claude-md/50-ops.md +34 -0
- package/claude-md/60-research.md +27 -0
- package/claude-md/90-footer.md +21 -0
- package/commands/spartan/brainstorm.md +134 -0
- package/commands/spartan/brownfield.md +157 -0
- package/commands/spartan/build.md +435 -0
- package/commands/spartan/careful.md +94 -0
- package/commands/spartan/commit-message.md +112 -0
- package/commands/spartan/content.md +17 -0
- package/commands/spartan/context-save.md +161 -0
- package/commands/spartan/contribute.md +140 -0
- package/commands/spartan/daily.md +42 -0
- package/commands/spartan/debug.md +308 -0
- package/commands/spartan/deep-dive.md +55 -0
- package/commands/spartan/deploy.md +207 -0
- package/commands/spartan/e2e.md +264 -0
- package/commands/spartan/env-setup.md +166 -0
- package/commands/spartan/epic.md +199 -0
- package/commands/spartan/fe-review.md +181 -0
- package/commands/spartan/figma-to-code.md +260 -0
- package/commands/spartan/forensics.md +46 -0
- package/commands/spartan/freeze.md +84 -0
- package/commands/spartan/fundraise.md +53 -0
- package/commands/spartan/gate-review.md +229 -0
- package/commands/spartan/gsd-upgrade.md +376 -0
- package/commands/spartan/guard.md +42 -0
- package/commands/spartan/init-project.md +178 -0
- package/commands/spartan/init-rules.md +298 -0
- package/commands/spartan/interview.md +154 -0
- package/commands/spartan/kickoff.md +73 -0
- package/commands/spartan/kotlin-service.md +109 -0
- package/commands/spartan/lean-canvas.md +222 -0
- package/commands/spartan/lint-rules.md +122 -0
- package/commands/spartan/map-codebase.md +124 -0
- package/commands/spartan/migration.md +82 -0
- package/commands/spartan/next-app.md +317 -0
- package/commands/spartan/next-feature.md +212 -0
- package/commands/spartan/onboard.md +326 -0
- package/commands/spartan/outreach.md +16 -0
- package/commands/spartan/phase.md +142 -0
- package/commands/spartan/pitch.md +18 -0
- package/commands/spartan/plan.md +210 -0
- package/commands/spartan/pr-ready.md +202 -0
- package/commands/spartan/project.md +106 -0
- package/commands/spartan/qa.md +222 -0
- package/commands/spartan/research.md +254 -0
- package/commands/spartan/review.md +132 -0
- package/commands/spartan/scan-rules.md +173 -0
- package/commands/spartan/sessions.md +143 -0
- package/commands/spartan/spec.md +131 -0
- package/commands/spartan/startup.md +257 -0
- package/commands/spartan/team.md +570 -0
- package/commands/spartan/teardown.md +161 -0
- package/commands/spartan/testcontainer.md +97 -0
- package/commands/spartan/tf-cost.md +123 -0
- package/commands/spartan/tf-deploy.md +116 -0
- package/commands/spartan/tf-drift.md +100 -0
- package/commands/spartan/tf-import.md +107 -0
- package/commands/spartan/tf-module.md +121 -0
- package/commands/spartan/tf-plan.md +100 -0
- package/commands/spartan/tf-review.md +106 -0
- package/commands/spartan/tf-scaffold.md +109 -0
- package/commands/spartan/tf-security.md +147 -0
- package/commands/spartan/think.md +221 -0
- package/commands/spartan/unfreeze.md +13 -0
- package/commands/spartan/update.md +134 -0
- package/commands/spartan/ux.md +1233 -0
- package/commands/spartan/validate.md +193 -0
- package/commands/spartan/web-to-prd.md +706 -0
- package/commands/spartan/workstreams.md +109 -0
- package/commands/spartan/write.md +16 -0
- package/commands/spartan.md +386 -0
- package/frameworks/00-framework-comparison-guide.md +317 -0
- package/frameworks/01-lean-canvas.md +196 -0
- package/frameworks/02-design-sprint.md +304 -0
- package/frameworks/03-foundation-sprint.md +337 -0
- package/frameworks/04-business-model-canvas.md +391 -0
- package/frameworks/05-customer-development.md +426 -0
- package/frameworks/06-jobs-to-be-done.md +358 -0
- package/frameworks/07-mom-test.md +392 -0
- package/frameworks/08-value-proposition-canvas.md +488 -0
- package/frameworks/09-javelin-board.md +428 -0
- package/frameworks/10-build-measure-learn.md +467 -0
- package/frameworks/11-mvp-approaches.md +533 -0
- package/frameworks/think-before-build.md +593 -0
- package/lib/assembler.js +197 -0
- package/lib/assembler.test.js +159 -0
- package/lib/detector.js +166 -0
- package/lib/detector.test.js +221 -0
- package/lib/packs.js +16 -0
- package/lib/resolver.js +272 -0
- package/lib/resolver.test.js +298 -0
- package/lib/worktree.sh +104 -0
- package/package.json +50 -0
- package/packs/backend-micronaut.yaml +35 -0
- package/packs/backend-nodejs.yaml +15 -0
- package/packs/backend-python.yaml +15 -0
- package/packs/core.yaml +37 -0
- package/packs/database.yaml +21 -0
- package/packs/frontend-react.yaml +24 -0
- package/packs/infrastructure.yaml +40 -0
- package/packs/ops.yaml +16 -0
- package/packs/packs.compiled.json +371 -0
- package/packs/product.yaml +22 -0
- package/packs/project-mgmt.yaml +24 -0
- package/packs/research.yaml +39 -0
- package/packs/shared-backend.yaml +14 -0
- package/packs/ux-design.yaml +21 -0
- package/rules/backend-micronaut/API_DESIGN.md +313 -0
- package/rules/backend-micronaut/BATCH_PROCESSING.md +92 -0
- package/rules/backend-micronaut/CONTROLLERS.md +388 -0
- package/rules/backend-micronaut/KOTLIN.md +414 -0
- package/rules/backend-micronaut/RETROFIT_PLACEMENT.md +290 -0
- package/rules/backend-micronaut/SERVICES_AND_BEANS.md +325 -0
- package/rules/core/NAMING_CONVENTIONS.md +208 -0
- package/rules/core/SKILL_AUTHORING.md +174 -0
- package/rules/core/TIMEZONE.md +316 -0
- package/rules/database/ORM_AND_REPO.md +289 -0
- package/rules/database/SCHEMA.md +146 -0
- package/rules/database/TRANSACTIONS.md +311 -0
- package/rules/frontend-react/FRONTEND.md +344 -0
- package/rules/infrastructure/MODULES.md +260 -0
- package/rules/infrastructure/NAMING.md +196 -0
- package/rules/infrastructure/PROVIDERS.md +309 -0
- package/rules/infrastructure/SECURITY.md +310 -0
- package/rules/infrastructure/STATE_AND_BACKEND.md +237 -0
- package/rules/infrastructure/STRUCTURE.md +234 -0
- package/rules/infrastructure/VARIABLES.md +285 -0
- package/rules/shared-backend/ARCHITECTURE.md +46 -0
- package/rules/ux-design/DESIGN_PROCESS.md +176 -0
- package/skills/api-endpoint-creator/SKILL.md +455 -0
- package/skills/api-endpoint-creator/error-handling-guide.md +244 -0
- package/skills/api-endpoint-creator/examples.md +522 -0
- package/skills/api-endpoint-creator/testing-patterns.md +302 -0
- package/skills/article-writing/SKILL.md +109 -0
- package/skills/article-writing/examples.md +59 -0
- package/skills/backend-api-design/SKILL.md +84 -0
- package/skills/backend-api-design/code-patterns.md +138 -0
- package/skills/brainstorm/SKILL.md +95 -0
- package/skills/browser-qa/SKILL.md +87 -0
- package/skills/browser-qa/playwright-snippets.md +110 -0
- package/skills/ci-cd-patterns/SKILL.md +108 -0
- package/skills/ci-cd-patterns/workflows.md +149 -0
- package/skills/competitive-teardown/SKILL.md +93 -0
- package/skills/competitive-teardown/example-analysis.md +50 -0
- package/skills/content-engine/SKILL.md +131 -0
- package/skills/content-engine/examples.md +72 -0
- package/skills/database-patterns/SKILL.md +72 -0
- package/skills/database-patterns/code-templates.md +114 -0
- package/skills/database-table-creator/SKILL.md +141 -0
- package/skills/database-table-creator/examples.md +552 -0
- package/skills/database-table-creator/kotlin-templates.md +400 -0
- package/skills/database-table-creator/migration-template.sql +68 -0
- package/skills/database-table-creator/validation-checklist.md +337 -0
- package/skills/deep-research/SKILL.md +80 -0
- package/skills/design-intelligence/SKILL.md +268 -0
- package/skills/design-workflow/SKILL.md +127 -0
- package/skills/design-workflow/checklists.md +45 -0
- package/skills/idea-validation/SKILL.md +129 -0
- package/skills/idea-validation/example-report.md +50 -0
- package/skills/investor-materials/SKILL.md +122 -0
- package/skills/investor-materials/example-outline.md +70 -0
- package/skills/investor-outreach/SKILL.md +112 -0
- package/skills/investor-outreach/examples.md +76 -0
- package/skills/kotlin-best-practices/SKILL.md +58 -0
- package/skills/kotlin-best-practices/code-patterns.md +132 -0
- package/skills/market-research/SKILL.md +99 -0
- package/skills/security-checklist/SKILL.md +65 -0
- package/skills/security-checklist/audit-reference.md +95 -0
- package/skills/service-debugging/SKILL.md +116 -0
- package/skills/service-debugging/common-issues.md +65 -0
- package/skills/startup-pipeline/SKILL.md +152 -0
- package/skills/terraform-best-practices/SKILL.md +244 -0
- package/skills/terraform-module-creator/SKILL.md +284 -0
- package/skills/terraform-review/SKILL.md +222 -0
- package/skills/terraform-security-audit/SKILL.md +280 -0
- package/skills/terraform-service-scaffold/SKILL.md +574 -0
- package/skills/testing-strategies/SKILL.md +116 -0
- package/skills/testing-strategies/examples.md +103 -0
- package/skills/testing-strategies/integration-test-setup.md +71 -0
- package/skills/ui-ux-pro-max/SKILL.md +238 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills/ui-ux-pro-max/python-setup.md +146 -0
- package/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/skills/web-to-prd/SKILL.md +478 -0
- package/templates/build-config.yaml +44 -0
- package/templates/commands-config.yaml +55 -0
- package/templates/competitor-analysis.md +60 -0
- package/templates/content/AGENT_TEMPLATE.md +47 -0
- package/templates/content/COMMAND_TEMPLATE.md +27 -0
- package/templates/content/RULE_TEMPLATE.md +40 -0
- package/templates/content/SKILL_TEMPLATE.md +41 -0
- package/templates/design-config.md +105 -0
- package/templates/design-doc.md +207 -0
- package/templates/epic.md +100 -0
- package/templates/feature-spec.md +181 -0
- package/templates/idea-canvas.md +47 -0
- package/templates/implementation-plan.md +159 -0
- package/templates/prd-template.md +86 -0
- package/templates/preamble.md +89 -0
- package/templates/project-readme.md +35 -0
- package/templates/quality-gates.md +230 -0
- package/templates/spartan-config.yaml +164 -0
- package/templates/user-interview.md +69 -0
- package/templates/validation-checklist.md +108 -0
- package/templates/workflow-backend-micronaut.md +409 -0
- package/templates/workflow-frontend-react.md +233 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
# API Endpoint Creator - Code Examples
|
|
2
|
+
|
|
3
|
+
This file has real patterns from the codebase. **Follow these patterns exactly**.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Complete Example: Project Management Endpoint
|
|
8
|
+
|
|
9
|
+
Shows the full flow from Controller → Manager → Repository.
|
|
10
|
+
|
|
11
|
+
### 1. Controller Layer
|
|
12
|
+
|
|
13
|
+
**File**: `app/api-application/src/main/kotlin/com/yourcompany/controller/admin/ProjectController.kt`
|
|
14
|
+
|
|
15
|
+
```kotlin
|
|
16
|
+
package com.yourcompany.controller.admin
|
|
17
|
+
|
|
18
|
+
import com.yourcompany.auth.contract.model.UserAuthentication
|
|
19
|
+
import com.yourcompany.client.request.{domain}.CreateProjectRequest
|
|
20
|
+
import com.yourcompany.client.request.{domain}.UpdateProjectRequest
|
|
21
|
+
import com.yourcompany.client.response.{domain}.ProjectResponse
|
|
22
|
+
import com.yourcompany.client.response.{domain}.ProjectListResponse
|
|
23
|
+
import com.yourcompany.exception.throwOrValue
|
|
24
|
+
import com.yourcompany.{domain}.contract.ProjectManager
|
|
25
|
+
import io.micronaut.http.annotation.*
|
|
26
|
+
import io.micronaut.scheduling.TaskExecutors
|
|
27
|
+
import io.micronaut.scheduling.annotation.ExecuteOn
|
|
28
|
+
import io.micronaut.security.annotation.Secured
|
|
29
|
+
import io.micronaut.validation.Validated
|
|
30
|
+
import io.swagger.v3.oas.annotations.tags.Tag
|
|
31
|
+
import jakarta.validation.Valid
|
|
32
|
+
import java.util.UUID
|
|
33
|
+
|
|
34
|
+
@ExecuteOn(TaskExecutors.IO)
|
|
35
|
+
@Validated
|
|
36
|
+
@Controller("/api/v1/admin")
|
|
37
|
+
@Tag(name = "Projects", description = "Project management API")
|
|
38
|
+
@Secured(OAuthSecurityRule.ADMIN)
|
|
39
|
+
class ProjectController(
|
|
40
|
+
private val projectManager: ProjectManager
|
|
41
|
+
) {
|
|
42
|
+
|
|
43
|
+
@Get("/projects")
|
|
44
|
+
suspend fun listProjects(
|
|
45
|
+
@QueryValue page: Int?,
|
|
46
|
+
@QueryValue limit: Int?,
|
|
47
|
+
@QueryValue status: String?
|
|
48
|
+
): ProjectListResponse {
|
|
49
|
+
return projectManager.list(
|
|
50
|
+
page = page ?: 1,
|
|
51
|
+
limit = limit ?: 20,
|
|
52
|
+
status = status
|
|
53
|
+
).throwOrValue()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Get("/project")
|
|
57
|
+
suspend fun getProject(
|
|
58
|
+
@QueryValue id: UUID
|
|
59
|
+
): ProjectResponse {
|
|
60
|
+
return projectManager.byId(id).throwOrValue()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Post("/project")
|
|
64
|
+
suspend fun createProject(
|
|
65
|
+
@Valid @Body request: CreateProjectRequest
|
|
66
|
+
): ProjectResponse {
|
|
67
|
+
return projectManager.create(request).throwOrValue()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Post("/project/update")
|
|
71
|
+
suspend fun updateProject(
|
|
72
|
+
@QueryValue id: UUID,
|
|
73
|
+
@Valid @Body request: UpdateProjectRequest
|
|
74
|
+
): ProjectResponse {
|
|
75
|
+
return projectManager.update(id, request).throwOrValue()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@Post("/project/delete")
|
|
79
|
+
suspend fun deleteProject(
|
|
80
|
+
@QueryValue id: UUID
|
|
81
|
+
): Boolean {
|
|
82
|
+
return projectManager.deleteById(id).throwOrValue()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Controller Pattern Key Points**:
|
|
88
|
+
- `@ExecuteOn(TaskExecutors.IO)` for suspend function support
|
|
89
|
+
- `@Controller` defines base path
|
|
90
|
+
- `@Secured(OAuthSecurityRule.ADMIN)` for admin-only access
|
|
91
|
+
- All IDs as `@QueryValue` (never path params)
|
|
92
|
+
- Methods are `suspend` for coroutines
|
|
93
|
+
- **Thin controllers**: Only delegate to manager
|
|
94
|
+
- Use `.throwOrValue()` to unwrap `Either`
|
|
95
|
+
- NO inline data classes
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
### 2. Manager Interface
|
|
100
|
+
|
|
101
|
+
**File**: `app/module-{domain}/module-api/src/main/kotlin/com/yourcompany/insight/contract/ProjectManager.kt`
|
|
102
|
+
|
|
103
|
+
```kotlin
|
|
104
|
+
package com.yourcompany.{domain}.contract
|
|
105
|
+
|
|
106
|
+
import arrow.core.Either
|
|
107
|
+
import com.yourcompany.client.request.{domain}.CreateProjectRequest
|
|
108
|
+
import com.yourcompany.client.request.{domain}.UpdateProjectRequest
|
|
109
|
+
import com.yourcompany.client.response.{domain}.ProjectResponse
|
|
110
|
+
import com.yourcompany.client.response.{domain}.ProjectListResponse
|
|
111
|
+
import com.yourcompany.exception.ClientException
|
|
112
|
+
import java.util.UUID
|
|
113
|
+
|
|
114
|
+
interface ProjectManager {
|
|
115
|
+
suspend fun list(
|
|
116
|
+
page: Int,
|
|
117
|
+
limit: Int,
|
|
118
|
+
status: String?
|
|
119
|
+
): Either<ClientException, ProjectListResponse>
|
|
120
|
+
|
|
121
|
+
suspend fun byId(id: UUID): Either<ClientException, ProjectResponse>
|
|
122
|
+
|
|
123
|
+
suspend fun create(
|
|
124
|
+
request: CreateProjectRequest
|
|
125
|
+
): Either<ClientException, ProjectResponse>
|
|
126
|
+
|
|
127
|
+
suspend fun update(
|
|
128
|
+
id: UUID,
|
|
129
|
+
request: UpdateProjectRequest
|
|
130
|
+
): Either<ClientException, ProjectResponse>
|
|
131
|
+
|
|
132
|
+
suspend fun deleteById(id: UUID): Either<ClientException, Boolean>
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Manager Interface Key Points**:
|
|
137
|
+
- All methods return `Either<ClientException, T>`
|
|
138
|
+
- Use `suspend` for async operations
|
|
139
|
+
- No implementation details
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### 3. Manager Implementation
|
|
144
|
+
|
|
145
|
+
**File**: `app/module-{domain}/module-impl/src/main/kotlin/com/yourcompany/insight/impl/DefaultProjectManager.kt`
|
|
146
|
+
|
|
147
|
+
```kotlin
|
|
148
|
+
package com.yourcompany.{domain}.impl
|
|
149
|
+
|
|
150
|
+
import arrow.core.Either
|
|
151
|
+
import arrow.core.left
|
|
152
|
+
import arrow.core.right
|
|
153
|
+
import com.yourcompany.database.DatabaseContext
|
|
154
|
+
import com.yourcompany.client.request.{domain}.CreateProjectRequest
|
|
155
|
+
import com.yourcompany.client.request.{domain}.UpdateProjectRequest
|
|
156
|
+
import com.yourcompany.client.response.{domain}.ProjectResponse
|
|
157
|
+
import com.yourcompany.client.response.{domain}.ProjectListResponse
|
|
158
|
+
import com.yourcompany.exception.ClientError
|
|
159
|
+
import com.yourcompany.exception.ClientException
|
|
160
|
+
import com.yourcompany.{domain}.contract.ProjectManager
|
|
161
|
+
import com.yourcompany.postgresql.entity.ProjectEntity
|
|
162
|
+
import com.yourcompany.postgresql.repository.ProjectRepository
|
|
163
|
+
import com.yourcompany.postgresql.repository.EmployeeRepository
|
|
164
|
+
import org.jetbrains.exposed.sql.transactions.transaction
|
|
165
|
+
import java.util.UUID
|
|
166
|
+
|
|
167
|
+
class DefaultProjectManager(
|
|
168
|
+
private val db: DatabaseContext,
|
|
169
|
+
private val projectRepository: ProjectRepository,
|
|
170
|
+
private val employeeRepository: EmployeeRepository
|
|
171
|
+
) : ProjectManager {
|
|
172
|
+
|
|
173
|
+
override suspend fun byId(id: UUID): Either<ClientException, ProjectResponse> {
|
|
174
|
+
val entity = projectRepository.byId(id)
|
|
175
|
+
?: return ClientError.PROJECT_NOT_FOUND.asException().left()
|
|
176
|
+
|
|
177
|
+
val ownerName = entity.ownerEmployeeId?.let {
|
|
178
|
+
employeeRepository.byId(it)?.name
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return ProjectResponse.from(entity, ownerName).right()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
override suspend fun create(
|
|
185
|
+
request: CreateProjectRequest
|
|
186
|
+
): Either<ClientException, ProjectResponse> {
|
|
187
|
+
val entity = ProjectEntity(
|
|
188
|
+
name = request.name,
|
|
189
|
+
description = request.description,
|
|
190
|
+
ownerEmployeeId = request.ownerEmployeeId,
|
|
191
|
+
githubOrg = request.githubOrg
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
val inserted = transaction(db.primary) {
|
|
195
|
+
projectRepository.insert(entity)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return ProjectResponse.from(inserted, ownerName = null).right()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
override suspend fun update(
|
|
202
|
+
id: UUID,
|
|
203
|
+
request: UpdateProjectRequest
|
|
204
|
+
): Either<ClientException, ProjectResponse> {
|
|
205
|
+
projectRepository.byId(id)
|
|
206
|
+
?: return ClientError.PROJECT_NOT_FOUND.asException().left()
|
|
207
|
+
|
|
208
|
+
val updated = transaction(db.primary) {
|
|
209
|
+
projectRepository.update(
|
|
210
|
+
id = id,
|
|
211
|
+
name = request.name,
|
|
212
|
+
description = request.description,
|
|
213
|
+
ownerEmployeeId = request.ownerEmployeeId,
|
|
214
|
+
status = request.status,
|
|
215
|
+
githubOrg = request.githubOrg
|
|
216
|
+
)
|
|
217
|
+
} ?: return ClientError.PROJECT_NOT_FOUND.asException().left()
|
|
218
|
+
|
|
219
|
+
val ownerName = updated.ownerEmployeeId?.let {
|
|
220
|
+
employeeRepository.byId(it)?.name
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return ProjectResponse.from(updated, ownerName).right()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
override suspend fun deleteById(id: UUID): Either<ClientException, Boolean> {
|
|
227
|
+
val deleted = transaction(db.primary) {
|
|
228
|
+
projectRepository.deleteById(id)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return if (deleted != null) {
|
|
232
|
+
true.right()
|
|
233
|
+
} else {
|
|
234
|
+
ClientError.PROJECT_NOT_FOUND.asException().left()
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
### 4. Response Model with Companion Object
|
|
243
|
+
|
|
244
|
+
**File**: `app/module-client/src/main/kotlin/com/yourcompany/client/response/insight/ProjectResponse.kt`
|
|
245
|
+
|
|
246
|
+
```kotlin
|
|
247
|
+
package com.yourcompany.client.response.{domain}
|
|
248
|
+
|
|
249
|
+
import com.yourcompany.postgresql.entity.ProjectEntity
|
|
250
|
+
import java.time.Instant
|
|
251
|
+
import java.util.UUID
|
|
252
|
+
|
|
253
|
+
data class ProjectResponse(
|
|
254
|
+
val id: UUID,
|
|
255
|
+
val name: String,
|
|
256
|
+
val description: String?,
|
|
257
|
+
val ownerEmployeeId: UUID?,
|
|
258
|
+
val ownerName: String?,
|
|
259
|
+
val status: String,
|
|
260
|
+
val githubOrg: String?,
|
|
261
|
+
val createdAt: Instant,
|
|
262
|
+
val updatedAt: Instant?
|
|
263
|
+
) {
|
|
264
|
+
companion object {
|
|
265
|
+
fun from(
|
|
266
|
+
entity: ProjectEntity,
|
|
267
|
+
ownerName: String?,
|
|
268
|
+
contributorCount: Int = 0
|
|
269
|
+
): ProjectResponse = ProjectResponse(
|
|
270
|
+
id = entity.id,
|
|
271
|
+
name = entity.name,
|
|
272
|
+
description = entity.description,
|
|
273
|
+
ownerEmployeeId = entity.ownerEmployeeId,
|
|
274
|
+
ownerName = ownerName,
|
|
275
|
+
status = entity.status,
|
|
276
|
+
githubOrg = entity.githubOrg,
|
|
277
|
+
createdAt = entity.createdAt,
|
|
278
|
+
updatedAt = entity.updatedAt
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
data class ProjectListResponse(
|
|
284
|
+
val items: List<ProjectResponse>,
|
|
285
|
+
val total: Int,
|
|
286
|
+
val page: Int,
|
|
287
|
+
val limit: Int,
|
|
288
|
+
val hasMore: Boolean
|
|
289
|
+
)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Response Model Key Points**:
|
|
293
|
+
- `companion object { fun from() }` for entity-to-response conversion
|
|
294
|
+
- Extra params for data not in the entity (ownerName, counts)
|
|
295
|
+
- Lives in `module-client/response/` directory
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
### 5. Request Model
|
|
300
|
+
|
|
301
|
+
**File**: `app/module-client/src/main/kotlin/com/yourcompany/client/request/insight/ProjectRequests.kt`
|
|
302
|
+
|
|
303
|
+
```kotlin
|
|
304
|
+
package com.yourcompany.client.request.{domain}
|
|
305
|
+
|
|
306
|
+
import java.util.UUID
|
|
307
|
+
|
|
308
|
+
data class CreateProjectRequest(
|
|
309
|
+
val name: String,
|
|
310
|
+
val description: String? = null,
|
|
311
|
+
val ownerEmployeeId: UUID? = null,
|
|
312
|
+
val githubOrg: String? = null
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
data class UpdateProjectRequest(
|
|
316
|
+
val name: String? = null,
|
|
317
|
+
val description: String? = null,
|
|
318
|
+
val ownerEmployeeId: UUID? = null,
|
|
319
|
+
val status: String? = null,
|
|
320
|
+
val githubOrg: String? = null
|
|
321
|
+
)
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
### 6. Factory Bean
|
|
327
|
+
|
|
328
|
+
**File**: `app/module-{domain}/module-impl/src/main/kotlin/com/yourcompany/runtime/factory/{Domain}ManagerFactory.kt`
|
|
329
|
+
|
|
330
|
+
```kotlin
|
|
331
|
+
package com.yourcompany.runtime.factory
|
|
332
|
+
|
|
333
|
+
import com.yourcompany.database.DatabaseContext
|
|
334
|
+
import com.yourcompany.{domain}.contract.ProjectManager
|
|
335
|
+
import com.yourcompany.{domain}.impl.DefaultProjectManager
|
|
336
|
+
import com.yourcompany.postgresql.repository.ProjectRepository
|
|
337
|
+
import com.yourcompany.postgresql.repository.EmployeeRepository
|
|
338
|
+
import io.micronaut.context.annotation.Factory
|
|
339
|
+
import jakarta.inject.Singleton
|
|
340
|
+
|
|
341
|
+
@Factory
|
|
342
|
+
class {Domain}ManagerFactory {
|
|
343
|
+
|
|
344
|
+
@Singleton
|
|
345
|
+
fun provideProjectManager(
|
|
346
|
+
db: DatabaseContext,
|
|
347
|
+
projectRepository: ProjectRepository,
|
|
348
|
+
employeeRepository: EmployeeRepository
|
|
349
|
+
): ProjectManager {
|
|
350
|
+
return DefaultProjectManager(
|
|
351
|
+
db = db,
|
|
352
|
+
projectRepository = projectRepository,
|
|
353
|
+
employeeRepository = employeeRepository
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
### 7. Retrofit Client
|
|
362
|
+
|
|
363
|
+
**File**: `app/module-client/src/main/kotlin/com/yourcompany/client/ProjectClient.kt`
|
|
364
|
+
|
|
365
|
+
```kotlin
|
|
366
|
+
package com.yourcompany.client
|
|
367
|
+
|
|
368
|
+
import com.yourcompany.client.request.{domain}.CreateProjectRequest
|
|
369
|
+
import com.yourcompany.client.request.{domain}.UpdateProjectRequest
|
|
370
|
+
import com.yourcompany.client.response.{domain}.ProjectResponse
|
|
371
|
+
import com.yourcompany.client.response.{domain}.ProjectListResponse
|
|
372
|
+
import retrofit2.http.*
|
|
373
|
+
import java.util.UUID
|
|
374
|
+
|
|
375
|
+
interface ProjectClient {
|
|
376
|
+
|
|
377
|
+
@GET("/api/v1/admin/projects")
|
|
378
|
+
suspend fun listProjects(
|
|
379
|
+
@Header("Authorization") authorization: String,
|
|
380
|
+
@Query("page") page: Int? = null,
|
|
381
|
+
@Query("limit") limit: Int? = null,
|
|
382
|
+
@Query("status") status: String? = null
|
|
383
|
+
): ProjectListResponse
|
|
384
|
+
|
|
385
|
+
@GET("/api/v1/admin/project")
|
|
386
|
+
suspend fun getProject(
|
|
387
|
+
@Header("Authorization") authorization: String,
|
|
388
|
+
@Query("id") id: UUID
|
|
389
|
+
): ProjectResponse
|
|
390
|
+
|
|
391
|
+
@POST("/api/v1/admin/project")
|
|
392
|
+
suspend fun createProject(
|
|
393
|
+
@Header("Authorization") authorization: String,
|
|
394
|
+
@Body request: CreateProjectRequest
|
|
395
|
+
): ProjectResponse
|
|
396
|
+
|
|
397
|
+
@POST("/api/v1/admin/project/update")
|
|
398
|
+
suspend fun updateProject(
|
|
399
|
+
@Header("Authorization") authorization: String,
|
|
400
|
+
@Query("id") id: UUID,
|
|
401
|
+
@Body request: UpdateProjectRequest
|
|
402
|
+
): ProjectResponse
|
|
403
|
+
|
|
404
|
+
@POST("/api/v1/admin/project/delete")
|
|
405
|
+
suspend fun deleteProject(
|
|
406
|
+
@Header("Authorization") authorization: String,
|
|
407
|
+
@Query("id") id: UUID
|
|
408
|
+
): Boolean
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Error Handling Patterns
|
|
415
|
+
|
|
416
|
+
### Pattern 1: Not Found
|
|
417
|
+
```kotlin
|
|
418
|
+
val entity = repository.byId(id)
|
|
419
|
+
?: return ClientError.PROJECT_NOT_FOUND.asException().left()
|
|
420
|
+
return ProjectResponse.from(entity, ownerName).right()
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Pattern 2: Already Exists
|
|
424
|
+
```kotlin
|
|
425
|
+
val existing = repository.byEmail(email)
|
|
426
|
+
if (existing != null) {
|
|
427
|
+
return ClientError.EMAIL_ALREADY_IN_USE.asException().left()
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Pattern 3: Multiple Validations
|
|
432
|
+
```kotlin
|
|
433
|
+
val existing = repository.byId(id)
|
|
434
|
+
?: return ClientError.NOT_FOUND.asException().left()
|
|
435
|
+
|
|
436
|
+
if (existing.ownerId != currentUserId) {
|
|
437
|
+
return ClientError.UNAUTHORIZED.asException().left()
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## Anti-Patterns to AVOID
|
|
444
|
+
|
|
445
|
+
**Wrong: Business logic in controller**
|
|
446
|
+
```kotlin
|
|
447
|
+
@Post("/project")
|
|
448
|
+
suspend fun create(@Valid @Body request: CreateProjectRequest): ProjectResponse {
|
|
449
|
+
if (request.name.isBlank()) { throw BadRequestException("Name required") }
|
|
450
|
+
val entity = repository.insert(...)
|
|
451
|
+
return ProjectResponse.from(entity)
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**Correct: Thin controller**
|
|
456
|
+
```kotlin
|
|
457
|
+
@Post("/project")
|
|
458
|
+
suspend fun create(@Valid @Body request: CreateProjectRequest): ProjectResponse {
|
|
459
|
+
return projectManager.create(request).throwOrValue()
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
**Wrong: Path parameters**
|
|
466
|
+
```kotlin
|
|
467
|
+
@Get("/project/{id}")
|
|
468
|
+
suspend fun getProject(@PathVariable id: UUID): ProjectResponse
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Correct: Query parameters**
|
|
472
|
+
```kotlin
|
|
473
|
+
@Get("/project")
|
|
474
|
+
suspend fun getProject(@QueryValue id: UUID): ProjectResponse
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
**Wrong: Manager throws exceptions**
|
|
480
|
+
```kotlin
|
|
481
|
+
override suspend fun byId(id: UUID): ProjectResponse {
|
|
482
|
+
val entity = repository.byId(id)
|
|
483
|
+
?: throw NotFoundException("Project not found")
|
|
484
|
+
return ProjectResponse.from(entity)
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
**Correct: Manager returns Either**
|
|
489
|
+
```kotlin
|
|
490
|
+
override suspend fun byId(id: UUID): Either<ClientException, ProjectResponse> {
|
|
491
|
+
val entity = repository.byId(id)
|
|
492
|
+
?: return ClientError.PROJECT_NOT_FOUND.asException().left()
|
|
493
|
+
return ProjectResponse.from(entity, ownerName).right()
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## Summary Checklist
|
|
500
|
+
|
|
501
|
+
**Controller**:
|
|
502
|
+
- [ ] Has `@ExecuteOn(TaskExecutors.IO)`
|
|
503
|
+
- [ ] Has `@Secured(...)`
|
|
504
|
+
- [ ] Uses `@QueryValue` for IDs (no path params)
|
|
505
|
+
- [ ] Methods are `suspend`
|
|
506
|
+
- [ ] Only delegates to manager
|
|
507
|
+
- [ ] Uses `.throwOrValue()`
|
|
508
|
+
- [ ] NO inline data classes
|
|
509
|
+
|
|
510
|
+
**Manager**:
|
|
511
|
+
- [ ] Returns `Either<ClientException, T>`
|
|
512
|
+
- [ ] Uses `ClientError.{NAME}.asException().left()` for errors
|
|
513
|
+
- [ ] Uses `.right()` for success
|
|
514
|
+
- [ ] NO `!!` operators
|
|
515
|
+
- [ ] Converts entities using `Response.from(entity)`
|
|
516
|
+
|
|
517
|
+
**Models**:
|
|
518
|
+
- [ ] Response models in `module-client/response/`
|
|
519
|
+
- [ ] Request models in `module-client/request/`
|
|
520
|
+
- [ ] Response has `companion object { fun from() }`
|
|
521
|
+
|
|
522
|
+
**Follow these patterns exactly for consistent code.**
|