refactor(backend): remove Ayrshare from execution & credentials manager (#10538)

This PR refactors the Ayrshare integration to remove the centralized
`get_ayrshare_profile_key` function from the credentials store and
instead retrieve the profile key directly within each Ayrshare block.
This change improves code organization by keeping Ayrshare-specific
logic within the Ayrshare module.

### Changes 🏗️

- **Refactored Ayrshare profile key retrieval**: Moved profile key
fetching logic from the credentials store into the Ayrshare blocks
- **Added `get_profile_key` helper function** in
`autogpt_platform/backend/backend/blocks/ayrshare/_util.py` to fetch the
profile key from user integrations
- **Updated all 15 Ayrshare social media blocks** to use `user_id`
instead of `profile_key` parameter and fetch the profile key internally
- **Removed `get_ayrshare_profile_key` method** from
`autogpt_platform/backend/backend/integrations/credentials_store.py`
- **Removed Ayrshare-specific logic** from
`autogpt_platform/backend/backend/executor/manager.py` that was passing
profile keys to blocks
- **Updated router** in
`autogpt_platform/backend/backend/server/integrations/router.py` to
directly fetch user integrations instead of using the removed method

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] Test posting to X/Twitter to check credentials flow
- [x] Verify profile key retrieval works correctly for authenticated
users
  - [x] Test Ayrshare SSO URL generation flow

---------

Co-authored-by: Zamil Majdy <zamil.majdy@agpt.co>
This commit is contained in:
Swifty
2025-08-04 15:36:54 +02:00
committed by GitHub
parent e043e4989b
commit 2182c7ba9e
17 changed files with 79 additions and 68 deletions

View File

@@ -1,14 +1,30 @@
from datetime import datetime
from typing import Optional
from autogpt_libs.utils.cache import thread_cached
from pydantic import BaseModel, Field
from backend.data.block import BlockSchema
from backend.data.model import SchemaField
from backend.data.model import SchemaField, UserIntegrations
from backend.integrations.ayrshare import AyrshareClient
from backend.util.exceptions import MissingConfigError
@thread_cached
def get_database_manager_client():
from backend.executor import DatabaseManagerAsyncClient
from backend.util.service import get_service_client
return get_service_client(DatabaseManagerAsyncClient, health_check=False)
async def get_profile_key(user_id: str):
user_integrations: UserIntegrations = (
await get_database_manager_client().get_user_integrations(user_id)
)
return user_integrations.managed_credentials.ayrshare_profile_key
class BaseAyrshareInput(BlockSchema):
"""Base input model for Ayrshare social media posts with common fields."""

View File

@@ -6,10 +6,9 @@ from backend.sdk import (
BlockSchema,
BlockType,
SchemaField,
SecretStr,
)
from ._util import BaseAyrshareInput, create_ayrshare_client
from ._util import BaseAyrshareInput, create_ayrshare_client, get_profile_key
class PostToBlueskyBlock(Block):
@@ -58,10 +57,12 @@ class PostToBlueskyBlock(Block):
self,
input_data: "PostToBlueskyBlock.Input",
*,
profile_key: SecretStr,
user_id: str,
**kwargs,
) -> BlockOutput:
"""Post to Bluesky with Bluesky-specific options."""
profile_key = await get_profile_key(user_id)
if not profile_key:
yield "error", "Please link a social account via Ayrshare"
return

View File

@@ -6,10 +6,14 @@ from backend.sdk import (
BlockSchema,
BlockType,
SchemaField,
SecretStr,
)
from ._util import BaseAyrshareInput, CarouselItem, create_ayrshare_client
from ._util import (
BaseAyrshareInput,
CarouselItem,
create_ayrshare_client,
get_profile_key,
)
class PostToFacebookBlock(Block):
@@ -116,10 +120,11 @@ class PostToFacebookBlock(Block):
self,
input_data: "PostToFacebookBlock.Input",
*,
profile_key: SecretStr,
user_id: str,
**kwargs,
) -> BlockOutput:
"""Post to Facebook with Facebook-specific options."""
profile_key = await get_profile_key(user_id)
if not profile_key:
yield "error", "Please link a social account via Ayrshare"
return

