Skip to main content

Chapter 22: Social Networks and Graph Applications

Overview

Social networks and graph-based applications are perfect use cases for Vektagraf's native graph capabilities combined with vector search. This chapter demonstrates how to build sophisticated social platforms with relationship modeling, friend recommendations, community detection, activity feeds, and privacy-preserving social graph analytics.

Learning Objectives

  • Model complex social graphs with relationships and properties
  • Implement friend recommendation algorithms using graph traversal and vector similarity
  • Build community detection and social clustering systems
  • Create real-time activity feeds and notification systems
  • Design privacy controls and social graph analytics
  • Optimize graph queries for social network scale

Prerequisites

  • Completed Chapters 1-6 (Foundations and Core Features)
  • Understanding of graph theory and social network concepts
  • Knowledge of vector embeddings and similarity search

Core Concepts

Social Graph Modeling

Vektagraf treats relationships as first-class objects, making social graph modeling natural:

class User extends VektaObject {
  late String username;
  late String email;
  late List<double> interestVector;    // User's interest embedding
  late Map<String, dynamic> profile;
  late List<String> followers;
  late List<String> following;
}

class Post extends VektaObject {
  late String content;
  late String authorId;
  late List<double> contentVector;     // Post content embedding
  late List<String> tags;
  late int likes;
  late int shares;
}

// Relationships are explicit objects
class Friendship extends VektaObject {
  late String userId1;
  late String userId2;
  late String status; // pending, accepted, blocked
  late DateTime createdAt;
  late double strength; // Relationship strength score
}

Graph Traversal Patterns

Vektagraf provides natural graph traversal syntax:

// Find friends of friends
final friendsOfFriends = await user
    .traverse('friendships')
    .traverse('friendships')
    .where('id', notEquals: user.id)
    .find();

// Multi-hop traversal with conditions
final recommendations = await user
    .traverse('friendships', where: {'status': 'accepted'})
    .traverse('posts', limit: 10)
    .where('createdAt', greaterThan: DateTime.now().subtract(Duration(days: 7)))
    .find();

Practical Examples

Complete Social Network Implementation

Let's build a comprehensive social networking platform:

1. Schema Definition

