Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Stream connectivity status #36

Merged
merged 31 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c0b8f2c
chore: Remove double exlamation mark
kevinszuchet Jan 17, 2025
0e4c6df
feat: 1st approach of getting connected peers
kevinszuchet Jan 17, 2025
e16d597
Merge branch 'main' into feat/filter-friends-by-connectivity-status
kevinszuchet Jan 20, 2025
aa01f9e
fix: Include setup test in jest config
kevinszuchet Jan 20, 2025
e40a465
feat: Scheduler adapter to store connected peers in redis
kevinszuchet Jan 20, 2025
ac62672
feat: Memory cache or Redis
kevinszuchet Jan 20, 2025
04e27d1
feat: Get Friends filter by connectivity status using Redis
kevinszuchet Jan 20, 2025
4d727f8
refactor: Move component interfaces to root types file
kevinszuchet Jan 20, 2025
f723311
chore: Remove unused imports
kevinszuchet Jan 20, 2025
cc1bca6
feat: GetFriends does not filter by connectivity status
kevinszuchet Jan 20, 2025
949f67f
test: Adapt tests
kevinszuchet Jan 21, 2025
b718edc
feat: Send id of the request to subscribe, get sent and get pending
kevinszuchet Jan 21, 2025
f13608a
feat: Peer Tracking + Subscribe to Friends Status Updates
kevinszuchet Jan 21, 2025
65ac2a1
test: Peer tracking tests
kevinszuchet Jan 21, 2025
c870131
refactor: Rename of one of the base components
kevinszuchet Jan 21, 2025
d188126
refactor: Reuse common queries
kevinszuchet Jan 21, 2025
3d16f8e
test: Archipelago Stats tests
kevinszuchet Jan 21, 2025
71534b5
test: Friend Updates Streamer tests
kevinszuchet Jan 21, 2025
57687f5
test: PubSub tests
kevinszuchet Jan 21, 2025
96c86a5
style: Missing eof
kevinszuchet Jan 22, 2025
8e7a2ea
chore: Add TODO
kevinszuchet Jan 22, 2025
ec0f029
fix: Propagate error if the fetch to stats fails
kevinszuchet Jan 22, 2025
e32122d
refactor: Rename confusing db function to filterActiveFriendshipsFrom…
kevinszuchet Jan 22, 2025
e85d1d7
test: New friendship function test
kevinszuchet Jan 22, 2025
3e7ae99
chore: First README iteration
kevinszuchet Jan 22, 2025
c5c69b7
chore: Update DER
kevinszuchet Jan 22, 2025
92bbb0d
chore: Improve seq diagram
kevinszuchet Jan 22, 2025
a599ae1
chore: More readme improvements
kevinszuchet Jan 22, 2025
a38b466
chore: Some fixes in the seq diag
kevinszuchet Jan 22, 2025
8f11a70
chore: Add TODO comment
kevinszuchet Jan 22, 2025
56beb58
test: Tiny improvements on tests for more coverage
kevinszuchet Jan 22, 2025
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
1 change: 1 addition & 0 deletions .env.default
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ HTTP_SERVER_HOST=0.0.0.0
# reset metrics at 00:00UTC
WKC_METRICS_RESET_AT_NIGHT=false

NATS_URL=localhost:4222
229 changes: 229 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,232 @@
# EA Social Service

