---
phase: 01-foundation
plan: 03
type: execute
wave: 3
depends_on: ["01-02"]
files_modified:
  - src/repository/RepositoryError.scala
  - src/repository/UserRepository.scala
  - src/model/User.scala
  - infra/project.scala
  - infra/Main.scala
  - Dockerfile
autonomous: true

must_haves:
  truths:
    - "Repository trait defines CRUD operations with typed ZIO errors"
    - "UserRepositoryLive implements queries using Quill context"
    - "Besom/Pulumi infrastructure code compiles and can run pulumi preview"
    - "Dockerfile builds fat JAR into runnable container"
  artifacts:
    - path: "src/repository/RepositoryError.scala"
      provides: "Typed error ADT for repository operations"
      exports: ["RepositoryError", "NotFound", "Conflict", "DatabaseError"]
    - path: "src/repository/UserRepository.scala"
      provides: "User repository trait and implementation"
      exports: ["UserRepository", "UserRepositoryLive"]
    - path: "src/model/User.scala"
      provides: "User domain model"
      exports: ["User"]
    - path: "infra/Main.scala"
      provides: "Pulumi infrastructure definition"
      contains: "Pulumi.run"
    - path: "Dockerfile"
      provides: "Container build configuration"
      contains: "FROM"
  key_links:
    - from: "src/repository/UserRepository.scala"
      to: "src/db/QuillContext.scala"
      via: "Quill.Postgres dependency"
      pattern: "Quill\\.Postgres"
    - from: "src/repository/UserRepository.scala"
      to: "src/model/User.scala"
      via: "CRUD operations on User"
      pattern: "User"
    - from: "src/repository/UserRepository.scala"
      to: "src/repository/RepositoryError.scala"
      via: "IO[RepositoryError, _] return type"
      pattern: "RepositoryError"
---

<objective>
Implement the Repository pattern with typed errors and create Pulumi infrastructure configuration using Besom.

Purpose: Establish the data access pattern that all future models will follow, and prepare deployment infrastructure.
Output: Working UserRepository with compile-time validated queries, Pulumi config for cloud deployment, Dockerfile for containerization.
</objective>

<execution_context>
@/home/jin/.claude/get-shit-done/workflows/execute-plan.md
@/home/jin/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/01-foundation/01-CONTEXT.md
@.planning/phases/01-foundation/01-RESEARCH.md
@.planning/phases/01-foundation/01-01-SUMMARY.md
@.planning/phases/01-foundation/01-02-SUMMARY.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create Repository pattern with typed errors</name>
  <files>
    src/model/User.scala
    src/repository/RepositoryError.scala
    src/repository/UserRepository.scala
  </files>
  <action>
Implement the Repository pattern following ZIO best practices.

1. src/model/User.scala:
```scala
package summercms.model

import java.time.Instant

case class User(
  id: Long,
  email: String,
  passwordHash: String,
  createdAt: Instant,
  updatedAt: Instant
)
```

2. src/repository/RepositoryError.scala:
```scala
package summercms.repository

sealed trait RepositoryError

object RepositoryError:
  case class NotFound(entity: String, id: Long) extends RepositoryError
  case class NotFoundByField(entity: String, field: String, value: String) extends RepositoryError
  case class Conflict(entity: String, message: String) extends RepositoryError
  case class ValidationError(errors: List[String]) extends RepositoryError
  case class DatabaseError(message: String, cause: Option[Throwable] = None) extends RepositoryError
```

3. src/repository/UserRepository.scala:
- Trait UserRepository with methods:
  - def findById(id: Long): IO[RepositoryError, Option[User]]
  - def findByEmail(email: String): IO[RepositoryError, Option[User]]
  - def create(email: String, passwordHash: String): IO[RepositoryError, User]
  - def update(user: User): IO[RepositoryError, User]
  - def delete(id: Long): IO[RepositoryError, Unit]

- Object UserRepository with:
  - val live: ZLayer[Quill.Postgres[SnakeCase], Nothing, UserRepository]

- Class UserRepositoryLive(quill: Quill.Postgres[SnakeCase]) extends UserRepository:
  - import quill.*
  - inline def users = quote(querySchema[User]("summer_users"))
  - Implement all methods using Quill queries
  - Use refineOrDie to convert SQLException to RepositoryError.DatabaseError
  - Handle unique constraint violations as Conflict errors

Pattern for error handling:
```scala
def create(email: String, passwordHash: String): IO[RepositoryError, User] =
  val now = Instant.now()
  val user = User(0, email, passwordHash, now, now)
  run(users.insertValue(lift(user)).returningGenerated(_.id))
    .map(id => user.copy(id = id))
    .refineOrDie {
      case e: java.sql.SQLException if e.getSQLState == "23505" =>
        RepositoryError.Conflict("User", s"Email $email already exists")
      case e: java.sql.SQLException =>
        RepositoryError.DatabaseError(e.getMessage, Some(e))
    }
```

IMPORTANT: Do not use "io" as a variable name (Quill package conflict).
  </action>
  <verify>`mill compile` succeeds, repository pattern established</verify>
  <done>User model, RepositoryError ADT, and UserRepository trait+implementation exist and compile</done>