{
  "name": "SocialNetworkPlatform",
  "version": "1.0.0",
  "objects": {
    "User": {
      "properties": {
        "username": {"type": "string", "required": true, "unique": true},
        "email": {"type": "string", "required": true, "unique": true},
        "displayName": {"type": "string", "required": true},
        "bio": {"type": "string"},
        "avatar": {"type": "string"},
        "interestVector": {
          "type": "vector",
          "dimensions": 128,
          "algorithm": "hnsw",
          "distance": "cosine"
        },
        "profile": {
          "type": "object",
          "properties": {
            "location": {"type": "string"},
            "website": {"type": "string"},
            "birthDate": {"type": "date"},
            "occupation": {"type": "string"},
            "interests": {"type": "array", "items": {"type": "string"}},
            "languages": {"type": "array", "items": {"type": "string"}}
          }
        },
        "privacy": {
          "type": "object",
          "properties": {
            "profileVisibility": {"type": "string", "enum": ["public", "friends", "private"], "default": "public"},
            "postVisibility": {"type": "string", "enum": ["public", "friends", "private"], "default": "friends"},
            "allowFriendRequests": {"type": "boolean", "default": true},
            "showOnlineStatus": {"type": "boolean", "default": true}
          }
        },
        "stats": {
          "type": "object",
          "properties": {
            "followerCount": {"type": "integer", "default": 0},
            "followingCount": {"type": "integer", "default": 0},
            "postCount": {"type": "integer", "default": 0},
            "reputation": {"type": "number", "default": 0.0}
          }
        },
        "lastActive": {"type": "datetime"},
        "createdAt": {"type": "datetime", "required": true}
      }
    },
    "Post": {
      "properties": {
        "content": {"type": "text", "required": true},
        "authorId": {"type": "string", "required": true},
        "contentVector": {
          "type": "vector",
          "dimensions": 128,
          "algorithm": "hnsw",
          "distance": "cosine"
        },
        "type": {"type": "string", "enum": ["text", "image", "video", "link", "poll"], "default": "text"},
        "media": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "url": {"type": "string"},
              "type": {"type": "string"},
              "thumbnail": {"type": "string"}
            }
          }
        },
        "tags": {"type": "array", "items": {"type": "string"}},
        "mentions": {"type": "array", "items": {"type": "string"}},
        "visibility": {"type": "string", "enum": ["public", "friends", "private"], "default": "friends"},
        "engagement": {
          "type": "object",
          "properties": {
            "likes": {"type": "integer", "default": 0},
            "shares": {"type": "integer", "default": 0},
            "comments": {"type": "integer", "default": 0},
            "views": {"type": "integer", "default": 0}
          }
        },
        "location": {"type": "string"},
        "createdAt": {"type": "datetime", "required": true},
        "updatedAt": {"type": "datetime"}
      }
    },
    "Friendship": {
      "properties": {
        "requesterId": {"type": "string", "required": true},
        "addresseeId": {"type": "string", "required": true},
        "status": {"type": "string", "enum": ["pending", "accepted", "declined", "blocked"], "required": true},
        "strength": {"type": "number", "default": 0.0},
        "mutualFriends": {"type": "integer", "default": 0},
        "interactionScore": {"type": "number", "default": 0.0},
        "createdAt": {"type": "datetime", "required": true},
        "updatedAt": {"type": "datetime"}
      }
    },
    "Follow": {
      "properties": {
        "followerId": {"type": "string", "required": true},
        "followeeId": {"type": "string", "required": true},
        "createdAt": {"type": "datetime", "required": true}
      }
    },
    "Community": {
      "properties": {
        "name": {"type": "string", "required": true},
        "description": {"type": "string"},
        "category": {"type": "string"},
        "memberIds": {"type": "array", "items": {"type": "string"}},
        "adminIds": {"type": "array", "items": {"type": "string"}},
        "interestVector": {
          "type": "vector",
          "dimensions": 128,
          "algorithm": "hnsw",
          "distance": "cosine"
        },
        "privacy": {"type": "string", "enum": ["public", "private", "invite_only"], "default": "public"},
        "stats": {
          "type": "object",
          "properties": {
            "memberCount": {"type": "integer", "default": 0},
            "postCount": {"type": "integer", "default": 0},
            "activityScore": {"type": "number", "default": 0.0}
          }
        },
        "createdAt": {"type": "datetime", "required": true}
      }
    },
    "Activity": {
      "properties": {
        "userId": {"type": "string", "required": true},
        "type": {"type": "string", "enum": ["post", "like", "comment", "share", "friend_request", "join_community"], "required": true},
        "targetId": {"type": "string", "required": true},
        "targetType": {"type": "string", "enum": ["user", "post", "community"], "required": true},
        "metadata": {"type": "object"},
        "visibility": {"type": "string", "enum": ["public", "friends", "private"], "default": "friends"},
        "timestamp": {"type": "datetime", "required": true}
      }
    }
  },
  "relationships": {
    "UserPosts": {
      "from": "User",
      "to": "Post",
      "type": "one_to_many",
      "foreignKey": "authorId"
    },
    "UserFriendships": {
      "from": "User",
      "to": "Friendship",
      "type": "many_to_many",
      "conditions": ["requesterId = User.id OR addresseeId = User.id"]
    },
    "UserFollows": {
      "from": "User",
      "to": "Follow",
      "type": "one_to_many",
      "foreignKey": "followerId"
    },
    "UserActivities": {
      "from": "User",
      "to": "Activity",
      "type": "one_to_many",
      "foreignKey": "userId"
    }
  }
}

2. Friend Recommendation System

class FriendRecommendationEngine {
  final VektaDatabase db;
  final EmbeddingService embeddingService;
  
  FriendRecommendationEngine(this.db, this.embeddingService);
  
  /// Generate friend recommendations using multiple algorithms
  Future<List<FriendRecommendation>> generateRecommendations(
    String userId, {
    int limit = 20,
  }) async {
    final user = await db.users.findById(userId);
    if (user == null) return [];
    
    // Get recommendations from different algorithms
    final mutualFriends = await _getMutualFriendsRecommendations(user, limit);
    final interestBased = await _getInterestBasedRecommendations(user, limit);
    final networkBased = await _getNetworkBasedRecommendations(user, limit);
    final locationBased = await _getLocationBasedRecommendations(user, limit ~/ 4);
    
    // Combine and rank recommendations
    final combined = _combineRecommendations([
      mutualFriends,
      interestBased,
      networkBased,
      locationBased,
    ], weights: [0.4, 0.3, 0.2, 0.1]);
    
    // Filter out existing friends and blocked users
    final filtered = await _filterExistingConnections(combined, userId);
    
    return filtered.take(limit).toList();
  }
  