View File

@@ -6,10 +6,9 @@ from backend.sdk import (
BlockSchema,
BlockType,
SchemaField,
SecretStr,
)
from ._util import BaseAyrshareInput, create_ayrshare_client
from ._util import BaseAyrshareInput, create_ayrshare_client, get_profile_key
class PostToGMBBlock(Block):
@@ -111,9 +110,10 @@ class PostToGMBBlock(Block):
)
async def run(
self, input_data: "PostToGMBBlock.Input", *, profile_key: SecretStr, **kwargs
self, input_data: "PostToGMBBlock.Input", *, user_id: str, **kwargs
) -> BlockOutput:
"""Post to Google My Business with GMB-specific options."""
profile_key = await get_profile_key(user_id)
if not profile_key:
yield "error", "Please link a social account via Ayrshare"
return

View File

@@ -8,10 +8,14 @@ from backend.sdk import (
BlockSchema,
BlockType,
SchemaField,
SecretStr,
)
from ._util import BaseAyrshareInput, InstagramUserTag, create_ayrshare_client
from ._util import (
BaseAyrshareInput,
InstagramUserTag,
create_ayrshare_client,
get_profile_key,
)
class PostToInstagramBlock(Block):
@@ -108,10 +112,11 @@ class PostToInstagramBlock(Block):
self,
input_data: "PostToInstagramBlock.Input",
*,
profile_key: SecretStr,
user_id: str,
**kwargs,
) -> BlockOutput:
"""Post to Instagram with Instagram-specific options."""
profile_key = await get_profile_key(user_id)
if not profile_key:
yield "error", "Please link a social account via Ayrshare"
return

View File

@@ -6,10 +6,9 @@ from backend.sdk import (
BlockSchema,
BlockType,
SchemaField,
SecretStr,
)
from ._util import BaseAyrshareInput, create_ayrshare_client
from ._util import BaseAyrshareInput, create_ayrshare_client, get_profile_key
class PostToLinkedInBlock(Block):
@@ -113,10 +112,11 @@ class PostToLinkedInBlock(Block):
self,
input_data: "PostToLinkedInBlock.Input",
*,
profile_key: SecretStr,
user_id: str,
**kwargs,
) -> BlockOutput:
"""Post to LinkedIn with LinkedIn-specific options."""
profile_key = await get_profile_key(user_id)
if not profile_key:
yield "error", "Please link a social account via Ayrshare"
return

View File

@@ -6,10 +6,14 @@ from backend.sdk import (
BlockSchema,
BlockType,
SchemaField,
SecretStr,
)
from ._util import BaseAyrshareInput, PinterestCarouselOption, create_ayrshare_client
from ._util import (
BaseAyrshareInput,
PinterestCarouselOption,
create_ayrshare_client,
get_profile_key,
)
class PostToPinterestBlock(Block):
@@ -88,10 +92,11 @@ class PostToPinterestBlock(Block):
self,
input_data: "PostToPinterestBlock.Input",
*,
profile_key: SecretStr,
user_id: str,
**kwargs,
) -> BlockOutput:
"""Post to Pinterest with Pinterest-specific options."""
profile_key = await get_profile_key(user_id)
if not profile_key:
yield "error", "Please link a social account via Ayrshare"
return

View File

