Tickets Document
These are implementation tickets structured for autonomous coding agents or human developers. They can be pasted sequentially into agentic IDEs (like Cursor, Codex, or Claude Code) or imported directly into project management tools (like Jira, ClickUp, or Linear) for sprint planning.
Phase 1: Infrastructure Foundation and Shared Layer
Ticket 1: Foundational AWS Resources (S3, CloudFront, API Gateway)
Objective: Define the core non-database AWS resources in the SAM template, including S3 buckets for storage, a CloudFront distribution for the frontend, and the API Gateway endpoint.
Context: This ticket establishes the initial infrastructure skeleton as defined in the Architecture Design Document. Creating the S3 buckets, CloudFront distribution, and API Gateway first provides the basic storage and request-handling layers upon which all other services will depend. This work is part of the template.yaml contract.
Technical Directives:
- All resources must be defined within a single AWS SAM
template.yaml file.
- The CloudFront distribution must be configured with an Origin Access Control (OAC) to privately access the frontend S3 bucket.
- The CloudFront response headers policy must be configured to enforce
Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options, Content-Security-Policy, and Referrer-Policy as specified in the implementation plan.
- The
Content-Security-Policy must restrict script sources to the CDN origin and the Stripe.js domain.
- The API Gateway must be configured to read CORS allowed origins, throttle burst limits, and throttle steady-state rates from SSM Parameter Store.
- The
PhotosBucket S3 resource must include a lifecycle rule to delete objects under the pending/ prefix after 24 hours.
Scope Boundaries:
- AWS SAM template (
template.yaml)
- S3 resources
- CloudFront resources
- API Gateway resources
Acceptance Criteria:
- The SAM template can be successfully deployed to an AWS account.
- Three S3 buckets (
PhotosBucket, ExportsBucket, FrontendBucket) are created.
- The
PhotosBucket has a lifecycle rule configured for the pending/ prefix.
- A CloudFront distribution is created and points to the
FrontendBucket.
- When accessing the CloudFront URL, the browser receives the specified security headers in the response.
- An API Gateway REST API is created with CORS and throttling configurations placeholder-ready for SSM parameters.
Dependencies: None
Ticket 2: Database Infrastructure (RDS, RDS Proxy)
Objective: Add the PostgreSQL RDS instance and RDS Proxy resources to the SAM template.
Context: This ticket provisions the stateful data layer as specified in the Architecture Design Document. The RDS Proxy is a critical component that enables stateless Lambda functions to communicate with the PostgreSQL database without exhausting connections. This completes the core data infrastructure required by all application logic.
Technical Directives:
- The RDS instance must be PostgreSQL version 15.
- The SAM template must conditionally enable Multi-AZ for the RDS instance only when the deployment environment is
production.
- The RDS instance size must be parameterized for different environments (
db.t3.micro for non-production, db.t3.medium for production).
- The RDS Proxy must be configured to use IAM authentication, connecting Lambda functions to the database without stored credentials.
- The RDS Proxy must be connected to the created RDS instance and configured with a maximum of 100 connections.
Scope Boundaries:
- AWS SAM template (
template.yaml)
- RDS resources
- RDS Proxy resources
Acceptance Criteria:
- The SAM template successfully deploys the new RDS and RDS Proxy resources.
- An RDS instance running PostgreSQL 15 is active.
- An RDS Proxy is created and shows as connected to the RDS instance in the AWS console.
- The RDS Proxy is configured for IAM database authentication.
Dependencies:
Ticket 3: Database Schema and Migrations
Objective: Create the complete set of SQL migration files to define the entire application database schema, including all tables, indexes, and constraints.
Context: This ticket implements the full data architecture specified in the ADD. By defining the entire schema upfront, subsequent development phases can build features against a stable and complete data model. The GiST exclusion constraint on the bookings table is the most critical piece, as it mathematically prevents double-bookings.
Technical Directives:
- Create SQL migration files under an
infrastructure/migrations/ directory.
- The schema must exactly match all tables, columns, types, and constraints specified in the
Target Phase documentation.
- The
btree_gist extension must be enabled before creating the GiST exclusion constraint.
- The GiST exclusion constraint on the
bookings table must prevent overlapping stay_range values for the same listing_id for bookings in pending, pending_approval, or confirmed status.
- All indexes specified in the
Indexing Strategy section of the ADD must be created.
- A unique index must be created on the
stripe_event_id column of the stripe_webhook_events table.
Scope Boundaries:
- Database schema (SQL migration files)
Acceptance Criteria:
- The SQL migration scripts execute successfully against the provisioned PostgreSQL database from start to finish.
- All specified tables (
users, bookings, listings, etc.) are created with the correct columns and types.
- The
no_overlapping_bookings exclusion constraint exists on the bookings table.
- All specified indexes are present on their respective tables.
- An attempt to insert two identical, overlapping bookings for the same listing fails with a database constraint violation.
Dependencies:
Ticket 4: Shared Utilities and Error Handling (packages/shared)
Objective: Implement the core, non-I/O modules of the shared Lambda Layer package, including custom errors, structured logger, API response helpers, and validation logic.
Context: This ticket creates the foundational code utilities that all Lambda functions will use for consistency in error handling, logging, and response formatting, as outlined in the ADD. Establishing these patterns in a shared layer prevents code duplication and enforces architectural standards across all microservices.
Technical Directives:
- Create a new TypeScript package at
packages/shared/.
- Implement all custom error classes (
StaybaseError, ValidationError, etc.) in errors.ts exactly as defined in the phase contract.
- Implement the
logger.ts module as a thin wrapper around @aws-lambda-powertools/logger that exports a pre-configured singleton instance for structured JSON logging.
- Implement
response.ts with successResponse and errorResponse functions that build valid API Gateway proxy results. The errorResponse function must not expose internal error details in the response body.
- Implement
validation.ts with a validate helper that wraps Zod parsing and throws the custom ValidationError on failure.
- Export all shared Zod schemas (
uuidSchema, dateSchema, paginationSchema) from validation.ts.
Scope Boundaries:
packages/shared/ TypeScript module
Acceptance Criteria:
- The
packages/shared/ package compiles without TypeScript errors.
- Unit tests confirm that
errorResponse correctly formats StaybaseError instances and generic Error instances into the specified JSON structure.
- Unit tests confirm that the
validate function returns the parsed value on success and throws a ValidationError on failure.
Dependencies: None
Ticket 5: Shared I/O and Configuration Utilities (packages/shared)
Objective: Implement the I/O-dependent modules of the shared Lambda Layer package for configuration, database access, and email.
Context: This ticket builds on the core utilities to provide standardized ways for Lambda functions to interact with external services like SSM, RDS, and SES. Centralizing this logic in the shared layer, as per the ADD, ensures all functions use the same caching, connection, and error handling strategies for these critical dependencies.
Technical Directives:
- Implement the
config.ts module with a getConfig function that fetches parameters from an SSM path and caches the result. It must throw a ConfigurationError if required parameters are missing.
- Implement the
db.ts module with a getDbClient function that returns a configured pg.Pool instance. It must throw a DatabaseConnectionError on connection failure.
- Implement the
email.ts module with a sendEmail function that sends email via the AWS SES API. SES errors must be logged but not re-thrown.
- The
index.ts file must re-export all public functions and types from all modules in the packages/shared/ directory.
Scope Boundaries:
packages/shared/ TypeScript module
Acceptance Criteria:
- The
packages/shared/ package compiles without TypeScript errors.
- In an integration test,
getConfig successfully fetches and parses parameters from SSM.
- In an integration test,
getDbClient successfully connects to the RDS Proxy endpoint and executes a simple query.
- In an integration test,
sendEmail successfully sends a test email using the SES sandbox.
Dependencies:
Ticket 6: Lambda Layer Definition and Build Process
Objective: Define the SharedLambdaLayer resource in the SAM template and configure the build process to package the shared TypeScript code into it.
Context: This ticket connects the shared code from the previous tickets to the AWS infrastructure. The Lambda Layer is a key part of the architecture (ADR-1), allowing for code reuse and smaller Lambda function deployment packages. This step makes the shared utilities available to all future Lambda functions.
Technical Directives:
- Add a
SharedLambdaLayer resource of type AWS::Serverless::LayerVersion to template.yaml.
- The layer resource's content URI must point to a build artifact containing the compiled JavaScript and
node_modules for the packages/shared directory.
- Configure the SAM build process (e.g., via
Metadata in template.yaml or a build script) to automatically transpile the TypeScript from packages/shared/ to JavaScript before deployment.
Scope Boundaries:
- AWS SAM template (
template.yaml)
- Build configuration (e.g.,
package.json scripts, SAM metadata)
Acceptance Criteria:
- Running
sam build successfully transpiles the packages/shared TypeScript code and prepares the layer artifact.
- Running
sam deploy successfully creates or updates the SharedLambdaLayer in AWS Lambda.
- The created Lambda Layer contains the compiled JavaScript files corresponding to the shared package modules.
Dependencies:
Ticket 7: Environment Configuration and Deployment Verification
Objective: Create the samconfig.toml file to manage environment-specific deployment parameters and perform a full deployment of the entire infrastructure stack.
Context: This final ticket ties all previous work together by defining the environment-specific configurations required for deployment. It ensures that the entire foundation is deployable and functional, providing a stable base for the application-level features that will be built in subsequent phases.
Technical Directives:
- Create a
samconfig.toml file at the root of the project.
- Define deployment configurations for
dev, staging, and production environments.
- Specify parameter overrides for all environment-specific values in the SAM template, such as RDS instance size, Multi-AZ setting, and references to SSM parameter paths.
- The configuration must successfully deploy the entire stack defined in the preceding tickets.
Scope Boundaries:
samconfig.toml file
- Deployment process
Acceptance Criteria:
- The command
sam deploy --stack-name staybase-dev --config-env dev completes successfully.
- All AWS resources defined in previous tickets (S3 buckets, CloudFront, API Gateway, RDS, RDS Proxy, Lambda Layer) are present and correctly configured in the
dev environment.
- The RDS instance is accessible via the RDS Proxy.
- The CloudFront distribution serves a placeholder file from its S3 origin.
- The created
SharedLambdaLayer is available to be attached to Lambda functions in the Following Phase.
Dependencies:
- Ticket 2
- Ticket 3
- Ticket 6
Phase 2: Authentication
Ticket 1: Implement JWT Signing and Verification Utility
Objective: This ticket creates a shared utility for generating and validating JSON Web Tokens (JWTs) according to the project's defined payload structure.
Context: This utility is a core security component required by both the Auth Lambda and the Lambda Authorizer. The Auth Lambda will use it to sign tokens upon successful registration or login, and the Lambda Authorizer will use it to verify the signature of incoming tokens. It implements the standard JWT payload shape defined in the Authentication phase contract.
Technical Directives:
- Create a utility function to sign a JWT payload, which must include
sub, email, roles, iat, and exp claims.
- Create a utility function to verify a JWT's signature and expiry.
- The signing and verification process must use the
JWT_SECRET retrieved from the shared configuration module.
- The utility should handle access tokens with a 15-minute expiry and refresh tokens with a 30-day expiry.
Scope Boundaries:
- Shared Lambda Layer (
packages/shared/)
Acceptance Criteria:
- The signing function, when given a user ID, email, and roles, produces a valid JWT string.
- The verification function, when given a valid token, returns the decoded payload.
- The verification function rejects a token with an invalid signature.
- The verification function rejects a token that is expired.
- The verification function rejects a token with a missing required claim.
Dependencies: None
Ticket 2: Implement User Registration Handler
Objective: This ticket creates the POST /auth/register endpoint to allow new users to create an account.
Objective: This ticket creates the POST /auth/register endpoint, enabling new users to create an account by providing an email, password, and display name.
Context: The registration handler is the primary entry point for users into the Staybase platform. As described in the ADD, it creates the user record with default guest permissions and issues the initial set of authentication tokens. This handler is part of the Auth Lambda.
Technical Directives:
- The handler must validate that the input email is a valid format and the password is at least 8 characters long.
- Use the shared
validate helper for input validation and throw a ValidationError for invalid data.
- Before creating a user, query the database to ensure an account with the provided email does not already exist. If it does, throw the
ConflictError as specified in the phase contract.
- Hash the user's password using bcrypt with a work factor of 12 before storing it in the
users table.
- On successful user creation, insert a new row into the
users table with the default role of guest.
- Generate a new access token and a new refresh token using the JWT utility.
- Hash the refresh token and store it in the
refresh_tokens table, associated with the new user ID.
- The response body must conform to the contract:
{ accessToken, refreshToken, user: { id, email, displayName, roles } }.
Scope Boundaries:
- Auth Lambda (
functions/auth/)
Acceptance Criteria:
- A
POST request to /auth/register with a unique email, valid password, and display name results in a new row in the users table.
- The
password_hash column in the new users row contains a valid bcrypt hash.
- A new row is created in the
refresh_tokens table linked to the new user.
- The API returns a valid access token, refresh token, and user object.
- A
POST request with an email that already exists returns a 409 Conflict response with the code EMAIL_IN_USE.
- A
POST request with a password shorter than 8 characters returns a 400 Bad Request response.
Dependencies: Ticket 1
Ticket 3: Implement User Login Handler with Brute-Force Protection
Objective: This ticket creates the POST /auth/login endpoint to authenticate existing users and implements an account lockout mechanism to prevent brute-force attacks.
Context: This handler provides the primary authentication mechanism for the system. Following the security requirements in the ADD, it includes logic to track failed login attempts and temporarily lock an account after repeated failures, mitigating the risk of password guessing attacks.
Technical Directives:
- The handler must validate the
email and password fields in the request body.
- It must look up the user by email. To prevent timing-based user enumeration attacks, the password comparison logic must execute whether the user is found or not.
- Compare the provided password with the stored hash using bcrypt.
- If the password is correct and the account is not locked, reset
login_attempt_count to 0 and clear the login_locked_until timestamp.
- If the password is incorrect, increment
login_attempt_count. If the count reaches 10, set login_locked_until to 15 minutes in the future.
- If a login attempt is made for an account where
login_locked_until is in the future, reject the attempt immediately without checking the password.
- In all failure cases (user not found, password incorrect, account locked), return the generic
AuthenticationError to avoid revealing account status.
- On successful authentication, issue and return a new set of access and refresh tokens, identical to the registration flow.
Scope Boundaries:
- Auth Lambda (
functions/auth/)
Acceptance Criteria:
- A
POST request to /auth/login with correct credentials for an existing user returns a new access token and refresh token.
- The
login_attempt_count is reset to 0 in the database upon successful login.
- A
POST request with an incorrect password returns a 401 Unauthorized response.
- The
login_attempt_count for the user increments after each failed login attempt.
- After the 10th consecutive failed login attempt,
login_locked_until is set to a future timestamp, and subsequent login attempts fail until it expires.
- A
POST request with a non-existent email address returns a 401 Unauthorized response.
Dependencies: Ticket 2
Ticket 4: Implement Token Refresh Handler
Objective: This ticket creates the POST /auth/refresh endpoint to allow clients to exchange a valid refresh token for a new set of access and refresh tokens.
Context: This handler supports the long-lived session management described in the ADD. By allowing clients to use a long-lived refresh token to obtain a new short-lived access token, it improves security by minimizing the exposure of the access token while providing a seamless user experience.
Technical Directives:
- The handler must accept a
refreshToken in the request body.
- Hash the received token to find a matching, non-revoked, and unexpired token in the
refresh_tokens table.
- If no valid token is found, throw the
AuthenticationError as specified in the phase contract.
- Upon finding a valid token, mark it as revoked by setting
revoked = true in the database.
- Issue a new access token (15-minute expiry) and a new refresh token (30-day expiry).
- Store the hash of the new refresh token in the
refresh_tokens table.
- Return the new
accessToken and refreshToken.
Scope Boundaries:
- Auth Lambda (
functions/auth/)
Acceptance Criteria:
- A
POST request to /auth/refresh with a valid, unrevoked refresh token returns a new access token and a new refresh token.
- The old refresh token's row in the
refresh_tokens table has its revoked column set to true.
- A new row for the new refresh token is created in the
refresh_tokens table.
- A
POST request with an invalid, expired, or already-revoked refresh token returns a 401 Unauthorized response.
Dependencies: Ticket 2
Ticket 5: Implement Lambda Authorizer
Objective: This ticket creates the Lambda Authorizer function responsible for validating JWTs and controlling access to protected API Gateway routes.
Context: As the central security gate for the API, the Lambda Authorizer enforces authentication for all protected endpoints as defined in the ADD. It decodes valid tokens and injects user identity information into the Lambda event context for downstream handlers to use for authorization checks.
Technical Directives:
- The authorizer must extract the bearer token from the
Authorization header of the incoming request.
- Use the JWT verification utility to validate the token's signature and expiry.
- If the token is valid, generate and return an
Allow IAM policy for the requested API endpoint ARN.
- The
Allow policy's context object must contain the sub, email, and roles claims from the decoded JWT payload. The roles array must be stringified using JSON.stringify.
- If the token is missing, malformed, or invalid, generate and return a
Deny IAM policy.
- The authorizer must not connect to the database.
Scope Boundaries:
- Lambda Authorizer (
functions/authorizer/)
Acceptance Criteria:
- An API Gateway request with a valid
Authorization: Bearer <token> header results in the authorizer returning an Allow policy.
- The
Allow policy's context object contains the correct sub, email, and a stringified roles array.
- A request with a missing
Authorization header results in a Deny policy.
- A request with an invalid or expired token results in a
Deny policy.
Dependencies: Ticket 1
Ticket 6: Implement Auth Lambda Router and Logout Handler
Objective: This ticket creates the internal router for the Auth Lambda and implements the protected POST /auth/logout endpoint.
Context: This ticket completes the Auth Lambda by adding the routing logic to direct API Gateway requests to the correct handler (register, login, etc.) and implementing the logout functionality. The logout handler is the first endpoint in the system to be protected by the new Lambda Authorizer, serving as a key integration test.
Technical Directives:
- Create a router that maps incoming API Gateway events to the correct handler function based on the HTTP method and resource path (
POST /auth/register, POST /auth/login, etc.).
- The router must be the single entry point for the Auth Lambda.
- The
logout handler must be a protected route.
- The
logout handler must accept a refreshToken in the request body.
- It must hash the received token and find the matching record in the
refresh_tokens table.
- It must set the
revoked column to true for the matching refresh token, invalidating it for future use.
Scope Boundaries:
- Auth Lambda (
functions/auth/)
Acceptance Criteria:
- The Auth Lambda's main handler correctly routes a
POST request to /auth/register to the registration logic.
- The Auth Lambda's main handler correctly routes a
POST request to /auth/login to the login logic.
- A
POST request to /auth/logout without a valid access token is rejected by the authorizer with a 401/403 status code.
- A
POST request to /auth/logout with a valid access token and a valid refresh token finds the corresponding row in refresh_tokens and sets revoked to true.
- The API returns
{ success: true } upon successful logout.
- Attempting to use a revoked refresh token at the
/auth/refresh endpoint results in a 401 Unauthorized error.
Dependencies: Ticket 3, Ticket 4, Ticket 5
Ticket 7: Update SAM Template and API Gateway Configuration
Objective: This ticket updates the template.yaml file to define the Auth Lambda and Lambda Authorizer as new AWS resources and configures the API Gateway routes.
Context: This final integration step makes the authentication system deployable and operational. It translates the logical components built in previous tickets into concrete cloud resources using Infrastructure as Code, ensuring the API Gateway is correctly configured to use the new authorizer for protected routes.
Technical Directives:
- Add a new
AWS::Serverless::Function resource to template.yaml for the Lambda Authorizer. Configure its handler path and set the cache TTL to 300 seconds.
- Add a new
AWS::Serverless::Function resource for the Auth Lambda, defining its handler path and API events.
- Define the API Gateway routes for
/auth/register, /auth/login, and /auth/refresh as public endpoints that integrate with the Auth Lambda.
- Define the API Gateway route for
/auth/logout, and configure it to use the new Lambda Authorizer.
- Ensure both Lambda functions reference the
SharedLambdaLayer created in Phase 1.
- Ensure the execution roles for both functions have the necessary permissions (e.g., CloudWatch Logs, SSM Parameter access, and RDS Proxy access for the Auth Lambda).
Scope Boundaries:
- Infrastructure as Code (
template.yaml)
Acceptance Criteria:
- After running
sam deploy, the new AuthLambda and LambdaAuthorizer functions are visible in the AWS Lambda console.
- In the API Gateway console, the
/auth/logout route shows that it is protected by the new authorizer.
- The
/auth/register and /auth/login routes are public and successfully invoke the Auth Lambda.
- A deployment to the
dev environment is successful and end-to-end tests for all authentication flows pass.
Dependencies: Ticket 6
Phase 3: Listing Management
Ticket 1: Database Schema for Listing Management
Objective: This ticket creates the PostgreSQL tables required to store all data related to listings, including photos, amenities, availability rules, and pricing overrides.
Context: This schema is the foundation for the entire listing management domain. It implements the data models described in the Architecture Design Document, enabling the Listings Lambda to store and retrieve host-provided information. These tables will be written to by this phase and read from by the Search and Bookings lambdas in subsequent phases.
Technical Directives:
- Create a
listings table to store core listing attributes, including a status column to support the soft-deletion requirement from the ADD.
- Create a
listing_photos table with a foreign key to listings, including columns for the S3 object key and display order.
- Create
listing_amenities, availability_rules, and pricing_overrides tables, each with a foreign key to the listings table.
- All table and column names must align with the data contracts specified for the
Listing, ListingPhoto, AvailabilityRule, and PricingOverride types.
Scope Boundaries:
- Database schema (migrations)
Acceptance Criteria:
- A database migration script is created that, when run, successfully creates the
listings, listing_photos, listing_amenities, availability_rules, and pricing_overrides tables.
- The created tables contain all necessary columns with appropriate data types to store the data defined in the
Listing response shape and its nested types.
- Foreign key constraints are established between the
listings table and all related tables.
Dependencies:
None
Ticket 2: Implement Shared Pricing and Availability Logic Libraries
Objective: This ticket implements and unit tests the shared evaluateAvailabilityRules and calculateStayPrice business logic functions.
Context: These two functions contain complex, critical business logic that must be consistent across the application. Per the Architecture Design Document, they are built as shared library modules within the Listings service but will be imported and used by the Bookings Lambda in a later phase to validate and price a potential stay.
Technical Directives:
- Implement
evaluateAvailabilityRules to check a given date range against all of a listing's availability rules in the correct order: blocked dates, minimum stay, and allowed check-in days.
- Implement
calculateStayPrice to calculate the total cost of a stay, correctly applying pricing overrides based on their priority (seasonal > weekend > base).
- The function signatures, input parameters, and return shapes must exactly match the
evaluateAvailabilityRules and calculateStayPrice contracts.
- Thrown errors for availability violations must be of the specified
ConflictError type.
Scope Boundaries:
functions/listings/src/lib/availability.ts module
functions/listings/src/lib/pricing.ts module
Acceptance Criteria:
- Unit tests for
evaluateAvailabilityRules confirm that it correctly throws a ConflictError when a requested stay overlaps a blocked_range.
- Unit tests for
evaluateAvailabilityRules confirm that it correctly throws a ConflictError when a stay duration is less than a min_stay rule.
- Unit tests for
evaluateAvailabilityRules confirm that it correctly throws a ConflictError when the check-in day is not allowed by a checkin_days rule.
- Unit tests for
calculateStayPrice confirm that the returned PriceBreakdown correctly calculates subtotal, service fee, and total, applying seasonal and weekend overrides with the correct priority.
- The modules are self-contained and can be imported without pulling in handler-specific dependencies.
Dependencies:
Ticket 1
Ticket 3: Implement Core Listing CRUD Endpoints
Objective: This ticket implements the API endpoints for creating, reading, updating, and soft-deleting a listing.
Context: These endpoints provide the fundamental host-side functionality for managing a listing's lifecycle. This work implements the core of the Listings Lambda as described in the ADD, building upon the database schema from Ticket 1 and adhering to the authentication contracts from the preceding phase.
Technical Directives:
- The
createListing, updateListing, and deleteListing handlers must be protected routes that verify the caller has the host role using the JWT context provided by the Lambda Authorizer.
- The
updateListing and deleteListing handlers must validate that the authenticated user is the owner of the listing record being modified.
- Deleting a listing must be a soft delete, achieved by setting the
status column to inactive, not by removing the row.
- The
getListing handler must be a public, unauthenticated route.
- The response for
getListing must conform to the Listing response shape, including all related photos, amenities, rules, and overrides.
Scope Boundaries:
functions/listings/src/handlers/listings.ts module
Acceptance Criteria:
- A
POST /listings request from an authenticated host creates a listing row with status = 'draft' and returns the new listing object.
- A
GET /listings/:id request returns the full listing details and returns a not found error for an invalid ID.
- An
PUT /listings/:id request from the listing's owner successfully updates the specified fields.
- A
DELETE /listings/:id request from the listing's owner changes the listing's status to inactive.
- Any attempt to create, update, or delete a listing by a user without the
host role, or who is not the owner, results in an authorization error.
Dependencies:
Ticket 1
Ticket 4: Implement Stripe Connect Onboarding Endpoints
Objective: This ticket implements the endpoints to generate a Stripe Connect onboarding URL for a user and to handle the subsequent redirect callback from Stripe.
Context: To become a host, a user must complete Stripe's onboarding process. This ticket provides the necessary API endpoints to initiate that flow, as outlined in the Integration Architecture section of the ADD. This is a critical step for enabling the platform's two-sided marketplace functionality.
Technical Directives:
- Implement the
getStripeOnboardingUrl handler, which must be a protected route.
- This handler must call the Stripe API to create a Connect account for the user if one does not already exist, and then generate an account link for onboarding.
- Implement the
handleStripeCallback handler, which must be a public route.
- The callback handler must perform a 302 redirect to a static frontend URL, as its only purpose is to return the user to the application after they complete the flow on Stripe's website.
Scope Boundaries:
functions/listings/src/handlers/connect.ts module
Acceptance Criteria:
- An authenticated user can make a request to
GET /connect/onboarding-url and receive a valid Stripe Connect onboarding URL in the response.
- A request to
GET /connect/callback results in an HTTP 302 redirect response to the configured frontend page.
Dependencies:
None
Ticket 5: Implement Listing Photo Management Endpoints
Objective: This ticket implements all endpoints for managing listing photos, including generating an upload URL, confirming the upload, deleting a photo, and reordering photos.
Context: As specified in the ADD, listing photos are uploaded directly from the client to S3 using a pre-signed URL to improve performance and reduce Lambda execution time. This ticket implements the backend orchestration for that flow, from generating the secure URL to finalizing the photo record in the database.
Technical Directives:
- The
getPhotoUploadUrl handler must generate a time-limited S3 pre-signed PUT URL for a key in a pending/ path and create an associated listing_photos database row marked as unconfirmed.
- The
confirmPhotoUpload handler must verify the object exists in S3, copy it to a permanent photos/ path, delete the original pending/ object, and update the database row to be confirmed.
- The
deletePhoto handler must delete both the S3 object and the corresponding listing_photos database row.
- The
updatePhotoOrder handler must update the display_order for a set of photos within a single database transaction.
- All handlers must be protected and must validate that the caller owns the parent listing.
Scope Boundaries:
functions/listings/src/handlers/photos.ts module
Acceptance Criteria:
- A call to
POST /listings/:id/photos/upload-url returns a valid S3 pre-signed URL and a photo ID.
- After a file is uploaded to the URL, a call to
POST /listings/:id/photos/confirm with the photo ID results in the S3 object being moved and the database record being marked as confirmed.
- A call to
DELETE /listings/:id/photos/:photoId removes the record from the database and the object from S3.
- A call to
PUT /listings/:id/photos/order with an array of photo IDs correctly updates the display_order column for each photo in the database.
Dependencies:
Ticket 3
Ticket 6: Implement Listing Attributes Management Endpoints
Objective: This ticket implements the API endpoints for setting a listing's amenities, availability rules, and pricing overrides.
Context: These endpoints allow hosts to manage the detailed configuration of their listings. Per the phase contract, all three handlers use "replace" semantics (deleting all existing records and inserting the new set) to simplify client-side state management and ensure data consistency.
Technical Directives:
- Implement
setAmenities, setAvailabilityRules, and setPricingOverrides handlers.
- Each handler must perform a full replacement of the associated records for the given listing within a single database transaction.
- Input must be validated to ensure required fields for each rule and override type are present (e.g.,
startDate for seasonal overrides).
- All handlers must be protected and must validate that the caller is the owner of the listing.
Scope Boundaries:
functions/listings/src/handlers/attributes.ts module
Acceptance Criteria:
- A
POST /listings/:id/amenities request with a list of amenities replaces all existing amenities for that listing with the new set.
- A
POST /listings/:id/availability-rules request with a list of rules replaces all existing availability rules for that listing with the new set.
- A
POST /listings/:id/pricing-overrides request with a list of overrides replaces all existing pricing overrides for that listing with the new set.
- Subsequent calls with different or empty lists correctly reflect the "full replace" behavior.
Dependencies:
Ticket 3
Ticket 7: Create and Configure Listings Lambda Router
Objective: This ticket creates the main Lambda entry point that routes API Gateway requests to the appropriate handler functions for the listing management domain.
Context: In accordance with ADR-1 ("Single Lambda per logical domain"), this ticket assembles all the previously implemented handlers into a single, deployable Lambda function. The router acts as the front controller for the entire Listings service, mapping incoming requests to the correct business logic.
Technical Directives:
- Create a router that maps HTTP methods and path patterns to the handler functions implemented in the previous tickets.
- The route table must exactly match the one specified in the
Phase 3: Listing Management contract.
- The module must export a single
handler function that serves as the entry point for the Lambda function.
- A
NotFoundError must be returned for any route that is not explicitly defined.
- Ensure the TypeScript types for
Listing and ListingSummary are exported from a shared types file so they can be imported by the Search Lambda in the next phase.
Scope Boundaries:
functions/listings/src/router.ts module
- Shared types definition file
Acceptance Criteria:
- Requests to
POST /listings, GET /listings/:id, and all other defined listing-related routes are correctly dispatched to their respective handler functions.
- A request to an undefined route, such as
GET /listings/invalid/path, returns a not found error response.
- The
ListingSummary type is successfully exported and available for import by other services. This fulfills the handoff to the Following Phase: Phase 4: Search.
Dependencies:
Ticket 3, Ticket 4, Ticket 5, Ticket 6