  /// Recommendations based on mutual friends
  Future<List<FriendRecommendation>> _getMutualFriendsRecommendations(
    User user,
    int limit,
  ) async {
    // Get user's friends
    final friendships = await db.friendships
        .where('status', 'accepted')
        .where(Query.or([
          Query.where('requesterId', user.id),
          Query.where('addresseeId', user.id),
        ]))
        .find();
    
    final friendIds = friendships.map((f) => 
        f.requesterId == user.id ? f.addresseeId : f.requesterId).toList();
    
    if (friendIds.isEmpty) return [];
    
    // Find friends of friends
    final friendsOfFriends = await db.friendships
        .where('status', 'accepted')
        .where(Query.or([
          Query.where('requesterId', whereIn: friendIds),
          Query.where('addresseeId', whereIn: friendIds),
        ]))
        .find();
    
    // Count mutual friends for each candidate
    final mutualCounts = <String, int>{};
    final mutualFriendsList = <String, List<String>>{};
    
    for (final friendship in friendsOfFriends) {
      final candidateId = friendIds.contains(friendship.requesterId) 
          ? friendship.addresseeId 
          : friendship.requesterId;
      
      if (candidateId != user.id && !friendIds.contains(candidateId)) {
        mutualCounts[candidateId] = (mutualCounts[candidateId] ?? 0) + 1;
        mutualFriendsList[candidateId] = 
            (mutualFriendsList[candidateId] ?? [])..add(
                friendIds.contains(friendship.requesterId) 
                    ? friendship.requesterId 
                    : friendship.addresseeId);
      }
    }
    
    // Sort by mutual friend count
    final sortedCandidates = mutualCounts.entries.toList()
      ..sort((a, b) => b.value.compareTo(a.value));
    
    final recommendations = <FriendRecommendation>[];
    for (final entry in sortedCandidates.take(limit)) {
      final candidate = await db.users.findById(entry.key);
      if (candidate != null) {
        recommendations.add(FriendRecommendation(
          user: candidate,
          score: entry.value.toDouble() / friendIds.length,
          algorithm: 'mutual_friends',
          reason: '${entry.value} mutual friends',
          mutualFriends: mutualFriendsList[entry.key] ?? [],
        ));
      }
    }
    
    return recommendations;
  }
  
  /// Recommendations based on interest similarity
  Future<List<FriendRecommendation>> _getInterestBasedRecommendations(
    User user,
    int limit,
  ) async {
    if (user.interestVector.isEmpty) {
      await _updateUserInterestVector(user);
    }
    
    // Find users with similar interests
    final similarUsers = await db.users
        .vectorSearch('interestVector', user.interestVector, limit: limit * 2)
        .where('id', notEquals: user.id)
        .find();
    
    final recommendations = <FriendRecommendation>[];
    for (final candidate in similarUsers) {
      final similarity = _calculateCosineSimilarity(
        user.interestVector,
        candidate.interestVector,
      );
      
      if (similarity > 0.6) {
        recommendations.add(FriendRecommendation(
          user: candidate,
          score: similarity,
          algorithm: 'interest_similarity',
          reason: 'Similar interests',
          commonInterests: _findCommonInterests(user, candidate),
        ));
      }
    }
    
    recommendations.sort((a, b) => b.score.compareTo(a.score));
    return recommendations.take(limit).toList();
  }
  
  /// Network-based recommendations using graph algorithms
  Future<List<FriendRecommendation>> _getNetworkBasedRecommendations(
    User user,
    int limit,
  ) async {
    // Use PageRank-like algorithm to find influential users in network
    final networkScores = await _calculateNetworkInfluence(user.id);
    
    final recommendations = <FriendRecommendation>[];
    for (final entry in networkScores.entries.take(limit)) {
      final candidate = await db.users.findById(entry.key);
      if (candidate != null) {
        recommendations.add(FriendRecommendation(
          user: candidate,
          score: entry.value,
          algorithm: 'network_influence',
          reason: 'Popular in your network',
        ));
      }
    }
    
    return recommendations;
  }
  