@@ -6,10 +6,9 @@ from backend.sdk import (
BlockSchema,
BlockType,
SchemaField,
SecretStr,
)
from ._util import BaseAyrshareInput, create_ayrshare_client
from ._util import BaseAyrshareInput, create_ayrshare_client, get_profile_key
class PostToRedditBlock(Block):
@@ -36,8 +35,9 @@ class PostToRedditBlock(Block):
)
async def run(
self, input_data: "PostToRedditBlock.Input", *, profile_key: SecretStr, **kwargs
self, input_data: "PostToRedditBlock.Input", *, user_id: str, **kwargs
) -> BlockOutput:
profile_key = await get_profile_key(user_id)
if not profile_key:
yield "error", "Please link a social account via Ayrshare"
return

View File

@@ -6,10 +6,9 @@ from backend.sdk import (
BlockSchema,
BlockType,
SchemaField,
SecretStr,
)
from ._util import BaseAyrshareInput, create_ayrshare_client
from ._util import BaseAyrshareInput, create_ayrshare_client, get_profile_key
class PostToSnapchatBlock(Block):
@@ -63,10 +62,11 @@ class PostToSnapchatBlock(Block):
self,
input_data: "PostToSnapchatBlock.Input",
*,
profile_key: SecretStr,
user_id: str,
**kwargs,
) -> BlockOutput:
"""Post to Snapchat with Snapchat-specific options."""
profile_key = await get_profile_key(user_id)
if not profile_key:
yield "error", "Please link a social account via Ayrshare"
return

View File

@@ -6,10 +6,9 @@ from backend.sdk import (
BlockSchema,
BlockType,
SchemaField,
SecretStr,
)
from ._util import BaseAyrshareInput, create_ayrshare_client
from ._util import BaseAyrshareInput, create_ayrshare_client, get_profile_key
class PostToTelegramBlock(Block):
@@ -58,10 +57,11 @@ class PostToTelegramBlock(Block):
self,
input_data: "PostToTelegramBlock.Input",
*,
profile_key: SecretStr,
user_id: str,
**kwargs,
) -> BlockOutput:
"""Post to Telegram with Telegram-specific validation."""
profile_key = await get_profile_key(user_id)
if not profile_key:
yield "error", "Please link a social account via Ayrshare"
return

View File

@@ -6,10 +6,9 @@ from backend.sdk import (
BlockSchema,
BlockType,
SchemaField,
SecretStr,
)
from ._util import BaseAyrshareInput, create_ayrshare_client
from ._util import BaseAyrshareInput, create_ayrshare_client, get_profile_key
class PostToThreadsBlock(Block):
@@ -51,10 +50,11 @@ class PostToThreadsBlock(Block):
self,
input_data: "PostToThreadsBlock.Input",
*,
profile_key: SecretStr,
user_id: str,
**kwargs,
) -> BlockOutput:
"""Post to Threads with Threads-specific validation."""
profile_key = await get_profile_key(user_id)
if not profile_key:
yield "error", "Please link a social account via Ayrshare"
return

View File

@@ -6,10 +6,9 @@ from backend.sdk import (
BlockSchema,
BlockType,
SchemaField,
SecretStr,
)
from ._util import BaseAyrshareInput, create_ayrshare_client
from ._util import BaseAyrshareInput, create_ayrshare_client, get_profile_key
class PostToTikTokBlock(Block):
@@ -108,9 +107,10 @@ class PostToTikTokBlock(Block):
)
async def run(
self, input_data: "PostToTikTokBlock.Input", *, profile_key: SecretStr, **kwargs
self, input_data: "PostToTikTokBlock.Input", *, user_id: str, **kwargs
) -> BlockOutput:
"""Post to TikTok with TikTok-specific validation and options."""
profile_key = await get_profile_key(user_id)
if not profile_key:
yield "error", "Please link a social account via Ayrshare"
return

View File

@@ -6,10 +6,9 @@ from backend.sdk import (
BlockSchema,
BlockType,
SchemaField,
SecretStr,
)
from ._util import BaseAyrshareInput, create_ayrshare_client
from ._util import BaseAyrshareInput, create_ayrshare_client, get_profile_key
class PostToXBlock(Block):
@@ -116,10 +115,11 @@ class PostToXBlock(Block):
self,
input_data: "PostToXBlock.Input",
*,
profile_key: SecretStr,
user_id: str,
**kwargs,
) -> BlockOutput:
"""Post to X / Twitter with enhanced X-specific options."""
profile_key = await get_profile_key(user_id)
if not profile_key:
yield "error", "Please link a social account via Ayrshare"
return

