Skip to content

Commit

Permalink
feat: Get Sent and Pending Friendship Requests return pagination data
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinszuchet committed Jan 29, 2025
1 parent e7507a4 commit 35ec226
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 40 deletions.
76 changes: 46 additions & 30 deletions src/adapters/db.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import SQL, { SQLStatement } from 'sql-template-strings'
import { randomUUID } from 'node:crypto'
import { PoolClient } from 'pg'
import { AppComponents, Friendship, FriendshipAction, FriendshipRequest, IDatabaseComponent, Friend } from '../types'
import {
AppComponents,
Friendship,
FriendshipAction,
FriendshipRequest,
IDatabaseComponent,
Friend,
Pagination
} from '../types'
import { FRIENDSHIPS_PER_PAGE } from './rpc-server/constants'
import { normalizeAddress } from '../utils/address'

Expand All @@ -24,7 +32,13 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
WHERE (LOWER(address_requester) = ${normalizedUserAddress} OR LOWER(address_requested) = ${normalizedUserAddress})`
}

function getFriendshipRequestBaseQuery(userAddress: string, type: 'sent' | 'received'): SQLStatement {
function getFriendshipRequestBaseQuery(
userAddress: string,
type: 'sent' | 'received',
{ onlyCount, pagination }: { onlyCount?: boolean; pagination?: Pagination } = { onlyCount: false }
): SQLStatement {
const { limit, offset } = pagination || {}

const columnMapping = {
sent: SQL` f.address_requested`,
received: SQL` f.address_requester`
Expand All @@ -34,9 +48,16 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
received: SQL` LOWER(f.address_requested)`
}

const baseQuery = SQL`SELECT fa.id,`
baseQuery.append(columnMapping[type])
baseQuery.append(SQL` as address, fa.timestamp, fa.metadata`)
const baseQuery = SQL`SELECT`

if (onlyCount) {
baseQuery.append(SQL` DISTINCT COUNT(1) as count`)
} else {
baseQuery.append(SQL` fa.id,`)
baseQuery.append(columnMapping[type])
baseQuery.append(SQL` as address, fa.timestamp, fa.metadata`)
}

baseQuery.append(SQL` FROM friendships f`)
baseQuery.append(SQL` INNER JOIN friendship_actions fa ON f.id = fa.friendship_id`)
baseQuery.append(SQL` WHERE`)
Expand All @@ -55,6 +76,14 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
ORDER BY fa.timestamp DESC
`)

if (limit) {
baseQuery.append(SQL` LIMIT ${limit}`)
}

if (offset) {
baseQuery.append(SQL` OFFSET ${offset}`)
}

return baseQuery
}

Expand Down Expand Up @@ -274,38 +303,25 @@ export function createDBComponent(components: Pick<AppComponents, 'pg' | 'logs'>
return uuid
},
async getReceivedFriendshipRequests(userAddress, pagination) {
const { limit, offset } = pagination || {}

const query = getFriendshipRequestBaseQuery(userAddress, 'received')

if (limit) {
query.append(SQL` LIMIT ${limit}`)
}

if (offset) {
query.append(SQL` OFFSET ${offset}`)
}

const query = getFriendshipRequestBaseQuery(userAddress, 'received', { pagination })
const results = await pg.query<FriendshipRequest>(query)

return results.rows
},
async getReceivedFriendshipRequestsCount(userAddress) {
const query = getFriendshipRequestBaseQuery(userAddress, 'received', { onlyCount: true })
const results = await pg.query<{ count: number }>(query)
return results.rows[0].count
},
async getSentFriendshipRequests(userAddress, pagination) {
const { limit, offset } = pagination || {}
const query = getFriendshipRequestBaseQuery(userAddress, 'sent')

if (limit) {
query.append(SQL` LIMIT ${limit}`)
}

if (offset) {
query.append(SQL` OFFSET ${offset}`)
}

const query = getFriendshipRequestBaseQuery(userAddress, 'sent', { pagination })
const results = await pg.query<FriendshipRequest>(query)

return results.rows
},
async getSentFriendshipRequestsCount(userAddress) {
const query = getFriendshipRequestBaseQuery(userAddress, 'sent', { onlyCount: true })
const results = await pg.query<{ count: number }>(query)
return results.rows[0].count
},
async getOnlineFriends(userAddress: string, onlinePotentialFriends: string[]) {
if (onlinePotentialFriends.length === 0) return []

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
PaginatedFriendshipRequestsResponse,
GetFriendshipRequestsPayload
} from '@dcl/protocol/out-js/decentraland/social_service/v2/social_service_v2.gen'
import { getPage } from '../../../utils/pagination'

