@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,400 @@
|
|
|
1
|
+
# Kotlin Implementation Templates
|
|
2
|
+
|
|
3
|
+
Code templates for Steps 3-10 of the database table creation workflow.
|
|
4
|
+
Referenced from SKILL.md — use these templates when generating Kotlin code.
|
|
5
|
+
|
|
6
|
+
## Step 3: Create Kotlin Table Object
|
|
7
|
+
|
|
8
|
+
Location: `app/module-repository/src/main/kotlin/com/yourcompany/postgresql/table/{TableName}Table.kt`
|
|
9
|
+
|
|
10
|
+
```kotlin
|
|
11
|
+
package com.yourcompany.postgresql.table
|
|
12
|
+
|
|
13
|
+
import org.jetbrains.exposed.dao.id.UUIDTable
|
|
14
|
+
import org.jetbrains.exposed.sql.javatime.timestamp
|
|
15
|
+
import java.time.Instant
|
|
16
|
+
|
|
17
|
+
object {TableName}Table : UUIDTable("{table_name}") { // MUST extend UUIDTable
|
|
18
|
+
// Business columns
|
|
19
|
+
val name = text("name") // Use text() not varchar()
|
|
20
|
+
val email = text("email").nullable()
|
|
21
|
+
val status = text("status").default("active")
|
|
22
|
+
val userId = uuid("user_id").nullable()
|
|
23
|
+
val count = integer("count").default(0)
|
|
24
|
+
val isActive = bool("is_active").default(true)
|
|
25
|
+
val metadata = jsonb<Map<String, Any>>("metadata").nullable()
|
|
26
|
+
|
|
27
|
+
// Standard timestamps (REQUIRED)
|
|
28
|
+
val createdAt = timestamp("created_at").clientDefault { Instant.now() }
|
|
29
|
+
val updatedAt = timestamp("updated_at").nullable()
|
|
30
|
+
val deletedAt = timestamp("deleted_at").nullable()
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Key Points**:
|
|
35
|
+
- MUST extend `UUIDTable` (not `Table`)
|
|
36
|
+
- Use `text()` for strings (not `varchar()`)
|
|
37
|
+
- Column names match SQL exactly (snake_case in SQL)
|
|
38
|
+
- Use `.nullable()` for optional fields
|
|
39
|
+
- All three timestamp columns required
|
|
40
|
+
|
|
41
|
+
## Step 4: Create Entity Data Class
|
|
42
|
+
|
|
43
|
+
Location: `app/module-repository/src/main/kotlin/com/yourcompany/postgresql/entity/{TableName}Entity.kt`
|
|
44
|
+
|
|
45
|
+
```kotlin
|
|
46
|
+
package com.yourcompany.postgresql.entity
|
|
47
|
+
|
|
48
|
+
import java.time.Instant
|
|
49
|
+
import java.util.UUID
|
|
50
|
+
|
|
51
|
+
data class {TableName}Entity(
|
|
52
|
+
override val id: UUID = UUID.randomUUID(),
|
|
53
|
+
val name: String,
|
|
54
|
+
val email: String? = null,
|
|
55
|
+
val status: String = "active",
|
|
56
|
+
val userId: UUID? = null,
|
|
57
|
+
val count: Int = 0,
|
|
58
|
+
val isActive: Boolean = true,
|
|
59
|
+
val metadata: Map<String, Any>? = null,
|
|
60
|
+
override val createdAt: Instant = Instant.now(),
|
|
61
|
+
override val updatedAt: Instant? = null,
|
|
62
|
+
override val deletedAt: Instant? = null
|
|
63
|
+
) : Entity<Instant> // MUST implement Entity<Instant>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Key Points**:
|
|
67
|
+
- MUST be `data class`
|
|
68
|
+
- MUST implement `Entity<Instant>`
|
|
69
|
+
- MUST override: id, createdAt, updatedAt, deletedAt
|
|
70
|
+
- Use proper Kotlin types (String for TEXT, UUID for ids, Instant for timestamps)
|
|
71
|
+
- Nullable fields have `? = null` defaults
|
|
72
|
+
|
|
73
|
+
## Step 5: Create Constants/Enums (if needed)
|
|
74
|
+
|
|
75
|
+
Location: `app/module-repository/src/main/kotlin/com/yourcompany/postgresql/constant/`
|
|
76
|
+
|
|
77
|
+
```kotlin
|
|
78
|
+
package com.yourcompany.postgresql.constant
|
|
79
|
+
|
|
80
|
+
enum class {TableName}Status {
|
|
81
|
+
ACTIVE,
|
|
82
|
+
INACTIVE,
|
|
83
|
+
ARCHIVED
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Create enums for status, type, or role columns.
|
|
88
|
+
|
|
89
|
+
## Step 6: Create Repository Interface
|
|
90
|
+
|
|
91
|
+
Location: `app/module-repository/src/main/kotlin/com/yourcompany/postgresql/repository/{TableName}Repository.kt`
|
|
92
|
+
|
|
93
|
+
```kotlin
|
|
94
|
+
package com.yourcompany.postgresql.repository
|
|
95
|
+
|
|
96
|
+
import com.yourcompany.postgresql.entity.{TableName}Entity
|
|
97
|
+
import java.util.UUID
|
|
98
|
+
|
|
99
|
+
interface {TableName}Repository {
|
|
100
|
+
fun insert(entity: {TableName}Entity): {TableName}Entity
|
|
101
|
+
fun update(id: UUID, name: String?, email: String?): {TableName}Entity?
|
|
102
|
+
fun byId(id: UUID): {TableName}Entity?
|
|
103
|
+
fun byEmail(email: String): {TableName}Entity?
|
|
104
|
+
fun deleteById(id: UUID): {TableName}Entity?
|
|
105
|
+
fun restoreById(id: UUID): {TableName}Entity?
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Step 7: Create Repository Implementation
|
|
110
|
+
|
|
111
|
+
Location: `app/module-repository/src/main/kotlin/com/yourcompany/postgresql/repository/Default{TableName}Repository.kt`
|
|
112
|
+
|
|
113
|
+
```kotlin
|
|
114
|
+
package com.yourcompany.postgresql.repository
|
|
115
|
+
|
|
116
|
+
import com.yourcompany.database.DatabaseContext
|
|
117
|
+
import com.yourcompany.postgresql.entity.{TableName}Entity
|
|
118
|
+
import com.yourcompany.postgresql.table.{TableName}Table
|
|
119
|
+
import org.jetbrains.exposed.sql.*
|
|
120
|
+
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
|
121
|
+
import org.jetbrains.exposed.sql.transactions.transaction
|
|
122
|
+
import java.time.Instant
|
|
123
|
+
import java.util.UUID
|
|
124
|
+
|
|
125
|
+
class Default{TableName}Repository(
|
|
126
|
+
private val db: DatabaseContext
|
|
127
|
+
) : {TableName}Repository {
|
|
128
|
+
|
|
129
|
+
override fun insert(entity: {TableName}Entity): {TableName}Entity {
|
|
130
|
+
return transaction(db.primary) { // Use db.primary for writes
|
|
131
|
+
{TableName}Table.insert {
|
|
132
|
+
it[id] = entity.id
|
|
133
|
+
it[name] = entity.name
|
|
134
|
+
it[email] = entity.email
|
|
135
|
+
it[status] = entity.status
|
|
136
|
+
// ... map all fields
|
|
137
|
+
it[createdAt] = entity.createdAt
|
|
138
|
+
it[updatedAt] = entity.updatedAt
|
|
139
|
+
it[deletedAt] = entity.deletedAt
|
|
140
|
+
}
|
|
141
|
+
entity
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
override fun update(
|
|
146
|
+
id: UUID,
|
|
147
|
+
name: String?,
|
|
148
|
+
email: String?
|
|
149
|
+
): {TableName}Entity? {
|
|
150
|
+
return transaction(db.primary) {
|
|
151
|
+
val updated = {TableName}Table.update(
|
|
152
|
+
where = {
|
|
153
|
+
{TableName}Table.id eq id and
|
|
154
|
+
{TableName}Table.deletedAt.isNull() // CRITICAL: Filter soft deletes
|
|
155
|
+
}
|
|
156
|
+
) {
|
|
157
|
+
name?.let { value -> it[{TableName}Table.name] = value }
|
|
158
|
+
email?.let { value -> it[{TableName}Table.email] = value }
|
|
159
|
+
// updatedAt automatically set by trigger
|
|
160
|
+
}
|
|
161
|
+
if (updated > 0) byId(id) else null
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
override fun byId(id: UUID): {TableName}Entity? {
|
|
166
|
+
return transaction(db.replica) { // Use db.replica for reads
|
|
167
|
+
{TableName}Table
|
|
168
|
+
.selectAll()
|
|
169
|
+
.where { {TableName}Table.id eq id }
|
|
170
|
+
.andWhere { {TableName}Table.deletedAt.isNull() } // CRITICAL
|
|
171
|
+
.map { convert(it) }
|
|
172
|
+
.singleOrNull() // Return null if not found
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
override fun byEmail(email: String): {TableName}Entity? {
|
|
177
|
+
return transaction(db.replica) {
|
|
178
|
+
{TableName}Table
|
|
179
|
+
.selectAll()
|
|
180
|
+
.where { {TableName}Table.email eq email }
|
|
181
|
+
.andWhere { {TableName}Table.deletedAt.isNull() } // ALWAYS filter
|
|
182
|
+
.map { convert(it) }
|
|
183
|
+
.singleOrNull()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
override fun deleteById(id: UUID): {TableName}Entity? {
|
|
188
|
+
return transaction(db.primary) {
|
|
189
|
+
val updated = {TableName}Table.update(
|
|
190
|
+
where = {
|
|
191
|
+
{TableName}Table.id eq id and
|
|
192
|
+
{TableName}Table.deletedAt.isNull() // Only delete active records
|
|
193
|
+
}
|
|
194
|
+
) {
|
|
195
|
+
it[deletedAt] = Instant.now() // Soft delete: set timestamp
|
|
196
|
+
}
|
|
197
|
+
if (updated > 0) {
|
|
198
|
+
// Return soft-deleted entity
|
|
199
|
+
{TableName}Table
|
|
200
|
+
.selectAll()
|
|
201
|
+
.where { {TableName}Table.id eq id }
|
|
202
|
+
.map { convert(it) }
|
|
203
|
+
.singleOrNull()
|
|
204
|
+
} else null
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
override fun restoreById(id: UUID): {TableName}Entity? {
|
|
209
|
+
return transaction(db.primary) {
|
|
210
|
+
val updated = {TableName}Table.update(
|
|
211
|
+
where = {
|
|
212
|
+
{TableName}Table.id eq id and
|
|
213
|
+
{TableName}Table.deletedAt.isNotNull() // Only restore deleted
|
|
214
|
+
}
|
|
215
|
+
) {
|
|
216
|
+
it[deletedAt] = null // Restore: clear timestamp
|
|
217
|
+
}
|
|
218
|
+
if (updated > 0) byId(id) else null
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Private helper for mapping
|
|
223
|
+
private fun convert(row: ResultRow): {TableName}Entity {
|
|
224
|
+
return {TableName}Entity(
|
|
225
|
+
id = row[{TableName}Table.id].value, // UUID needs .value
|
|
226
|
+
name = row[{TableName}Table.name],
|
|
227
|
+
email = row[{TableName}Table.email],
|
|
228
|
+
status = row[{TableName}Table.status],
|
|
229
|
+
userId = row[{TableName}Table.userId],
|
|
230
|
+
count = row[{TableName}Table.count],
|
|
231
|
+
isActive = row[{TableName}Table.isActive],
|
|
232
|
+
metadata = row[{TableName}Table.metadata],
|
|
233
|
+
createdAt = row[{TableName}Table.createdAt],
|
|
234
|
+
updatedAt = row[{TableName}Table.updatedAt],
|
|
235
|
+
deletedAt = row[{TableName}Table.deletedAt]
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Critical**:
|
|
242
|
+
- Use `transaction(db.primary)` for writes
|
|
243
|
+
- Use `transaction(db.replica)` for reads
|
|
244
|
+
- ALWAYS filter `deletedAt.isNull()` in queries
|
|
245
|
+
- Soft delete sets `deletedAt = Instant.now()`
|
|
246
|
+
- Return null when not found (don't throw)
|
|
247
|
+
- NO `!!` operators anywhere
|
|
248
|
+
|
|
249
|
+
## Step 8: Register Factory Bean
|
|
250
|
+
|
|
251
|
+
Edit `app/module-repository/src/main/kotlin/com/yourcompany/runtime/factory/RepositoryFactory.kt`:
|
|
252
|
+
|
|
253
|
+
```kotlin
|
|
254
|
+
@Factory
|
|
255
|
+
class RepositoryFactory {
|
|
256
|
+
|
|
257
|
+
@Singleton
|
|
258
|
+
fun provide{TableName}Repository(db: DatabaseContext): {TableName}Repository {
|
|
259
|
+
return Default{TableName}Repository(db)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Step 9: Write Repository Tests
|
|
265
|
+
|
|
266
|
+
Location: `app/module-repository/src/test/kotlin/com/yourcompany/postgresql/repository/Default{TableName}RepositoryTest.kt`
|
|
267
|
+
|
|
268
|
+
```kotlin
|
|
269
|
+
package com.yourcompany.postgresql.repository
|
|
270
|
+
|
|
271
|
+
import assertk.assertThat
|
|
272
|
+
import assertk.assertions.*
|
|
273
|
+
import com.yourcompany.postgresql.entity.{TableName}Entity
|
|
274
|
+
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
|
|
275
|
+
import jakarta.inject.Inject
|
|
276
|
+
import org.junit.jupiter.api.BeforeEach
|
|
277
|
+
import org.junit.jupiter.api.Test
|
|
278
|
+
import java.util.UUID
|
|
279
|
+
|
|
280
|
+
@MicronautTest(environments = ["test"])
|
|
281
|
+
class Default{TableName}RepositoryTest : AbstractRepositoryTest() {
|
|
282
|
+
|
|
283
|
+
@Inject
|
|
284
|
+
lateinit var repository: {TableName}Repository
|
|
285
|
+
|
|
286
|
+
@BeforeEach
|
|
287
|
+
fun setup() {
|
|
288
|
+
database.primary.truncateAllTables()
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@Test
|
|
292
|
+
fun `insert - creates entity successfully`() {
|
|
293
|
+
val entity = dummyEntity()
|
|
294
|
+
|
|
295
|
+
val result = repository.insert(entity)
|
|
296
|
+
|
|
297
|
+
assertThat(result.id).isEqualTo(entity.id)
|
|
298
|
+
assertThat(result.name).isEqualTo(entity.name)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@Test
|
|
302
|
+
fun `update - updates selected fields only`() {
|
|
303
|
+
val entity = repository.insert(dummyEntity())
|
|
304
|
+
val newName = "Updated Name"
|
|
305
|
+
|
|
306
|
+
val updated = repository.update(
|
|
307
|
+
id = entity.id,
|
|
308
|
+
name = newName,
|
|
309
|
+
email = null
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
assertThat(updated).isNotNull()
|
|
313
|
+
assertThat(updated?.name).isEqualTo(newName)
|
|
314
|
+
assertThat(updated?.email).isEqualTo(entity.email) // Unchanged
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
@Test
|
|
318
|
+
fun `byId - returns entity when exists`() {
|
|
319
|
+
val entity = repository.insert(dummyEntity())
|
|
320
|
+
|
|
321
|
+
val result = repository.byId(entity.id)
|
|
322
|
+
|
|
323
|
+
assertThat(result).isNotNull()
|
|
324
|
+
assertThat(result?.id).isEqualTo(entity.id)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
@Test
|
|
328
|
+
fun `byId - returns null when not exists`() {
|
|
329
|
+
val randomId = UUID.randomUUID()
|
|
330
|
+
|
|
331
|
+
val result = repository.byId(randomId)
|
|
332
|
+
|
|
333
|
+
assertThat(result).isNull()
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
@Test
|
|
337
|
+
fun `byId - returns null when soft deleted`() {
|
|
338
|
+
val entity = repository.insert(dummyEntity())
|
|
339
|
+
repository.deleteById(entity.id)
|
|
340
|
+
|
|
341
|
+
val result = repository.byId(entity.id)
|
|
342
|
+
|
|
343
|
+
assertThat(result).isNull() // Critical: soft deleted not returned
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@Test
|
|
347
|
+
fun `deleteById - soft deletes entity`() {
|
|
348
|
+
val entity = repository.insert(dummyEntity())
|
|
349
|
+
|
|
350
|
+
val deleted = repository.deleteById(entity.id)
|
|
351
|
+
|
|
352
|
+
assertThat(deleted).isNotNull()
|
|
353
|
+
assertThat(deleted?.deletedAt).isNotNull() // Has timestamp
|
|
354
|
+
assertThat(repository.byId(entity.id)).isNull() // Not found
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
@Test
|
|
358
|
+
fun `restoreById - restores soft deleted entity`() {
|
|
359
|
+
val entity = repository.insert(dummyEntity())
|
|
360
|
+
repository.deleteById(entity.id)
|
|
361
|
+
|
|
362
|
+
val restored = repository.restoreById(entity.id)
|
|
363
|
+
|
|
364
|
+
assertThat(restored).isNotNull()
|
|
365
|
+
assertThat(restored?.deletedAt).isNull() // Timestamp cleared
|
|
366
|
+
assertThat(repository.byId(entity.id)).isNotNull() // Found again
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private fun dummyEntity(
|
|
370
|
+
id: UUID = UUID.randomUUID(),
|
|
371
|
+
name: String = randomText(),
|
|
372
|
+
email: String = randomEmail()
|
|
373
|
+
) = {TableName}Entity(
|
|
374
|
+
id = id,
|
|
375
|
+
name = name,
|
|
376
|
+
email = email
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Required tests** (minimum 7):
|
|
382
|
+
1. Insert happy path
|
|
383
|
+
2. Update modifies fields
|
|
384
|
+
3. byId returns entity when exists
|
|
385
|
+
4. byId returns null when not exists
|
|
386
|
+
5. byId returns null when soft deleted (critical)
|
|
387
|
+
6. deleteById soft deletes
|
|
388
|
+
7. restoreById restores soft deleted
|
|
389
|
+
|
|
390
|
+
## Step 10: Run Tests
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
# Run repository tests
|
|
394
|
+
./gradlew :app:module-repository:test --tests "Default{TableName}RepositoryTest"
|
|
395
|
+
|
|
396
|
+
# Run all tests
|
|
397
|
+
./gradlew test
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
All tests MUST pass.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- Description: [REPLACE: Describe what this migration does]
|
|
3
|
+
-- Table: [REPLACE: table_name]
|
|
4
|
+
-- Date: [REPLACE: YYYY-MM-DD]
|
|
5
|
+
-- ============================================================================
|
|
6
|
+
|
|
7
|
+
-- Create table
|
|
8
|
+
CREATE TABLE [REPLACE_table_name] (
|
|
9
|
+
-- Primary key (REQUIRED for all tables)
|
|
10
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
11
|
+
|
|
12
|
+
-- [REPLACE: Add your business columns here]
|
|
13
|
+
-- Examples:
|
|
14
|
+
-- name TEXT NOT NULL,
|
|
15
|
+
-- email TEXT,
|
|
16
|
+
-- status TEXT DEFAULT 'active',
|
|
17
|
+
-- user_id UUID,
|
|
18
|
+
-- count INTEGER DEFAULT 0,
|
|
19
|
+
-- is_active BOOLEAN DEFAULT true,
|
|
20
|
+
-- metadata JSONB,
|
|
21
|
+
|
|
22
|
+
-- Standard timestamp columns (REQUIRED for all tables)
|
|
23
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
24
|
+
updated_at TIMESTAMP,
|
|
25
|
+
deleted_at TIMESTAMP
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
-- Indexes
|
|
29
|
+
-- CRITICAL: ALL indexes MUST include "WHERE deleted_at IS NULL"
|
|
30
|
+
|
|
31
|
+
-- Unique index example (prevents duplicate active records, allows duplicate soft-deleted)
|
|
32
|
+
-- CREATE UNIQUE INDEX idx_[REPLACE_table_name]_[REPLACE_column]_unique
|
|
33
|
+
-- ON [REPLACE_table_name]([REPLACE_column]) WHERE deleted_at IS NULL;
|
|
34
|
+
|
|
35
|
+
-- Foreign key index example (no FK constraint, just index for performance)
|
|
36
|
+
-- CREATE INDEX idx_[REPLACE_table_name]_[REPLACE_foreign_key]
|
|
37
|
+
-- ON [REPLACE_table_name]([REPLACE_foreign_key]) WHERE deleted_at IS NULL;
|
|
38
|
+
|
|
39
|
+
-- Soft delete index (REQUIRED for all tables with soft deletes)
|
|
40
|
+
CREATE INDEX idx_[REPLACE_table_name]_deleted_at
|
|
41
|
+
ON [REPLACE_table_name](deleted_at) WHERE deleted_at IS NOT NULL;
|
|
42
|
+
|
|
43
|
+
-- Update trigger (REQUIRED for all tables except immutable audit tables)
|
|
44
|
+
CREATE TRIGGER update_[REPLACE_table_name]_updated_at
|
|
45
|
+
BEFORE UPDATE ON [REPLACE_table_name]
|
|
46
|
+
FOR EACH ROW
|
|
47
|
+
EXECUTE FUNCTION update_updated_at();
|
|
48
|
+
|
|
49
|
+
-- ============================================================================
|
|
50
|
+
-- CRITICAL RULES (from rules/database/SCHEMA.md):
|
|
51
|
+
-- ============================================================================
|
|
52
|
+
-- ✅ Use TEXT for strings (NEVER VARCHAR)
|
|
53
|
+
-- ✅ Use UUID for primary keys and foreign keys
|
|
54
|
+
-- ✅ Use TIMESTAMP for dates/times
|
|
55
|
+
-- ✅ Include: id, created_at, updated_at, deleted_at
|
|
56
|
+
-- ✅ Add WHERE deleted_at IS NULL to ALL indexes
|
|
57
|
+
-- ✅ Add soft delete index on deleted_at
|
|
58
|
+
-- ✅ Add update trigger for updated_at
|
|
59
|
+
--
|
|
60
|
+
-- ❌ NO FOREIGN KEY constraints
|
|
61
|
+
-- ❌ NO REFERENCES clauses
|
|
62
|
+
-- ❌ NO ON DELETE CASCADE
|
|
63
|
+
-- ❌ NO VARCHAR (use TEXT instead)
|
|
64
|
+
-- ============================================================================
|
|
65
|
+
|
|
66
|
+
-- [OPTIONAL: Add comments explaining business logic]
|
|
67
|
+
-- COMMENT ON TABLE [REPLACE_table_name] IS 'Brief description of table purpose';
|
|
68
|
+
-- COMMENT ON COLUMN [REPLACE_table_name].[REPLACE_column] IS 'Brief description of column';
|