Tickets Document
Here is your step-by-step ticket backlog to build Chroma. Whether you're pasting these sequentially into Claude Code or importing them into Linear, Jira, or ClickUp for your team, these tickets provide the exact implementation steps to bring this architecture to life.
Phase 1: Data Layer and Schema
Ticket 1.1: Create User and Social Graph SQL Schema
Objective: This ticket establishes the core users and follows tables in PostgreSQL, which are foundational for all other data entities.
Context: The users table is the primary entity store for user accounts, as defined in the Core Data Entities section of the Architecture Design Document. The follows table represents the social graph, a critical component for the Social Service and the feed fanout logic. This initial schema work is essential before any application logic can be built.
Technical Directives:
- Create an SQL migration file named
0001_users.sql.
- Define the
users table with the specified columns: id, username, phone_hash, profile_pic_url, bio, display_name, is_private, is_deleted, gdpr_deletion_requested_at, and created_at.
- Ensure column types, constraints (UNIQUE, NOT NULL), and default values match the Implementation Plan exactly.
- Implement a case-insensitive uniqueness constraint on the
username column using a lowercased index.
- Create a GIN index on a tsvector column combining
username and display_name to support full-text search.
- Create a separate SQL migration file named
0003_follows.sql.
- Define the
follows table with a composite primary key on follower_id and followee_id, and columns state and created_at.
- Create indexes on
follower_id, followee_id, and created_at DESC as specified.
- Foreign keys must reference the
users.id column.
Scope Boundaries:
- PostgreSQL database schema
- SQL migration files within
internal/db/schema
Acceptance Criteria:
- The
0001_users.sql migration runs successfully against a clean PostgreSQL 16 database.
- The
users table is created with all specified columns, data types, and constraints.
- A user can be inserted with a unique username, and an attempt to insert a second user with the same username (different case) fails due to the unique index.
- The
0003_follows.sql migration runs successfully after the users migration.
- The
follows table is created with the correct composite primary key and indexes.
Dependencies:
None
Ticket 1.2: Create Content SQL Schema
Objective: This ticket creates the posts table schema for storing media metadata and adds the media_urls column to support processed media variants.
Context: The posts table is the central entity for the Content Service, tracking all user-generated content from initial upload to final publication. The state machine defined in this table's state column is critical for the media processing pipeline, as described in the Component Architecture section of the ADD.
Technical Directives:
- Create an SQL migration file named
0002_posts.sql.
- Define the
posts table with the specified columns: id, author_id, media_type, state, caption, location, like_count, comment_count, is_reel, created_at, and published_at.
- The
author_id column must be a foreign key referencing users.id.
- The
media_type ENUM must contain 'photo' and 'video'.
- The
state ENUM must contain 'pending', 'processing', 'published', 'removed_csam', 'removed_moderation', 'deleted', 'failed_upload', and 'failed_validation'.
- Create indexes on
author_id, created_at DESC, and state (for published posts).
- Create a separate SQL migration file named
0011_media_urls.sql.
- This migration must alter the
posts table to add a media_urls column of type JSONB, which can be nullable.
Scope Boundaries:
- PostgreSQL database schema
- SQL migration files within
internal/db/schema
Acceptance Criteria:
- The
0002_posts.sql migration runs successfully against the database.
- The
posts table exists with all specified columns, data types, and constraints, including the correct ENUM values.
- The
0011_media_urls.sql migration runs successfully after the posts migration.
- The
posts table schema is successfully altered to include the media_urls JSONB column.
Dependencies:
Ticket 1.1
Ticket 1.3: Create Engagement SQL Schema
Objective: This ticket creates the comments and likes tables to store user interactions with posts.
Context: The likes table is designed to handle high write concurrency by offloading real-time counting to Redis, as specified in ADR-004. The PostgreSQL table provides the source of truth and allows for point lookups. The comments table supports threaded replies and is queried directly for post views.
Technical Directives:
- Create an SQL migration file named
0004_comments.sql.
- Define the
comments table with columns id, post_id, author_id, body, parent_id, and created_at.
- Foreign keys for
post_id and author_id must reference the posts and users tables, respectively.
- The
parent_id must be a self-referencing nullable foreign key to comments.id.
- Create an SQL migration file named
0005_likes.sql.
- Define the
likes table with a composite primary key on post_id and user_id, and a created_at column.
- The table must be partitioned by hash on the
post_id column into exactly 8 partitions.
- Create an index on
user_id to facilitate efficient data deletion for GDPR compliance.
Scope Boundaries:
- PostgreSQL database schema
- SQL migration files within
internal/db/schema
Acceptance Criteria:
- The
0004_comments.sql migration runs successfully.
- The
comments table is created with all specified columns, foreign keys, and indexes.
- The
0005_likes.sql migration runs successfully.
- The
likes table is created with the correct composite primary key.
- Querying the database confirms that 8 partitions have been created for the
likes table.
- The index on
user_id in the likes table is present.
Dependencies:
Ticket 1.1, Ticket 1.2
Ticket 1.4: Create Hashtag and Tagging SQL Schema
Objective: This ticket creates the necessary tables for hashtag storage and the association between posts and hashtags.
Context: As detailed in the Data Architecture section, hashtags are stored canonically in PostgreSQL to support search and GDPR-compliant deletion, while trending counts are managed in Redis. This schema provides the relational backbone for hashtag functionality.
Technical Directives:
- Create an SQL migration file named
0006_hashtags.sql.
- Define a
hashtags table with columns id, tag (unique), and created_at.
- Define a
post_hashtags junction table with columns post_id and hashtag_id.
- The junction table must have a composite primary key on
post_id and hashtag_id.
- Foreign keys in the junction table must reference the
posts and hashtags tables.
Scope Boundaries:
- PostgreSQL database schema
- SQL migration files within
internal/db/schema
Acceptance Criteria:
- The
0006_hashtags.sql migration runs successfully.
- The
hashtags table is created with a unique constraint on the tag column.
- The
post_hashtags junction table is created with the correct composite primary key and foreign key constraints.
Dependencies:
Ticket 1.2
Ticket 1.5: Create Notification and Device Management SQL Schema
Objective: This ticket establishes the schema for storing in-app notifications and user device tokens for push notifications.
Context: The Notification Service, as described in the Component Architecture, relies on these tables. The notifications table stores a persistent history of user notifications, while the device_tokens table is essential for targeting pushes via Firebase Cloud Messaging (FCM).
Technical Directives:
- Create an SQL migration file named
0007_notifications.sql.
- Define the
notifications table with columns id, recipient_id, type, actor_id, post_id, read, and created_at.
- The
type ENUM must contain 'like', 'comment', 'follow', and 'mention'.
- Define foreign keys for
recipient_id, actor_id, and post_id.
- Create an index on (
recipient_id, created_at DESC).
- Create an SQL migration file named
0008_device_tokens.sql.
- Define the
device_tokens table with columns id, user_id, token, platform, created_at, and updated_at.
- The
platform ENUM must contain 'ios' and 'android'.
- The
token column must have a unique index.
Scope Boundaries:
- PostgreSQL database schema
- SQL migration files within
internal/db/schema
Acceptance Criteria:
- The
0007_notifications.sql migration runs successfully.
- The
notifications table is created with the correct columns, ENUM values, and index.
- The
0008_device_tokens.sql migration runs successfully.
- The
device_tokens table is created with the correct columns, ENUM values, and a unique index on the token.
Dependencies:
Ticket 1.1, Ticket 1.2
Ticket 1.6: Create GDPR Compliance SQL Schema
Objective: This ticket creates the table to track user account deletion requests, a core part of the GDPR compliance pipeline.
Context: The GDPR deletion pipeline, as described in the Data Lifecycle section of the ADD, is triggered by entries in this table. It ensures that user deletion requests are formally logged and processed in a timely and auditable manner.
Technical Directives:
- Create an SQL migration file named
0009_gdpr_deletion_requests.sql.
- Define the
gdpr_deletion_requests table with columns user_id (primary key), requested_at, delete_posts, and processed_at.
- The
user_id must be a foreign key referencing the users table.
- The
processed_at column must be nullable.
Scope Boundaries:
- PostgreSQL database schema
- SQL migration files within
internal/db/schema
Acceptance Criteria:
- The
0009_gdpr_deletion_requests.sql migration runs successfully.
- The
gdpr_deletion_requests table is created with the specified columns, primary key, and foreign key.
Dependencies:
Ticket 1.1
Ticket 1.7: Initialize River Job Queue Schema
Objective: This ticket creates the necessary tables and schema for the River job queue within the PostgreSQL database.
Context: The architecture (ADR-002) uses River, a PostgreSQL-backed job queue, to avoid the operational overhead of a separate message broker. This ticket sets up the schema River requires, enabling subsequent worker phases to enqueue and consume jobs.
Technical Directives:
- Create an SQL migration file named
0010_river.sql.
- The implementation must use the River Go client's built-in schema migration functionality.
- The migration should create all required River tables (e.g.,
river_jobs) within a dedicated river schema.
- This setup should be part of the overall database migration runner.
Scope Boundaries:
- PostgreSQL database schema
- SQL migration files within
internal/db/schema
Acceptance Criteria:
- Running the
0010_river.sql migration (or the Go function it calls) successfully creates the river schema.
- The
river_jobs table and other related River tables exist within the river schema after the migration completes.
Dependencies:
None
Ticket 1.8: Define Redis Key Convention Module
Objective: This ticket creates a Go module that defines all Redis key naming conventions as constants and functions.
Context: Standardizing Redis key names in a central module prevents inconsistencies and bugs across different services that interact with Redis. This module will be a shared dependency for the Feed Service, Social Service, Counter Sync Worker, and API Gateway, as outlined in the Data Architecture.
Technical Directives:
- Create a Go package named
redis within the internal/db module.
- Implement exported functions or constants for every key schema defined in the Implementation Plan.
- Functions that require an ID (e.g.,
userID, postID) must accept it as an argument and return the fully formatted key string.
- The module must be self-contained and have no dependencies on other application packages.
- Document the Redis data type and any TTL or capping policies associated with each key in the code comments.
Scope Boundaries:
- Go module
internal/db/redis
Acceptance Criteria:
- The
internal/db/redis module compiles successfully.
- All specified key-generating functions and constants are exported from the package.
- The output of each function matches the documented key format exactly (e.g.,
RedisKeyFeedQueue("user123") returns "feed:v1:user123").
- This module is ready to be consumed by the shared libraries in the Following Phase.
Dependencies:
None
Ticket 1.9: Create Local Development Environment with Docker Compose
Objective: This ticket creates a docker-compose.yml file to run a complete local development stack, including automated database migrations.
Context: A reproducible local development environment is critical for developer productivity and consistency. As per the Implementation Plan, this setup allows any developer to start the required backing services (PostgreSQL, Redis, MinIO) with a single command and have a fully initialized database ready for use.
Technical Directives:
- Create a
docker-compose.yml file in the project root.
- Define services for PostgreSQL 16, Redis 7, and MinIO (as a GCS-compatible object store).
- Configure the PostgreSQL service to automatically run all SQL migrations created in the preceding tickets upon startup. This can be achieved using an init container or by mounting the migration files and using the official PostgreSQL image's init-db script mechanism.
- Ensure all services are on a shared Docker network.
- Expose the default ports for each service to the host machine.
Scope Boundaries:
- Docker Compose configuration
Acceptance Criteria:
- Running
docker-compose up from the project root successfully starts containers for PostgreSQL, Redis, and MinIO without errors.
- After startup, connecting to the local PostgreSQL instance reveals that all tables, indexes, partitions, and the River schema from tickets 1-7 have been created.
- The local Redis and MinIO instances are accessible on their default ports.
Dependencies:
Ticket 1.1, Ticket 1.2, Ticket 1.3, Ticket 1.4, Ticket 1.5, Ticket 1.6, Ticket 1.7
Phase 2: Shared Infrastructure Libraries
Ticket 2.1: Structured Logger Implementation
Objective: Create a reusable, structured JSON logger that all backend services will use for logging.
Context: This foundational library ensures consistent, machine-readable logging across all services, which is critical for debugging and monitoring in a distributed system. As specified in the Architecture Design Document, all logs are written to standard output to be ingested by Cloud Logging on GKE.
Technical Directives:
- Implement the
New function within the internal/platform/logger module.
- The function must accept a service name string and return a standard
slog.Logger instance.
- The returned logger must be configured to output logs in JSON format.
- The provided service name must be included as a default field in every log entry produced by the logger instance.
- Log output must be directed to standard output (
stdout).
Scope Boundaries:
internal/platform/logger module
Acceptance Criteria:
- Calling
logger.New("test-service") returns a non-nil logger instance.
- When the logger instance is used to log a message, the output is a single line of valid JSON.
- The JSON output contains the log level, message, timestamp, and the service name "test-service".
Dependencies:
None
Ticket 2.2: Secret Manager Client
Objective: Implement a client to securely fetch secrets from Google Secret Manager using Workload Identity.
Context: This module abstracts away the specifics of accessing secrets, providing a simple interface for other platform components like the database connector and JWT key loader. The Architecture Design Document mandates using Google Secret Manager and Workload Identity to avoid static credentials in the environment.
Technical Directives:
- Implement the
Load function and associated error types (SecretNotFoundError, SecretAccessError) in the internal/platform/secrets module.
- The
Load function must use the ambient Google Cloud credentials provided by Workload Identity; it must not require any explicit credential configuration.
- The function must fetch the latest version of the secret specified by name.
- If the secret name does not exist, the function must return a
SecretNotFoundError.
- For any other failure (e.g., permissions error, network issue), the function must return a
SecretAccessError that wraps the underlying cause.
Scope Boundaries:
internal/platform/secrets module
Acceptance Criteria:
- Calling
secrets.Load with the name of an existing secret returns its string payload and a nil error.
- Calling
secrets.Load with a name that does not exist returns a SecretNotFoundError.
- Disabling the Secret Manager API or providing invalid permissions and calling
secrets.Load results in a SecretAccessError.
- Unit tests for this module successfully mock the Secret Manager client to validate all error and success paths.
Dependencies:
Ticket 2.1
Ticket 2.3: PostgreSQL Database Connector
Objective: Create a function to establish a connection pool to the Cloud SQL PostgreSQL database.
Context: This module provides a standardized way for all data-driven services to connect to the primary database. It leverages the secrets module created in the previous ticket to securely fetch the database password at runtime, as per the architecture's security requirements.
Technical Directives:
- Implement the
Connect function and DBConfig struct in the internal/platform/db module.
- The function must use the
secrets.Load function to fetch the database password from the secret named in DBConfig.PasswordSecretName.
- It must establish a connection pool using the
pgxpool library.
- The connection pool must be configured with a minimum of 2 and a maximum of 20 connections.
- The connection timeout must be set to 30 seconds.
- On any failure to connect or fetch the secret, the function must return a
DBConnectionError that wraps the underlying cause.
Scope Boundaries:
internal/platform/db module
Acceptance Criteria:
- Calling
db.Connect with valid configuration (and a running database instance via docker-compose) returns a connected *pgxpool.Pool and a nil error.
- Calling
db.Connect with an invalid password secret name results in a DBConnectionError.
- Calling
db.Connect with an incorrect hostname results in a DBConnectionError.
- The returned connection pool can successfully execute a simple query (e.g.,
SELECT 1).
Dependencies:
Ticket 2.2
Ticket 2.4: Redis Cache Connector
Objective: Create a function to establish a client connection to the Memorystore Redis instance.
Context: This module provides a standardized connector for services that interact with Redis for caching, session storage, and feed management. It ensures all services use a consistent client configuration.
Technical Directives:
- Implement the
Connect function and RedisConfig struct in the internal/platform/cache module.
- The function must establish a client connection to the Redis server specified in the
RedisConfig.
- On any failure to connect, the function must return a
CacheConnectionError that wraps the underlying cause.
Scope Boundaries:
internal/platform/cache module
Acceptance Criteria:
- Calling
cache.Connect with valid configuration for a running Redis instance returns a connected client and a nil error.
- The returned client can successfully execute a PING command against the Redis server.
- Calling
cache.Connect with an incorrect hostname results in a CacheConnectionError.
Dependencies:
Ticket 2.1
Ticket 2.5: JWT Authentication Library
Objective: Implement functions for loading JWT signing keys and for issuing and validating RS256 JSON Web Tokens.
Context: This module centralizes all JWT logic, ensuring that token issuance (by the Social Service) and validation (by the API Gateway and other services) use the same cryptographic keys and validation rules. Keys are loaded securely via the secrets module, per the ADD.
Technical Directives:
- Implement all functions and error types specified in the
internal/platform/auth module contract.
LoadKeyPair must use secrets.Load to fetch the PEM-encoded private and public keys from Secret Manager, using environment variables for the secret names. It must parse them into RSA key objects.
IssueAccessToken must generate a JWT signed with the provided private key, including the specified claims (sub, jti, exp, iat) and a 15-minute expiry.
IssueRefreshToken must generate a cryptographically secure random string suitable for use as a refresh token.
ValidateAccessToken must verify the token's signature against the public key and check for expiry. It must return the claims on success or a typed error (TokenExpiredError, TokenInvalidError) on failure.
Scope Boundaries:
internal/platform/auth module
Acceptance Criteria:
LoadKeyPair successfully fetches and parses valid PEM-encoded keys.
- A token issued by
IssueAccessToken can be successfully validated by ValidateAccessToken before it expires.
ValidateAccessToken returns a TokenExpiredError for a token whose expiration time is in the past.
ValidateAccessToken returns a TokenInvalidError for a token with a bad signature or malformed content.
IssueRefreshToken returns a non-empty, securely random token string and its ID.
Dependencies:
Ticket 2.2
Ticket 2.6: Standard API Error Response Utilities
Objective: Create a standardized structure and helper function for writing JSON error responses from API endpoints.
Context: This module ensures that all services return errors in a consistent, predictable format, which simplifies client-side error handling. The APIError struct and standard codes defined here will be used by all HTTP handlers in subsequent phases.
Technical Directives:
- Define the
APIError struct in the internal/platform/apierror module exactly as specified in the contract.
- Implement the
WriteError helper function.
WriteError must accept an http.ResponseWriter, an HTTP status code, a machine-readable error code string, and a human-readable message string.
- The function must set the
Content-Type header to application/json.
- It must write the HTTP status code to the response header.
- It must write a JSON-encoded
APIError object to the response body.
Scope Boundaries:
internal/platform/apierror module
Acceptance Criteria:
- Calling
apierror.WriteError with a test http.ResponseWriter writes the correct HTTP status code.
- The response body written by the function is a valid JSON object matching the
APIError structure.
- The
Content-Type header of the response is set to application/json.
Dependencies:
Ticket 2.1
Ticket 2.7: Input Sanitization Utilities
Objective: Develop a set of reusable functions for validating and sanitizing user-provided text inputs.
Context: This library provides a critical security function by ensuring all user input is cleaned before being processed or stored, preventing cross-site scripting (XSS) and other injection attacks. As defined in the ADD, sanitization is enforced at the service layer.
Technical Directives:
- Implement the
Text and Username functions, along with the ValidationError type, in the internal/platform/sanitize module.
Text must strip all HTML tags, reject any input containing null bytes, trim leading/trailing whitespace, and enforce the specified maximum length.
Username must convert the input to lowercase, verify it contains only alphanumeric characters and underscores, and check that its length is between 1 and 30 characters.
- Both functions must return a
ValidationError with a descriptive reason if any validation rule is violated.
Scope Boundaries:
internal/platform/sanitize module
Acceptance Criteria:
sanitize.Text correctly removes <b> and <script> tags from an input string.
sanitize.Text returns a ValidationError if the input contains a null byte (\x00).
sanitize.Text returns a ValidationError if the input (after stripping HTML) exceeds the maxLen.
sanitize.Username returns a ValidationError for inputs containing spaces, hyphens, or other special characters.
sanitize.Username returns a ValidationError for inputs that are too long or empty.
sanitize.Username correctly lowercases a valid mixed-case username.
Dependencies:
Ticket 2.1
Phase 3: Social Service — Registration, Auth, Profiles, and Follow Graph
Ticket 3.1: Define Shared Data Models
Objective: This ticket creates the internal/models package containing the Go structs for all core database entities used across services.
Context: As per the Architecture Design Document, all services share a common understanding of core data entities like User, Post, and Comment. This package establishes that single source of truth, preventing model drift between services and ensuring a consistent data representation layer. While the Social Service only directly uses User, Follow, and RefreshToken in this phase, defining all shared models now prevents breaking changes later.
Technical Directives:
- Create a new Go package at
internal/models.
- Implement the Go structs for
User, Follow, RefreshToken, Post, Comment, Notification, and DeviceToken exactly as defined in the "Target Phase" section's internal/models contract.
- Use standard Go types (
string, bool, int64, time.Time) and pointers for nullable fields.
- Do not include any database-specific tags (e.g.,
db:"...") or business logic in these structs; they are plain data transfer objects.
Scope Boundaries:
Acceptance Criteria:
- The
internal/models package exists and compiles successfully.
- All specified structs (
User, Follow, RefreshToken, Post, Comment, Notification, DeviceToken) are present in the package with the correct field names and types.
Dependencies:
None
Ticket 3.2: Implement Social Service Database Schema
Objective: This ticket creates the SQL migration scripts to define the database schema for the Social Service.
Context: The Social Service is the authority for user identity, profiles, and relationships. This ticket lays the foundational PostgreSQL schema required to store this data, as described in the Architecture Design Document's Data Architecture section. It includes tables for users, authentication tokens, and the social graph (follows and blocks).
Technical Directives:
- Create a database migration for the following tables, based on the structs defined in the
internal/models package:
users: Include columns for all fields in the User model. Create a UNIQUE index on the lowercased username and on phone_hash. Create a GIN index on a tsvector column combining username and display_name for full-text search.
refresh_tokens: Include columns for all fields in the RefreshToken model.
follows: Include a composite primary key on follower_id and followee_id, and an index on followee_id to support listing followers.
blocks: Include a composite primary key on blocker_id and blocked_id.
- Use UUID types for all
id columns.
- Use
TIMESTAMPTZ for all timestamp columns.
- Ensure foreign key constraints are established where appropriate (e.g., from
follows to users).
Scope Boundaries:
- Database schema migrations
Acceptance Criteria:
- A new database migration can be successfully applied to a clean PostgreSQL database.
- The
users, refresh_tokens, follows, and blocks tables are created with the correct columns, types, constraints, and indexes.
- The GIN index required for full-text search exists on the
users table.
Dependencies:
Ticket 3.1
Ticket 3.3: Implement Firebase Token Verification Module
Objective: This ticket implements the VerifyFirebaseToken function to validate Firebase ID tokens and extract user phone numbers.
Context: As specified in the Architecture Design Document, user registration and login are handled via Firebase Auth's OTP flow to offload SMS delivery complexity. The Social Service must verify the short-lived token provided by the client after a successful OTP flow to trust the user's identity. This module provides that core verification logic.
Technical Directives:
- Create the
services/social/auth module.
- Implement the
VerifyFirebaseToken function according to its contract.
- The function must fetch and cache Firebase's public keys to perform JWT validation locally.
- Upon successful validation, the function must extract the E.164 formatted phone number from the token's claims.
- Return a
FirebaseTokenError for any validation failure, including invalid signature, expiry, or missing claims.
Scope Boundaries:
services/social/auth module
Acceptance Criteria:
- Calling
VerifyFirebaseToken with a valid, unexpired Firebase ID token returns the correct phone number and no error.
- Calling the function with an invalid, expired, or malformed token returns a
FirebaseTokenError.
- The implementation successfully caches and uses Firebase's public keys for validation.
Dependencies:
None
Ticket 3.4: Implement User and Refresh Token Store Functions
Objective: This ticket implements the data access functions in services/social/store for creating, retrieving, and updating users, and for managing refresh tokens.
Context: This module forms the core data access layer for user identity management. It directly interacts with the database schema created in a previous ticket and provides a clean API for the service's handlers to perform CRUD operations on user and authentication data. It utilizes the internal/platform/db package for database connectivity.
Technical Directives:
- Create the
services/social/store module.
- Implement the functions:
CreateUser, GetUserByPhoneHash, GetUserByUsername, GetUserByID, and UpdateUser.
- Implement the functions:
CreateRefreshToken, GetRefreshToken, and RevokeRefreshToken.
- All functions must accept a
*pgxpool.Pool from the internal/platform/db package.
- All functions must use the model types from the
internal/models package for parameters and return values.
CreateUser must hash the phone number using SHA-256 before storing it in the phone_hash column.
Scope Boundaries:
services/social/store module
Acceptance Criteria:
- Calling
CreateUser inserts a new row into the users table and returns the corresponding models.User struct.
- All
GetUserBy... functions correctly retrieve a user from the database or return an error if not found.
UpdateUser correctly modifies the specified fields for a given user ID.
CreateRefreshToken, GetRefreshToken, and RevokeRefreshToken correctly manage records in the refresh_tokens table.
Dependencies:
Ticket 3.2
Ticket 3.5: Implement Follow and Block Store Functions
Objective: This ticket implements the data access functions in services/social/store for all follow graph and block list operations.
Context: The Social Service owns the social graph. These store functions provide the low-level database operations for creating and deleting follow relationships and block records, which are fundamental to the application's social features and safety policies.
Technical Directives:
- Within the
services/social/store module, implement the functions: CreateFollow, DeleteFollow, ApproveFollow, GetFollow, ListFollowers, and ListFollowing.
- Implement the functions:
CreateBlock, DeleteBlock, and IsBlocked.
ListFollowers and ListFollowing must support cursor-based pagination using the created_at timestamp.
- The
IsBlocked function should check for a block relationship in either direction between two user IDs.
Scope Boundaries:
services/social/store module
Acceptance Criteria:
CreateFollow and DeleteFollow correctly add and remove rows from the follows table.
ListFollowers and ListFollowing return a correctly paginated list of models.User.
CreateBlock and DeleteBlock correctly add and remove rows from the blocks table.
IsBlocked returns true if a block exists in either direction between two users, and false otherwise.
Dependencies:
Ticket 3.2
Ticket 3.6: Implement User Search Store Function
Objective: This ticket implements the user search function in services/social/store using PostgreSQL's full-text search capabilities.
Context: As per ADR-005, user search at launch will be handled by PostgreSQL Full-Text Search to minimize operational overhead. This ticket implements the specific query that leverages the GIN index created in the schema ticket to provide efficient search over usernames and display names.
Technical Directives:
- Within the
services/social/store module, implement the SearchUsers function.
- The function must use a
to_tsvector and plainto_tsquery query against the indexed tsvector column in the users table.
- The function must exclude users who have been soft-deleted (
is_deleted is true).
- The function should implement cursor-based pagination.
Scope Boundaries:
services/social/store module
Acceptance Criteria:
- Calling
SearchUsers with a query string returns a list of matching users from the database.
- The search is case-insensitive and matches on partial words in both username and display name.
- Soft-deleted users are not included in search results.
Dependencies:
Ticket 3.2
Ticket 3.7: Implement Social Service Cache Functions
Objective: This ticket implements the Redis-backed caching functions in services/social/cache for active user tracking and follow relationships.
Context: The Architecture Design Document specifies a hybrid feed model that relies on knowing which users are "active". This module implements the TouchActiveUser function to update this Redis set. It also provides caching for follow relationships to reduce database load on hot query paths, such as rendering feed items. It utilizes the internal/platform/cache package for Redis connectivity.
Technical Directives:
- Create the
services/social/cache module.
- Implement
TouchActiveUser to add a user ID to a Redis sorted set with the current Unix timestamp as the score.
- Implement
IsActiveUser to check if a user exists in the sorted set with a score from the last 48 hours.
- Implement
SetFollowCache, GetFollowCache, and InvalidateFollowCache to manage a simple key-value cache in Redis for follower:followee relationships. The cache should have a 5-minute TTL.
Scope Boundaries:
services/social/cache module
Acceptance Criteria:
TouchActiveUser successfully adds or updates a user's score in the active-users Redis sorted set.
IsActiveUser correctly returns true for a recently touched user and false for an inactive or nonexistent user.
SetFollowCache and GetFollowCache correctly round-trip a boolean follow state to Redis.
InvalidateFollowCache successfully deletes the corresponding cache key.
Dependencies:
Ticket 3.1
Ticket 3.8: Implement Registration and Login API Endpoints
Objective: This ticket implements the POST /v1/auth/register and POST /v1/auth/login HTTP handlers.
Context: These endpoints are the primary entry points for users into the application. They orchestrate the process of verifying a user's phone number via Firebase, creating a user record, and issuing the initial set of authentication tokens.
Technical Directives:
- Implement the
register and login handlers as specified in the services/social/handler contract.
- Use the
services/social/auth.VerifyFirebaseToken function to validate the incoming token.
- Use the sanitization functions from the
internal/platform/sanitize package for username and display name.
- Use the user store functions from
services/social/store to create or retrieve user records.
- Use the
IssueAccessToken and IssueRefreshToken functions from the internal/platform/auth package to generate JWTs.
- Store the new refresh token using
services/social/store.CreateRefreshToken.
- Return the
UserResponse shape along with the tokens.
- Implement correct error handling for conflicts (username/phone taken), invalid input, and failed Firebase verification.
Scope Boundaries:
services/social/handler module
Acceptance Criteria:
- A
POST to /v1/auth/register with a valid Firebase token and unique username creates a user and returns a 201 status with tokens and user data.
- A
POST to /v1/auth/register with an already-used username or phone returns a 409 Conflict error.
- A
POST to /v1/auth/login with a valid Firebase token for an existing user returns a 200 status with new tokens.
- A
POST to /v1/auth/login for a non-existent user returns a 404 Not Found error.
Dependencies:
Ticket 3.3, Ticket 3.4, Ticket 3.7
Ticket 3.9: Implement Token Refresh and Logout API Endpoints
Objective: This ticket implements the POST /v1/auth/refresh and POST /v1/auth/logout HTTP handlers.
Context: These endpoints manage the lifecycle of an authenticated session. The refresh endpoint allows a client to obtain a new short-lived access token using a long-lived refresh token, while the logout endpoint provides a secure way to invalidate the current session.
Technical Directives:
- Implement the
refresh and logout handlers as specified in the services/social/handler contract.
- For
refresh, use store.GetRefreshToken to look up the token and validate it. Issue a new access token using platform/auth.IssueAccessToken.
- For
logout, extract the token ID (jti claim) from the validated access token in the request context and call store.RevokeRefreshToken.
- The
logout endpoint must be behind the authentication middleware.
- Return the appropriate HTTP status codes and error responses for invalid, expired, or revoked tokens.
Scope Boundaries:
services/social/handler module
Acceptance Criteria:
- A
POST to /v1/auth/refresh with a valid refresh token returns a 200 status with a new access token.
- A
POST to /v1/auth/refresh with an invalid or revoked token returns a 401 Unauthorized error.
- An authenticated
POST to /v1/auth/logout returns a 204 No Content status and marks the corresponding refresh token as revoked in the database.
Dependencies:
Ticket 3.4
Ticket 3.10: Implement User Profile API Endpoints
Objective: This ticket implements the GET /v1/users/:username and PATCH /v1/users/me HTTP handlers for viewing and updating user profiles.
Context: These endpoints allow users to view public profiles and manage their own profile information (display name, bio, etc.). This is a core feature of any social application.
Technical Directives:
- Implement the
getUserProfile and updateUserProfile handlers.
getUserProfile must fetch user data using store.GetUserByUsername. It must enforce privacy rules: if the target user is private and the requester does not follow them, return a limited view.
updateUserProfile is an authenticated endpoint for the /me path. It must only allow a user to update their own profile.
- All user-provided input in the
PATCH request must be sanitized using the internal/platform/sanitize package.
- Both endpoints should return data in the
UserResponse shape.
Scope Boundaries:
services/social/handler module
Acceptance Criteria:
- A
GET to /v1/users/some_user returns the correct user's public profile data.
- An authenticated
PATCH to /v1/users/me with valid data updates the user's record in the database and returns the updated profile.
- Attempting to
PATCH with data that fails sanitization (e.g., bio is too long) returns a 400 Validation Error.
Dependencies:
Ticket 3.4, Ticket 3.5
Ticket 3.11: Implement Follow Management API Endpoints
Objective: This ticket implements the API endpoints for a user to follow, unfollow, and approve follow requests.
Context: These authenticated endpoints manipulate the core social graph. They orchestrate calls to the database store and the Redis cache to ensure the follow state is updated correctly and consistently.
Technical Directives:
- Implement the
followUser, unfollowUser, and approveFollow handlers as specified in the services/social/handler contract.
- All endpoints must be authenticated.
- The
followUser handler must check if the target user is private and create a pending follow record if so.
- When following a public user, call
cache.TouchActiveUser to mark the follower as active.
- When a follow is created or deleted, call
cache.InvalidateFollowCache to remove the stale cache entry.
- The
approveFollow handler must verify that the authenticated user is the one being followed (the followee).
- Enforce block list rules: a user cannot follow someone who has blocked them, or whom they have blocked.
Scope Boundaries:
services/social/handler module
Acceptance Criteria:
- An authenticated
POST to /v1/users/:userID/follow creates a follow relationship. The state is 'accepted' for public users and 'pending' for private users.
- An authenticated
DELETE to /v1/users/:userID/follow removes the follow relationship and invalidates the cache.
- An authenticated
POST to /v1/users/:userID/follow/approve by the correct user changes a 'pending' follow to 'accepted'.
- Attempting to follow a user when a block is in place returns a 403 Forbidden error.
Dependencies:
Ticket 3.5, Ticket 3.7
Ticket 3.12: Implement Follower and Following List API Endpoints
Objective: This ticket implements the paginated API endpoints for listing a user's followers and the accounts they follow.
Context: These read-only endpoints are crucial for navigating the social graph within the mobile app. They must be performant and support pagination to handle users with large numbers of followers or followings.
Technical Directives:
- Implement the
listFollowers and listFollowing handlers.
- Both handlers must support cursor-based pagination using the
created_at timestamp from the follows table. The cursor should be an opaque string.
- The
limit query parameter should be respected, with a maximum value of 50.
- Use the
store.ListFollowers and store.ListFollowing functions to retrieve the data.
- Return the data as a list of
UserResponse objects along with a next_cursor field.
- Enforce privacy rules for viewing these lists based on the target user's
is_private status.
Scope Boundaries:
services/social/handler module
Acceptance Criteria:
- A
GET request to /v1/users/:userID/followers returns the first page of followers.
- Providing the
next_cursor from a previous response in a subsequent request returns the next page of results.
- The response structure matches the contract, including the list of users and the next cursor.
Dependencies:
Ticket 3.5
Ticket 3.13: Implement Block Management API Endpoints
Objective: This ticket implements the API endpoints for blocking and unblocking other users.
Context: Blocking is a critical safety feature. These endpoints provide the functionality for users to manage their block lists, which immediately severs all visibility and interaction between the two accounts.
Technical Directives:
- Implement the
blockUser and unblockUser handlers.
- Both endpoints must be authenticated.
- When
blockUser is called, it must create a record in the blocks table and also delete any existing follow relationships between the two users in both directions.
- When
unblockUser is called, it must remove the record from the blocks table.
- Both handlers should return a 204 No Content status on success.
Scope Boundaries:
services/social/handler module
Acceptance Criteria:
- An authenticated
POST to /v1/users/:userID/block creates a block record and removes any existing follow relationship.
- An authenticated
DELETE to /v1/users/:userID/block removes the block record.
- After a user is blocked, they can no longer be followed, and they do not appear in lists or search results for the blocker.
Dependencies:
Ticket 3.5
Ticket 3.14: Implement User Search API Endpoint
Objective: This ticket implements the GET /v1/users/search API endpoint for finding users by username or display name.
Context: This endpoint exposes the PostgreSQL full-text search capability implemented in the store layer. It allows users to discover other users on the platform.
Technical Directives:
- Implement the
searchUsers handler.
- The handler must validate the
q query parameter (3-30 characters).
- Call the
store.SearchUsers function to execute the search.
- Before returning results, filter out any users that the authenticated requester has blocked or is blocked by.
- The endpoint should support pagination via cursor.
- Return the results in the
{ users: [], next_cursor: "..." } shape.
Scope Boundaries:
services/social/handler module
Acceptance Criteria:
- A
GET to /v1/users/search?q=test returns a paginated list of users matching the query.
- Users blocked by the requester, or who have blocked the requester, do not appear in the search results.
- Requests with a query parameter outside the length constraints return a 400 Validation Error.
Dependencies:
Ticket 3.5, Ticket 3.6
Ticket 3.15: Wire Up Social Service Application
Objective: This ticket creates the main executable for the Social Service, wiring up the HTTP server, router, middleware, and all dependencies.
Context: This is the final step in making the Social Service a runnable application. It integrates all the previously built modules (handler, store, cache, auth) with the shared platform libraries (db, cache, logger, auth) to create a complete, functioning microservice.
Technical Directives:
- Create a
main.go file in a cmd/social directory.
- Initialize the structured logger using
platform/logger.New.
- Establish connections to PostgreSQL and Redis using the
platform/db.Connect and platform/cache.Connect functions.
- Load the JWT key pair using
platform/auth.LoadKeyPair.
- Set up an HTTP router and register all the API endpoint handlers created in previous tickets.
- Implement a JWT authentication middleware that validates the access token on protected routes and places the user's claims into the request context. This middleware will use
platform/auth.ValidateAccessToken.
- The server should listen on a configurable port and handle graceful shutdown.
Scope Boundaries:
cmd/social application executable
- HTTP routing and middleware layer
Acceptance Criteria:
- The Social Service binary can be built and started without errors.
- The service successfully connects to PostgreSQL and Redis on startup.
- Unauthenticated endpoints (register, login) are accessible.
- Authenticated endpoints return a 401 Unauthorized error if no valid JWT is provided.
- When a valid JWT is provided, authenticated endpoints function correctly.
- All endpoints are registered on the correct paths and methods.
Dependencies:
Ticket 3.8, Ticket 3.9, Ticket 3.10, Ticket 3.11, Ticket 3.12, Ticket 3.13, Ticket 3.14
Phase 4: Direct Messaging and WebSockets
Phase 5: Push Notifications and Email
Phase 6: Advanced Content Moderation (PhotoDNA + AI)
Phase 7: Analytics and Data Warehouse Export
Phase 8: Search Infrastructure (Elasticsearch)
Phase 9: Admin Dashboard and Tooling
Phase 10: Production Readiness and Auto-scaling