Skip to content

chippy-kennedy/orbit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Orbit πŸ“Έ

Photos from people you actually know.

Orbit is a privacy-focused social photo sharing app that creates an intimate feed of photos from your real-world contacts. By syncing your contacts and mirroring Instagram content, Orbit shows you a chronological feed from people in your 1st and 2nd-degree networksβ€”without the noise of traditional social media.

🎯 Concept

Unlike traditional social networks where you're bombarded with content from strangers, Orbit creates an intimate photo feed exclusively from:

  • 1st-degree contacts: People in your phone's contact list who use Orbit
  • 2nd-degree contacts: Friends of your friends (contacts of your contacts)

This creates a natural, familiar network where every photo comes from someone you know or are connected to through a mutual friend.

✨ Features

πŸ” Privacy-First Architecture

  • Client-side hashing: Phone numbers are hashed on-device before transmission
  • Zero raw data storage: Server never sees actual phone numbers
  • Salt-based encryption: SHA-256 hashing with server-side salt
  • Secure token storage: JWT authentication with secure storage

πŸ“± Core Functionality

  • Phone-based authentication: SMS OTP verification (Twilio integration ready)
  • Contact discovery: Find friends already on the platform via hashed contact matching
  • Instagram mirroring: Connect Instagram to automatically sync your photos
  • Chronological feed: No algorithmsβ€”just photos from your network, newest first
  • Network expansion: See content from friends-of-friends (2nd degree)

🎨 User Experience

  • Clean, minimalist mobile interface
  • Pull-to-refresh feed
  • Profile avatars with fallback initials
  • Empty state guidance for new users
  • Onboarding flow with skip options

πŸ—οΈ Architecture

Tech Stack

Mobile App (/mobile)

  • Framework: React Native with Expo SDK 51+
  • Language: TypeScript (strict mode)
  • Navigation: Expo Router (file-based routing)
  • State Management: React Context API
  • Security: expo-secure-store, expo-crypto
  • Permissions: expo-contacts

Backend API (/api)

  • Framework: Fastify (Node.js)
  • Language: TypeScript
  • Database: PostgreSQL with @databases/pg
  • Authentication: JWT with @fastify/jwt
  • Validation: Zod schemas
  • Job Queue: BullMQ + Redis (ready for background jobs)
  • SMS: Twilio (integration ready, currently mocked)

Infrastructure (Planned)

  • Media Storage: AWS S3 + CloudFront CDN
  • Cache Layer: Redis
  • OAuth: Instagram Basic Display API

πŸ“‚ Project Structure

orbit/
β”œβ”€β”€ api/                          # Backend API
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ config.ts            # Environment configuration
β”‚   β”‚   β”œβ”€β”€ index.ts             # Fastify server setup
β”‚   β”‚   β”œβ”€β”€ db/
β”‚   β”‚   β”‚   β”œβ”€β”€ index.ts         # PostgreSQL connection pool
β”‚   β”‚   β”‚   └── schema.sql       # Database schema
β”‚   β”‚   β”œβ”€β”€ middleware/
β”‚   β”‚   β”‚   └── auth.ts          # JWT authentication middleware
β”‚   β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”‚   β”œβ”€β”€ auth.ts          # Phone verification endpoints
β”‚   β”‚   β”‚   β”œβ”€β”€ contacts.ts      # Contact matching endpoints
β”‚   β”‚   β”‚   β”œβ”€β”€ instagram.ts     # Instagram OAuth & sync
β”‚   β”‚   β”‚   β”œβ”€β”€ feed.ts          # Feed retrieval logic
β”‚   β”‚   β”‚   └── account.ts       # User account management
β”‚   β”‚   └── services/
β”‚   β”‚       β”œβ”€β”€ phoneService.ts  # OTP generation & verification
β”‚   β”‚       β”œβ”€β”€ userService.ts   # User CRUD operations
β”‚   β”‚       β”œβ”€β”€ contactService.ts # Contact matching logic
β”‚   β”‚       └── instagramService.ts # Instagram API integration
β”‚   β”œβ”€β”€ package.json
β”‚   └── tsconfig.json
β”‚
β”œβ”€β”€ mobile/                       # React Native mobile app
β”‚   β”œβ”€β”€ app/                     # Expo Router screens
β”‚   β”‚   β”œβ”€β”€ _layout.tsx          # Root layout with AuthProvider
β”‚   β”‚   β”œβ”€β”€ index.tsx            # Entry point / auth router
β”‚   β”‚   β”œβ”€β”€ welcome.tsx          # Landing screen
β”‚   β”‚   β”œβ”€β”€ phone-input.tsx      # Phone number entry
β”‚   β”‚   β”œβ”€β”€ verify-otp.tsx       # OTP verification
β”‚   β”‚   β”œβ”€β”€ contacts-permission.tsx # Contact sync onboarding
β”‚   β”‚   β”œβ”€β”€ connect-instagram.tsx   # Instagram connection
β”‚   β”‚   └── feed.tsx             # Main feed screen
β”‚   β”œβ”€β”€ context/
β”‚   β”‚   └── AuthContext.tsx      # Global auth state
β”‚   β”œβ”€β”€ utils/
β”‚   β”‚   β”œβ”€β”€ api.ts               # API client with typed methods
β”‚   β”‚   β”œβ”€β”€ contacts.ts          # Contact hashing utilities
β”‚   β”‚   └── storage.ts           # Secure storage wrapper
β”‚   β”œβ”€β”€ package.json
β”‚   └── tsconfig.json
β”‚
└── README.md                     # You are here