View File

@@ -9,10 +9,9 @@ from backend.sdk import (
BlockSchema,
BlockType,
SchemaField,
SecretStr,
)
from ._util import BaseAyrshareInput, create_ayrshare_client
from ._util import BaseAyrshareInput, create_ayrshare_client, get_profile_key
class YouTubeVisibility(str, Enum):
@@ -138,10 +137,12 @@ class PostToYouTubeBlock(Block):
self,
input_data: "PostToYouTubeBlock.Input",
*,
profile_key: SecretStr,
user_id: str,
**kwargs,
) -> BlockOutput:
"""Post to YouTube with YouTube-specific validation and options."""
profile_key = await get_profile_key(user_id)
if not profile_key:
yield "error", "Please link a social account via Ayrshare"
return

View File

@@ -38,7 +38,6 @@ from autogpt_libs.utils.cache import thread_cached
from prometheus_client import Gauge, start_http_server
from backend.blocks.agent import AgentExecutorBlock
from backend.blocks.ayrshare import AYRSHARE_BLOCK_IDS
from backend.data import redis_client as redis
from backend.data.block import (
BlockData,
@@ -186,17 +185,6 @@ async def execute_node(
)
extra_exec_kwargs[field_name] = credentials
if node_block.id in AYRSHARE_BLOCK_IDS:
profile_key = await creds_manager.store.get_ayrshare_profile_key(user_id)
if not profile_key:
logger.error(
"Ayrshare profile not configured. Please link a social account via Ayrshare integration first."
)
raise ValueError(
"Ayrshare profile not configured. Please link a social account via Ayrshare integration first."
)
extra_exec_kwargs["profile_key"] = profile_key
output_size = 0
try:
async for output_name, output_data in node_block.execute(

View File

@@ -363,21 +363,6 @@ class IntegrationCredentialsStore:
# ============== SYSTEM-MANAGED CREDENTIALS ============== #
async def get_ayrshare_profile_key(self, user_id: str) -> SecretStr | None:
"""Get the Ayrshare profile key for a user.
The profile key is used to authenticate API requests to Ayrshare's social media posting service.
See https://www.ayrshare.com/docs/apis/profiles/overview for more details.
Args:
user_id: The ID of the user to get the profile key for
Returns:
The profile key as a SecretStr if set, None otherwise
"""
user_integrations = await self._get_user_integrations(user_id)
return user_integrations.managed_credentials.ayrshare_profile_key
async def set_ayrshare_profile_key(self, user_id: str, profile_key: str) -> None:
"""Set the Ayrshare profile key for a user.

View File

@@ -29,7 +29,9 @@ from backend.data.model import (
CredentialsType,
HostScopedCredentials,
OAuth2Credentials,
UserIntegrations,
)
from backend.data.user import get_user_integrations
from backend.executor.utils import add_graph_execution
from backend.integrations.ayrshare import AyrshareClient, SocialPlatform
from backend.integrations.creds_manager import IntegrationCredentialsManager
@@ -585,7 +587,10 @@ async def get_ayrshare_sso_url(
# Ayrshare profile key is stored in the credentials store
# It is generated when creating a new profile, if there is no profile key,
# we create a new profile and store the profile key in the credentials store
profile_key = await creds_manager.store.get_ayrshare_profile_key(user_id)
user_integrations: UserIntegrations = await get_user_integrations(user_id)
profile_key = user_integrations.managed_credentials.ayrshare_profile_key
if not profile_key:
logger.debug(f"Creating new Ayrshare profile for user {user_id}")
try: