Skip to content

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).

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.

  • common-module (Arda-cards/common-module) — Phase 2a: shared S3 library
  • operations (Arda-cards/operations) — Phase 2b: REST endpoint and service modifications

Phase 2a — common-module Library:

  • General-purpose S3 asset service (S3AssetService in cards.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 in CsvS3BucketDirectAccess. Follows the same Result-based error handling and AppError hierarchy 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 of S3AssetService for 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.
  • TestsS3AssetServiceTest (MockAWS/LocalStack), AssetKeyGeneratorTest (unit), CdnUrlResolverTest (unit). Each class must have passing unit tests before any dependent class is started. Existing CsvS3DirectAccessTest must continue to pass after refactoring.

Phase 2b — operations Endpoints:

  • ImageUploadEndpoint (cards.arda.operations.reference.item.api.rest) — new REST endpoint for POST /v1/item/<itemEId>/image-upload-url. Delegates to S3AssetService for presigned POST generation. Returns uploadUrl, formFields, objectKey, and cdnUrl.
  • ItemService modification — add imageUrl validation step before persist: call CdnUrlResolver.validate() for CDN pattern match and tenant prefix check, then S3AssetService.headObject() to verify the uploaded object exists and validate x-amz-meta-tenant-id matches the requesting tenant. Accept null to clear the image field. Validation is skipped when the imageUrl value is unchanged from the currently persisted value (TD-15) — this grandfathers pre-existing non-CDN URLs.
  • pre-install.cfn.yml update — 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.yaml to application config (following the existing uploadBucketArn pattern in system.reference.item.extras).
  • TestsImageUploadEndpointTest (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.
  • BFF routes and cookie signing logic — Phase 3.
  • SPA components and upload orchestration — Phase 4.
  • API tests in api-test repository — 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 in common-module, deferred to a future project.
  • S3 object lifecycle/cleanup for orphaned uploads — deferred (NFR-012).
  1. Baseline verification: Before any code changes, verify that all existing unit tests pass in both common-module and operations to establish a known-good baseline.
  2. Local composite build for development: Use Gradle includeBuild in operations/settings.gradle.kts to reference the local common-module during development. Do not commit the modified settings.gradle.kts — CI resolves common-module from GitHub Packages. The common-module artifact must be published before the operations PR can merge.
  3. 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.
  4. Pattern alignment: Follow existing common-module conventions — Result<T> return types, AppError hierarchy, coroutine suspend functions, Kotest StringSpec for tests, MockAWS for S3 integration tests.
  5. 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.
  6. Existing tests must pass: The refactoring of CsvS3BucketDirectAccess must not break existing CsvS3DirectAccessTest or S3BucketAccessTest.
  7. 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.
  8. 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.
#DeliverableLocation
1S3AssetService — general-purpose presigned POST, HEAD, metadata validationlib/src/main/kotlin/.../infra/storage/ (new)
2AssetKeyGenerator — tenant-scoped key constructionlib/src/main/kotlin/.../infra/storage/ (new)
3CdnUrlResolver — CDN URL construction and validationlib/src/main/kotlin/.../infra/storage/ (new)
4Refactored CsvS3BucketDirectAccess — uses shared S3AssetServicelib/src/main/kotlin/.../infra/storage/CsvS3ObjectDirectService.kt (modify)
5S3AssetServiceTestlib/src/test/kotlin/.../infra/storage/ (new)
6AssetKeyGeneratorTestlib/src/test/kotlin/.../infra/storage/ (new)
7CdnUrlResolverTestlib/src/test/kotlin/.../infra/storage/ (new)
#DeliverableLocation
8ImageUploadEndpointPOST /v1/item/<itemEId>/image-upload-urlsrc/main/kotlin/.../item/api/rest/ (new)
9ItemService modification — imageUrl validation before persistsrc/main/kotlin/.../item/service/ItemService.kt (modify)
10pre-install.cfn.yml — import image infrastructure exportssrc/main/cloudformation/pre-install.cfn.yml (modify)
11Helm configmap — wire image infrastructure valuessrc/main/helm/templates/configmap.yaml (modify)
12Module configuration — load image infrastructure configsrc/main/kotlin/.../item/Module.kt (modify)
13ImageUploadEndpointTestsrc/test/kotlin/.../item/api/rest/ (new)
14Integration tests for imageUrl validation on entity updatesrc/test/kotlin/.../item/service/ (new or extend)
  1. AssetKeyGeneratorTest passes — key format <tenantId>/images/<uuid>.<ext>, tenant prefix isolation, UUID uniqueness, extension mapping from content type.
  2. CdnUrlResolverTest passes — URL construction from object key, pattern validation (rejects non-CDN URLs), tenant isolation (rejects cross-tenant URLs), null handling.
  3. S3AssetServiceTest passes (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.
  4. Existing CsvS3DirectAccessTest and S3BucketAccessTest continue to pass after refactoring.
  5. common-module builds successfully (make clean build).
  1. ImageUploadEndpointTest passes (Harness + MockAWS) — authentication, presigned POST generation, response includes uploadUrl, formFields, objectKey, cdnUrl.
  2. Entity update with CDN-pattern imageUrl succeeds after HEAD verification.
  3. Entity update with non-CDN imageUrl rejected (400).
  4. Entity update with imageUrl: null clears the image field.
  5. Entity update with unchanged non-CDN imageUrl succeeds without validation (TD-15 grandfathering).
  6. pre-install.cfn.yml includes IAM policies for the image asset bucket and presigning role, following existing UploadBucket import pattern.
  7. operations builds successfully (make clean build).
  1. No regressions in existing CSV upload functionality.
  2. Code coverage meets or exceeds targets configured in Gradle build scripts for both repositories.
  3. CHANGELOGs updated for both common-module and operations.
  • 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)

Coding and Implementation:

Testing:

Architecture and Patterns:

Build, Release, and Deployment:

Planning:

Agent Personas (for team execution):

  • back-end-engineer — Kotlin/Ktor feature implementation
  • quality-reviewer — code review for standards and idioms
  • devops-engineer — CloudFormation, Helm configuration
  • principal-engineer — architecture decisions and design review

Copyright: (c) Arda Systems 2025-2026, All rights reserved