sojorn/sojorn_docs/DEVELOPMENT_COMPREHENSIVE.md

21 KiB

Development & Architecture Comprehensive Guide

Overview

This guide consolidates all development, architecture, and design system documentation for the Sojorn platform, covering the philosophical foundations, technical architecture, and implementation patterns.


Architecture Philosophy

Core Principles

Sojorn's friendliness is not aspirational—it is structural. The architecture enforces behavioral philosophy through database constraints, API design, and systematic patterns that make certain behaviors impossible, not just discouraged.

1. Blocking: Complete Disappearance

Principle: When you block someone, they disappear from your world and you from theirs.

Implementation:

  • Database function: has_block_between(user_a, user_b) checks bidirectional blocks
  • API middleware prevents blocked users from:
    • Seeing each other's profiles
    • Seeing each other's posts
    • Seeing each other's follows
    • Interacting in any way

Effect: No notifications, no traces, no conflict. The system enforces separation silently.

Principle: You cannot reply to someone unless you mutually follow each other.

Implementation:

  • Database function: is_mutual_follow(user_a, user_b) verifies bidirectional following
  • Comment creation requires mutual follow relationship
  • API endpoints enforce conversation gating

Effect: Unwanted replies are impossible. Conversation is opt-in by structure.

3. Exposure: Opt-In by Default

Principle: Users choose what content they see. Filtering is private and encouraged.

Implementation:

  • All categories except general have default_off = true
  • Users must explicitly enable categories to see posts
  • Feed algorithms respect user preferences

Effect: Users control their content exposure without social pressure.


Technical Architecture

System Overview

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Flutter App   │    │   Go Backend    │    │  PostgreSQL     │
│                 │    │                 │    │                 │
│ - UI/UX         │◄──►│ - REST API      │◄──►│ - Data Store    │
│ - State Mgmt    │    │ - Business Logic│    │ - Constraints   │
│ - Navigation    │    │ - Validation    │    │ - Functions     │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         │                       │                       │
         ▼                       ▼                       ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Local Storage │    │   File System   │    │   Extensions    │
│                 │    │                 │    │                 │
│ - Secure Storage│    │ - Uploads       │    │ - PostGIS       │
│ - Cache         │    │ - Logs          │    │ - pg_trgm       │
│ - Preferences   │    │ - Temp Files    │    │ - uuid-ossp     │
└─────────────────┘    └─────────────────┘    └─────────────────┘

Backend Architecture

Layer Structure

┌─────────────────────────────────────────┐
│              API Layer                  │
│  ┌─────────────┐  ┌─────────────────┐   │
│  │   Gin       │  │   Middleware    │   │
│  │   Router    │  │   - Auth        │   │
│  │             │  │   - CORS        │   │
│  │             │  │   - Rate Limit  │   │
│  └─────────────┘  └─────────────────┘   │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│            Service Layer                │
│  ┌─────────────┐  ┌─────────────────┐   │
│  │   Business  │  │   External      │   │
│  │   Logic     │  │   Services      │   │
│  │             │  │   - FCM         │   │
│  │             │  │   - R2 Storage  │   │
│  └─────────────┘  └─────────────────┘   │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│           Repository Layer              │
│  ┌─────────────┐  ┌─────────────────┐   │
│  │   Data      │  │   Database      │   │
│  │   Access    │  │   - PostgreSQL  │   │
│  │             │  │   - Migrations  │   │
│  │             │  │   - Queries     │   │
│  └─────────────┘  └─────────────────┘   │
└─────────────────────────────────────────┘

Key Components

1. Authentication & Authorization

  • JWT-based authentication with refresh tokens
  • Role-based access control
  • Session management with secure cookies

2. Data Validation

  • Structured request/response models
  • Input sanitization and validation
  • Error handling with proper HTTP status codes

3. Business Logic Services

  • User management and relationships
  • Content moderation and filtering
  • Notification and messaging systems

4. External Integrations

  • Firebase Cloud Messaging
  • Cloudflare R2 storage
  • Email services

Database Architecture

Core Schema Design

Identity & Relationships

profiles (users, identity, settings)
├── follows (mutual relationships)
├── blocks (complete separation)
└── user_category_settings (content preferences)

Content & Engagement

posts (content, metadata)
├── post_metrics (engagement data)
├── post_likes (boosts only)
├── post_saves (private bookmarks)
└── comments (mutual-follow-only)

Moderation & Trust

reports (community moderation)
├── trust_state (harmony scoring)
└── audit_log (transparency trail)

