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:
@@ -58,6 +58,10 @@ FEATURE_GROUPS: Dict[str, FeatureGroup] = {
|
||||
"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"),
|
||||
"youtube": ("core", "youtube"),
|
||||
"blog_writer": ("core", "blog_writer"),
|
||||
"backlinking": ("core", "backlinking"),
|
||||
"planning": ("core", "content_planning"),
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ OPTIONAL_ROUTER_REGISTRY = [
|
||||
{"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": "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 = {
|
||||
|
||||
@@ -138,7 +138,6 @@ if _is_full_mode():
|
||||
from routers.image_studio import router as image_studio_router
|
||||
from routers.product_marketing import router as product_marketing_router
|
||||
from routers.campaign_creator import router as campaign_creator_router
|
||||
from routers.backlink_outreach import router as backlink_outreach_router
|
||||
else:
|
||||
# In feature-only modes, only load essential assets router
|
||||
from api.assets_serving import router as assets_serving_router
|
||||
@@ -147,7 +146,6 @@ else:
|
||||
image_studio_router = None
|
||||
product_marketing_router = None
|
||||
campaign_creator_router = None
|
||||
backlink_outreach_router = None
|
||||
|
||||
# Import hallucination detector router
|
||||
try:
|
||||
@@ -683,8 +681,6 @@ if _is_full_mode():
|
||||
app.include_router(product_marketing_router)
|
||||
if campaign_creator_router:
|
||||
app.include_router(campaign_creator_router)
|
||||
if backlink_outreach_router:
|
||||
app.include_router(backlink_outreach_router)
|
||||
|
||||
router_group_status["platform_extensions"] = {
|
||||
"mounted": True,
|
||||
@@ -799,6 +795,24 @@ async def startup_event():
|
||||
else:
|
||||
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)
|
||||
wix_api_key = os.getenv('WIX_API_KEY')
|
||||
if wix_api_key:
|
||||
|
||||
@@ -193,7 +193,7 @@ const App: React.FC = () => {
|
||||
<Route path="/dashboard" element={<ProtectedRoute><MainDashboard /></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="/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="/facebook-writer" element={<ProtectedRoute><FeatureRoute feature="social"><FacebookWriter /></FeatureRoute></ProtectedRoute>} />
|
||||
<Route path="/linkedin-writer" element={<ProtectedRoute><FeatureRoute feature="social"><LinkedInWriter /></FeatureRoute></ProtectedRoute>} />
|
||||
|
||||
@@ -28,6 +28,7 @@ export const FEATURE_KEYS = {
|
||||
WIX: 'wix',
|
||||
BING: 'bing',
|
||||
ASSET_LIBRARY: 'asset-library',
|
||||
BACKLINKING: 'backlinking',
|
||||
} as const;
|
||||
|
||||
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][] = [
|
||||
['podcast', '/podcast-maker'],
|
||||
['blog_writer', '/blog-writer'],
|
||||
['backlinking', '/backlink-outreach'],
|
||||
['story', '/story-writer'],
|
||||
['image', '/image-studio'],
|
||||
['video', '/video-studio'],
|
||||
|
||||
Reference in New Issue
Block a user