export async function getPendingFriendshipRequestsService({
components: { logs, db, catalystClient, config }
Expand All @@ -16,10 +17,14 @@ export async function getPendingFriendshipRequestsService({
context: RpcServerContext
): Promise<PaginatedFriendshipRequestsResponse> {
try {
const pendingRequests = await db.getReceivedFriendshipRequests(context.address, request.pagination)
const { limit, offset } = request.pagination || {}
const [pendingRequests, pendingRequestsCount] = await Promise.all([
db.getReceivedFriendshipRequests(context.address, request.pagination),
db.getReceivedFriendshipRequestsCount(context.address)
])
const pendingRequestsAddresses = pendingRequests.map(({ address }) => address)

const pendingRequesterProfiles = await catalystClient.getEntitiesByPointers(pendingRequestsAddresses)

const requests = parseFriendshipRequestsToFriendshipRequestResponses(
pendingRequests,
pendingRequesterProfiles,
Expand All @@ -32,6 +37,10 @@ export async function getPendingFriendshipRequestsService({
requests: {
requests
}
},
paginationData: {
total: pendingRequestsCount,
page: getPage(limit ?? pendingRequestsCount, offset)
}
}
} catch (error: any) {
Expand Down
13 changes: 11 additions & 2 deletions src/adapters/rpc-server/services/get-sent-friendship-requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
PaginatedFriendshipRequestsResponse,
GetFriendshipRequestsPayload
} from '@dcl/protocol/out-js/decentraland/social_service/v2/social_service_v2.gen'
import { getPage } from '../../../utils/pagination'

export async function getSentFriendshipRequestsService({
components: { logs, db, catalystClient, config }
Expand All @@ -16,9 +17,13 @@ export async function getSentFriendshipRequestsService({
context: RpcServerContext
): Promise<PaginatedFriendshipRequestsResponse> {
try {
const sentRequests = await db.getSentFriendshipRequests(context.address, request.pagination)
const sentRequestsAddresses = sentRequests.map(({ address }) => address)
const { limit, offset } = request.pagination || {}
const [sentRequests, sentRequestsCount] = await Promise.all([
db.getSentFriendshipRequests(context.address, request.pagination),
db.getSentFriendshipRequestsCount(context.address)
])

const sentRequestsAddresses = sentRequests.map(({ address }) => address)
const sentRequestedProfiles = await catalystClient.getEntitiesByPointers(sentRequestsAddresses)

const requests = parseFriendshipRequestsToFriendshipRequestResponses(
Expand All @@ -33,6 +38,10 @@ export async function getSentFriendshipRequestsService({
requests: {
requests
}
},
paginationData: {
total: sentRequestsCount,
page: getPage(limit ?? sentRequestsCount, offset)
}
}
} catch (error: any) {
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ export interface IDatabaseComponent {
txClient?: PoolClient
): Promise<string>
getReceivedFriendshipRequests(userAddress: string, pagination?: Pagination): Promise<FriendshipRequest[]>
getReceivedFriendshipRequestsCount(userAddress: string): Promise<number>
getSentFriendshipRequests(userAddress: string, pagination?: Pagination): Promise<FriendshipRequest[]>
getSentFriendshipRequestsCount(userAddress: string): Promise<number>
getOnlineFriends(userAddress: string, potentialFriends: string[]): Promise<Friend[]>
executeTx<T>(cb: (client: PoolClient) => Promise<T>): Promise<T>
}
Expand Down
2 changes: 2 additions & 0 deletions test/mocks/components/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const mockDb: jest.Mocked<IDatabaseComponent> = {
getLastFriendshipActionByUsers: jest.fn(),
recordFriendshipAction: jest.fn(),
getReceivedFriendshipRequests: jest.fn(),
getReceivedFriendshipRequestsCount: jest.fn(),
getSentFriendshipRequests: jest.fn(),
getSentFriendshipRequestsCount: jest.fn(),
executeTx: jest.fn()
}
25 changes: 23 additions & 2 deletions test/unit/adapters/db.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,17 @@ describe('db', () => {
})
})

describe('getReceivedFriendshipRequestsCount', () => {
it('should return the count of received friendship requests', async () => {
const mockCount = 5
mockPg.query.mockResolvedValueOnce({ rows: [{ count: mockCount }], rowCount: 1 })

const result = await dbComponent.getReceivedFriendshipRequestsCount('0x456')

expect(result).toBe(mockCount)
})
})

describe('getSentFriendshipRequests', () => {
it('should retrieve sent friendship requests', async () => {
const mockRequests = [
Expand Down Expand Up @@ -317,6 +328,17 @@ describe('db', () => {
})
})

describe('getSentFriendshipRequestsCount', () => {
it('should return the count of sent friendship requests', async () => {
const mockCount = 5
mockPg.query.mockResolvedValueOnce({ rows: [{ count: mockCount }], rowCount: 1 })

const result = await dbComponent.getSentFriendshipRequestsCount('0x123')

expect(result).toBe(mockCount)
})
})

describe('recordFriendshipAction', () => {
it.each([false, true])('should record a friendship action', async (withTxClient: boolean) => {
const mockClient = withTxClient ? await mockPg.getPool().connect() : undefined
Expand Down Expand Up @@ -426,8 +448,7 @@ describe('db', () => {
})

// Helpers

function expectPaginatedQueryToHaveBeenCalledWithProperLimitAndOffset(limit, offset) {
function expectPaginatedQueryToHaveBeenCalledWithProperLimitAndOffset(limit: number, offset: number) {
expect(mockPg.query).toHaveBeenCalledWith(
expect.objectContaining({
text: expect.stringContaining('LIMIT'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('getPendingFriendshipRequestsService', () => {
const mockProfiles = mockPendingRequests.map(({ address }) => createMockProfile(address))

mockDb.getReceivedFriendshipRequests.mockResolvedValueOnce(mockPendingRequests)
mockDb.getReceivedFriendshipRequestsCount.mockResolvedValueOnce(mockPendingRequests.length)
mockCatalystClient.getEntitiesByPointers.mockResolvedValueOnce(mockProfiles)
const result: PaginatedFriendshipRequestsResponse = await getPendingRequests(emptyRequest, rpcContext)

Expand All @@ -42,12 +43,19 @@ describe('getPendingFriendshipRequestsService', () => {
createMockExpectedFriendshipRequest('id2', '0x789', mockProfiles[1], '2025-01-02T00:00:00Z')
]
}
},
paginationData: {
total: mockPendingRequests.length,
page: 1
}
})
})

it('should handle database errors gracefully', async () => {
mockDb.getReceivedFriendshipRequests.mockImplementationOnce(() => {
it.each([
['getReceivedFriendshipRequests', mockDb.getReceivedFriendshipRequests],
['getReceivedFriendshipRequestsCount', mockDb.getReceivedFriendshipRequestsCount]
])('should handle database errors in the %s method gracefully', async (_methodName, method) => {
method.mockImplementationOnce(() => {
throw new Error('Database error')
})

Expand All @@ -66,6 +74,7 @@ describe('getPendingFriendshipRequestsService', () => {
const mockProfiles = mockPendingRequests.map(({ address }) => createMockProfile(address))

mockDb.getReceivedFriendshipRequests.mockResolvedValueOnce(mockPendingRequests)
mockDb.getReceivedFriendshipRequestsCount.mockResolvedValueOnce(mockPendingRequests.length)
mockCatalystClient.getEntitiesByPointers.mockResolvedValueOnce(mockProfiles)

const result: PaginatedFriendshipRequestsResponse = await getPendingRequests(emptyRequest, rpcContext)
Expand All @@ -76,6 +85,10 @@ describe('getPendingFriendshipRequestsService', () => {
requests: {
requests: [createMockExpectedFriendshipRequest('id1', '0x456', mockProfiles[0], '2025-01-01T00:00:00Z', '')]
}
},
paginationData: {
total: mockPendingRequests.length,
page: 1
}
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('getSentFriendshipRequestsService', () => {
const mockProfiles = mockSentRequests.map(({ address }) => createMockProfile(address))

mockDb.getSentFriendshipRequests.mockResolvedValueOnce(mockSentRequests)
mockDb.getSentFriendshipRequestsCount.mockResolvedValueOnce(mockSentRequests.length)
mockCatalystClient.getEntitiesByPointers.mockResolvedValueOnce(mockProfiles)

const result: PaginatedFriendshipRequestsResponse = await getSentRequests(emptyRequest, rpcContext)
Expand All @@ -43,12 +44,19 @@ describe('getSentFriendshipRequestsService', () => {
createMockExpectedFriendshipRequest('id2', '0x789', mockProfiles[1], '2025-01-02T00:00:00Z')
]
}
},
paginationData: {
total: mockSentRequests.length,
page: 1
}
})
})

it('should handle database errors gracefully', async () => {
mockDb.getSentFriendshipRequests.mockImplementationOnce(() => {
it.each([
['getSentFriendshipRequests', mockDb.getSentFriendshipRequests],
['getSentFriendshipRequestsCount', mockDb.getSentFriendshipRequestsCount]
])('should handle database errors in the %s method gracefully', async (_methodName, method) => {
method.mockImplementationOnce(() => {
throw new Error('Database error')
})

Expand All @@ -67,6 +75,7 @@ describe('getSentFriendshipRequestsService', () => {
const mockProfiles = mockSentRequests.map(({ address }) => createMockProfile(address))

mockDb.getSentFriendshipRequests.mockResolvedValueOnce(mockSentRequests)
mockDb.getSentFriendshipRequestsCount.mockResolvedValueOnce(mockSentRequests.length)
mockCatalystClient.getEntitiesByPointers.mockResolvedValueOnce(mockProfiles)

const result: PaginatedFriendshipRequestsResponse = await getSentRequests(emptyRequest, rpcContext)
Expand All @@ -77,6 +86,10 @@ describe('getSentFriendshipRequestsService', () => {
requests: {
requests: [createMockExpectedFriendshipRequest('id1', '0x456', mockProfiles[0], '2025-01-01T00:00:00Z')]
}
},
paginationData: {
total: mockSentRequests.length,
page: 1
}
})
})
Expand Down

0 comments on commit 35ec226

Please sign in to comment.