  /// Update user interest vector based on activities
  Future<void> _updateUserInterestVector(User user) async {
    // Get user's recent posts and interactions
    final posts = await db.posts
        .where('authorId', user.id)
        .orderBy('createdAt', descending: true)
        .limit(50)
        .find();
    
    final activities = await db.activities
        .where('userId', user.id)
        .where('type', whereIn: ['like', 'comment', 'share'])
        .orderBy('timestamp', descending: true)
        .limit(100)
        .find();
    
    // Combine content from posts and liked content
    final contentTexts = <String>[];
    
    // Add user's own posts
    for (final post in posts) {
      contentTexts.add(post.content);
    }
    
    // Add content from liked posts
    final likedPostIds = activities
        .where((a) => a.type == 'like' && a.targetType == 'post')
        .map((a) => a.targetId)
        .toList();
    
    if (likedPostIds.isNotEmpty) {
      final likedPosts = await db.posts
          .where('id', whereIn: likedPostIds)
          .find();
      
      for (final post in likedPosts) {
        contentTexts.add(post.content);
      }
    }
    
    // Generate interest vector from combined content
    if (contentTexts.isNotEmpty) {
      final combinedContent = contentTexts.join(' ');
      user.interestVector = await embeddingService.generateEmbedding(combinedContent);
      await db.users.save(user);
    }
  }
}

3. Community Detection and Management

class CommunityDetectionEngine {
  final VektaDatabase db;
  
  CommunityDetectionEngine(this.db);
  
  /// Detect communities using graph clustering algorithms
  Future<List<Community>> detectCommunities({
    int minSize = 5,
    double threshold = 0.3,
  }) async {
    // Get all friendships
    final friendships = await db.friendships
        .where('status', 'accepted')
        .find();
    
    // Build adjacency graph
    final graph = _buildFriendshipGraph(friendships);
    
    // Apply Louvain algorithm for community detection
    final communities = await _louvainClustering(graph, threshold);
    
    // Filter communities by minimum size
    final validCommunities = communities
        .where((c) => c.memberIds.length >= minSize)
        .toList();
    
    // Create community objects
    final results = <Community>[];
    for (final communityData in validCommunities) {
      final community = await _createCommunity(communityData);
      results.add(community);
    }
    
    return results;
  }
  
  /// Suggest communities for user to join
  Future<List<CommunityRecommendation>> suggestCommunities(
    String userId, {
    int limit = 10,
  }) async {
    final user = await db.users.findById(userId);
    if (user == null) return [];
    
    // Get user's current communities
    final currentCommunities = await db.communities
        .where('memberIds', arrayContains: userId)
        .find();
    
    final currentCommunityIds = currentCommunities.map((c) => c.id).toSet();
    
    // Find communities with similar interests
    final interestBasedCommunities = await db.communities
        .vectorSearch('interestVector', user.interestVector, limit: limit * 2)
        .where('id', whereNotIn: currentCommunityIds.toList())
        .find();
    
    // Find communities with friends
    final friendIds = await _getUserFriendIds(userId);
    final friendBasedCommunities = await db.communities
        .where('memberIds', arrayContainsAny: friendIds)
        .where('id', whereNotIn: currentCommunityIds.toList())
        .find();
    
    // Combine and score recommendations
    final recommendations = <CommunityRecommendation>[];
    
    // Interest-based recommendations
    for (final community in interestBasedCommunities) {
      final similarity = _calculateCosineSimilarity(
        user.interestVector,
        community.interestVector,
      );
      
      recommendations.add(CommunityRecommendation(
        community: community,
        score: similarity,
        reason: 'Matches your interests',
        algorithm: 'interest_similarity',
      ));
    }
    
    // Friend-based recommendations
    for (final community in friendBasedCommunities) {
      final mutualFriends = community.memberIds
          .where((id) => friendIds.contains(id))
          .length;
      
      recommendations.add(CommunityRecommendation(
        community: community,
        score: mutualFriends / friendIds.length,
        reason: '$mutualFriends friends are members',
        algorithm: 'friend_based',
        mutualFriends: mutualFriends,
      ));
    }
    
    // Remove duplicates and sort by score
    final uniqueRecommendations = <String, CommunityRecommendation>{};
    for (final rec in recommendations) {
      final existing = uniqueRecommendations[rec.community.id];
      if (existing == null || rec.score > existing.score) {
        uniqueRecommendations[rec.community.id] = rec;
      }
    }
    
    final result = uniqueRecommendations.values.toList()
      ..sort((a, b) => b.score.compareTo(a.score));
    
    return result.take(limit).toList();
  }
  