πŸš€ Getting Started

Prerequisites

  • Node.js: 18+ (20+ recommended)
  • PostgreSQL: 14+
  • Redis: 6+ (for background jobs)
  • Expo CLI: npm install -g expo-cli
  • iOS Simulator or Android Emulator or Expo Go app

1. Database Setup

Create a PostgreSQL database:

psql postgres
CREATE DATABASE orbit;
\q

Run the schema migration:

psql orbit < api/src/db/schema.sql

2. API Setup

Install dependencies

cd api
npm install

Configure environment

Create api/.env.local:

# Server
NODE_ENV=development
PORT=3000
API_VERSION=v1

# JWT
JWT_SECRET=your-super-secret-jwt-key-change-in-production
JWT_EXPIRES_IN=30d

# Database
DATABASE_URL=postgresql://postgres:password@localhost:5432/orbit

# Redis
REDIS_URL=redis://localhost:6379

# Twilio (optional for development)
TWILIO_ACCOUNT_SID=your_twilio_account_sid
TWILIO_AUTH_TOKEN=your_twilio_auth_token
TWILIO_VERIFY_SERVICE_SID=your_verify_service_sid

# Instagram Basic Display API (optional for development)
INSTAGRAM_CLIENT_ID=your_instagram_app_id
INSTAGRAM_CLIENT_SECRET=your_instagram_app_secret
INSTAGRAM_REDIRECT_URI=http://localhost:3000/v1/instagram/callback

# Contact Hashing
CONTACT_HASH_SALT=your-unique-salt-string-keep-secret

Start the API server

npm run dev

The API will be available at http://localhost:3000

Check health: curl http://localhost:3000/health

3. Mobile App Setup

Install dependencies

cd mobile
npm install

Configure API endpoint

Update mobile/utils/api.ts with your local IP address (for physical devices) or appropriate emulator endpoints:

export const API_BASE_URL = Platform.select({
  android: 'http://10.0.2.2:3000/v1',  // Android emulator
  ios: 'http://localhost:3000/v1',      // iOS simulator
  default: 'http://YOUR_LOCAL_IP:3000/v1' // Physical device (e.g., 192.168.1.100)
});

Start Expo

npm start

Then:

  • Press i for iOS simulator
  • Press a for Android emulator
  • Scan QR code with Expo Go app for physical device

πŸ—„οΈ Database Schema

users

Stores user accounts with hashed phone numbers and Instagram connection status.

Column Type Description
id UUID Primary key
phone_hash VARCHAR(64) SHA-256 hash of phone number
display_name VARCHAR(100) User's display name (nullable)
avatar_url TEXT Profile picture URL (nullable)
insta_connected BOOLEAN Instagram connection status
insta_user_id VARCHAR(100) Instagram user ID
insta_access_token TEXT Encrypted Instagram token
insta_token_expires_at TIMESTAMP Token expiration
created_at TIMESTAMP Account creation time
updated_at TIMESTAMP Last update time

