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
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:
parent
9444c3484e
commit
779f0b0b73
@ -388,7 +388,8 @@ def social_publisher_fb_posts(company_id):
|
|||||||
finally:
|
finally:
|
||||||
db.close()
|
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)
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -273,22 +273,28 @@ class FacebookGraphService:
|
|||||||
'reactions_total': result.get('reactions', {}).get('summary', {}).get('total_count', 0),
|
'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.
|
"""Get recent posts from a Facebook Page with engagement metrics.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
page_id: Facebook Page ID
|
page_id: Facebook Page ID
|
||||||
limit: Number of posts to fetch (max 100)
|
limit: Number of posts to fetch (max 100)
|
||||||
|
after: Pagination cursor for next page
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of post dicts or None on failure
|
Dict with 'posts' list and 'next_cursor' (or None), or None on failure
|
||||||
"""
|
"""
|
||||||
fields = (
|
fields = (
|
||||||
'id,message,created_time,full_picture,permalink_url,status_type,'
|
'id,message,created_time,full_picture,permalink_url,status_type,'
|
||||||
'likes.summary(true).limit(0),comments.summary(true).limit(0),'
|
'likes.summary(true).limit(0),comments.summary(true).limit(0),'
|
||||||
'shares,reactions.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:
|
if not result:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -306,7 +312,14 @@ class FacebookGraphService:
|
|||||||
'shares': item.get('shares', {}).get('count', 0) if item.get('shares') else 0,
|
'shares': item.get('shares', {}).get('count', 0) if item.get('shares') else 0,
|
||||||
'reactions_total': item.get('reactions', {}).get('summary', {}).get('total_count', 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]:
|
def get_post_insights_metrics(self, post_id: str) -> Optional[Dict]:
|
||||||
"""Get detailed insights metrics for a specific post.
|
"""Get detailed insights metrics for a specific post.
|
||||||
|
|||||||
@ -603,10 +603,11 @@ class SocialPublisherService:
|
|||||||
|
|
||||||
# ---- Facebook Page Posts (read from API) ----
|
# ---- 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.
|
"""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()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -615,31 +616,41 @@ class SocialPublisherService:
|
|||||||
return {'success': False, 'error': 'Brak konfiguracji Facebook dla tej firmy.'}
|
return {'success': False, 'error': 'Brak konfiguracji Facebook dla tej firmy.'}
|
||||||
|
|
||||||
page_id = config.page_id
|
page_id = config.page_id
|
||||||
cache_key = (company_id, page_id)
|
|
||||||
|
|
||||||
# Check cache
|
# Cache only first page (no cursor)
|
||||||
cached = _posts_cache.get(cache_key)
|
if not after:
|
||||||
if cached and (time.time() - cached['ts']) < _CACHE_TTL:
|
cache_key = (company_id, page_id)
|
||||||
return {
|
cached = _posts_cache.get(cache_key)
|
||||||
'success': True,
|
if cached and (time.time() - cached['ts']) < _CACHE_TTL:
|
||||||
'posts': cached['data'],
|
return {
|
||||||
'page_name': config.page_name or '',
|
'success': True,
|
||||||
'cached': True,
|
'posts': cached['data'],
|
||||||
}
|
'next_cursor': cached.get('next_cursor'),
|
||||||
|
'page_name': config.page_name or '',
|
||||||
|
'cached': True,
|
||||||
|
}
|
||||||
|
|
||||||
from facebook_graph_service import FacebookGraphService
|
from facebook_graph_service import FacebookGraphService
|
||||||
fb = FacebookGraphService(access_token)
|
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.'}
|
return {'success': False, 'error': 'Nie udało się pobrać postów z Facebook API.'}
|
||||||
|
|
||||||
# Update cache
|
posts = result['posts']
|
||||||
_posts_cache[cache_key] = {'data': posts, 'ts': time.time()}
|
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 {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'posts': posts,
|
'posts': posts,
|
||||||
|
'next_cursor': next_cursor,
|
||||||
'page_name': config.page_name or '',
|
'page_name': config.page_name or '',
|
||||||
'cached': False,
|
'cached': False,
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user