403Webshell
Server IP : 3.147.158.171  /  Your IP : 216.73.216.88
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/www/html/api.turmansolutions.ai/app/services/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /tsai/www/html/api.turmansolutions.ai/app/services/posting_history.py
"""Append-only log of cron posting attempts (articles + comments).

The hourly cron scripts (`create_articles.py`, the comments agent) record every
posting attempt here; the schedule calendar (`schedule_calendar.py`) reads it
back to render concrete "what was posted" events with links and success/failure
status alongside the forward-looking recurring schedule.

Storage is a per-environment JSONL file under `app/data/` — one JSON object per
line — mirroring the `site_configs_{env}.json` cache pattern. JSONL is chosen so
independent processes can append safely without a read-modify-write race: the
two timers run sequentially (articles :05, comments :35) and each write is a
single short line. Recording is best-effort and never raises into the caller — a
logging failure must not break posting.
"""

import json
import logging
import os
from datetime import datetime, timedelta
from pathlib import Path
from typing import List, Optional

from ..appTypes import PostingRecord, SiteConfig

CACHE_DIR = Path(__file__).parent.parent / 'data'


def _history_path() -> Path:
    """Env-suffixed history file, matching SiteConfigService's cache mapping."""
    env = os.getenv('TSAI_ENV', 'development')
    if env == 'production':
        suffix = 'tsai'
    elif env == 'scike_ai':
        suffix = 'scike'
    else:
        suffix = 'development'
    return CACHE_DIR / f'posting_history_{suffix}.jsonl'


def record_post(
    site: SiteConfig,
    *,
    kind: str,
    character: str,
    status: str,
    character_slug: Optional[str] = None,
    storage_id: Optional[str] = None,
    post_id: Optional[int] = None,
    url: Optional[str] = None,
    title: Optional[str] = None,
    excerpt: Optional[str] = None,
    model: Optional[str] = None,
    error: Optional[str] = None,
    when: Optional[datetime] = None,
) -> None:
    """Append one posting attempt to the history log. Best-effort — never raises."""
    try:
        now = when or datetime.now()
        record = PostingRecord(
            ts=now.isoformat(timespec='seconds'),
            date=now.strftime('%Y-%m-%d'),
            site=site.slug,
            kind=kind,
            character=character,
            status=status,
            character_slug=character_slug,
            storage_id=storage_id,
            post_id=post_id,
            url=url,
            title=title,
            excerpt=excerpt,
            model=model,
            error=error,
        )
        path = _history_path()
        path.parent.mkdir(parents=True, exist_ok=True)
        with open(path, 'a', encoding='utf-8') as f:
            f.write(json.dumps(record.model_dump(exclude_none=True)) + '\n')
            f.flush()
    except Exception as e:
        logging.error(f"Failed to record posting history ({kind}/{character}): {e}")


def read_history(site_slug: str, *, since_days: int = 60) -> List[PostingRecord]:
    """Return this site's posting records within the last `since_days` days.

    Tolerant of malformed / partially-written trailing lines — those are skipped.
    """
    path = _history_path()
    if not path.exists():
        return []

    cutoff = (datetime.now() - timedelta(days=since_days)).strftime('%Y-%m-%d')
    records: List[PostingRecord] = []
    try:
        with open(path, 'r', encoding='utf-8') as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                try:
                    data = json.loads(line)
                except json.JSONDecodeError:
                    continue
                if data.get('site') != site_slug:
                    continue
                if str(data.get('date', '')) < cutoff:
                    continue
                try:
                    records.append(PostingRecord(**data))
                except Exception:
                    continue
    except Exception as e:
        logging.error(f"Failed to read posting history for {site_slug}: {e}")
        return records

    return records

Youez - 2016 - github.com/yon3zu
LinuXploit