feat: add backlinking feature flag following blog_writer pattern

- Register 'backlinking' FeatureGroup in feature_registry.py with
  routers=routers.backlink_outreach:router
- Add 'backlinking' profile to PROFILE_GROUP_MAP (core + backlinking)
- Add backlink_outreach to OPTIONAL_ROUTER_REGISTRY with
  features={'all', 'backlinking'}
- Remove direct import/include of backlink_outreach from app.py
  (router manager handles both 'all' and 'backlinking' modes)
- Add BACKLINKING key to FEATURE_KEYS and route priority in
  frontend demoMode.ts
- Change frontend route gate from feature='seo' to feature='backlinking'
  so ALWRITY_ENABLED_FEATURES=backlinking enables the route
This commit is contained in:
ajaysi
2026-06-03 20:19:41 +05:30
parent 8699ffc27d
commit 9a3d704c5c
5 changed files with 27 additions and 5 deletions

View File

@@ -58,6 +58,10 @@ FEATURE_GROUPS: Dict[str, FeatureGroup] = {
"api.blog_writer.seo_analysis:router", "api.blog_writer.seo_analysis:router",
), ),
), ),
"backlinking": FeatureGroup(
features=("backlinking",),
routers=("routers.backlink_outreach:router",),
),
} }
@@ -67,5 +71,6 @@ PROFILE_GROUP_MAP: Dict[str, Tuple[str, ...]] = {
"podcast": ("core", "podcast"), "podcast": ("core", "podcast"),
"youtube": ("core", "youtube"), "youtube": ("core", "youtube"),
"blog_writer": ("core", "blog_writer"), "blog_writer": ("core", "blog_writer"),
"backlinking": ("core", "backlinking"),
"planning": ("core", "content_planning"), "planning": ("core", "content_planning"),
} }

View File

