--- 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" --- 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. @/home/jin/.claude/get-shit-done/workflows/execute-plan.md @/home/jin/.claude/get-shit-done/templates/summary.md @.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 Task 1: Create Repository pattern with typed errors src/model/User.scala src/repository/RepositoryError.scala src/repository/UserRepository.scala 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). `mill compile` succeeds, repository pattern established User model, RepositoryError ADT, and UserRepository trait+implementation exist and compile Task 2: Create Pulumi infrastructure with Besom infra/project.scala infra/Main.scala 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. Run from infra/ directory: - `scala-cli compile .` compiles - `pulumi preview` shows resources (requires Pulumi CLI and AWS credentials) Besom infrastructure code compiles, defines RDS and S3 resources Task 3: Create Dockerfile for containerization Dockerfile 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 ``` `docker build -t summercms .` succeeds `docker run -p 8080:8080 summercms` starts (will fail at DB connect without DB, that's expected) Dockerfile exists with multi-stage build, health check, and env var configuration 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 - 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 After completion, create `.planning/phases/01-foundation/01-03-SUMMARY.md`