Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Docker Compose Port Configurations (Change in your local .env to fix port conflicts)
COMPOSE_PORT_POSTGRES=5432
COMPOSE_PORT_REDIS=6379
COMPOSE_PORT_SOROBAN=8000
COMPOSE_PORT_BACKEND=3000
COMPOSE_PORT_ML=8001
COMPOSE_PORT_EXPO=8081
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,24 @@ npm run lint
- Make sure you're connected to the same Stellar network as the app (testnet/public)
</details>

## Local Development Environment

SubTrackr utilizes a fully containerized local environment orchestrated via Docker Compose, eliminating the need to manually install dependencies like PostgreSQL, Redis, Soroban CLI, Rust, and Node.js.

### Architecture
* **API Gateway (Backend):** Port 3000
* **Background Workers:** Billing queues
* **Webhook Dispatcher:** Payload deliveries
* **ML Service (Python):** Port 8001
* **Database & Cache:** PostgreSQL (5432), Redis (6379)
* **Soroban Node:** Standalone local network (8000)

### Quick Setup

1. **Initialize the Environment**
```bash
./scripts/setup.sh

## Contributing

We welcome contributions! SubTrackr participates in the **Stellar Wave Program** via [Drips](https://www.drips.network/). Contributors can earn points and rewards by picking up issues labeled **`Stellar Wave`**.
Expand Down
22 changes: 22 additions & 0 deletions docker-compose.override.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: '3.8'
# Maps local directories to containers for instant hot-reloading (ts-node-dev / uvicorn)
services:
backend:
volumes:
- .:/usr/src/app
- /usr/src/app/node_modules
workers:
volumes:
- .:/usr/src/app
- /usr/src/app/node_modules
webhook-dispatcher:
volumes:
- .:/usr/src/app
- /usr/src/app/node_modules
ml-service:
volumes:
- ./ml-service:/app
mobile:
volumes:
- .:/usr/src/app
- /usr/src/app/node_modules
203 changes: 139 additions & 64 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,87 +1,162 @@
# Local development services for SubTrackr backend.
#
# Usage:
# docker compose up -d
# npm run server:start
#
# Environment (optional .env):
# DB_HOST=localhost DB_PORT=5432 DB_NAME=subtrackr DB_USER=postgres DB_PASSWORD=postgres
# REDIS_HOST=localhost REDIS_PORT=6379
version: '3.8'

# Common limits: 8 services * 0.5 CPU = 4 CPUs | 8 services * 1G = 8GB RAM
x-resources: &default-resources
deploy:
resources:
limits:
cpus: '0.5'
memory: 1G

services:
redis:
image: redis:7-alpine
container_name: subtrackr-redis
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_PASSWORD: ${DB_PASS:-postgres}
POSTGRES_DB: ${DB_NAME:-subtrackr}
ports:
- '${REDIS_PORT:-6379}:6379'
command: ['redis-server', '--save', '', '--appendonly', 'no']
- "${COMPOSE_PORT_POSTGRES:-5432}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-subtrackr}"]
interval: 5s
timeout: 3s
timeout: 5s
retries: 5
volumes:
- redis-data:/data
<<: *default-resources

postgres:
image: postgres:16-alpine
container_name: subtrackr-postgres
redis:
image: redis:7-alpine
ports:
- '${DB_PORT:-5432}:5432'
environment:
POSTGRES_DB: ${DB_NAME:-subtrackr}
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
- "${COMPOSE_PORT_REDIS:-6379}:6379"
volumes:
- redis_data:/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-subtrackr}']
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
timeout: 5s
retries: 5
volumes:
- postgres-data:/var/lib/postgresql/data
<<: *default-resources

rabbitmq:
image: rabbitmq:3-management-alpine
container_name: subtrackr-rabbitmq
soroban-standalone:
image: stellar/quickstart:testing
command: --standalone --enable-soroban-rpc
ports:
- '5672:5672'
- '15672:15672'
environment:
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER:-guest}
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASS:-guest}
- "${COMPOSE_PORT_SOROBAN:-8000}:8000"
healthcheck:
test: ['CMD', 'rabbitmq-diagnostics', 'ping']
interval: 5s
timeout: 3s
test: ["CMD", "curl", "-f", "http://localhost:8000/"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- rabbitmq-data:/var/lib/rabbitmq
<<: *default-resources

notification-service:
backend:
build:
context: ./services/notification
dockerfile: Dockerfile
container_name: subtrackr-notification-service
context: .
dockerfile: docker/backend.Dockerfile
ports:
- '${NOTIFICATION_PORT:-3001}:3000'
- "${COMPOSE_PORT_BACKEND:-3000}:3000"
environment:
RABBITMQ_URL: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASS:-guest}@rabbitmq:5672
RABBITMQ_QUEUE: notification.deliver
SENDGRID_API_KEY: ${SENDGRID_API_KEY}
TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID}
TWILIO_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN}
TWILIO_FROM_NUMBER: ${TWILIO_FROM_NUMBER}
EXPO_ACCESS_TOKEN: ${EXPO_ACCESS_TOKEN}
- NODE_ENV=development
- DB_HOST=postgres
- DB_PORT=5432
- DB_USER=${DB_USER:-postgres}
- DB_PASS=${DB_PASS:-postgres}
- DB_NAME=${DB_NAME:-subtrackr}
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
- SOROBAN_RPC_URL=${SOROBAN_RPC_URL:-http://soroban-standalone:8000/rpc}
command: ["npx", "--yes", "ts-node-dev", "--respawn", "--transpile-only", "backend/server/start.ts"]
depends_on:
rabbitmq:
postgres:
condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 10s
timeout: 5s
retries: 5
redis:
condition: service_healthy
<<: *default-resources

workers:
build:
context: .
dockerfile: docker/backend.Dockerfile
environment:
- NODE_ENV=development
- DB_HOST=postgres
- DB_USER=${DB_USER:-postgres}
- DB_PASS=${DB_PASS:-postgres}
- DB_NAME=${DB_NAME:-subtrackr}
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
command: ["npx", "--yes", "ts-node-dev", "--respawn", "--transpile-only", "backend/billing/jobs/billingJobQueue.ts"]
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
<<: *default-resources

webhook-dispatcher:
build:
context: .
dockerfile: docker/backend.Dockerfile
environment:
- NODE_ENV=development
- DB_HOST=postgres
- DB_USER=${DB_USER:-postgres}
- DB_PASS=${DB_PASS:-postgres}
- DB_NAME=${DB_NAME:-subtrackr}
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
command: ["npx", "--yes", "ts-node-dev", "--respawn", "--transpile-only", "backend/webhook/jobs/webhookDeliveryJob.ts"]
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
<<: *default-resources

ml-service:
build:
context: .
dockerfile: docker/ml.Dockerfile
ports:
- "${COMPOSE_PORT_ML:-8001}:8000"
environment:
- DB_HOST=postgres
command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
depends_on:
postgres:
condition: service_healthy
<<: *default-resources

mobile:
build:
context: .
dockerfile: docker/backend.Dockerfile
ports:
- "${COMPOSE_PORT_EXPO:-8081}:8081"
environment:
- NODE_ENV=development
- EXPO_DEVTOOLS_LISTEN_ADDRESS=0.0.0.0
- REACT_NATIVE_PACKAGER_HOSTNAME=${HOST_IP:-127.0.0.1}
command: ["npx", "expo", "start"]
profiles:
- mobile
<<: *default-resources

seed:
image: postgres:15-alpine
environment:
- PGHOST=postgres
- PGUSER=${DB_USER:-postgres}
- PGPASSWORD=${DB_PASS:-postgres}
- PGDATABASE=${DB_NAME:-subtrackr}
volumes:
- ./docker/seed:/seed
depends_on:
postgres:
condition: service_healthy
command: ["psql", "-f", "/seed/seed.sql"]
profiles:
- tools

volumes:
redis-data:
postgres-data:
rabbitmq-data:
postgres_data:
redis_data:
20 changes: 20 additions & 0 deletions docker/backend.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM node:18-alpine

# Install build tools for native dependencies
RUN apk add --no-cache python3 make g++ curl bash

WORKDIR /usr/src/app

# Leverage Docker cache for npm install
COPY package*.json ./
COPY .npmrc ./

# Copy the scripts folder so postinstall hooks (like patch-metro.js) can execute
COPY scripts/ ./scripts/

RUN npm install

# Copy the rest of the application code
COPY . .

EXPOSE 3000 8081
7 changes: 7 additions & 0 deletions docker/ml.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM python:3.10-slim
WORKDIR /app
RUN apt-get update && apt-get install -y gcc curl && rm -rf /var/lib/apt/lists/*
COPY ml-service/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt || true
COPY ml-service/ .
EXPOSE 8000
12 changes: 12 additions & 0 deletions docker/seed/seed.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS plans (id VARCHAR PRIMARY KEY, name VARCHAR, price DECIMAL, currency VARCHAR);
CREATE TABLE IF NOT EXISTS users (id VARCHAR PRIMARY KEY, email VARCHAR, name VARCHAR);
CREATE TABLE IF NOT EXISTS invoices (id VARCHAR PRIMARY KEY, user_id VARCHAR, plan_id VARCHAR, amount DECIMAL, status VARCHAR);

INSERT INTO plans (id, name, price, currency) VALUES
('plan_1', 'Basic', 9.99, 'USD'), ('plan_2', 'Pro', 19.99, 'USD'), ('plan_3', 'Enterprise', 49.99, 'USD'), ('plan_4', 'Starter', 4.99, 'USD'), ('plan_5', 'Premium', 99.99, 'USD') ON CONFLICT DO NOTHING;

INSERT INTO users (id, email, name) VALUES
('usr_1', 'u1@test.com', 'User 1'), ('usr_2', 'u2@test.com', 'User 2'), ('usr_3', 'u3@test.com', 'User 3'), ('usr_4', 'u4@test.com', 'User 4'), ('usr_5', 'u5@test.com', 'User 5'), ('usr_6', 'u6@test.com', 'User 6'), ('usr_7', 'u7@test.com', 'User 7'), ('usr_8', 'u8@test.com', 'User 8'), ('usr_9', 'u9@test.com', 'User 9'), ('usr_10', 'u10@test.com', 'User 10') ON CONFLICT DO NOTHING;

INSERT INTO invoices (id, user_id, plan_id, amount, status) VALUES
('inv_1', 'usr_1', 'plan_1', 9.99, 'paid'), ('inv_2', 'usr_2', 'plan_2', 19.99, 'paid'), ('inv_3', 'usr_3', 'plan_3', 49.99, 'paid'), ('inv_4', 'usr_4', 'plan_4', 4.99, 'paid'), ('inv_5', 'usr_5', 'plan_5', 99.99, 'paid'), ('inv_6', 'usr_6', 'plan_1', 9.99, 'pending'), ('inv_7', 'usr_7', 'plan_2', 19.99, 'paid'), ('inv_8', 'usr_8', 'plan_3', 49.99, 'paid'), ('inv_9', 'usr_9', 'plan_4', 4.99, 'failed'), ('inv_10', 'usr_10', 'plan_5', 99.99, 'paid'), ('inv_11', 'usr_1', 'plan_1', 9.99, 'paid'), ('inv_12', 'usr_2', 'plan_2', 19.99, 'paid'), ('inv_13', 'usr_3', 'plan_3', 49.99, 'paid'), ('inv_14', 'usr_4', 'plan_4', 4.99, 'paid'), ('inv_15', 'usr_5', 'plan_5', 99.99, 'paid'), ('inv_16', 'usr_6', 'plan_1', 9.99, 'paid'), ('inv_17', 'usr_7', 'plan_2', 19.99, 'paid'), ('inv_18', 'usr_8', 'plan_3', 49.99, 'paid'), ('inv_19', 'usr_9', 'plan_4', 4.99, 'paid'), ('inv_20', 'usr_10', 'plan_5', 99.99, 'paid') ON CONFLICT DO NOTHING;
Binary file added ml-service/__pycache__/main.cpython-310.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
26 changes: 26 additions & 0 deletions scripts/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -e

echo "==========================================="
echo " SubTrackr Local Environment Setup"
echo "==========================================="

if ! command -v docker &> /dev/null; then
echo "❌ Error: Docker is not installed."
exit 1
fi

if [ ! -f .env ]; then
echo "Creating .env from .env.example..."
cp .env.example .env
fi

echo "Pulling latest base images..."
docker compose pull

echo "Building local services..."
docker compose build

echo "✅ Setup complete!"
echo "➡️ Start the stack: docker compose up -d"
echo "➡️ Seed test data: docker compose run --rm seed"
Loading