posts

Stores photos from Instagram or native uploads.

Column Type Description
id UUID Primary key
user_id UUID Foreign key to users
source VARCHAR(20) 'instagram' or 'native'
external_id VARCHAR(100) Instagram media ID
media_url TEXT CDN URL of image
caption TEXT Post caption (nullable)
taken_at TIMESTAMP When photo was taken
created_at TIMESTAMP When synced to Orbit

contact_matches

Maps user relationships based on contact syncing.

Column Type Description
user_id UUID User who uploaded contacts
match_user_id UUID Matched user in their contacts
created_at TIMESTAMP When match was created

contact_uploads

Audit log of contact sync events.

Column Type Description
id UUID Primary key
user_id UUID User who uploaded
uploaded_count INTEGER Number of contacts uploaded
created_at TIMESTAMP Upload time

privacy_settings

Per-user privacy preferences.

Column Type Description
user_id UUID Foreign key (primary)
hide_from_contacts BOOLEAN Hide profile from contacts
mutual_only BOOLEAN Show posts to mutual contacts only
updated_at TIMESTAMP Last update time

πŸ”Œ API Endpoints

Authentication

POST /v1/auth/phone/start

Start phone verification by sending OTP.

Request:

{
  "phone_number": "+15551234567"
}

Response:

{
  "success": true
}

POST /v1/auth/phone/verify

Verify OTP code and authenticate user.

Request:

{
  "phone_number": "+15551234567",
  "code": "123456"
}

Response:

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "user": {
    "id": "uuid",
    "display_name": "John Doe",
    "avatar_url": null,
    "phone_hash": "abc123...",
    "insta_connected": false
  }
}

Contacts

POST /v1/contacts/match

Upload hashed contacts and find matches.

Headers: Authorization: Bearer <token>

Request:

{
  "hashes": ["abc123...", "def456...", ...]
}

Response:

{
  "matches": [
    {
      "id": "uuid",
      "display_name": "Jane Smith",
      "avatar_url": "https://...",
      "phone_hash": "abc123...",
      "insta_connected": true
    }
  ],
  "total_contacts": 150,
  "matched_count": 12
}

GET /v1/contacts/mutual

Get list of mutual contacts.

Headers: Authorization: Bearer <token>

Response:

{
  "contacts": [
    {
      "id": "uuid",
      "display_name": "Jane Smith",
      "avatar_url": "https://...",
      "phone_hash": "abc123...",
      "insta_connected": true
    }
  ]
}

Instagram

POST /v1/instagram/exchange

Exchange OAuth code for access token and connect account.

Headers: Authorization: Bearer <token>

Request:

{
  "code": "instagram_oauth_code"
}

Response:

{
  "insta_connected": true,
  "username": "johndoe",
  "last_sync_at": "2025-10-26T12:00:00Z"
}

POST /v1/instagram/sync

Manually trigger Instagram media sync.

Headers: Authorization: Bearer <token>

Response:

{
  "success": true,
  "synced_at": "2025-10-26T12:00:00Z"
}

POST /v1/instagram/disconnect

Disconnect Instagram and delete synced posts.

Headers: Authorization: Bearer <token>

Response:

{
  "insta_connected": false
}

Feed

GET /v1/feed?cursor=<timestamp>&limit=<number>

Get chronological feed from network.

Headers: Authorization: Bearer <token>

Query Params:

  • cursor (optional): ISO timestamp for pagination
  • limit (optional): Number of posts (default: 20)

Response:

{
  "items": [
    {
      "id": "uuid",
      "user": {
        "id": "uuid",
        "display_name": "Jane Smith",
        "avatar_url": "https://...",
        "phone_hash": "abc123..."
      },
      "source": "instagram",
      "media_url": "https://cdn.orbit.photo/...",
      "caption": "Beautiful sunset πŸŒ…",
      "taken_at": "2025-10-25T18:30:00Z",
      "created_at": "2025-10-26T10:00:00Z"
    }
  ],
  "next_cursor": "2025-10-25T18:30:00Z",
  "network_size": 45
}

πŸ”’ Security & Privacy