@@ -67,6 +67,7 @@ OPTIONAL_ROUTER_REGISTRY = [
{"name": "oauth_token_monitoring", "module": "api.oauth_token_monitoring_routes", "attr": "router", "features": {"all", "core"}}, {"name": "oauth_token_monitoring", "module": "api.oauth_token_monitoring_routes", "attr": "router", "features": {"all", "core"}},
{"name": "agents", "module": "api.agents_api", "attr": "router", "features": {"all"}}, {"name": "agents", "module": "api.agents_api", "attr": "router", "features": {"all"}},
{"name": "today_workflow", "module": "api.today_workflow", "attr": "router", "features": {"all"}}, {"name": "today_workflow", "module": "api.today_workflow", "attr": "router", "features": {"all"}},
{"name": "backlink_outreach", "module": "routers.backlink_outreach", "attr": "router", "features": {"all", "backlinking"}},
] ]
OPTIONAL_MODULE_MATRIX = { OPTIONAL_MODULE_MATRIX = {

View File

@@ -138,7 +138,6 @@ if _is_full_mode():
from routers.image_studio import router as image_studio_router from routers.image_studio import router as image_studio_router
from routers.product_marketing import router as product_marketing_router from routers.product_marketing import router as product_marketing_router
from routers.campaign_creator import router as campaign_creator_router from routers.campaign_creator import router as campaign_creator_router
from routers.backlink_outreach import router as backlink_outreach_router
else: else:
# In feature-only modes, only load essential assets router # In feature-only modes, only load essential assets router
from api.assets_serving import router as assets_serving_router from api.assets_serving import router as assets_serving_router
@@ -147,7 +146,6 @@ else:
image_studio_router = None image_studio_router = None
product_marketing_router = None product_marketing_router = None
campaign_creator_router = None campaign_creator_router = None
backlink_outreach_router = None
# Import hallucination detector router # Import hallucination detector router
try: try:
@@ -683,8 +681,6 @@ if _is_full_mode():
app.include_router(product_marketing_router) app.include_router(product_marketing_router)
if campaign_creator_router: if campaign_creator_router:
app.include_router(campaign_creator_router) app.include_router(campaign_creator_router)
if backlink_outreach_router:
app.include_router(backlink_outreach_router)
router_group_status["platform_extensions"] = { router_group_status["platform_extensions"] = {
"mounted": True, "mounted": True,
@@ -799,6 +795,24 @@ async def startup_event():
else: else:
logger.info(f"[FEATURE-MODE] Skipping scheduler startup (features: {enabled_features})") logger.info(f"[FEATURE-MODE] Skipping scheduler startup (features: {enabled_features})")
# Recover stale YouTube tasks on startup
if _is_feature_enabled("youtube"):
try:
from api.youtube.task_manager import task_manager
from services.database import get_all_user_ids
user_ids = get_all_user_ids()
recovered = 0
for uid in user_ids:
try:
count = task_manager.recover_stale_tasks(uid)
recovered += count
except Exception:
pass
if recovered > 0:
logger.info(f"[STARTUP] Recovered {recovered} stale YouTube tasks across {len(user_ids)} users")
except Exception as e:
logger.warning(f"[STARTUP] YouTube task recovery skipped: {e}")
# Check Wix configuration (OAuth-based, API key optional) # Check Wix configuration (OAuth-based, API key optional)
wix_api_key = os.getenv('WIX_API_KEY') wix_api_key = os.getenv('WIX_API_KEY')
if wix_api_key: if wix_api_key:

View File

@@ -193,7 +193,7 @@ const App: React.FC = () => {
<Route path="/dashboard" element={<ProtectedRoute><MainDashboard /></ProtectedRoute>} /> <Route path="/dashboard" element={<ProtectedRoute><MainDashboard /></ProtectedRoute>} />
<Route path="/seo" element={<ProtectedRoute><FeatureRoute feature="seo"><SEODashboard /></FeatureRoute></ProtectedRoute>} /> <Route path="/seo" element={<ProtectedRoute><FeatureRoute feature="seo"><SEODashboard /></FeatureRoute></ProtectedRoute>} />
<Route path="/seo-dashboard" element={<ProtectedRoute><FeatureRoute feature="seo"><SEODashboard /></FeatureRoute></ProtectedRoute>} /> <Route path="/seo-dashboard" element={<ProtectedRoute><FeatureRoute feature="seo"><SEODashboard /></FeatureRoute></ProtectedRoute>} />
<Route path="/backlink-outreach" element={<ProtectedRoute><FeatureRoute feature="seo"><BacklinkOutreachDashboard /></FeatureRoute></ProtectedRoute>} /> <Route path="/backlink-outreach" element={<ProtectedRoute><FeatureRoute feature="backlinking"><BacklinkOutreachDashboard /></FeatureRoute></ProtectedRoute>} />
<Route path="/content-planning" element={<ProtectedRoute><FeatureRoute feature="content-planning"><ContentPlanningDashboard /></FeatureRoute></ProtectedRoute>} /> <Route path="/content-planning" element={<ProtectedRoute><FeatureRoute feature="content-planning"><ContentPlanningDashboard /></FeatureRoute></ProtectedRoute>} />
<Route path="/facebook-writer" element={<ProtectedRoute><FeatureRoute feature="social"><FacebookWriter /></FeatureRoute></ProtectedRoute>} /> <Route path="/facebook-writer" element={<ProtectedRoute><FeatureRoute feature="social"><FacebookWriter /></FeatureRoute></ProtectedRoute>} />
<Route path="/linkedin-writer" element={<ProtectedRoute><FeatureRoute feature="social"><LinkedInWriter /></FeatureRoute></ProtectedRoute>} /> <Route path="/linkedin-writer" element={<ProtectedRoute><FeatureRoute feature="social"><LinkedInWriter /></FeatureRoute></ProtectedRoute>} />

View File

@@ -28,6 +28,7 @@ export const FEATURE_KEYS = {
WIX: 'wix', WIX: 'wix',
BING: 'bing', BING: 'bing',
ASSET_LIBRARY: 'asset-library', ASSET_LIBRARY: 'asset-library',
BACKLINKING: 'backlinking',
} as const; } as const;
export type FeatureKey = typeof FEATURE_KEYS[keyof typeof FEATURE_KEYS]; export type FeatureKey = typeof FEATURE_KEYS[keyof typeof FEATURE_KEYS];
@@ -124,6 +125,7 @@ export function getSingleFeature(): string | null {
const FEATURE_ROUTE_PRIORITY: [string, string][] = [ const FEATURE_ROUTE_PRIORITY: [string, string][] = [
['podcast', '/podcast-maker'], ['podcast', '/podcast-maker'],
['blog_writer', '/blog-writer'], ['blog_writer', '/blog-writer'],
['backlinking', '/backlink-outreach'],
['story', '/story-writer'], ['story', '/story-writer'],
['image', '/image-studio'], ['image', '/image-studio'],
['video', '/video-studio'], ['video', '/video-studio'],