feat: add toggle visibility for published Facebook posts (debug ↔ live)
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
Adds bidirectional visibility control: published posts can be switched between public (live) and draft (debug/admin-only) mode via Facebook Graph API. Includes is_live column, status indicator, and toggle buttons. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5950a55416
commit
f26341d8cc
@ -318,6 +318,18 @@ def social_publisher_delete(post_id):
|
|||||||
return redirect(url_for('admin.social_publisher_list'))
|
return redirect(url_for('admin.social_publisher_list'))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/social-publisher/<int:post_id>/toggle-visibility', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
@role_required(SystemRole.MANAGER)
|
||||||
|
def social_publisher_toggle_visibility(post_id):
|
||||||
|
"""Przelacz widocznosc posta miedzy debug a live na Facebook."""
|
||||||
|
from services.social_publisher_service import social_publisher
|
||||||
|
|
||||||
|
success, message = social_publisher.toggle_visibility(post_id)
|
||||||
|
flash(message, 'success' if success else 'danger')
|
||||||
|
return redirect(url_for('admin.social_publisher_edit', post_id=post_id))
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/social-publisher/<int:post_id>/refresh-engagement', methods=['POST'])
|
@bp.route('/social-publisher/<int:post_id>/refresh-engagement', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@role_required(SystemRole.MANAGER)
|
@role_required(SystemRole.MANAGER)
|
||||||
|
|||||||
@ -5375,6 +5375,9 @@ class SocialPost(Base):
|
|||||||
scheduled_at = Column(DateTime, nullable=True)
|
scheduled_at = Column(DateTime, nullable=True)
|
||||||
published_at = Column(DateTime, nullable=True)
|
published_at = Column(DateTime, nullable=True)
|
||||||
|
|
||||||
|
# Publish mode
|
||||||
|
is_live = Column(Boolean, default=False) # True = public, False = debug/draft on FB
|
||||||
|
|
||||||
# Facebook response
|
# Facebook response
|
||||||
meta_post_id = Column(String(100))
|
meta_post_id = Column(String(100))
|
||||||
meta_response = Column(JSONBType)
|
meta_response = Column(JSONBType)
|
||||||
|
|||||||
5
database/migrations/072_social_post_is_live.sql
Normal file
5
database/migrations/072_social_post_is_live.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
-- Add is_live column to social_media_posts
|
||||||
|
-- Tracks whether a published post is publicly visible (true) or debug/draft (false)
|
||||||
|
ALTER TABLE social_media_posts ADD COLUMN IF NOT EXISTS is_live BOOLEAN DEFAULT FALSE;
|
||||||
|
|
||||||
|
GRANT ALL ON TABLE social_media_posts TO nordabiz_app;
|
||||||
@ -239,6 +239,17 @@ class FacebookGraphService:
|
|||||||
"""
|
"""
|
||||||
return self._post(post_id, data={'is_published': 'true'})
|
return self._post(post_id, data={'is_published': 'true'})
|
||||||
|
|
||||||
|
def unpublish_post(self, post_id: str) -> Optional[Dict]:
|
||||||
|
"""Unpublish a public post (make it draft/hidden).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
post_id: Facebook post ID (format: PAGE_ID_POST_ID)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
API response or None on failure
|
||||||
|
"""
|
||||||
|
return self._post(post_id, data={'is_published': 'false'})
|
||||||
|
|
||||||
def get_post_engagement(self, post_id: str) -> Optional[Dict]:
|
def get_post_engagement(self, post_id: str) -> Optional[Dict]:
|
||||||
"""Get engagement metrics for a published post.
|
"""Get engagement metrics for a published post.
|
||||||
|
|
||||||
|
|||||||
@ -481,6 +481,7 @@ class SocialPublisherService:
|
|||||||
post.published_at = datetime.now()
|
post.published_at = datetime.now()
|
||||||
post.meta_post_id = result['id']
|
post.meta_post_id = result['id']
|
||||||
post.meta_response = result
|
post.meta_response = result
|
||||||
|
post.is_live = published
|
||||||
post.updated_at = datetime.now()
|
post.updated_at = datetime.now()
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
@ -501,6 +502,60 @@ class SocialPublisherService:
|
|||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
def toggle_visibility(self, post_id: int) -> Tuple[bool, str]:
|
||||||
|
"""Toggle post visibility between live (public) and debug (draft) on Facebook.
|
||||||
|
|
||||||
|
If post.is_live=True -> unpublish (make draft)
|
||||||
|
If post.is_live=False -> publish live (make public)
|
||||||
|
"""
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
post = db.query(SocialPost).filter(SocialPost.id == post_id).first()
|
||||||
|
if not post:
|
||||||
|
return False, "Post nie znaleziony"
|
||||||
|
|
||||||
|
if post.status != 'published' or not post.meta_post_id:
|
||||||
|
return False, "Post musi być opublikowany na Facebook, aby zmienić widoczność."
|
||||||
|
|
||||||
|
pub_company_id = post.publishing_company_id
|
||||||
|
if not pub_company_id:
|
||||||
|
return False, "Brak firmy publikującej."
|
||||||
|
|
||||||
|
access_token, config = self._get_publish_token(db, pub_company_id)
|
||||||
|
if not access_token:
|
||||||
|
return False, "Brak tokena Facebook dla wybranej firmy."
|
||||||
|
|
||||||
|
from facebook_graph_service import FacebookGraphService
|
||||||
|
fb = FacebookGraphService(access_token)
|
||||||
|
|
||||||
|
if post.is_live:
|
||||||
|
# Currently public -> make draft
|
||||||
|
result = fb.unpublish_post(post.meta_post_id)
|
||||||
|
if result:
|
||||||
|
post.is_live = False
|
||||||
|
post.updated_at = datetime.now()
|
||||||
|
db.commit()
|
||||||
|
logger.info(f"Post #{post_id} toggled to DRAFT (unpublished)")
|
||||||
|
return True, "Post zmieniony na tryb debug (widoczny tylko dla adminów strony)."
|
||||||
|
return False, "Nie udało się ukryć posta na Facebook."
|
||||||
|
else:
|
||||||
|
# Currently draft -> make public
|
||||||
|
result = fb.publish_draft(post.meta_post_id)
|
||||||
|
if result:
|
||||||
|
post.is_live = True
|
||||||
|
post.updated_at = datetime.now()
|
||||||
|
db.commit()
|
||||||
|
logger.info(f"Post #{post_id} toggled to LIVE (published)")
|
||||||
|
return True, "Post zmieniony na tryb publiczny (widoczny dla wszystkich)."
|
||||||
|
return False, "Nie udało się upublicznić posta na Facebook."
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
logger.error(f"Failed to toggle visibility for post #{post_id}: {e}")
|
||||||
|
return False, f"Błąd zmiany widoczności: {str(e)}"
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
# ---- AI Content Generation ----
|
# ---- AI Content Generation ----
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@ -201,6 +201,26 @@
|
|||||||
.fb-link:hover {
|
.fb-link:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.visibility-status {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
padding: var(--spacing-xs) var(--spacing-sm);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visibility-status.live {
|
||||||
|
background: #dcfce7;
|
||||||
|
color: #166534;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visibility-status.debug {
|
||||||
|
background: #fef9c3;
|
||||||
|
color: #854d0e;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -229,6 +249,14 @@
|
|||||||
{% if post.published_at %}
|
{% if post.published_at %}
|
||||||
| <strong>Opublikowano:</strong> {{ post.published_at.strftime('%Y-%m-%d %H:%M') }}
|
| <strong>Opublikowano:</strong> {{ post.published_at.strftime('%Y-%m-%d %H:%M') }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if post.status == 'published' and post.meta_post_id %}
|
||||||
|
|
|
||||||
|
{% if post.is_live %}
|
||||||
|
<span class="visibility-status live">Publiczny</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="visibility-status debug">Debug (tylko admin)</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@ -378,12 +406,28 @@
|
|||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if post.status == 'published' and is_debug %}
|
{% if post.status == 'published' and post.meta_post_id %}
|
||||||
<button type="submit" name="action" value="publish_live" class="btn btn-success"
|
</div>
|
||||||
style="background: #dc2626; border: none;"
|
<div class="btn-group" style="margin-top: var(--spacing-md);">
|
||||||
onclick="return confirm('Post zostanie ponownie opublikowany PUBLICZNIE. Kontynuowac?');">
|
<strong style="align-self: center;">Widoczność na FB:</strong>
|
||||||
Opublikuj publicznie
|
{% if post.is_live %}
|
||||||
</button>
|
<form method="POST" action="{{ url_for('admin.social_publisher_toggle_visibility', post_id=post.id) }}" style="display:inline;">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
|
<button type="submit" class="btn btn-secondary"
|
||||||
|
onclick="return confirm('Post zostanie UKRYTY — widoczny tylko dla adminów strony FB. Kontynuować?');">
|
||||||
|
Zmień na debug
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<form method="POST" action="{{ url_for('admin.social_publisher_toggle_visibility', post_id=post.id) }}" style="display:inline;">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
|
<button type="submit" class="btn btn-success"
|
||||||
|
style="background: #dc2626; border: none;"
|
||||||
|
onclick="return confirm('Post zostanie UPUBLICZNIONY — widoczny dla wszystkich. Kontynuować?');">
|
||||||
|
Opublikuj publicznie
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if post.status != 'published' %}
|
{% if post.status != 'published' %}
|
||||||
<button type="submit" name="action" value="delete" class="btn btn-error"
|
<button type="submit" name="action" value="delete" class="btn btn-error"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user