  /// Create community from detected cluster
  Future<Community> _createCommunity(CommunityCluster cluster) async {
    // Calculate community interest vector
    final memberVectors = <List<double>>[];
    for (final memberId in cluster.memberIds) {
      final user = await db.users.findById(memberId);
      if (user != null && user.interestVector.isNotEmpty) {
        memberVectors.add(user.interestVector);
      }
    }
    
    final interestVector = _calculateCentroid(memberVectors);
    
    // Generate community name based on common interests
    final communityName = await _generateCommunityName(cluster.memberIds);
    
    final community = Community()
      ..name = communityName
      ..description = 'Auto-detected community based on social connections'
      ..memberIds = cluster.memberIds
      ..adminIds = [cluster.memberIds.first] // First member as admin
      ..interestVector = interestVector
      ..privacy = 'public'
      ..stats = {
        'memberCount': cluster.memberIds.length,
        'postCount': 0,
        'activityScore': cluster.cohesion,
      }
      ..createdAt = DateTime.now();
    
    await db.communities.save(community);
    return community;
  }
  
  /// Apply Louvain algorithm for community detection
  Future<List<CommunityCluster>> _louvainClustering(
    Map<String, Set<String>> graph,
    double threshold,
  ) async {
    // Initialize each node as its own community
    final communities = <String, String>{};
    for (final node in graph.keys) {
      communities[node] = node;
    }
    
    bool improved = true;
    while (improved) {
      improved = false;
      
      for (final node in graph.keys) {
        final currentCommunity = communities[node]!;
        String bestCommunity = currentCommunity;
        double bestGain = 0.0;
        
        // Check neighboring communities
        final neighbors = graph[node] ?? {};
        final neighborCommunities = neighbors
            .map((n) => communities[n]!)
            .toSet();
        
        for (final neighborCommunity in neighborCommunities) {
          if (neighborCommunity != currentCommunity) {
            final gain = _calculateModularityGain(
              node, currentCommunity, neighborCommunity, graph, communities);
            
            if (gain > bestGain && gain > threshold) {
              bestGain = gain;
              bestCommunity = neighborCommunity;
            }
          }
        }
        
        if (bestCommunity != currentCommunity) {
          communities[node] = bestCommunity;
          improved = true;
        }
      }
    }
    
    // Group nodes by community
    final communityGroups = <String, List<String>>{};
    for (final entry in communities.entries) {
      communityGroups[entry.value] = 
          (communityGroups[entry.value] ?? [])..add(entry.key);
    }
    
    // Create community clusters
    return communityGroups.values.map((memberIds) => CommunityCluster(
      memberIds: memberIds,
      cohesion: _calculateCohesion(memberIds, graph),
    )).toList();
  }
}

4. Activity Feed and Notification System

class ActivityFeedManager {
  final VektaDatabase db;
  final NotificationService notificationService;
  
  ActivityFeedManager(this.db, this.notificationService);
  
  /// Generate personalized activity feed
  Future<List<FeedItem>> generateFeed(
    String userId, {
    int limit = 50,
    String? cursor,
  }) async {
    final user = await db.users.findById(userId);
    if (user == null) return [];
    
    // Get user's social graph
    final friendIds = await _getUserFriendIds(userId);
    final followingIds = await _getUserFollowingIds(userId);
    final communityIds = await _getUserCommunityIds(userId);
    
    // Get activities from social network
    final activities = await _getNetworkActivities(
      friendIds, followingIds, communityIds, limit * 2, cursor);
    
    // Score and rank activities
    final scoredActivities = await _scoreActivities(activities, user);
    
    // Apply diversity and freshness filters
    final diverseActivities = _applyDiversityFilter(scoredActivities);
    
    // Convert to feed items
    final feedItems = await _convertToFeedItems(diverseActivities);
    
    return feedItems.take(limit).toList();
  }
  
  /// Record user activity
  Future<void> recordActivity(
    String userId,
    String type,
    String targetId,
    String targetType, {
    Map<String, dynamic>? metadata,
    String visibility = 'friends',
  }) async {
    final activity = Activity()
      ..userId = userId
      ..type = type
      ..targetId = targetId
      ..targetType = targetType
      ..metadata = metadata ?? {}
      ..visibility = visibility
      ..timestamp = DateTime.now();
    
    await db.activities.save(activity);
    
    // Trigger notifications for relevant users
    await _triggerActivityNotifications(activity);
    
    // Update user stats
    await _updateUserStats(userId, type);
  }
  
