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
6 changes: 4 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxx
# ===========================================
# API
# ===========================================
# Backend API URL (Next.js web app)
API_BASE_URL=https://your-api.com
# Backend API URL (Dart Frog on Railway in production)
# Local: http://localhost:8080
# Production: https://<your-service>.up.railway.app
API_BASE_URL=http://localhost:8080

# ===========================================
# FIREBASE (Analytics, Crashlytics, Push)
Expand Down
62 changes: 62 additions & 0 deletions .github/workflows/backend-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Deploy Backend to Railway

on:
push:
branches: [prod]
paths:
- 'backend/**'
- '.github/workflows/backend-deploy.yml'

jobs:
# ── Test backend before deploying ──────────────────────────
test-backend:
name: Test Backend
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Dart SDK
uses: dart-lang/setup-dart@v1
with:
sdk: stable

- name: Install dependencies
working-directory: backend
run: dart pub get

- name: Analyze backend code
working-directory: backend
run: dart analyze --fatal-infos

- name: Run backend tests
working-directory: backend
run: dart test

# ── Deploy to Railway on push to prod ──────────────────────
deploy-backend:
name: Deploy to Railway
needs: test-backend
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Railway CLI
run: npm install -g @railway/cli

- name: Deploy to Railway
working-directory: backend
run: railway up --service familiarise-mobile-api --detach
env:
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}

- name: Verify deployment health
run: |
echo "Waiting for deployment to stabilize..."
sleep 30
curl --fail --retry 5 --retry-delay 10 \
"${{ secrets.PRODUCTION_API_BASE_URL }}/api/health" || \
echo "::warning::Health check failed - check Railway dashboard"
Comment on lines +60 to +62

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The post-deploy health check can’t fail the workflow because it’s followed by || echo "::warning::...". That means a bad deploy still reports success, which undermines the purpose of a health check gate. If you want this to block approval/deploy, let curl --fail ... exit non-zero (or explicitly exit 1 on failure) so the job fails when the service is unhealthy.

Suggested change
curl --fail --retry 5 --retry-delay 10 \
"${{ secrets.PRODUCTION_API_BASE_URL }}/api/health" || \
echo "::warning::Health check failed - check Railway dashboard"
if ! curl --fail --retry 5 --retry-delay 10 "${{ secrets.PRODUCTION_API_BASE_URL }}/api/health"; then
echo "::warning::Health check failed - check Railway dashboard"
exit 1
fi

Copilot uses AI. Check for mistakes.
125 changes: 120 additions & 5 deletions .github/workflows/flutter-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,22 @@ name: Flutter CI/CD

on:
push:
branches: [main, develop]
branches: [prod, dev]
tags: ['v*.*.*']
pull_request:
branches: [main, develop]
branches: [prod, dev]
release:
types: [published]
workflow_dispatch:
inputs:
deploy_type:
description: 'Deployment type'
required: false
default: 'patch'
type: choice
options:
- patch
- release
Comment on lines +12 to +20

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

workflow_dispatch defines an input deploy_type, but it isn’t referenced anywhere in job if: conditions or steps. As written, manually dispatching the workflow will always run shorebird-patch (and never shorebird-release unless also pushing a tag), regardless of the selected option. Either wire inputs.deploy_type into the job conditions, or remove the unused input to avoid confusion.

Suggested change
inputs:
deploy_type:
description: 'Deployment type'
required: false
default: 'patch'
type: choice
options:
- patch
- release

Copilot uses AI. Check for mistakes.

env:
FLUTTER_VERSION: '3.24.3'
Expand All @@ -32,7 +43,7 @@ jobs:
run: flutter pub get

- name: Generate code
run: flutter pub run build_runner build --delete-conflicting-outputs
run: dart run build_runner build --delete-conflicting-outputs

- name: Analyze code
run: flutter analyze --fatal-infos
Expand Down Expand Up @@ -85,7 +96,7 @@ jobs:
run: flutter pub get

- name: Generate code
run: flutter pub run build_runner build --delete-conflicting-outputs
run: dart run build_runner build --delete-conflicting-outputs

- name: Decode Keystore
if: github.event_name == 'release'
Expand Down Expand Up @@ -159,7 +170,7 @@ jobs:
run: flutter pub get

- name: Generate code
run: flutter pub run build_runner build --delete-conflicting-outputs
run: dart run build_runner build --delete-conflicting-outputs