Database Functions

Relationship Checking

-- Bidirectional blocking
CREATE OR REPLACE FUNCTION has_block_between(user_a UUID, user_b UUID)
RETURNS BOOLEAN AS $$
    SELECT EXISTS (
        SELECT 1 FROM blocks 
        WHERE (blocker_id = user_a AND blocked_id = user_b)
           OR (blocker_id = user_b AND blocked_id = user_a)
    );
$$ LANGUAGE SQL;

-- Mutual follow verification
CREATE OR REPLACE FUNCTION is_mutual_follow(user_a UUID, user_b UUID)
RETURNS BOOLEAN AS $$
    SELECT EXISTS (
        SELECT 1 FROM follows f1
        JOIN follows f2 ON f1.follower_id = f2.following_id
                      AND f1.following_id = f2.follower_id
        WHERE f1.follower_id = user_a AND f1.following_id = user_b
    );
$$ LANGUAGE SQL;

Rate Limiting

-- Posting rate limits
CREATE OR REPLACE FUNCTION can_post(user_id UUID)
RETURNS BOOLEAN AS $$
    SELECT get_post_rate_limit(user_id) > 0;
$$ LANGUAGE SQL;

Design System

Visual Philosophy

Warm, Not Overwhelming

  • Warm neutrals (beige/paper tones) instead of cold grays
  • Soft shadows, never harsh
  • Muted semantic colors that inform without alarming

Modern, Not Trendy

  • Timeless color palette
  • Classic typography hierarchy
  • Subtle animations and transitions

Text-Forward

  • Generous line height (1.6-1.65 for body text)
  • Optimized for reading, not scanning
  • Clear hierarchy without relying on color

Intentionally Slow

  • Animation durations: 300-400ms
  • Ease curves that feel deliberate
  • No jarring transitions

Color System

Background Palette

background        = #F8F7F4  // Warm off-white (like paper)
surface           = #FFFFFD  // Barely warm white
surfaceElevated   = #FFFFFF  // Pure white for cards
surfaceVariant    = #F0EFEB  // Subtle warm gray (inputs)

Semantic Colors

primary           = #6B5B95  // Soft purple (friendly authority)
secondary         = #8B7355  // Warm brown (earth tone)
success           = #6B8E6F  // Muted green (gentle confirmation)
warning           = #B8956A  // Soft amber (warm caution)
error             = #B86B6B  // Muted rose (gentle error)

Border System

borderSubtle      = #E8E6E1  // Barely visible dividers
border            = #D8D6D1  // Default borders
borderStrong      = #C8C6C1  // Emphasized borders

Typography

Font Hierarchy

// Display
displayLarge      = 32px / 48px / 300
displayMedium     = 28px / 42px / 300
displaySmall      = 24px / 36px / 300

// Headings
headlineLarge     = 20px / 30px / 400
headlineMedium    = 18px / 27px / 400
headlineSmall     = 16px / 24px / 500

// Body
bodyLarge         = 16px / 26px / 400
bodyMedium        = 14px / 22px / 400
bodySmall         = 12px / 20px / 400

// Labels
labelLarge        = 14px / 20px / 500
labelMedium       = 12px / 16px / 500
labelSmall        = 10px / 14px / 500

Typography Principles

  • Line Height: 1.6-1.65 for body text (optimized for reading)
  • Font Weight: Conservative use (300-500, rarely 700)
  • Letter Spacing: Subtle adjustments for readability
  • Color: Primary use of gray scale, semantic colors sparingly

Component System

Buttons

// Primary Button
ButtonStyle(
  backgroundColor: MaterialStateProperty.all<Color>(primary),
  foregroundColor: MaterialStateProperty.all<Color>(surface),
  elevation: MaterialStateProperty.all<double>(2),
  padding: MaterialStateProperty.all<EdgeInsets>(
    EdgeInsets.symmetric(horizontal: 24, vertical: 16)
  ),
  shape: MaterialStateProperty.all<RoundedRectangleBorder>(
    RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))
  )
)

// Secondary Button
ButtonStyle(
  backgroundColor: MaterialStateProperty.all<Color>(surfaceVariant),
  foregroundColor: MaterialStateProperty.all<Color>(primary),
  elevation: MaterialStateProperty.all<double>(1),
  // ... same padding and shape
)

Cards

Card(
  elevation: 3,
  shadowColor: Colors.black.withOpacity(0.1),
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(12)
  ),
  child: Padding(
    padding: EdgeInsets.all(16),
    child: // content
  )
)

Input Fields