  /// Score activities for personalized ranking
  Future<List<ScoredActivity>> _scoreActivities(
    List<Activity> activities,
    User user,
  ) async {
    final scoredActivities = <ScoredActivity>[];
    
    for (final activity in activities) {
      double score = 0.0;
      
      // Recency score (exponential decay)
      final hoursSince = DateTime.now().difference(activity.timestamp).inHours;
      final recencyScore = math.exp(-hoursSince / 24.0); // 24-hour half-life
      score += recencyScore * 0.3;
      
      // Relationship strength score
      final relationshipScore = await _getRelationshipStrength(user.id, activity.userId);
      score += relationshipScore * 0.4;
      
      // Content relevance score
      if (activity.type == 'post') {
        final post = await db.posts.findById(activity.targetId);
        if (post != null && post.contentVector.isNotEmpty && user.interestVector.isNotEmpty) {
          final relevanceScore = _calculateCosineSimilarity(
            user.interestVector,
            post.contentVector,
          );
          score += relevanceScore * 0.2;
        }
      }
      
      // Engagement score
      final engagementScore = await _getActivityEngagement(activity);
      score += engagementScore * 0.1;
      
      scoredActivities.add(ScoredActivity(
        activity: activity,
        score: score,
      ));
    }
    
    scoredActivities.sort((a, b) => b.score.compareTo(a.score));
    return scoredActivities;
  }
  
  /// Apply diversity filter to avoid echo chambers
  List<ScoredActivity> _applyDiversityFilter(List<ScoredActivity> activities) {
    final diverseActivities = <ScoredActivity>[];
    final seenAuthors = <String>{};
    final seenTypes = <String, int>{};
    
    for (final activity in activities) {
      // Limit activities per author
      if (seenAuthors.contains(activity.activity.userId)) {
        if (seenAuthors.length > activities.length * 0.3) continue;
      }
      
      // Limit activities per type
      final typeCount = seenTypes[activity.activity.type] ?? 0;
      if (typeCount > activities.length * 0.4) continue;
      
      diverseActivities.add(activity);
      seenAuthors.add(activity.activity.userId);
      seenTypes[activity.activity.type] = typeCount + 1;
    }
    
    return diverseActivities;
  }
  
  /// Trigger notifications for activity
  Future<void> _triggerActivityNotifications(Activity activity) async {
    switch (activity.type) {
      case 'friend_request':
        await notificationService.sendNotification(
          activity.targetId,
          NotificationType.friendRequest,
          {
            'requesterId': activity.userId,
            'message': 'sent you a friend request',
          },
        );
        break;
        
      case 'like':
        if (activity.targetType == 'post') {
          final post = await db.posts.findById(activity.targetId);
          if (post != null) {
            await notificationService.sendNotification(
              post.authorId,
              NotificationType.postLike,
              {
                'likerId': activity.userId,
                'postId': activity.targetId,
                'message': 'liked your post',
              },
            );
          }
        }
        break;
        
      case 'comment':
        if (activity.targetType == 'post') {
          final post = await db.posts.findById(activity.targetId);
          if (post != null) {
            await notificationService.sendNotification(
              post.authorId,
              NotificationType.postComment,
              {
                'commenterId': activity.userId,
                'postId': activity.targetId,
                'message': 'commented on your post',
              },
            );
          }
        }
        break;
    }
  }
}

5. Privacy Controls and Social Graph Analytics

class SocialPrivacyManager {
  final VektaDatabase db;
  
  SocialPrivacyManager(this.db);
  
  /// Check if user can see another user's content
  Future<bool> canViewContent(
    String viewerId,
    String contentOwnerId,
    String contentType,
    String visibility,
  ) async {
    // Public content is always visible
    if (visibility == 'public') return true;
    
    // Owner can always see their own content
    if (viewerId == contentOwnerId) return true;
    
    // Private content only visible to owner
    if (visibility == 'private') return false;
    
    // Friends-only content requires friendship
    if (visibility == 'friends') {
      return await _areFriends(viewerId, contentOwnerId);
    }
    
    return false;
  }
  
  /// Apply privacy filtering to social graph queries
  Future<List<User>> filterUsersByPrivacy(
    List<User> users,
    String viewerId,
  ) async {
    final filteredUsers = <User>[];
    
    for (final user in users) {
      if (await canViewContent(
          viewerId, user.id, 'profile', user.privacy['profileVisibility'])) {
        filteredUsers.add(user);
      }
    }
    
    return filteredUsers;
  }
  