- name: Install CocoaPods
run: |
Expand Down Expand Up @@ -270,3 +281,107 @@ jobs:
--file build/*.ipa \
--apiKey $APP_STORE_CONNECT_KEY_ID \
--apiIssuer $APP_STORE_CONNECT_ISSUER_ID

# ============================================
# Test Dart Frog Backend (runs on every push and PR)
# ============================================
test-backend:
name: Test Backend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Dart SDK
uses: dart-lang/setup-dart@v1
with:
sdk: stable

- name: Install backend dependencies
working-directory: backend
run: dart pub get

- name: Analyze backend code
working-directory: backend
run: dart analyze --fatal-infos

- name: Run backend tests
working-directory: backend
run: dart test

# ============================================
# Shorebird Release (alongside store release, on tags)
# ============================================
shorebird-release:
name: Shorebird Release
needs: [deploy-android, deploy-ios]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shorebird-release is configured to run on tag pushes (if: startsWith(github.ref, 'refs/tags/')) but it needs: [deploy-android, deploy-ios]. Those deploy jobs only run when github.event_name == 'release', so on a tag push they will be skipped and this job won’t run either. To make Shorebird releases actually happen on tags, adjust the dependencies/conditions (e.g., depend on the build jobs for tag pushes, or run Shorebird release on release events instead of tag pushes).

Suggested change
if: startsWith(github.ref, 'refs/tags/')
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/')

Copilot uses AI. Check for mistakes.

steps:
- uses: actions/checkout@v4

- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
channel: stable
cache: true

- name: Install dependencies
run: flutter pub get

- name: Generate code
run: dart run build_runner build --delete-conflicting-outputs

- name: Install Shorebird CLI
uses: shorebirdtech/setup-shorebird@v1

- name: Create Shorebird release (Android)
run: shorebird release android --flutter-version ${{ env.FLUTTER_VERSION }}
env:
SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}

- name: Create Shorebird release (iOS)
run: shorebird release ios --flutter-version ${{ env.FLUTTER_VERSION }}
env:
Comment on lines +314 to +346

Copilot AI Apr 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shorebird release ios requires a macOS runner (Xcode toolchain). Running the Shorebird release job on ubuntu-latest will fail for iOS; split Android/iOS into separate jobs (ubuntu for android, macos for ios) or use a matrix with per-platform runs-on.

Copilot uses AI. Check for mistakes.
SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}

# ============================================
# Shorebird Patch (hotfix — no store review needed)
# Triggers: push to hotfix/* OR manual workflow_dispatch
# ============================================
shorebird-patch:
name: Shorebird Hotfix Patch
runs-on: ubuntu-latest
if: |
github.event_name == 'workflow_dispatch' ||
startsWith(github.ref, 'refs/heads/hotfix/')

steps:
- uses: actions/checkout@v4

- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
channel: stable
cache: true

- name: Install dependencies
run: flutter pub get

- name: Generate code
run: dart run build_runner build --delete-conflicting-outputs

- name: Install Shorebird CLI
uses: shorebirdtech/setup-shorebird@v1

- name: Apply patch (Android)
run: shorebird patch android
env:
SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}

- name: Apply patch (iOS)
run: shorebird patch ios
env:
Comment on lines +353 to +386

Copilot AI Apr 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This job runs on ubuntu-latest but executes shorebird patch ios, which typically requires macOS/Xcode. To avoid guaranteed failures, run the iOS patch step on a macOS runner (separate job or matrix) and keep Android patching on Linux.

Copilot uses AI. Check for mistakes.
SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ key.properties

# iOS build files
/ios/Pods/
/ios/Podfile.lock
# /ios/Podfile.lock # Tracked — locks CocoaPods versions for reproducible builds
/ios/.symlinks/
/ios/Flutter/Flutter.framework
/ios/Flutter/Flutter.podspec
Expand Down
4 changes: 4 additions & 0 deletions .mcp.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
"chrome-devtools": {
"command": "npx",
"args": ["-y", "chrome-devtools-mcp@latest"]
},
"railway": {
"command": "npx",
"args": ["-y", "@railway/mcp-server"]
}
}
}
68 changes: 68 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Familiarise Mobile — System Architecture

## Infrastructure

### Backend hosting — Railway

The Dart Frog API (`backend/`) is deployed on Railway.

- Auto-deploys on every push to `prod` that touches `backend/**`
- Health check endpoint: `GET /api/health`
- Environment variables are set in the Railway dashboard (not in code)
- Uses Supabase connection pooler (port 6543) for `DATABASE_URL` at runtime
- Uses direct connection (port 5432) for `DIRECT_URL` (migrations only)
- The backend uses `DotEnv(includePlatformEnvironment: true)` so it works both locally (with `.env` file) and in Docker/Railway (where env vars are injected by the platform)

**Local dev:** `cd backend && dart_frog dev` → `http://localhost:8080`

### OTA updates — Shorebird

Shorebird enables Dart-only patches to be delivered to users without App Store/Play Store review.

- A Shorebird **release** is created automatically alongside every tagged store release
- A Shorebird **patch** is applied automatically when pushing to any `hotfix/*` branch
- Patches can also be triggered manually via GitHub Actions → workflow_dispatch
- Patches apply silently on the user's next app launch (no update prompt)

**Limitation:** Shorebird patches Dart code only. Changes to native Android/iOS code, new Flutter plugins with native bindings, or new assets require a full store release.

### Update decision tree

```
Need to fix something?
├── Backend logic / API / DB query
│ └── Push to prod → auto-deploys to Railway → users see it instantly
├── Flutter Dart code (UI, state, business logic, API calls)
│ ├── Minor hotfix → push to hotfix/* branch → shorebird patch
│ └── Larger change with tests → PR → merge to dev → tag release
└── Native code (new plugin, permission, asset, icon)
└── Full release: tag → GitHub Actions builds + deploys to stores
```

### Branch strategy

- `dev` — development branch (default)
- `prod` — production branch (Railway deploys from here)
- `feature/*` — feature branches (PR to dev)
- `hotfix/*` — hotfix branches (triggers Shorebird patch)

### Environment configuration

- **Flutter app:** Uses `envied` package to read from `.env` at build time. CI generates `.env` from GitHub Secrets. Do NOT use `--dart-define` — it's incompatible with the envied approach.
- **Backend:** Uses `dotenv` package with `includePlatformEnvironment: true`. Works with both `.env` file (local) and platform env vars (Railway/Docker).

### CI/CD workflows

| Workflow | File | Triggers |
|----------|------|----------|
| Flutter CI/CD | `flutter-ci.yml` | Push to prod/dev, PRs, releases, tags, manual |
| Backend Deploy | `backend-deploy.yml` | Push to prod (backend/** changes) |

### Known gotchas

- `backend/lib/generated/` has a `Platform` enum (from Prisma schema) that conflicts with `dart:io.Platform`. Use `import 'dart:io' as io show Platform;` or explicit `show` clauses.
- `flutter analyze` runs on both frontend AND backend (the whole workspace).
- Use `scripts/regenerate-build.sh --prisma` when generated models are stale.
Loading
Loading