Contact Privacy

  1. Client-side hashing: Phone numbers are hashed using SHA-256 with a salt before leaving the device
  2. No raw storage: Server never stores or logs raw phone numbers
  3. Hashed matching: Contact discovery uses hash matching, preserving privacy
  4. Salt rotation: Server-side salt can be rotated to invalidate old hashes

Authentication

  • JWT tokens: Stateless authentication with 30-day expiration
  • Secure storage: Tokens stored in device keychain/keystore via expo-secure-store
  • Bearer token: Standard Authorization: Bearer <token> header

Data Protection

  • Parameterized queries: All SQL queries use parameterized inputs to prevent injection
  • Zod validation: Request bodies validated with strict schemas
  • CORS enabled: Cross-origin protection configured
  • Instagram tokens: Encrypted at rest (implementation pending)

Privacy Controls (Planned)

  • Hide profile from contacts who have your number
  • Mutual-only posting (only show posts to mutual contacts)
  • Block list
  • Account deletion with full data wipe

🚧 Development Status & TODOs

βœ… Completed

  • Phone authentication flow (mock OTP)
  • Contact hashing and matching
  • Instagram connection (mock OAuth)
  • Feed generation with 1st & 2nd degree network
  • Basic mobile UI/UX
  • JWT authentication
  • Database schema with indexes

🚧 In Progress

  • Real Twilio SMS integration
  • Real Instagram OAuth flow with WebView
  • Profile customization (display name, avatar)
  • Native photo uploads (camera/gallery)

πŸ“‹ Planned Features

  • Push notifications for new posts
  • Background Instagram sync (BullMQ jobs)
  • Story-style ephemeral posts
  • Photo reactions/comments
  • Privacy settings UI
  • Account deletion flow
  • S3 + CloudFront media hosting
  • Image optimization pipeline
  • Admin dashboard
  • Analytics & monitoring

πŸ› Known Issues

  • OTP is logged to console (mock mode only)
  • Instagram OAuth uses mock flow
  • No rate limiting on API endpoints
  • Missing error boundaries in mobile app
  • Network requests not cached
  • No offline support

πŸ§ͺ Testing

Manual Testing Flow

  1. Sign Up:

    • Start app β†’ Welcome screen
    • Enter phone number
    • Use OTP from API logs
    • Grant contacts permission
    • Connect Instagram (mock)
  2. Verify Database:

    -- Check user creation
    SELECT * FROM users;
    
    -- Check contact matches
    SELECT * FROM contact_matches;
    
    -- Check synced posts
    SELECT * FROM posts;
  3. Test Feed:

    • Pull to refresh
    • Verify posts from network
    • Check empty state

API Testing with curl

# Health check
curl http://localhost:3000/health

# Start verification
curl -X POST http://localhost:3000/v1/auth/phone/start \
  -H "Content-Type: application/json" \
  -d '{"phone_number": "+15551234567"}'

# Verify OTP (check API logs for code)
curl -X POST http://localhost:3000/v1/auth/phone/verify \
  -H "Content-Type: application/json" \
  -d '{"phone_number": "+15551234567", "code": "123456"}'

# Get feed (use token from verify response)
curl http://localhost:3000/v1/feed \
  -H "Authorization: Bearer YOUR_TOKEN"

🀝 Contributing

Coding Standards

  • TypeScript strict mode - No any types without justification
  • Functional components - Use hooks, avoid class components
  • Async/await - Prefer over raw promises
  • Error handling - Always use try-catch for async operations
  • Descriptive names - Variables and functions should be self-documenting
  • Zod validation - All API inputs must be validated

Git Workflow

  1. Create feature branch from main
  2. Use conventional commits:
    • feat: Add user profile editing
    • fix: Resolve contact sync crash
    • docs: Update API documentation
    • refactor: Extract feed service
  3. Open PR with description
  4. Require review before merge

File Organization

  • Mobile: Feature-based folders in /app
  • API: Controllers (routes), services, repositories pattern
  • Shared: Types in /shared (to be created)

πŸ“ License

Currently unlicensed. All rights reserved.

πŸ™ Acknowledgments

Built with modern tools:


Built with ❀️ for authentic connections in a noisy digital world.

For questions or suggestions, open an issue or reach out to the maintainers.

About

Better Social Media

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published