  /// Anonymize social graph data for analytics
  Future<Map<String, dynamic>> getAnonymizedGraphMetrics() async {
    final friendships = await db.friendships
        .where('status', 'accepted')
        .find();
    
    final users = await db.users.find();
    
    // Calculate network metrics without exposing individual data
    final metrics = {
      'totalUsers': users.length,
      'totalFriendships': friendships.length,
      'averageFriends': friendships.length * 2 / users.length,
      'networkDensity': _calculateNetworkDensity(users.length, friendships.length),
      'clusteringCoefficient': await _calculateClusteringCoefficient(),
      'communityCount': await _countCommunities(),
    };
    
    return metrics;
  }
  
  /// Generate privacy-preserving user insights
  Future<Map<String, dynamic>> getUserInsights(String userId) async {
    final user = await db.users.findById(userId);
    if (user == null) return {};
    
    // Only return aggregated, non-identifying insights
    final friendships = await db.friendships
        .where('status', 'accepted')
        .where(Query.or([
          Query.where('requesterId', userId),
          Query.where('addresseeId', userId),
        ]))
        .find();
    
    final activities = await db.activities
        .where('userId', userId)
        .where('timestamp', greaterThan: DateTime.now().subtract(Duration(days: 30)))
        .find();
    
    return {
      'socialScore': _calculateSocialScore(friendships, activities),
      'activityLevel': _categorizeActivityLevel(activities.length),
      'networkPosition': await _calculateNetworkPosition(userId),
      'communityInvolvement': await _calculateCommunityInvolvement(userId),
    };
  }
  
  Future<bool> _areFriends(String userId1, String userId2) async {
    final friendship = await db.friendships
        .where('status', 'accepted')
        .where(Query.or([
          Query.and([
            Query.where('requesterId', userId1),
            Query.where('addresseeId', userId2),
          ]),
          Query.and([
            Query.where('requesterId', userId2),
            Query.where('addresseeId', userId1),
          ]),
        ]))
        .findFirst();
    
    return friendship != null;
  }
}

Best Practices

1. Graph Query Optimization

class GraphQueryOptimizer {
  /// Optimize friend-of-friend queries
  Future<List<User>> optimizedFriendsOfFriends(String userId) async {
    // Use indexed queries instead of multiple traversals
    final friendIds = await db.friendships
        .where('status', 'accepted')
        .where(Query.or([
          Query.where('requesterId', userId),
          Query.where('addresseeId', userId),
        ]))
        .select(['requesterId', 'addresseeId'])
        .find()
        .then((friendships) => friendships.map((f) => 
            f.requesterId == userId ? f.addresseeId : f.requesterId).toList());
    
    if (friendIds.isEmpty) return [];
    
    // Single query for friends of friends
    final friendsOfFriends = await db.friendships
        .where('status', 'accepted')
        .where(Query.or([
          Query.where('requesterId', whereIn: friendIds),
          Query.where('addresseeId', whereIn: friendIds),
        ]))
        .find();
    
    final candidateIds = <String>{};
    for (final friendship in friendsOfFriends) {
      final candidateId = friendIds.contains(friendship.requesterId)
          ? friendship.addresseeId
          : friendship.requesterId;
      
      if (candidateId != userId && !friendIds.contains(candidateId)) {
        candidateIds.add(candidateId);
      }
    }
    
    return await db.users
        .where('id', whereIn: candidateIds.toList())
        .find();
  }
  
  /// Batch load social graph data
  Future<Map<String, List<String>>> batchLoadFriendships(
    List<String> userIds,
  ) async {
    final friendships = await db.friendships
        .where('status', 'accepted')
        .where(Query.or([
          Query.where('requesterId', whereIn: userIds),
          Query.where('addresseeId', whereIn: userIds),
        ]))
        .find();
    
    final userFriends = <String, List<String>>{};
    
    for (final friendship in friendships) {
      // Add friendship for requester
      if (userIds.contains(friendship.requesterId)) {
        userFriends[friendship.requesterId] = 
            (userFriends[friendship.requesterId] ?? [])..add(friendship.addresseeId);
      }
      
      // Add friendship for addressee
      if (userIds.contains(friendship.addresseeId)) {
        userFriends[friendship.addresseeId] = 
            (userFriends[friendship.addresseeId] ?? [])..add(friendship.requesterId);
      }
    }
    
    return userFriends;
  }
}

2. Real-Time Updates and Caching

class SocialGraphCache {
  final Map<String, List<String>> _friendsCache = {};
  final Map<String, DateTime> _cacheTimestamps = {};
  final Duration _cacheExpiry = Duration(minutes: 30);
  
