Entities List API
Overviewβ
The Entities List API allows authenticated users to retrieve a paginated list of entities (organizations and companies) from the Ring Platform. This endpoint supports filtering based on user access levels and provides comprehensive entity information including company details, social links, and visibility settings.
Endpoint Detailsβ
- URL:
/api/entities
- Method:
GET
- Authentication: Required (NextAuth.js session)
- Content-Type:
application/json
Authenticationβ
This endpoint requires user authentication via NextAuth.js session:
import { getSession } from 'next-auth/react'
const session = await getSession()
if (!session) {
// Handle unauthenticated state
}
Query Parametersβ
Parameter | Type | Required | Description |
---|---|---|---|
limit | number | No | Number of entities to return (default: 20, max: 100) |
startAfter | string | No | Cursor for pagination - entity ID to start after |
Example Requestsβ
// Get first 20 entities
GET /api/entities
// Get next 10 entities after a specific entity
GET /api/entities?limit=10&startAfter=entity123
// Get maximum entities per page
GET /api/entities?limit=100
Response Formatβ
Success Response (200)β
{
"entities": [
{
"id": "entity123",
"name": "TechCorp AI Solutions",
"description": "Leading AI and machine learning solutions provider",
"industry": "ai-ml",
"founded": 2020,
"employeeCount": "50-100",
"website": "https://techcorp.ai",
"location": {
"city": "San Francisco",
"country": "USA",
"timezone": "America/Los_Angeles"
},
"socialLinks": {
"linkedin": "https://linkedin.com/company/techcorp-ai",
"twitter": "https://twitter.com/techcorp_ai",
"facebook": null,
"instagram": null
},
"visibility": "public",
"certifications": [
"ISO 27001",
"SOC 2 Type II"
],
"partnerships": [
"Google Cloud Partner",
"AWS Advanced Partner"
],
"upcomingEvents": [
{
"title": "AI Innovation Summit 2024",
"date": "2024-03-15T09:00:00Z",
"location": "San Francisco, CA"
}
],
"logo": "https://storage.googleapis.com/ring-platform/logos/entity123.png",
"banner": "https://storage.googleapis.com/ring-platform/banners/entity123.jpg",
"verified": true,
"memberCount": 245,
"opportunityCount": 12,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T14:22:00Z"
}
],
"lastVisible": "entity123",
"hasMore": true,
"totalCount": 1247
}
Empty Response (200)β
{
"entities": [],
"lastVisible": null,
"hasMore": false,
"totalCount": 0
}
Error Responsesβ
Unauthorized (401)β
{
"error": "Unauthorized"
}
Forbidden (403)β
{
"error": "Permission denied to access entities"
}
Internal Server Error (500)β
{
"error": "Internal Server Error"
}
Implementation Examplesβ
React Component with Paginationβ
import { useState, useEffect, useCallback } from 'react'
import { useSession } from 'next-auth/react'
interface Entity {
id: string
name: string
description: string
industry: string
website: string
logo: string
verified: boolean
memberCount: number
opportunityCount: number
}
interface EntitiesResponse {
entities: Entity[]
lastVisible: string | null
hasMore: boolean
totalCount: number
}
function EntitiesList() {
const { data: session } = useSession()
const [entities, setEntities] = useState<Entity[]>([])
const [loading, setLoading] = useState(true)
const [loadingMore, setLoadingMore] = useState(false)
const [error, setError] = useState<string | null>(null)
const [lastVisible, setLastVisible] = useState<string | null>(null)
const [hasMore, setHasMore] = useState(true)
const fetchEntities = useCallback(async (reset = false) => {
if (!session) return
if (reset) {
setLoading(true)
setEntities([])
setLastVisible(null)
} else {
setLoadingMore(true)
}
setError(null)
try {
const params = new URLSearchParams({
limit: '20'
})
if (!reset && lastVisible) {
params.append('startAfter', lastVisible)
}
const response = await fetch(`/api/entities?${params}`)
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.error || 'Failed to fetch entities')
}
const data: EntitiesResponse = await response.json()
if (reset) {
setEntities(data.entities)
} else {
setEntities(prev => [...prev, ...data.entities])
}
setLastVisible(data.lastVisible)
setHasMore(data.hasMore)
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error occurred')
} finally {
setLoading(false)
setLoadingMore(false)
}
}, [session, lastVisible])
useEffect(() => {
fetchEntities(true)
}, [session])
const handleLoadMore = () => {
if (!loadingMore && hasMore) {
fetchEntities(false)
}
}
if (loading) {
return <div className="flex justify-center p-8">Loading entities...</div>
}
if (error) {
return (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
Error: {error}
</div>
)
}
return (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{entities.map((entity) => (
<div key={entity.id} className="bg-white border rounded-lg shadow-sm p-6">
<div className="flex items-start space-x-4">
{entity.logo && (
<img
src={entity.logo}
alt={`${entity.name} logo`}
className="w-12 h-12 rounded-lg object-cover"
/>
)}
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2">
<h3 className="text-lg font-semibold text-gray-900 truncate">
{entity.name}
</h3>
{entity.verified && (
<span className="flex-shrink-0 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">
Verified
</span>
)}
</div>
<p className="text-sm text-gray-500 mt-1 line-clamp-2">
{entity.description}
</p>
<div className="flex items-center justify-between mt-4 text-sm text-gray-500">
<span>{entity.memberCount} members</span>
<span>{entity.opportunityCount} opportunities</span>
</div>
</div>
</div>
</div>
))}
</div>
{hasMore && (
<div className="flex justify-center">
<button
onClick={handleLoadMore}
disabled={loadingMore}
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50"
>
{loadingMore ? 'Loading...' : 'Load More'}
</button>
</div>
)}
</div>
)
}
Custom Hook for Entity Managementβ
import { useState, useEffect, useCallback } from 'react'
import { useSession } from 'next-auth/react'
export function useEntities(initialLimit = 20) {
const { data: session } = useSession()
const [entities, setEntities] = useState<Entity[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [lastVisible, setLastVisible] = useState<string | null>(null)
const [hasMore, setHasMore] = useState(true)
const [totalCount, setTotalCount] = useState(0)
const fetchEntities = useCallback(async (reset = false) => {
if (!session) return
try {
const params = new URLSearchParams({
limit: initialLimit.toString()
})
if (!reset && lastVisible) {
params.append('startAfter', lastVisible)
}
const response = await fetch(`/api/entities?${params}`)
if (!response.ok) {
throw new Error('Failed to fetch entities')
}
const data: EntitiesResponse = await response.json()
if (reset) {
setEntities(data.entities)
} else {
setEntities(prev => [...prev, ...data.entities])
}
setLastVisible(data.lastVisible)
setHasMore(data.hasMore)
setTotalCount(data.totalCount)
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error')
} finally {
setLoading(false)
}
}, [session, initialLimit, lastVisible])
const loadMore = useCallback(() => {
if (hasMore && !loading) {
fetchEntities(false)
}
}, [hasMore, loading, fetchEntities])
const refresh = useCallback(() => {
setLoading(true)
setError(null)
setLastVisible(null)
fetchEntities(true)
}, [fetchEntities])
useEffect(() => {
fetchEntities(true)
}, [session])
return {
entities,
loading,
error,
hasMore,
totalCount,
loadMore,
refresh
}
}
Server-Side Rendering (Next.js)β
import { GetServerSideProps } from 'next'
import { getServerAuthSession } from '@/auth'
interface EntitiesPageProps {
initialEntities: Entity[]
initialLastVisible: string | null
initialHasMore: boolean
totalCount: number
}
export default function EntitiesPage({
initialEntities,
initialLastVisible,
initialHasMore,
totalCount
}: EntitiesPageProps) {
const [entities, setEntities] = useState(initialEntities)
const [lastVisible, setLastVisible] = useState(initialLastVisible)
const [hasMore, setHasMore] = useState(initialHasMore)
return (
<div className="container mx-auto px-4 py-8">
<div className="flex items-center justify-between mb-8">
<h1 className="text-3xl font-bold text-gray-900">
Entities Directory
</h1>
<span className="text-sm text-gray-500">
{totalCount.toLocaleString()} total entities
</span>
</div>
<EntitiesList
entities={entities}
lastVisible={lastVisible}
hasMore={hasMore}
onUpdate={setEntities}
/>
</div>
)
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getServerAuthSession(context)
if (!session) {
return {
redirect: {
destination: '/auth/signin',
permanent: false,
},
}
}
try {
// Fetch initial entities server-side
const response = await fetch(`${process.env.NEXTAUTH_URL}/api/entities?limit=20`, {
headers: {
'Cookie': context.req.headers.cookie || '',
},
})
if (!response.ok) {
throw new Error('Failed to fetch entities')
}
const data = await response.json()
return {
props: {
initialEntities: data.entities,
initialLastVisible: data.lastVisible,
initialHasMore: data.hasMore,
totalCount: data.totalCount
},
}
} catch (error) {
return {
props: {
initialEntities: [],
initialLastVisible: null,
initialHasMore: false,
totalCount: 0
},
}
}
}
cURL Exampleβ
# Get first page of entities
curl -X GET https://ring.ck.ua/api/entities \
-H "Cookie: next-auth.session-token=your-session-token"
# Get specific number of entities
curl -X GET "https://ring.ck.ua/api/entities?limit=50" \
-H "Cookie: next-auth.session-token=your-session-token"
# Get next page of entities
curl -X GET "https://ring.ck.ua/api/entities?limit=20&startAfter=entity123" \
-H "Cookie: next-auth.session-token=your-session-token"
Business Logicβ
The endpoint performs the following operations:
- Authentication Check: Verifies user session and extracts user role
- Role-Based Filtering: Applies visibility filters based on user access level
- Pagination Setup: Configures limit and cursor-based pagination
- Database Query: Fetches entities from Firestore with proper ordering
- Response Formatting: Returns entities with pagination metadata
Access Controlβ
Visibility Levelsβ
- Public: Available to all authenticated users
- Subscriber: Available to subscribers and above
- Member: Available to members and above
- Confidential: Available to confidential level and above
Role-Based Accessβ
const getVisibilityFilter = (userRole: UserRole) => {
switch (userRole) {
case UserRole.ADMIN:
case UserRole.CONFIDENTIAL:
return ['public', 'subscriber', 'member', 'confidential']
case UserRole.MEMBER:
return ['public', 'subscriber', 'member']
case UserRole.SUBSCRIBER:
return ['public', 'subscriber']
default:
return ['public']
}
}
Performance Optimizationsβ
- Firestore Indexing: Entities are indexed by visibility and creation date
- Pagination: Cursor-based pagination for efficient large dataset handling
- Caching: Response caching with appropriate cache headers
- Field Selection: Only necessary fields are returned to minimize bandwidth
Rate Limitingβ
- User-based: 60 requests per minute per authenticated user
- Global: 1000 requests per minute across all users
Database Query Optimizationβ
// Optimized Firestore query
const entitiesQuery = db
.collection('entities')
.where('visibility', 'in', visibilityLevels)
.orderBy('createdAt', 'desc')
.limit(limit)
if (startAfter) {
const startDoc = await db.collection('entities').doc(startAfter).get()
entitiesQuery = entitiesQuery.startAfter(startDoc)
}
Testingβ
Unit Test Exampleβ
import { GET } from '@/app/api/entities/route'
import { NextRequest } from 'next/server'
jest.mock('@/auth')
jest.mock('@/services/entities/get-entities')
describe('/api/entities', () => {
it('should return entities successfully', async () => {
(getServerAuthSession as jest.Mock).mockResolvedValue({
user: { id: 'user123', role: 'member' }
})
const mockEntities = [
{ id: 'entity1', name: 'Test Entity 1', visibility: 'public' },
{ id: 'entity2', name: 'Test Entity 2', visibility: 'member' }
]
(getEntities as jest.Mock).mockResolvedValue({
entities: mockEntities,
lastVisible: 'entity2'
})
const request = new NextRequest('http://localhost:3000/api/entities')
const response = await GET(request)
const data = await response.json()
expect(response.status).toBe(200)
expect(data.entities).toHaveLength(2)
expect(data.lastVisible).toBe('entity2')
})
it('should handle pagination correctly', async () => {
const request = new NextRequest('http://localhost:3000/api/entities?limit=10&startAfter=entity123')
const response = await GET(request)
expect(getEntities).toHaveBeenCalledWith(10, 'entity123')
})
})
Related Endpointsβ
POST /api/entities/create
- Create new entityGET /api/entities/[id]
- Get specific entity detailsPUT /api/entities/update/[id]
- Update entity informationDELETE /api/entities/delete/[id]
- Delete entityGET /api/confidential/entities
- Get confidential entities