Goal: Backend Services for Item Image Upload
Implement the Kotlin backend services that support image upload for the Item entity. This covers Phase 2a (common-module library) and Phase 2b (operations endpoints) of the implementation phasing — presigned POST credential generation, entity persistence with URL validation, and upload verification.
The Backend does not handle image file bytes (TD-06). It brokers credentials and metadata only (TD-03).
Context
Section titled “Context”The Item Image Upload project has completed system design. The backend specification defines the services, interfaces, and modules required. The AWS infrastructure (Phase 1) is code-complete: S3 bucket, CloudFront CDN with OAC, IAM presigning role, and signing key group are all provisioned. The cross-stack export names, S3 key format, and CDN domain pattern are defined and available from CDK synth.
The common-module repository already contains CsvS3BucketDirectAccess
(cards.arda.common.lib.infra.storage) — a CSV-specific S3 service that
provides presigned PUT URL generation, HEAD verification, and metadata
validation. This project extracts the common S3 capabilities from that class
into a general-purpose S3AssetService, then reimplements the CSV-specific
functionality in terms of the new shared abstraction. The image upload
presigned POST generation, key construction, CDN URL resolution, and HEAD
verification are built on top of this same shared foundation.
Repositories
Section titled “Repositories”common-module(Arda-cards/common-module) — Phase 2a: shared S3 libraryoperations(Arda-cards/operations) — Phase 2b: REST endpoint and service modifications
In Scope
Section titled “In Scope”Phase 2a — common-module Library:
- General-purpose S3 asset service (
S3AssetServiceincards.arda.common.lib.infra.storage) — presigned POST generation with configurable policy conditions, HEAD verification, and post-upload metadata validation. Extracts and generalizes the presigning, role assumption, and metadata validation capabilities currently embedded inCsvS3BucketDirectAccess. Follows the sameResult-based error handling andAppErrorhierarchy as existing code. - Asset key generator (
AssetKeyGenerator) — constructs tenant-scoped S3 keys in the format<tenantId>/images/<uuid>.<ext>. UUID generated server-side; extension derived from content type. - CDN URL resolver (
CdnUrlResolver) — constructs CDN URLs from object keys (https://<cdn-host>/<key>). Validates that a given URL matches the expected CDN host and key pattern. Rejects cross-tenant URLs. - Refactor
CsvS3BucketDirectAccess— reimplement in terms ofS3AssetServicefor the shared capabilities (presigning, HEAD, metadata validation), retaining the CSV-specific behavior (GET with decompression, row/batch flow parsing). The right level of extraction is a design decision — balance generality and reuse against effort and complexity. - Tests —
S3AssetServiceTest(MockAWS/LocalStack),AssetKeyGeneratorTest(unit),CdnUrlResolverTest(unit). Each class must have passing unit tests before any dependent class is started. ExistingCsvS3DirectAccessTestmust continue to pass after refactoring.
Phase 2b — operations Endpoints:
ImageUploadEndpoint(cards.arda.operations.reference.item.api.rest) — new REST endpoint forPOST /v1/item/<itemEId>/image-upload-url. Delegates toS3AssetServicefor presigned POST generation. ReturnsuploadUrl,formFields,objectKey, andcdnUrl.ItemServicemodification — addimageUrlvalidation step before persist: callCdnUrlResolver.validate()for CDN pattern match and tenant prefix check, thenS3AssetService.headObject()to verify the uploaded object exists and validatex-amz-meta-tenant-idmatches the requesting tenant. Acceptnullto clear the image field. Validation is skipped when theimageUrlvalue is unchanged from the currently persisted value (TD-15) — this grandfathers pre-existing non-CDN URLs.pre-install.cfn.ymlupdate — import image infrastructure exports from the infrastructure stack:ImageAssetBucketArn,ImageAssetBucketName,ImagePresignRoleArn,ImageCdnDomain. Follow the existing import pattern (Fn::ImportValue: ${Infrastructure}-${Purpose}-API-<ExportName>).- Helm configuration — wire new infrastructure values through
configmap.yamlto application config (following the existinguploadBucketArnpattern insystem.reference.item.extras). - Tests —
ImageUploadEndpointTest(integration with Harness + MockAWS). Integration tests for entity update with CDN URL validation (accepted, rejected, null clears, unchanged non-CDN URL grandfathered). Each class must have passing tests before dependent classes are started.
Out of Scope
Section titled “Out of Scope”- BFF routes and cookie signing logic — Phase 3.
- SPA components and upload orchestration — Phase 4.
- API tests in
api-testrepository — Phase 5. - Phase 2c live environment verification — separate deployment activity after Phase 1 infrastructure is deployed and Phase 2b is built.
- Protobuf-related refactoring of CSV upload functionality in
operations— deeper structural change that requires protobuf support incommon-module, deferred to a future project. - S3 object lifecycle/cleanup for orphaned uploads — deferred (NFR-012).
Constraints
Section titled “Constraints”- Baseline verification: Before any code changes, verify that all existing
unit tests pass in both
common-moduleandoperationsto establish a known-good baseline. - Local composite build for development: Use Gradle
includeBuildinoperations/settings.gradle.ktsto reference the localcommon-moduleduring development. Do not commit the modifiedsettings.gradle.kts— CI resolvescommon-modulefrom GitHub Packages. Thecommon-moduleartifact must be published before the operations PR can merge. - Entry criteria from Phase 1: The S3 key format
(
<tenantId>/images/<uuid>.<ext>), CDN domain pattern (<partition>.<infra>.assets.arda.cards), and cross-stack export names are defined by CDK synth. Live infrastructure deployment is not required for development — LocalStack provides S3 emulation for tests. See TD-16 for the consolidated URL format reference. - Pattern alignment: Follow existing
common-moduleconventions —Result<T>return types,AppErrorhierarchy, coroutine suspend functions, Kotest StringSpec for tests, MockAWS for S3 integration tests. - Decisions in parent log: All design decisions (e.g., extraction boundary for CSV refactoring, presigned POST vs PUT trade-offs) must be recorded in the project decision log with sequential TD-numbers continuing from TD-16.
- Existing tests must pass: The refactoring of
CsvS3BucketDirectAccessmust not break existingCsvS3DirectAccessTestorS3BucketAccessTest. - Presigned POST (not PUT): The backend generates presigned POST forms (not presigned PUT URLs) per TD-08. POST allows policy conditions enforcing content type, size limits, and metadata — capabilities not available with presigned PUT.
- Test-first ordering: Each deliverable must have passing tests before work on dependent classes begins. Tests are not a final phase — they are part of each deliverable.
Deliverables
Section titled “Deliverables”Phase 2a — common-module
Section titled “Phase 2a — common-module”| # | Deliverable | Location |
|---|---|---|
| 1 | S3AssetService — general-purpose presigned POST, HEAD, metadata validation | lib/src/main/kotlin/.../infra/storage/ (new) |
| 2 | AssetKeyGenerator — tenant-scoped key construction | lib/src/main/kotlin/.../infra/storage/ (new) |
| 3 | CdnUrlResolver — CDN URL construction and validation | lib/src/main/kotlin/.../infra/storage/ (new) |
| 4 | Refactored CsvS3BucketDirectAccess — uses shared S3AssetService | lib/src/main/kotlin/.../infra/storage/CsvS3ObjectDirectService.kt (modify) |
| 5 | S3AssetServiceTest | lib/src/test/kotlin/.../infra/storage/ (new) |
| 6 | AssetKeyGeneratorTest | lib/src/test/kotlin/.../infra/storage/ (new) |
| 7 | CdnUrlResolverTest | lib/src/test/kotlin/.../infra/storage/ (new) |
Phase 2b — operations
Section titled “Phase 2b — operations”| # | Deliverable | Location |
|---|---|---|
| 8 | ImageUploadEndpoint — POST /v1/item/<itemEId>/image-upload-url | src/main/kotlin/.../item/api/rest/ (new) |
| 9 | ItemService modification — imageUrl validation before persist | src/main/kotlin/.../item/service/ItemService.kt (modify) |
| 10 | pre-install.cfn.yml — import image infrastructure exports | src/main/cloudformation/pre-install.cfn.yml (modify) |
| 11 | Helm configmap — wire image infrastructure values | src/main/helm/templates/configmap.yaml (modify) |
| 12 | Module configuration — load image infrastructure config | src/main/kotlin/.../item/Module.kt (modify) |
| 13 | ImageUploadEndpointTest | src/test/kotlin/.../item/api/rest/ (new) |
| 14 | Integration tests for imageUrl validation on entity update | src/test/kotlin/.../item/service/ (new or extend) |
Success Criteria
Section titled “Success Criteria”Phase 2a
Section titled “Phase 2a”AssetKeyGeneratorTestpasses — key format<tenantId>/images/<uuid>.<ext>, tenant prefix isolation, UUID uniqueness, extension mapping from content type.CdnUrlResolverTestpasses — URL construction from object key, pattern validation (rejects non-CDN URLs), tenant isolation (rejects cross-tenant URLs), null handling.S3AssetServiceTestpasses (MockAWS/LocalStack) — presigned POST form generation with all policy conditions (key, Content-Type, content-length-range, tenant-id, author, arda-key, SSE), HEAD verification, post-upload metadata validation.- Existing
CsvS3DirectAccessTestandS3BucketAccessTestcontinue to pass after refactoring. common-modulebuilds successfully (make clean build).
Phase 2b
Section titled “Phase 2b”ImageUploadEndpointTestpasses (Harness + MockAWS) — authentication, presigned POST generation, response includesuploadUrl,formFields,objectKey,cdnUrl.- Entity update with CDN-pattern
imageUrlsucceeds after HEAD verification. - Entity update with non-CDN
imageUrlrejected (400). - Entity update with
imageUrl: nullclears the image field. - Entity update with unchanged non-CDN
imageUrlsucceeds without validation (TD-15 grandfathering). pre-install.cfn.ymlincludes IAM policies for the image asset bucket and presigning role, following existingUploadBucketimport pattern.operationsbuilds successfully (make clean build).
Cross-Phase
Section titled “Cross-Phase”- No regressions in existing CSV upload functionality.
- Code coverage meets or exceeds targets configured in Gradle build scripts for both repositories.
- CHANGELOGs updated for both
common-moduleandoperations.
Reference Documents
Section titled “Reference Documents”- Backend Specification — primary specification for this project
- System Design — structural overview of all sub-systems
- Phasing — Phase 2a/2b/2c definition, entry and exit criteria
- AWS Specification — S3 key format, CDN domain, cross-stack exports
- AWS Infrastructure Specification — implemented infrastructure constructs and export names
- Requirements — FR and NFR traceability
- Project Decision Log — TD-03 (metadata only), TD-05 (URL validation), TD-06 (no file bytes), TD-08 (presigned POST), TD-13 (entity-scoped paths), TD-14 (direct CdnUrlResolver usage), TD-15 (grandfather existing imageUrl), TD-16 (URL format reference), TD-17 (S3AssetService PUT+POST), TD-18 (CSV delegation), TD-19 (AssetKeyGenerator configurable namespace), TD-20 (ApplicationContext for tenant), TD-21 (BE-FR-003 backward-compatibility note), TD-22 (routes in ItemEndpoint), TD-23 (validation in ItemValidator), TD-24 (S3AssetService injected into CSV), TD-25 (verify ItemValidator DI precedent)
Skills and Guidelines
Section titled “Skills and Guidelines”Coding and Implementation:
- Kotlin Coding Standards — Result handling, AppError hierarchy, Exposed ORM conventions, null semantics, formatting rules
- Data Authority Endpoint Guide —
Kotlin endpoint patterns (relevant for
ImageUploadEndpoint) - Data Authority API Guide — API design conventions
- Implementation Task Workflow — analysis, planning, byproduct generation, session logging phases
- Multi-Repo Development — Gradle composite builds, dependency management across repos
Testing:
- Backend Testing — Kotest, MockK patterns, database and AWS emulation (LocalStack), Harness pattern
- Mocking Patterns — MockK/Kotest best practices
- API Testing with Bruno — Bruno API test framework (for Phase 2c verification)
Architecture and Patterns:
- Implementation Patterns — pattern index
- Exception Handling — Result type handling, AppError hierarchy
- Data Authority Pattern — module pattern for data services
Build, Release, and Deployment:
- Dev Workflows — build, test, and deploy commands for operations and common-module
- Release Lifecycle — CHANGELOG management, PR ordering, version catalog updates for multi-repo projects
- Gradle Project Setup — Gradle configuration conventions
Planning:
- Project Planning — structured plan generation from goal files
- Project Decomposition — splitting large plans into sequenced runs
Agent Personas (for team execution):
back-end-engineer— Kotlin/Ktor feature implementationquality-reviewer— code review for standards and idiomsdevops-engineer— CloudFormation, Helm configurationprincipal-engineer— architecture decisions and design review
Copyright: (c) Arda Systems 2025-2026, All rights reserved
Copyright: © Arda Systems 2025-2026, All rights reserved