InputDecoration(
  filled: true,
  fillColor: surfaceVariant,
  border: OutlineInputBorder(
    borderRadius: BorderRadius.circular(8),
    borderSide: BorderSide(color: border)
  ),
  focusedBorder: OutlineInputBorder(
    borderRadius: BorderRadius.circular(8),
    borderSide: BorderSide(color: primary, width: 2)
  ),
  contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12)
)

Development Patterns

Code Organization

Flutter Structure

lib/
├── main.dart                 # App entry point
├── config/                   # Configuration
│   ├── api_config.dart
│   └── theme_config.dart
├── models/                   # Data models
│   ├── user.dart
│   ├── post.dart
│   └── chat.dart
├── providers/                # State management
│   ├── auth_provider.dart
│   ├── feed_provider.dart
│   └── theme_provider.dart
├── services/                 # Business logic
│   ├── api_service.dart
│   ├── auth_service.dart
│   └── notification_service.dart
├── screens/                  # UI screens
│   ├── auth/
│   ├── home/
│   └── chat/
├── widgets/                  # Reusable components
│   ├── post_card.dart
│   ├── user_avatar.dart
│   └── chat_bubble.dart
└── utils/                    # Utilities
    ├── constants.dart
    ├── helpers.dart
    └── validators.dart

Go Structure

cmd/
└── api/
    └── main.go               # Application entry

internal/
├── config/                   # Configuration
│   └── config.go
├── models/                   # Data models
│   ├── user.go
│   ├── post.go
│   └── chat.go
├── handlers/                 # HTTP handlers
│   ├── auth_handler.go
│   ├── post_handler.go
│   └── chat_handler.go
├── services/                 # Business logic
│   ├── auth_service.go
│   ├── post_service.go
│   └── notification_service.go
├── repository/               # Data access
│   ├── user_repository.go
│   ├── post_repository.go
│   └── chat_repository.go
├── middleware/               # HTTP middleware
│   ├── auth.go
│   ├── cors.go
│   └── ratelimit.go
└── database/                 # Database
    ├── migrations/
    └── queries.go

State Management Patterns

Flutter Provider Pattern

// Authentication Provider
final authServiceProvider = Provider<AuthService>((ref) {
  return AuthService();
});

final currentUserProvider = Provider<User?>((ref) {
  final authService = ref.watch(authServiceProvider);
  ref.watch(authStateProvider);
  return authService.currentUser;
});

final authStateProvider = StreamProvider<AuthState>((ref) {
  final authService = ref.watch(authServiceProvider);
  return authService.authStateChanges;
});

Go Service Pattern

// Service Interface
type PostService interface {
  CreatePost(ctx context.Context, req *CreatePostRequest) (*Post, error)
  GetFeed(ctx context.Context, userID string, pagination *Pagination) ([]*Post, error)
  LikePost(ctx context.Context, userID, postID string) error
}

// Service Implementation
type postService struct {
  postRepo  repository.PostRepository
  userRepo  repository.UserRepository
  notifier  services.NotificationService
}

func NewPostService(postRepo, userRepo repository.PostRepository, notifier services.NotificationService) PostService {
  return &postService{
    postRepo: postRepo,
    userRepo: userRepo,
    notifier: notifier,
  }
}

Error Handling Patterns

Flutter Error Handling

// Result Type
class Result<T> {
  final T? data;
  final String? error;
  
  Result.success(this.data) : error = null;
  Result.error(this.error) : data = null;
  
  bool get isSuccess => error == null;
  bool get isError => error != null;
}

// Usage
Future<Result<Post>> createPost(CreatePostRequest request) async {
  try {
    final post = await apiService.createPost(request);
    return Result.success(post);
  } catch (e) {
    return Result.error(e.toString());
  }
}

Go Error Handling

// Custom Error Types
type ValidationError struct {
  Field   string
  Message string
}

func (e ValidationError) Error() string {
  return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}

// Service Error Handling
func (s *postService) CreatePost(ctx context.Context, req *CreatePostRequest) (*Post, error) {
  if err := req.Validate(); err != nil {
    return nil, ValidationError{Field: "request", Message: err.Error()}
  }
  
  post, err := s.postRepo.Create(ctx, req)
  if err != nil {
    return nil, fmt.Errorf("failed to create post: %w", err)
  }
  
  return post, nil
}

Testing Strategy

Flutter Testing

Unit Tests