</task>

<task type="auto">
  <name>Task 2: Create Pulumi infrastructure with Besom</name>
  <files>
    infra/project.scala
    infra/Main.scala
  </files>
  <action>
Create Pulumi infrastructure using Besom (Scala Pulumi SDK).

IMPORTANT: Besom does NOT support Mill. Use Scala CLI for the infra/ directory.

1. infra/project.scala (Scala CLI project config):
```scala
//> using scala 3.3.4
//> using dep org.virtuslab::besom-core:0.4.0
//> using dep org.virtuslab::besom-aws:6.56.0
```

Note: Use Besom 0.4.0 (latest stable on Maven Central, not 0.5.0 from research).

2. infra/Main.scala:
```scala
import besom.*
import besom.api.aws

@main def main = Pulumi.run {
  // Configuration
  val config = besom.Config()
  val environment = config.get("environment").getOrElse("dev")

  // S3 bucket for assets (optional CDN origin)
  val assetsBucket = aws.s3.Bucket(
    s"summercms-assets-$environment",
    aws.s3.BucketArgs()
  )

  // RDS PostgreSQL instance
  val db = aws.rds.Instance(
    s"summercms-db-$environment",
    aws.rds.InstanceArgs(
      engine = "postgres",
      engineVersion = "16.4",
      instanceClass = "db.t3.micro",
      allocatedStorage = 20,
      dbName = "summercms",
      username = "summercms",
      password = config.requireSecret("dbPassword"),
      skipFinalSnapshot = environment == "dev",
      publiclyAccessible = environment == "dev"
    )
  )

  // ECS for container deployment (placeholder for now)
  // Full ECS setup deferred to when we need actual deployment

  Stack.exports(
    assetsBucketName = assetsBucket.bucket,
    dbEndpoint = db.endpoint,
    dbPort = db.port
  )
}
```

This provides:
- Multi-environment support via Pulumi config
- S3 bucket for future asset storage
- RDS PostgreSQL for database
- Secrets management for DB password
- Outputs for connecting the application

Note: Full ECS/container deployment is deferred. This establishes the pattern.
  </action>
  <verify>
Run from infra/ directory:
- `scala-cli compile .` compiles
- `pulumi preview` shows resources (requires Pulumi CLI and AWS credentials)
  </verify>
  <done>Besom infrastructure code compiles, defines RDS and S3 resources</done>
</task>

<task type="auto">
  <name>Task 3: Create Dockerfile for containerization</name>
  <files>Dockerfile</files>
  <action>
Create multi-stage Dockerfile for building and running SummerCMS.

Dockerfile:
```dockerfile
# Stage 1: Build
FROM eclipse-temurin:21-jdk AS builder

# Install Mill
RUN curl -L https://github.com/com-lihaoyi/mill/releases/download/0.12.3/0.12.3 > /usr/local/bin/mill && \
    chmod +x /usr/local/bin/mill

WORKDIR /app
COPY . .

# Build fat JAR
RUN mill assembly

# Stage 2: Runtime
FROM eclipse-temurin:21-jre

WORKDIR /app

# Copy the fat JAR from builder
COPY --from=builder /app/out/assembly.dest/out.jar /app/summercms.jar

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# Install curl for health check
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

EXPOSE 8080

# Run the application
ENTRYPOINT ["java", "-jar", "/app/summercms.jar"]
```

Features:
- Multi-stage build (builder has JDK+Mill, runtime has only JRE)
- Uses Eclipse Temurin JDK 21 (LTS)
- Health check using /health endpoint
- Minimal runtime image size
- Environment variables configure the app (via application.conf ${?VAR} substitution)

Usage:
```bash
docker build -t summercms .
docker run -p 8080:8080 \
  -e DB_HOST=host.docker.internal \
  -e DB_PASSWORD=secret \
  summercms
```
  </action>
  <verify>
`docker build -t summercms .` succeeds
`docker run -p 8080:8080 summercms` starts (will fail at DB connect without DB, that's expected)
  </verify>
  <done>Dockerfile exists with multi-stage build, health check, and env var configuration</done>
</task>

</tasks>

<verification>
1. `mill compile` succeeds (repository code compiles)
2. Repository pattern test (manual in mill console or separate test):
   - UserRepository.findById returns IO[RepositoryError, Option[User]]
   - Type errors when using wrong types
3. Infrastructure:
   - `cd infra && scala-cli compile .` succeeds
   - `cd infra && pulumi preview` shows planned resources (requires setup)
4. Docker:
   - `docker build -t summercms .` builds successfully
   - Image contains /app/summercms.jar
</verification>

<success_criteria>
- User model and RepositoryError ADT defined
- UserRepository trait with CRUD methods, typed errors
- UserRepositoryLive implements queries with Quill
- Besom infrastructure defines RDS and S3 for AWS
- Dockerfile builds fat JAR in multi-stage build
- All code compiles without errors
</success_criteria>

<output>
After completion, create `.planning/phases/01-foundation/01-03-SUMMARY.md`
</output>
