feat: add cursor-based pagination to Facebook posts API
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

Previously get_page_posts returned a flat list with no pagination support.
Now returns dict with posts and next_cursor, enabling infinite scrolling
through all Facebook page posts via the after query parameter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-02-19 16:51:31 +01:00
parent 9444c3484e
commit 779f0b0b73
3 changed files with 46 additions and 21 deletions

View File

@ -388,7 +388,8 @@ def social_publisher_fb_posts(company_id):
finally:
db.close()
result = social_publisher.get_page_recent_posts(company_id)
after = request.args.get('after')
result = social_publisher.get_page_recent_posts(company_id, after=after)
return jsonify(result)

View File

@ -273,22 +273,28 @@ class FacebookGraphService:
'reactions_total': result.get('reactions', {}).get('summary', {}).get('total_count', 0),
}
def get_page_posts(self, page_id: str, limit: int = 10) -> Optional[List[Dict]]:
def get_page_posts(self, page_id: str, limit: int = 10,
after: str = None) -> Optional[Dict]:
"""Get recent posts from a Facebook Page with engagement metrics.
Args:
page_id: Facebook Page ID
limit: Number of posts to fetch (max 100)
after: Pagination cursor for next page
Returns:
List of post dicts or None on failure
Dict with 'posts' list and 'next_cursor' (or None), or None on failure
"""
fields = (
'id,message,created_time,full_picture,permalink_url,status_type,'
'likes.summary(true).limit(0),comments.summary(true).limit(0),'
'shares,reactions.summary(true).limit(0)'
)
result = self._get(f'{page_id}/posts', {'fields': fields, 'limit': limit})
params = {'fields': fields, 'limit': limit}
if after:
params['after'] = after
result = self._get(f'{page_id}/posts', params)
if not result:
return None
@ -306,7 +312,14 @@ class FacebookGraphService:
'shares': item.get('shares', {}).get('count', 0) if item.get('shares') else 0,
'reactions_total': item.get('reactions', {}).get('summary', {}).get('total_count', 0),
})
return posts
# Extract next page cursor
next_cursor = None
paging = result.get('paging', {})
if paging.get('next'):
next_cursor = paging.get('cursors', {}).get('after')
return {'posts': posts, 'next_cursor': next_cursor}
def get_post_insights_metrics(self, post_id: str) -> Optional[Dict]:
"""Get detailed insights metrics for a specific post.

View File

@ -603,10 +603,11 @@ class SocialPublisherService:
# ---- Facebook Page Posts (read from API) ----
def get_page_recent_posts(self, company_id: int, limit: int = 10) -> Dict:
def get_page_recent_posts(self, company_id: int, limit: int = 10,
after: str = None) -> Dict:
"""Fetch recent posts from company's Facebook page with engagement metrics.
Uses in-memory cache with 5-minute TTL.
Uses in-memory cache with 5-minute TTL (first page only).
"""
db = SessionLocal()
try:
@ -615,31 +616,41 @@ class SocialPublisherService:
return {'success': False, 'error': 'Brak konfiguracji Facebook dla tej firmy.'}
page_id = config.page_id
cache_key = (company_id, page_id)
# Check cache
# Cache only first page (no cursor)
if not after:
cache_key = (company_id, page_id)
cached = _posts_cache.get(cache_key)
if cached and (time.time() - cached['ts']) < _CACHE_TTL:
return {
'success': True,
'posts': cached['data'],
'next_cursor': cached.get('next_cursor'),
'page_name': config.page_name or '',
'cached': True,
}
from facebook_graph_service import FacebookGraphService
fb = FacebookGraphService(access_token)
posts = fb.get_page_posts(page_id, limit)
result = fb.get_page_posts(page_id, limit, after=after)
if posts is None:
if result is None:
return {'success': False, 'error': 'Nie udało się pobrać postów z Facebook API.'}
# Update cache
_posts_cache[cache_key] = {'data': posts, 'ts': time.time()}
posts = result['posts']
next_cursor = result.get('next_cursor')
# Update cache for first page only
if not after:
cache_key = (company_id, page_id)
_posts_cache[cache_key] = {
'data': posts, 'next_cursor': next_cursor, 'ts': time.time()
}
return {
'success': True,
'posts': posts,
'next_cursor': next_cursor,
'page_name': config.page_name or '',
'cached': False,
}