| Server IP : 3.147.158.171 / Your IP : 216.73.216.216 Web Server : Apache/2.4.67 (Amazon Linux) OpenSSL/3.5.5 System : Linux ip-172-31-2-178.us-east-2.compute.internal 6.1.172-216.329.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Wed May 20 06:31:34 UTC 2026 x86_64 User : ec2-user ( 1000) PHP Version : 8.4.21 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /tsai/repo/api/app/services/ |
Upload File : |
import json
import logging
import os
from pathlib import Path
from typing import List, Optional
from urllib.parse import urlparse
from ..appTypes import SiteConfig
CACHE_DIR = Path(__file__).parent.parent / 'data'
class SiteConfigService:
_sites: List[SiteConfig] = []
_loaded: bool = False
def __init__(self):
if not SiteConfigService._loaded:
self._load_config()
SiteConfigService._loaded = True
def _get_cache_path(self) -> Path:
env = os.getenv('TSAI_ENV', 'development')
if env == 'production':
return CACHE_DIR / 'site_configs_tsai.json'
elif env == 'scike_ai':
return CACHE_DIR / 'site_configs_scike.json'
return CACHE_DIR / 'site_configs_development.json'
def _load_config(self):
try:
self._load_from_drupal()
except Exception as e:
logging.warning(f"Drupal site config fetch failed, using cache: {e}")
self._load_from_cache()
self._load_dev_aliases()
def _load_from_drupal(self):
from .drupal import DrupalService
drupal_svc = DrupalService()
data_list, included_list = drupal_svc.fetch_all_site_configs()
sites = drupal_svc.parse_site_config_nodes(data_list, included_list)
if not sites:
raise Exception("Drupal returned 0 site configs — content type may not be installed yet")
SiteConfigService._sites = sites
self._write_cache(sites)
print(f"Loaded {len(sites)} site configs from Drupal")
def _load_from_cache(self):
cache_path = self._get_cache_path()
if not cache_path.exists():
logging.error(f"No cache file at {cache_path} and Drupal is unreachable")
SiteConfigService._sites = []
return
with open(cache_path) as f:
config = json.load(f)
api_host = os.getenv('API_HOST', 'http://localhost:8000/api/v1')
drupal_host = os.getenv('DRUPAL_HOST', 'http://drupal.test')
drupal_api = os.getenv('DRUPAL_API', '/jjknkk/jptb69dhj027ooae/jsonapi')
sites = []
for site_data in config.get('sites', []):
sites.append(SiteConfig(
cb_host=site_data['cb_host'],
wp_host=site_data['wp_host'],
wp_dir=site_data['wp_dir'],
slug=site_data['slug'],
api_host=api_host,
drupal_host=drupal_host,
drupal_api=drupal_api,
drupal_uid=site_data.get('drupal_uid'),
drupal_target_id=site_data.get('drupal_target_id'),
drupal_node_id=site_data.get('drupal_node_id'),
))
SiteConfigService._sites = sites
print(f"Loaded {len(sites)} site configs from cache: {cache_path.name}")
def _write_cache(self, sites: List[SiteConfig]):
cache_path = self._get_cache_path()
cache_data = {
"sites": [
{
"cb_host": s.cb_host,
"wp_host": s.wp_host,
"wp_dir": s.wp_dir,
"slug": s.slug,
"drupal_uid": s.drupal_uid,
"drupal_target_id": s.drupal_target_id,
"drupal_node_id": s.drupal_node_id,
}
for s in sites
]
}
try:
with open(cache_path, 'w') as f:
json.dump(cache_data, f, indent=2)
except Exception as e:
logging.warning(f"Failed to write site config cache: {e}")
def _load_dev_aliases(self):
env = os.getenv('TSAI_ENV', 'development')
if env in ('production', 'scike_ai'):
return
aliases_path = CACHE_DIR / 'dev_aliases.json'
if not aliases_path.exists():
return
try:
with open(aliases_path) as f:
aliases = json.load(f)
except Exception as e:
logging.warning(f"Failed to read dev aliases: {e}")
return
count = 0
for alias_host, slug in aliases.items():
site = self.get_site_by_slug(slug)
if not site:
logging.warning(f"Dev alias {alias_host} → {slug}: slug not found, skipping")
continue
alias_site = site.model_copy(update={'cb_host': alias_host, 'drupal_node_id': None})
SiteConfigService._sites.append(alias_site)
count += 1
if count:
print(f"Loaded {count} dev aliases")
@classmethod
def reload(cls):
"""Force a fresh reload from Drupal."""
cls._loaded = False
cls._sites = []
cls()
def get_all_sites(self) -> List[SiteConfig]:
return SiteConfigService._sites
def get_site_by_cb_host(self, host: str) -> Optional[SiteConfig]:
for site in SiteConfigService._sites:
if site.cb_host == host:
return site
wp_hostname = urlparse(host if '://' in host else f'//{host}').hostname
if not wp_hostname:
return None
for site in SiteConfigService._sites:
if urlparse(site.wp_host).hostname == wp_hostname:
return site
return None
def get_site_by_slug(self, slug: str) -> Optional[SiteConfig]:
for site in SiteConfigService._sites:
if site.slug == slug:
return site
return None
def get_drupal_user(self, username: str) -> Optional[str]:
"""Get a Drupal user UUID by site slug."""
site = self.get_site_by_slug(username)
return site.drupal_uid if site else None
def get_slug_by_drupal_uuid(self, uuid: str) -> Optional[str]:
"""Get a site slug by Drupal user UUID."""
for site in SiteConfigService._sites:
if site.drupal_uid == uuid:
return site.slug
return None
async def add_site(self, cb_host: str, wp_host: str, wp_dir: str, slug: str, drupal_user: str = "") -> SiteConfig:
"""Create a site_config node in Drupal and add to in-memory cache."""
from .drupal import DrupalService
import requests as req
drupal_svc = DrupalService()
api_host = os.getenv('API_HOST', 'http://localhost:8000/api/v1')
drupal_host = os.getenv('DRUPAL_HOST', 'http://drupal.test')
drupal_api = os.getenv('DRUPAL_API', '/jjknkk/jptb69dhj027ooae/jsonapi')
owner_uuid = None
drupal_target_id = None
session = req.Session()
try:
await drupal_svc.login(session)
csrf_token = await drupal_svc.get_csrf_token(session)
if drupal_user:
owner_uuid, drupal_target_id = drupal_svc.fetch_drupal_user_by_name(drupal_user, session=session)
node_data = await drupal_svc.create_site_config_node(
session, csrf_token,
slug=slug, cb_host=cb_host, wp_host=wp_host,
wp_dir=wp_dir,
owner_uuid=owner_uuid or '',
)
node_uuid = node_data.get('id') if node_data else None
finally:
session.close()
new_site = SiteConfig(
cb_host=cb_host,
wp_host=wp_host,
wp_dir=wp_dir,
slug=slug,
api_host=api_host,
drupal_host=drupal_host,
drupal_api=drupal_api,
drupal_uid=owner_uuid,
drupal_target_id=drupal_target_id,
drupal_node_id=node_uuid,
)
SiteConfigService._sites.append(new_site)
self._write_cache(SiteConfigService._sites)
return new_site
async def remove_site(self, cb_host: str) -> Optional[dict]:
"""Remove a site_config node from Drupal and from in-memory cache."""
from .drupal import DrupalService
import requests as req
site = None
for s in SiteConfigService._sites:
if s.cb_host == cb_host:
site = s
break
if not site:
return None
removed = {
'cb_host': site.cb_host,
'slug': site.slug,
'drupal_user': site.slug,
}
if site.drupal_node_id:
drupal_svc = DrupalService()
session = req.Session()
try:
await drupal_svc.login(session)
csrf_token = await drupal_svc.get_csrf_token(session)
await drupal_svc.delete_site_config_node(session, csrf_token, site.drupal_node_id)
except Exception as e:
logging.error(f"Failed to delete site_config node from Drupal: {e}")
finally:
session.close()
SiteConfigService._sites = [s for s in SiteConfigService._sites if s.cb_host != cb_host]
self._write_cache(SiteConfigService._sites)
return removed