-
Notifications
You must be signed in to change notification settings - Fork 178
Feat/incremental message sync #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Adds docker-compose.yml with pre-built image support: - app: pulls from docker.korshakov.com/handy-server - postgres: PostgreSQL 16 - redis: Redis 7 with persistence - minio: S3-compatible storage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Adds minio-init service that creates 'happy' bucket on startup - Sets bucket to allow anonymous downloads - App now waits for minio-init to complete before starting - Adds healthcheck to minio service 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Changed docker-compose to build locally instead of pulling from registry - Added prisma folder to Dockerfile runner stage for migrations - Changed metrics port from 9090 to 9091 to avoid conflicts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Two-part approach to reduce iOS app load time from 2-7s to <500ms: Part A - Incremental Sync: - Add updatedAfter/before query params to messages API - Track lastSyncTimestamp per session on iOS - Fetch only new/edited messages (typically 0-5 vs 150) Part B - Prefetch on App Active: - Prefetch messages for top 5 active sessions when app opens - Timeout protection (3s sessions, 5s prefetch total) - Data ready before user navigates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Defines interactive deployment script that: - Auto-detects environment (system vs Docker Caddy) - Handles existing data with user prompts - Configures PostgreSQL password securely - Runs full health verification after deployment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements deploy.sh with: - Auto-detection of system vs Docker Caddy - Interactive prompts for domain and credentials - PostgreSQL password configuration - Full health verification after deployment - Idempotent - safe to run multiple times Usage: ./deploy.sh 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds docker/init-postgres.sh that runs on postgres container initialization to ensure the password matches the expected value. Also enables remote debug logging and mounts logs volume. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds a postgres-init sidecar container that runs ALTER USER to ensure the PostgreSQL password matches the expected value before the app starts. This prevents the recurring database authentication failures. The app now depends on postgres-init completing successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Port 5432 was exposed to the public internet, causing constant brute-force auth attempts from bots. Binding to 127.0.0.1 restricts database access to local connections only while still allowing debugging from the host machine. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use data.newestTimestamp instead of Date.now() to avoid clock skew - Add "Known Limitations" section documenting: - Deleted messages handling (not in scope) - Clock skew protection approach Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Detailed task-by-task plan for adding updatedAfter/before params to the messages endpoint and database index. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request introduces production deployment infrastructure and documentation for an incremental message synchronization feature. The changes include a comprehensive deployment script for Docker-based orchestration, service configuration, and detailed technical planning documents for implementing efficient message syncing.
- Adds automated deployment tooling with interactive configuration for domains, passwords, and data handling
- Introduces Docker Compose orchestration for the application stack with PostgreSQL, Redis, MinIO, and optional Caddy
- Documents incremental sync architecture to reduce message fetch latency from seconds to milliseconds
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| deploy.sh | Interactive deployment script handling Docker services, database setup, Caddy configuration, and health verification |
| docker-compose.yml | Orchestrates app, PostgreSQL, Redis, MinIO, and related initialization services with healthchecks and volumes |
| Dockerfile | Adds prisma directory to runtime image for database schema access |
| docs/plans/2026-01-01-deploy-script-design.md | Design documentation for the deployment script architecture and workflow |
| docs/plans/2025-01-01-message-sync-latency-design.md | Technical design for reducing message sync latency via incremental fetching |
| docs/plans/2025-01-01-incremental-message-sync-implementation.md | Step-by-step implementation plan for the incremental sync feature |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - "3005:3005" | ||
| - "9091:9090" | ||
| environment: | ||
| - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/handy |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default password "postgres" is hardcoded in the DATABASE_URL. This means even if the postgres service is configured with a different password through POSTGRES_PASSWORD, the app will still try to connect with "postgres" as the password. This configuration should reference the environment variable or be consistent with the password setup.
| - S3_ACCESS_KEY=minioadmin | ||
| - S3_SECRET_KEY=minioadmin |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded credentials "minioadmin/minioadmin" for S3_ACCESS_KEY and S3_SECRET_KEY are default MinIO credentials. These should be configurable and generated securely, similar to the PostgreSQL password handling in the deploy script.
| Choice [1]: _ | ||
| ``` | ||
|
|
||
| Credentials stored in `.env` file (gitignored). |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PostgreSQL password stored in the .env file is in plaintext. While the file is gitignored, consider adding a note in the documentation about securing this file with appropriate file permissions (e.g., chmod 600 .env) to prevent unauthorized access on shared systems.
| Credentials stored in `.env` file (gitignored). | |
| Credentials stored in `.env` file (gitignored); ensure it is protected with restrictive permissions (for example, `chmod 600 .env`) to prevent unauthorized access on shared systems. |
| })), | ||
| hasMore: messages.length === limit, | ||
| ...(messages.length > 0 ? { | ||
| oldestTimestamp: Math.min(...messages.map(m => m.createdAt.getTime())), |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The newestTimestamp calculation uses updatedAt while oldestTimestamp uses createdAt. This inconsistency could cause issues when the client uses newestTimestamp for the next updatedAfter query, as it's comparing different timestamp fields. Both should likely use the same field (probably updatedAt for consistency with the incremental sync feature).
| oldestTimestamp: Math.min(...messages.map(m => m.createdAt.getTime())), | |
| oldestTimestamp: Math.min(...messages.map(m => m.updatedAt.getTime())), |
| environment: | ||
| - DATABASE_URL=postgresql://postgres:postgres@postgres:5432/handy | ||
| - REDIS_URL=redis://redis:6379 | ||
| - HANDY_MASTER_SECRET=your-super-secret-key-change-in-production |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded value "your-super-secret-key-change-in-production" for HANDY_MASTER_SECRET is insecure. This secret is used directly in the docker-compose file without any prompting or generation during deployment. Anyone with access to the repository can see this default value, creating a significant security vulnerability.
| - HANDY_MASTER_SECRET=your-super-secret-key-change-in-production | |
| - HANDY_MASTER_SECRET=${HANDY_MASTER_SECRET:?HANDY_MASTER_SECRET not set} |
|
|
||
| # Fix PostgreSQL password | ||
| info "Configuring PostgreSQL password..." | ||
| $COMPOSE_CMD exec -T postgres psql -U postgres -c "ALTER USER postgres WITH PASSWORD '$POSTGRES_PASSWORD';" > /dev/null |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PostgreSQL password is interpolated directly into the shell command without proper escaping. If the password contains special characters (like $, `, ", ), the ALTER USER command could fail or behave unexpectedly. Consider using proper quoting or escaping for the password value.
| $COMPOSE_CMD exec -T postgres psql -U postgres -c "ALTER USER postgres WITH PASSWORD '$POSTGRES_PASSWORD';" > /dev/null | |
| ESCAPED_POSTGRES_PASSWORD=$(printf "%s" "$POSTGRES_PASSWORD" | sed "s/'/''/g") | |
| $COMPOSE_CMD exec -T postgres psql -U postgres -c "ALTER USER postgres WITH PASSWORD '${ESCAPED_POSTGRES_PASSWORD}';" > /dev/null |
| updatedAfter: z.coerce.number().int().min(0).optional(), | ||
| before: z.coerce.number().int().min(0).optional(), | ||
| limit: z.coerce.number().int().min(1).max(150).default(150) | ||
| }) |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The querystring schema field should be marked as optional at the schema level using .optional(), but the inner object fields (updatedAfter, before, limit) are already optional. This creates a discrepancy where the entire querystring object is required but its contents are optional. Consider making the querystring itself optional or ensuring all fields have appropriate defaults.
| updatedAfter: z.coerce.number().int().min(0).optional(), | |
| before: z.coerce.number().int().min(0).optional(), | |
| limit: z.coerce.number().int().min(1).max(150).default(150) | |
| }) | |
| updatedAfter: z.coerce.number().int().min(0), | |
| before: z.coerce.number().int().min(0), | |
| limit: z.coerce.number().int().min(1).max(150) | |
| }).partial().default({ limit: 150 }).optional() |
| - S3_ACCESS_KEY=minioadmin | ||
| - S3_SECRET_KEY=minioadmin | ||
| - S3_BUCKET=happy | ||
| - S3_PUBLIC_URL=http://localhost:9000/happy |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The S3_PUBLIC_URL uses "localhost" which will not work correctly in production or when accessed from outside the Docker network. This should be configurable based on the domain provided during deployment, similar to how the Caddy configuration uses the domain variable.
| - S3_PUBLIC_URL=http://localhost:9000/happy | |
| - S3_PUBLIC_URL=http://${DOMAIN:-localhost}:9000/happy |
| - S3_SECRET_KEY=minioadmin | ||
| - S3_BUCKET=happy | ||
| - S3_PUBLIC_URL=http://localhost:9000/happy | ||
| - DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING=true |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING to true in a production deployment configuration is concerning. The variable name itself indicates this should not be enabled in production. This should either be removed or set to false by default, with an option to enable it only for development environments.
| - DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING=true | |
| - DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING=false |
| - MINIO_ROOT_USER=minioadmin | ||
| - MINIO_ROOT_PASSWORD=minioadmin |
Copilot
AI
Jan 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same default MinIO credentials "minioadmin/minioadmin" are used here. These should match whatever credentials are configured for the app service and should be securely generated rather than using defaults.
This pull request introduces a comprehensive, production-ready deployment setup for the project, along with a technical implementation plan for incremental message sync. The most significant changes are the addition of a robust
deploy.shscript for interactive and reliable Docker-based deployments, a newdocker-compose.ymlfor orchestrating services, and documentation for implementing incremental sync in the messages API. Additionally, the Docker build now includes the Prisma schema directory.Deployment & Infrastructure
deploy.shscript that interactively configures environment variables, manages Docker Compose services, handles database initialization/reset, configures HTTPS with Caddy, and verifies service health for easy local or production deployment.docker-compose.ymldefining services for the app, PostgreSQL (with initialization), Redis, Minio (S3-compatible storage), and Caddy, including healthchecks, dependencies, and persistent volumes for robust orchestration.Dockerfileto copy theprismadirectory into the image, ensuring database schema access at runtime.Documentation & Planning
docs/plans/2025-01-01-incremental-message-sync-implementation.md) for supporting incremental message synchronization in the API, including query parameter handling, Prisma indexing, and manual testing steps.