Skip to content

Commit

Permalink
feat: Get sent and received friendship requests optimizations (#38)
Browse files Browse the repository at this point in the history
* chore: seq diag improvements

* feat: Use the profile image url to create the full url

* feat: Get Sent and Received Friendship Requests respond with profile data

* chore: Remove unused imports

* refactor: Try with filter boolean but doesn't work

* refactor: Pick specific props from entity

* chore: Use Avatar type from schemas
  • Loading branch information
kevinszuchet authored Jan 23, 2025
1 parent 746e8d2 commit 34da607
Show file tree
Hide file tree
Showing 21 changed files with 237 additions and 152 deletions.
2 changes: 1 addition & 1 deletion .env.default
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ WKC_METRICS_RESET_AT_NIGHT=false
NATS_URL=localhost:4222

#CATALYST_CONTENT_URL_LOADBALANCER=https://peer.decentraland.org/
#CONTENT_SERVER_URL=https://peer.decentraland.org/content
#PROFILE_IMAGES_URL=https://profile-images.decentraland.org
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,12 @@ sequenceDiagram
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
NATS-->>Redis: Publish Peer Connection Update Event
Redis-->>RPC Server: Broadcast Friend Status Update Event
RPC Server->>Redis: Get Cached Peers
Redis-->>RPC Server: Cached Peers
RPC Server->>DB: Request Online Friends
DB-->>RPC Server: Online Friends
RPC Server->>DB: Query Online Friends
DB-->>RPC Server: Online Friends
RPC Server-->>Client: Stream Friend Status Updates
Note over RPC Server: (online/offline)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
},
"dependencies": {
"@dcl/platform-crypto-middleware": "^1.1.0",
"@dcl/protocol": "https://sdk-team-cdn.decentraland.org/@dcl/protocol/branch//dcl-protocol-1.0.0-12890706635.commit-a7e4210.tgz",
"@dcl/protocol": "https://sdk-team-cdn.decentraland.org/@dcl/protocol/branch//dcl-protocol-1.0.0-12916692077.commit-190ed21.tgz",
"@dcl/rpc": "^1.1.2",
"@dcl/schemas": "^15.6.0",
"@well-known-components/env-config-provider": "^1.2.0",
Expand Down
8 changes: 6 additions & 2 deletions src/adapters/rpc-server/rpc-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@ export async function createRpcServerComponent({

const getFriends = await getFriendsService({ components: { logs, db, catalystClient, config } })
const getMutualFriends = await getMutualFriendsService({ components: { logs, db, catalystClient, config } })
const getPendingFriendshipRequests = getPendingFriendshipRequestsService({ components: { logs, db } })
const getSentFriendshipRequests = getSentFriendshipRequestsService({ components: { logs, db } })
const getPendingFriendshipRequests = await getPendingFriendshipRequestsService({
components: { logs, db, catalystClient, config }
})
const getSentFriendshipRequests = await getSentFriendshipRequestsService({
components: { logs, db, catalystClient, config }
})
const upsertFriendship = upsertFriendshipService({ components: { logs, db, pubsub } })
const getFriendshipStatus = getFriendshipStatusService({ components: { logs, db } })
const subscribeToFriendshipUpdates = subscribeToFriendshipUpdatesService({ components: { logs } })
Expand Down
13 changes: 8 additions & 5 deletions src/adapters/rpc-server/services/get-friends.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ import { getPage } from '../../../utils/pagination'
import { FRIENDSHIPS_PER_PAGE } from '../constants'
import {
GetFriendsPayload,
PaginatedUsersResponse
PaginatedFriendsProfilesResponse
} from '@dcl/protocol/out-js/decentraland/social_service/v2/social_service_v2.gen'

export async function getFriendsService({
components: { logs, db, catalystClient, config }
}: RPCServiceContext<'logs' | 'db' | 'catalystClient' | 'config'>) {
const logger = logs.getLogger('get-friends-service')
const contentServerUrl = await config.requireString('CONTENT_SERVER_URL')
const profileImagesUrl = await config.requireString('PROFILE_IMAGES_URL')

return async function (request: GetFriendsPayload, context: RpcServerContext): Promise<PaginatedUsersResponse> {
return async function (
request: GetFriendsPayload,
context: RpcServerContext
): Promise<PaginatedFriendsProfilesResponse> {
const { pagination } = request
const { address: loggedUserAddress } = context

Expand All @@ -27,7 +30,7 @@ export async function getFriendsService({
const profiles = await catalystClient.getEntitiesByPointers(friends.map((friend) => friend.address))

return {
users: parseProfilesToFriends(profiles, contentServerUrl),
friends: parseProfilesToFriends(profiles, profileImagesUrl),
paginationData: {
total,
page: getPage(pagination?.limit || FRIENDSHIPS_PER_PAGE, pagination?.offset)
Expand All @@ -36,7 +39,7 @@ export async function getFriendsService({
} catch (error: any) {
logger.error(`Error getting friends: ${error.message}`)
return {
users: [],
friends: [],
paginationData: {
total: 0,
page: 1
Expand Down
13 changes: 8 additions & 5 deletions src/adapters/rpc-server/services/get-mutual-friends.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { RpcServerContext, RPCServiceContext } from '../../../types'
import { FRIENDSHIPS_PER_PAGE } from '../constants'
import {
GetMutualFriendsPayload,
PaginatedUsersResponse
PaginatedFriendsProfilesResponse
} from '@dcl/protocol/out-js/decentraland/social_service/v2/social_service_v2.gen'
import { normalizeAddress } from '../../../utils/address'
import { getPage } from '../../../utils/pagination'
Expand All @@ -12,9 +12,12 @@ export async function getMutualFriendsService({
components: { logs, db, catalystClient, config }
}: RPCServiceContext<'logs' | 'db' | 'catalystClient' | 'config'>) {
const logger = logs.getLogger('get-mutual-friends-service')
const contentServerUrl = await config.requireString('CONTENT_SERVER_URL')
const profileImagesUrl = await config.requireString('PROFILE_IMAGES_URL')

return async function (request: GetMutualFriendsPayload, context: RpcServerContext): Promise<PaginatedUsersResponse> {
return async function (
request: GetMutualFriendsPayload,
context: RpcServerContext
): Promise<PaginatedFriendsProfilesResponse> {
logger.debug(`Getting mutual friends ${context.address}<>${request.user!.address}`)

try {
Expand All @@ -30,7 +33,7 @@ export async function getMutualFriendsService({
const profiles = await catalystClient.getEntitiesByPointers(mutualFriends.map((friend) => friend.address))

return {
users: parseProfilesToFriends(profiles, contentServerUrl),
friends: parseProfilesToFriends(profiles, profileImagesUrl),
paginationData: {
total,
page: getPage(pagination?.limit || FRIENDSHIPS_PER_PAGE, pagination?.offset)
Expand All @@ -39,7 +42,7 @@ export async function getMutualFriendsService({
} catch (error: any) {
logger.error(`Error getting mutual friends: ${error.message}`)
return {
users: [],
friends: [],
paginationData: {
total: 0,
page: 1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import { parseFriendshipRequestsToFriendshipRequestResponses } from '../../../logic/friendships'
import { RpcServerContext, RPCServiceContext } from '../../../types'
import {
PaginatedFriendshipRequestsResponse,
GetFriendshipRequestsPayload
} from '@dcl/protocol/out-js/decentraland/social_service/v2/social_service_v2.gen'

export function getPendingFriendshipRequestsService({ components: { logs, db } }: RPCServiceContext<'logs' | 'db'>) {
export async function getPendingFriendshipRequestsService({
components: { logs, db, catalystClient, config }
}: RPCServiceContext<'logs' | 'db' | 'catalystClient' | 'config'>) {
const logger = logs.getLogger('get-pending-friendship-requests-service')
const profileImagesUrl = await config.requireString('PROFILE_IMAGES_URL')

return async function (
request: GetFriendshipRequestsPayload,
context: RpcServerContext
): Promise<PaginatedFriendshipRequestsResponse> {
try {
const pendingRequests = await db.getReceivedFriendshipRequests(context.address, request.pagination)
const mappedRequests = pendingRequests.map(({ id, address, timestamp, metadata }) => ({
id,
user: { address },
createdAt: new Date(timestamp).getTime(),
message: metadata?.message || ''
}))
const pendingRequestsAddresses = pendingRequests.map(({ address }) => address)

const pendingRequesterProfiles = await catalystClient.getEntitiesByPointers(pendingRequestsAddresses)
const requests = parseFriendshipRequestsToFriendshipRequestResponses(
pendingRequests,
pendingRequesterProfiles,
profileImagesUrl
)

return {
response: {
$case: 'requests',
requests: {
requests: mappedRequests
requests
}
}
}
Expand Down
25 changes: 16 additions & 9 deletions src/adapters/rpc-server/services/get-sent-friendship-requests.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
import { parseFriendshipRequestsToFriendshipRequestResponses } from '../../../logic/friendships'
import { RpcServerContext, RPCServiceContext } from '../../../types'
import {
PaginatedFriendshipRequestsResponse,
GetFriendshipRequestsPayload
} from '@dcl/protocol/out-js/decentraland/social_service/v2/social_service_v2.gen'

export function getSentFriendshipRequestsService({ components: { logs, db } }: RPCServiceContext<'logs' | 'db'>) {
export async function getSentFriendshipRequestsService({
components: { logs, db, catalystClient, config }
}: RPCServiceContext<'logs' | 'db' | 'catalystClient' | 'config'>) {
const logger = logs.getLogger('get-sent-friendship-requests-service')
const profileImagesUrl = await config.requireString('PROFILE_IMAGES_URL')

return async function (
request: GetFriendshipRequestsPayload,
context: RpcServerContext
): Promise<PaginatedFriendshipRequestsResponse> {
try {
const pendingRequests = await db.getSentFriendshipRequests(context.address, request.pagination)
const mappedRequests = pendingRequests.map(({ id, address, timestamp, metadata }) => ({
id,
user: { address },
createdAt: new Date(timestamp).getTime(),
message: metadata?.message || ''
}))
const sentRequests = await db.getSentFriendshipRequests(context.address, request.pagination)
const sentRequestsAddresses = sentRequests.map(({ address }) => address)

const sentRequestedProfiles = await catalystClient.getEntitiesByPointers(sentRequestsAddresses)

const requests = parseFriendshipRequestsToFriendshipRequestResponses(
sentRequests,
sentRequestedProfiles,
profileImagesUrl
)

return {
response: {
$case: 'requests',
requests: {
requests: mappedRequests
requests
}
}
}
Expand Down
24 changes: 13 additions & 11 deletions src/logic/friends.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { User } from '@dcl/protocol/out-js/decentraland/social_service/v2/social_service_v2.gen'
import { FriendProfile } from '@dcl/protocol/out-js/decentraland/social_service/v2/social_service_v2.gen'
import { Entity } from '@dcl/schemas'
import { getProfileAvatar, getProfilePictureUrl } from './profiles'
import { normalizeAddress } from '../utils/address'

export function parseProfilesToFriends(profiles: Entity[], contentServerUrl: string): User[] {
return profiles.map((profile) => {
const { userId, name, hasClaimedName } = getProfileAvatar(profile)
export function parseProfileToFriend(profile: Entity, contentServerUrl: string): FriendProfile {
const { userId, name, hasClaimedName } = getProfileAvatar(profile)

return {
address: normalizeAddress(userId),
name,
hasClaimedName,
profilePictureUrl: getProfilePictureUrl(contentServerUrl, profile)
}
})
return {
address: normalizeAddress(userId),
name,
hasClaimedName,
profilePictureUrl: getProfilePictureUrl(contentServerUrl, profile)
}
}

export function parseProfilesToFriends(profiles: Entity[], contentServerUrl: string): FriendProfile[] {
return profiles.map((profile) => parseProfileToFriend(profile, contentServerUrl))
}
40 changes: 39 additions & 1 deletion src/logic/friendships.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@ import {
FriendshipUpdate,
UpsertFriendshipPayload,
FriendshipStatus as FriendshipRequestStatus,
FriendUpdate
FriendUpdate,
FriendshipRequestResponse
} from '@dcl/protocol/out-js/decentraland/social_service/v2/social_service_v2.gen'
import {
Action,
FRIENDSHIP_ACTION_TRANSITIONS,
FriendshipAction,
FriendshipRequest,
FriendshipStatus,
SubscriptionEventsEmitter
} from '../types'
import { normalizeAddress } from '../utils/address'
import { parseProfileToFriend } from './friends'
import { Entity } from '@dcl/schemas'
import { getProfileAvatar } from './profiles'

const FRIENDSHIP_STATUS_BY_ACTION: Record<
Action,
Expand Down Expand Up @@ -207,3 +212,36 @@ export function getFriendshipRequestStatus(
const statusResolver = FRIENDSHIP_STATUS_BY_ACTION[action]
return statusResolver?.(acting_user, loggedUserAddress) ?? FriendshipRequestStatus.UNRECOGNIZED
}

export function parseFriendshipRequestToFriendshipRequestResponse(
request: FriendshipRequest,
profile: Entity,
profileImagesUrl: string
): FriendshipRequestResponse {
return {
id: request.id,
friend: parseProfileToFriend(profile, profileImagesUrl),
createdAt: new Date(request.timestamp).getTime(),
message: request.metadata?.message || ''
}
}

export function parseFriendshipRequestsToFriendshipRequestResponses(
requests: FriendshipRequest[],
profiles: Entity[],
profileImagesUrl: string
): FriendshipRequestResponse[] {
const profilesMap = new Map(profiles.map((profile) => [getProfileAvatar(profile).userId, profile]))

return requests
.map((request) => {
const profile = profilesMap.get(request.address)

if (!profile) {
return null
}

return parseFriendshipRequestToFriendshipRequestResponse(request, profile, profileImagesUrl)
})
.filter((request) => !!request)
}
22 changes: 4 additions & 18 deletions src/logic/profiles.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,15 @@
import { Entity } from '@dcl/schemas'
import { Entity, Avatar } from '@dcl/schemas'

type Avatar = {
userId: string
name: string
hasClaimedName: boolean
snapshots: {
face256: string
}
}

export function getProfileAvatar(profile: Entity): Avatar {
export function getProfileAvatar(profile: Pick<Entity, 'metadata'>): Avatar {
const [avatar] = profile.metadata.avatars

if (!avatar) throw new Error('Missing profile avatar')

return avatar
}

export function getProfilePictureUrl(baseUrl: string, profile: Entity): string {
export function getProfilePictureUrl(baseUrl: string, { id }: Pick<Entity, 'id'>): string {
if (!baseUrl) throw new Error('Missing baseUrl for profile picture')

const avatar = getProfileAvatar(profile)
const hash = avatar?.snapshots.face256

if (!hash) throw new Error('Missing snapshot hash for profile picture')

return `${baseUrl}/contents/${hash}`
return `${baseUrl}/entities/${id}/face.png`
}
12 changes: 7 additions & 5 deletions test/mocks/friend.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { Entity } from '@dcl/schemas'
import { Friend } from '../../src/types'
import { getProfileAvatar } from '../../src/logic/profiles'

export const createMockFriend = (address: string): Friend => ({
address
})

export function parseExpectedFriends(contentServerUrl: string) {
return (address: string) => ({
address,
name: `Profile name ${address}`,
export function parseExpectedFriends(profileImagesUrl: string) {
return (profile: Entity) => ({
address: getProfileAvatar(profile).userId,
name: getProfileAvatar(profile).name,
hasClaimedName: true,
profilePictureUrl: `${contentServerUrl}/contents/bafybeiasdfqwer`
profilePictureUrl: `${profileImagesUrl}/entities/${profile.id}/face.png`
})
}
Loading

0 comments on commit 34da607

Please sign in to comment.