// Example: Post Service Test
void main() {
  group('PostService', () {
    late MockApiService mockApiService;
    late PostService postService;
    
    setUp(() {
      mockApiService = MockApiService();
      postService = PostService(mockApiService);
    });
    
    test('createPost should return post on success', () async {
      // Arrange
      final mockPost = Post(id: '1', content: 'Test post');
      when(mockApiService.createPost(any))
          .thenAnswer((_) async => mockPost);
      
      // Act
      final result = await postService.createPost(CreatePostRequest(content: 'Test post'));
      
      // Assert
      expect(result.isSuccess, true);
      expect(result.data?.content, 'Test post');
    });
  });
}

Widget Tests

void main() {
  testWidgets('PostCard displays post content', (WidgetTester tester) async {
    // Arrange
    final post = Post(id: '1', content: 'Test post', author: User(name: 'Test User'));
    
    // Act
    await tester.pumpWidget(
      MaterialApp(
        home: PostCard(post: post),
      ),
    );
    
    // Assert
    expect(find.text('Test post'), findsOneWidget);
    expect(find.text('Test User'), findsOneWidget);
  });
}

Go Testing

Unit Tests

func TestPostService_CreatePost(t *testing.T) {
  tests := []struct {
    name    string
    request *CreatePostRequest
    want    *Post
    wantErr bool
  }{
    {
      name: "valid request",
      request: &CreatePostRequest{
        UserID:  "user123",
        Content: "Test post",
      },
      want: &Post{
        ID:      "post123",
        UserID:  "user123",
        Content: "Test post",
      },
      wantErr: false,
    },
    {
      name: "invalid request",
      request: &CreatePostRequest{
        UserID:  "",
        Content: "Test post",
      },
      wantErr: true,
    },
  }
  
  for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
      mockRepo := &MockPostRepository{}
      service := NewPostService(mockRepo, nil, nil)
      
      if !tt.wantErr {
        mockRepo.On("Create", mock.Anything, tt.request).Return(tt.want, nil)
      }
      
      got, err := service.CreatePost(context.Background(), tt.request)
      
      if tt.wantErr {
        assert.Error(t, err)
      } else {
        assert.NoError(t, err)
        assert.Equal(t, tt.want, got)
      }
      
      mockRepo.AssertExpectations(t)
    })
  }
}

Performance Optimization

Flutter Performance

Key Optimizations

  1. Const Constructors: Use const for immutable widgets
  2. Lazy Loading: Implement ListView.builder for large lists
  3. Image Caching: Use cached_network_image for remote images
  4. State Management: Minimize rebuilds with selective providers

Example: Optimized List View

ListView.builder(
  itemCount: posts.length,
  itemBuilder: (context, index) {
    return PostCard(
      key: ValueKey(posts[index].id),
      post: posts[index],
    );
  },
)

Go Performance

Database Optimizations

  1. Connection Pooling: Configure appropriate pool sizes
  2. Query Optimization: Use prepared statements and proper indexes
  3. Caching: Implement Redis for frequently accessed data

Example: Database Configuration

config, err := pgxpool.ParseConfig(databaseURL)
if err != nil {
  return nil, err
}

config.MaxConns = 25
config.MinConns = 5
config.MaxConnLifetime = time.Hour
config.HealthCheckPeriod = time.Minute * 5

pool, err := pgxpool.NewWithConfig(context.Background(), config)

Security Considerations

Authentication Security

  • JWT tokens with proper expiration
  • Secure token storage (FlutterSecureStorage)
  • Refresh token rotation
  • Rate limiting on auth endpoints

Data Protection

  • Input validation and sanitization
  • SQL injection prevention with parameterized queries
  • XSS protection in web views
  • CSRF protection for state-changing operations

Privacy Protection

  • Data minimization in API responses
  • Anonymous analytics collection
  • User data export and deletion capabilities
  • GDPR compliance considerations

Deployment Architecture

Production Stack

Internet
    ↓
Nginx (SSL Termination, Static Files)
    ↓
Go Backend (API, Business Logic)
    ↓
PostgreSQL (Data, PostGIS)
    ↓
File System (Uploads) / Cloudflare R2

Environment Configuration

  • Development: Local PostgreSQL, mock services
  • Staging: Production-like environment with test data
  • Production: Full stack with monitoring and backups

Monitoring & Observability

  • Application Metrics: Request latency, error rates, user activity
  • Infrastructure Metrics: CPU, memory, disk usage
  • Database Metrics: Query performance, connection pool status
  • Business Metrics: User engagement, content creation rates

Last Updated: January 30, 2026 Version: 1.0 Next Review: February 15, 2026