[![Coverage Status](https://coveralls.io/repos/github/decentraland/social-service-ea/badge.svg)](https://coveralls.io/github/decentraland/social-service-ea)

A microservice that handles social interactions (friendships) for Decentraland, built using the Well Known Components architecture pattern.

## Table of Contents

- [🌟 Features](#-features)
- [🏗 Architecture](#-architecture)
- [Component-Based Architecture](#component-based-architecture)
- [Database Design](#database-design)
- [Flow Diagrams](#flow-diagrams)
- [🚀 Getting Started](#-getting-started)
- [Prerequisites](#prerequisites)
- [Local Development](#local-development)
- [Environment Variables](#environment-variables)
- [🧪 Testing](#-testing)
- [Test Coverage](#test-coverage)
- [🔄 CI/CD](#-ci/cd)
- [Deployment Environments](#deployment-environments)

## 🌟 Features

- Friendship management (requests, accepts, rejects, cancellations)
- Real-time friend status updates
- Mutual friends discovery
- Online status tracking
- Integration with Archipelago for peer synchronization

## 🏗 Architecture

### Component-Based Architecture

This service follows the Well Known Components pattern, where each component is a self-contained unit with a clear interface. The main components are:

- **Database (PostgreSQL)**: Stores friendship relationships and actions
- **Cache (Redis)**: Handles temporary data and real-time status
- **RPC Server**: Manages client-server RPC communication
- **PubSub**: Handles real-time updates
- **Archipelago Stats**: Integrates with Decentraland's peer discovery system
- **Peer Tracking**: Monitors online status of users through the NATS messaging system
- **Peers Synchronization**: Synchronizes peers with the Archipelago Stats service and store them in Redis

### Database Design

```plantuml
@startuml
!define table(x) class x << (T,#FFAAAA) >>
!define primary_key(x) <u>x</u>
!define foreign_key(x) #x#
hide methods
hide stereotypes

table(friendships) {
primary_key(id): uuid
address_requester: varchar
address_requested: varchar
is_active: boolean
created_at: timestamp
updated_at: timestamp
--
indexes
..
hash(address_requester)
hash(address_requested)
btree(LOWER(address_requester))
btree(LOWER(address_requested))
}

table(friendship_actions) {
primary_key(id): uuid
foreign_key(friendship_id): uuid
action: varchar
acting_user: varchar
metadata: jsonb
timestamp: timestamp
--
indexes
..
btree(friendship_id)
}

friendships ||--|{ friendship_actions
@enduml
```

The database schema supports:

- Bidirectional friendships
- Action history tracking
- Metadata for requests
- Optimized queries with proper indexes

See migrations for details: [migrations](./src/migrations)

### Flow Diagrams

```mermaid
sequenceDiagram
participant Client
participant WebSocket
participant RPC Server
participant Redis
participant NATS
participant DB

Note over Client,DB: Connection Setup
Client->>WebSocket: WS Handshake
activate WebSocket
WebSocket-->>Client: Connection Established
Client->>WebSocket: Auth Message
WebSocket->>RPC Server: Attach Transport
activate RPC Server

Note over RPC Server,NATS: Subscriptions Setup
RPC Server->>Redis: Subscribe to updates channels
activate Redis
Note over Redis: friendship.updates
Note over Redis: friend.status.updates
RPC Server->>NATS: Subscribe to peer events
activate NATS
Note over NATS: peer.*.connected
Note over NATS: peer.*.disconnected
Note over NATS: peer.*.heartbeat

Note over Client,DB: Friendship Request Flow
Client->>RPC Server: Friend Request
RPC Server->>DB: Create Friendship Record
DB-->>RPC Server: Friendship Created
RPC Server->>DB: Record Friendship Action
RPC Server->>Redis: Publish Friendship Update
RPC Server-->>Client: Request Confirmation

Note over Client,DB: Friendship Updates Flow
Redis-->>RPC Server: Friendship Update Event
RPC Server-->>Client: Stream Friendship Updates
Note over RPC Server: (accept/cancel/reject/delete)

Note over Client,DB: Friends Lifecycle
NATS-->>Redis: Peer Heartbeat
Redis-->>RPC Server: Friend Status Update
RPC Server->>Redis: Request Cached Peers
Redis-->>RPC Server: Cached Peers
RPC Server->>DB: Request Online Friends
DB-->>RPC Server: Online Friends
RPC Server->>DB: Query Online Friends
RPC Server-->>Client: Stream Friend Status Updates
Note over RPC Server: (online/offline)

Note over Client,DB: Cleanup
Client->>WebSocket: Connection Close
WebSocket->>RPC Server: Detach Transport
RPC Server->>Redis: Unsubscribe
RPC Server->>NATS: Unsubscribe
deactivate WebSocket
deactivate RPC Server
deactivate Redis
deactivate NATS
```

## 🚀 Getting Started

### Prerequisites

- Node.js v18.20.4
- Docker and Docker Compose
- PostgreSQL
- Redis

### Local Development

1. Clone the repository
2. Install dependencies:

```bash
yarn install
```

3. Start the development environment:

```bash
docker-compose up -d
```

4. Run migrations:

```bash
yarn migrate up
```

5. Run the service:

```bash
yarn start
```

### Environment Variables

Key environment variables needed:

- `REDIS_HOST`: URL of the Redis instance
- `PG_COMPONENT_PSQL_CONNECTION_STRING`: URL of the PostgreSQL instance
- `ARCHIPELAGO_STATS_URL`: URL of the Archipelago Stats service

See `.env.default` for all available options.

## 🧪 Testing

The project uses Jest for testing. Run tests with:

```bash
yarn test
```

### Test Coverage

Coverage reports are generated in the `/coverage` directory and uploaded to Coveralls.

## 🔄 CI/CD

The project uses GitHub Actions for:

- Continuous Integration
- Docker image building
- Automated deployments to dev/prod environments
- Dependency management with Dependabot

### Deployment Environments

- **Development**: Automatic deployments on main branch
- **Production**: Manual deployments via GitHub releases
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ module.exports = {
coverageDirectory: 'coverage',
collectCoverageFrom: ['src/**/*.ts', 'src/**/*.js', '!src/migrations/**'],
testMatch: ['**/*.spec.(ts)'],
testEnvironment: 'node'
testEnvironment: 'node',
setupFilesAfterEnv: ['<rootDir>/test/setupTests.ts']
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,19 @@
},
"dependencies": {
"@dcl/platform-crypto-middleware": "^1.1.0",
"@dcl/protocol": "^1.0.0-12815643167.commit-c4162c4",
"@dcl/protocol": "https://sdk-team-cdn.decentraland.org/@dcl/protocol/branch//dcl-protocol-1.0.0-12890706635.commit-a7e4210.tgz",
"@dcl/rpc": "^1.1.2",
"@well-known-components/env-config-provider": "^1.2.0",
"@well-known-components/fetch-component": "^2.0.2",
"@well-known-components/http-server": "^2.1.0",
"@well-known-components/interfaces": "^1.4.3",
"@well-known-components/logger": "^3.1.3",
"@well-known-components/metrics": "^2.1.0",
"@well-known-components/nats-component": "^2.0.0",
"@well-known-components/pg-component": "^0.2.2",
"@well-known-components/uws-http-server": "^0.0.2",
"fp-future": "^1.0.1",
"lru-cache": "^10.4.3",
"mitt": "^3.0.1",
"redis": "^4.6.13",
"sql-template-strings": "^2.2.2",
Expand Down
34 changes: 34 additions & 0 deletions src/adapters/archipelago-stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { AppComponents, IArchipelagoStatsComponent } from '../types'
import { PEERS_CACHE_KEY } from '../utils/peers'

export async function createArchipelagoStatsComponent({
logs,
config,
fetcher,
redis
}: Pick<AppComponents, 'logs' | 'config' | 'fetcher' | 'redis'>): Promise<IArchipelagoStatsComponent> {
const logger = logs.getLogger('archipelago-stats-component')
const url = await config.getString('ARCHIPELAGO_STATS_URL')

return {
async getPeers() {
try {
const response = await fetcher.fetch(`${url}/comms/peers`)

if (!response.ok) {
throw new Error(`Error fetching peers: ${response.statusText}`)
}

const { peers } = await response.json()

return peers.map((peer: { id: string }) => peer.id)
} catch (error: any) {
logger.error(`Error fetching peers from archipelago stats: ${error.message}`)
throw error
}
},
async getPeersFromCache() {
return (await redis.get<string[]>(PEERS_CACHE_KEY)) || []
}
}
}
Loading
Loading