  /// Get cached friends list
  Future<List<String>> getCachedFriends(String userId) async {
    final timestamp = _cacheTimestamps[userId];
    
    if (timestamp != null && 
        DateTime.now().difference(timestamp) < _cacheExpiry &&
        _friendsCache.containsKey(userId)) {
      return _friendsCache[userId]!;
    }
    
    // Load fresh data
    final friends = await _loadUserFriends(userId);
    _friendsCache[userId] = friends;
    _cacheTimestamps[userId] = DateTime.now();
    
    return friends;
  }
  
  /// Invalidate cache when friendships change
  Future<void> invalidateFriendsCache(String userId1, String userId2) async {
    _friendsCache.remove(userId1);
    _friendsCache.remove(userId2);
    _cacheTimestamps.remove(userId1);
    _cacheTimestamps.remove(userId2);
  }
  
  /// Preload cache for active users
  Future<void> preloadActiveUserCaches() async {
    final activeUsers = await db.users
        .where('lastActive', greaterThan: DateTime.now().subtract(Duration(hours: 24)))
        .select(['id'])
        .find();
    
    // Batch load friendships for active users
    final userIds = activeUsers.map((u) => u.id).toList();
    final friendships = await batchLoadFriendships(userIds);
    
    // Update cache
    for (final entry in friendships.entries) {
      _friendsCache[entry.key] = entry.value;
      _cacheTimestamps[entry.key] = DateTime.now();
    }
  }
}

Advanced Topics

Social Graph Machine Learning

class SocialGraphML {
  /// Predict friendship likelihood using graph features
  Future<double> predictFriendshipProbability(
    String userId1,
    String userId2,
  ) async {
    final features = await _extractGraphFeatures(userId1, userId2);
    
    // Use trained model to predict friendship probability
    final probability = await _friendshipModel.predict(features);
    
    return probability;
  }
  
  /// Extract graph-based features for ML models
  Future<Map<String, double>> _extractGraphFeatures(
    String userId1,
    String userId2,
  ) async {
    final user1 = await db.users.findById(userId1);
    final user2 = await db.users.findById(userId2);
    
    if (user1 == null || user2 == null) return {};
    
    // Calculate various graph features
    final mutualFriends = await _countMutualFriends(userId1, userId2);
    final commonCommunities = await _countCommonCommunities(userId1, userId2);
    final interestSimilarity = _calculateCosineSimilarity(
      user1.interestVector,
      user2.interestVector,
    );
    
    final shortestPath = await _calculateShortestPath(userId1, userId2);
    final clusteringCoefficient1 = await _calculateUserClusteringCoefficient(userId1);
    const clusteringCoefficient2 = await _calculateUserClusteringCoefficient(userId2);
    
    return {
      'mutual_friends': mutualFriends.toDouble(),
      'common_communities': commonCommunities.toDouble(),
      'interest_similarity': interestSimilarity,
      'shortest_path': shortestPath.toDouble(),
      'clustering_coefficient_1': clusteringCoefficient1,
      'clustering_coefficient_2': clusteringCoefficient2,
      'age_difference': (user1.profile['age'] - user2.profile['age']).abs().toDouble(),
      'location_match': user1.profile['location'] == user2.profile['location'] ? 1.0 : 0.0,
    };
  }
}

Summary

This chapter demonstrated how to build sophisticated social networks and graph applications using Vektagraf's native graph capabilities. Key takeaways include:

  • Graph Modeling: Use relationships as first-class objects for complex social structures
  • Friend Recommendations: Combine multiple algorithms for accurate suggestions
  • Community Detection: Apply graph clustering algorithms to find natural communities
  • Activity Feeds: Create personalized, diverse feeds with real-time updates
  • Privacy Controls: Implement fine-grained privacy and content filtering
  • Performance: Optimize graph queries and implement effective caching strategies

Vektagraf's graph-first approach makes it particularly well-suited for social applications, as relationships are treated as first-class objects with properties and can be queried efficiently.

Next Steps

  • Chapter 23: AI/ML Integration Patterns - Learn advanced ML integration for social features
  • Part VII: Reference documentation for complete API coverage
  • Chapter 14: Performance Tuning - Optimize for social network scale
  • Graph Operations Documentation (Chapter 6)
  • Vector Search (Chapter 5)
  • Security and Privacy (Chapters 8-10)
  • Performance Optimization (Chapter 7)