Skip to content

Implement TemplateStore abstraction in apps/api #19

Description

@patoperpetua

Parent: #2, #3
Depends on: #17
Blocks: #16 (send endpoint)

Goal

Add a TemplateStore interface to apps/api and an Azure Blob Storage implementation that loads compiled template artifacts (template.html + metadata.json) by tenant, environment, and template key. The API must not depend on EmailBuilder.js source or a database at runtime.

Background

Per the architecture, compiled templates live in Azure Blob Storage under tenants/{tenant}/{environment}/templates/{templateKey}/. The post-kit-publisher (epic #3) puts them there; the API reads them. This issue implements the read side.

Scope

Interface (in apps/api/src/templates/)

import type { CompiledTemplate, TenantContext } from '@singleton-sd/post-kit-types';

interface TemplateStore {
  load(tenant: TenantContext, templateKey: string): Promise<CompiledTemplate>;
}
  • load throws with PostKitErrorCode.TEMPLATE_NOT_FOUND if the blob does not exist.
  • load throws with PostKitErrorCode.INVALID_TEMPLATE if the blob exists but cannot be parsed.

Azure Blob Storage implementation

  • Use @azure/storage-blob (check whether it is already in apps/api/package.json before adding).
  • Storage account name and container name loaded from Azure App Configuration (use existing src/config/app-configuration.ts pattern).
  • Blob path: tenants/{tenantId}/{environment}/templates/{templateKey}/template.html and .../metadata.json.
  • Prefer Managed Identity (DefaultAzureCredential) for auth — no connection strings committed.
  • Validate the resolved blob path — reject keys with path traversal characters (../, absolute paths, etc.).

Configuration

  • Add TEMPLATE_STORAGE_ACCOUNT and TEMPLATE_STORAGE_CONTAINER keys to .env.example (no real values).

Constraints

  • Template keys provided by callers must be validated/sanitised before use in blob paths.
  • No connection strings in code or config files — use Managed Identity or DefaultAzureCredential.
  • The TemplateStore interface must be injected (not imported directly) into the send handler so it can be replaced in tests.
  • Do not make the send endpoint or contact endpoint depend on each other.
  • Follow apps/api/src/config/app-configuration.ts conventions for config loading.

Acceptance criteria

  • TemplateStore interface is defined and exported from apps/api.
  • Azure Blob implementation loads template.html and metadata.json for a given tenant/environment/key.
  • Path traversal in templateKey is rejected before the blob path is constructed.
  • TEMPLATE_NOT_FOUND returned when blob does not exist; INVALID_TEMPLATE on parse failure.
  • .env.example updated with storage config keys.
  • Unit tests cover: successful load, not-found, parse failure, path traversal rejection.
  • Tests do not require a live Azure connection (mock @azure/storage-blob or inject a fake store).
  • pnpm -r --if-present run test passes.

Agent implementation notes

Read apps/api/src/config/app-configuration.ts to understand how Azure App Configuration is accessed. Check apps/api/package.json for existing Azure SDK dependencies before adding new ones. Start from origin/main: branch feat/19-template-store (use actual issue number).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-readyMeets every criterion in docs/github-source-of-truth.md, section 4 — safe for an agent to claimenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions