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

 
 
 

Contributors