Initial project commit
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import json
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.audit_log import AuditLog
|
||||
|
||||
|
||||
async def log_action(
|
||||
db: AsyncSession,
|
||||
operator_id: int,
|
||||
action: str,
|
||||
target_type: str,
|
||||
target_id: int | None = None,
|
||||
detail: dict | str | None = None,
|
||||
) -> AuditLog:
|
||||
detail_str = None
|
||||
if detail is not None:
|
||||
detail_str = json.dumps(detail, ensure_ascii=False) if isinstance(detail, dict) else str(detail)
|
||||
|
||||
entry = AuditLog(
|
||||
operator_id=operator_id,
|
||||
action=action,
|
||||
target_type=target_type,
|
||||
target_id=target_id,
|
||||
detail=detail_str,
|
||||
)
|
||||
db.add(entry)
|
||||
await db.flush()
|
||||
return entry
|
||||
@@ -0,0 +1,65 @@
|
||||
import logging
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_SENSITIVE_WORDS: list[str] = []
|
||||
_PATTERN: re.Pattern | None = None
|
||||
|
||||
|
||||
def _load_words():
|
||||
global _SENSITIVE_WORDS, _PATTERN
|
||||
words_file = Path(__file__).parent.parent.parent / "sensitive_words.txt"
|
||||
if words_file.exists():
|
||||
raw = words_file.read_text(encoding="utf-8")
|
||||
_SENSITIVE_WORDS = [w.strip() for w in raw.splitlines() if w.strip()]
|
||||
else:
|
||||
_SENSITIVE_WORDS = []
|
||||
|
||||
if _SENSITIVE_WORDS:
|
||||
escaped = [re.escape(w) for w in _SENSITIVE_WORDS]
|
||||
_PATTERN = re.compile("|".join(escaped), re.IGNORECASE)
|
||||
else:
|
||||
_PATTERN = None
|
||||
|
||||
|
||||
_load_words()
|
||||
|
||||
|
||||
def reload_sensitive_words():
|
||||
"""Hot-reload the sensitive word list from disk."""
|
||||
_load_words()
|
||||
logger.info("Reloaded %d sensitive words", len(_SENSITIVE_WORDS))
|
||||
|
||||
|
||||
def check_text(text: str) -> dict:
|
||||
"""
|
||||
Check text against the sensitive word list.
|
||||
Returns {"safe": True/False, "matched": [...]}
|
||||
"""
|
||||
if not text or _PATTERN is None:
|
||||
return {"safe": True, "matched": []}
|
||||
|
||||
matches = _PATTERN.findall(text)
|
||||
if matches:
|
||||
unique = list(set(matches))
|
||||
return {"safe": False, "matched": unique}
|
||||
return {"safe": True, "matched": []}
|
||||
|
||||
|
||||
def filter_text(text: str, replacement: str = "**") -> str:
|
||||
"""Replace sensitive words with the replacement string."""
|
||||
if not text or _PATTERN is None:
|
||||
return text
|
||||
return _PATTERN.sub(replacement, text)
|
||||
|
||||
|
||||
async def check_image_safety(image_url: str) -> dict:
|
||||
"""
|
||||
Placeholder for third-party image audit.
|
||||
In production, integrate with Tencent Cloud / Aliyun content moderation API.
|
||||
Returns {"safe": True/False, "labels": [...]}
|
||||
"""
|
||||
logger.debug("Image safety check (placeholder): %s", image_url)
|
||||
return {"safe": True, "labels": []}
|
||||
@@ -0,0 +1,25 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.notification import Notification
|
||||
|
||||
|
||||
async def send_notification(
|
||||
db: AsyncSession,
|
||||
user_id: int,
|
||||
type: str,
|
||||
title: str,
|
||||
content: str | None = None,
|
||||
ref_type: str | None = None,
|
||||
ref_id: int | None = None,
|
||||
):
|
||||
n = Notification(
|
||||
user_id=user_id,
|
||||
type=type,
|
||||
title=title,
|
||||
content=content,
|
||||
ref_type=ref_type,
|
||||
ref_id=ref_id,
|
||||
)
|
||||
db.add(n)
|
||||
await db.flush()
|
||||
return n
|
||||
@@ -0,0 +1,34 @@
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.point_ledger import PointLedger
|
||||
|
||||
|
||||
async def grant_points(
|
||||
db: AsyncSession,
|
||||
user_id: int,
|
||||
change: int,
|
||||
reason: str,
|
||||
ref_type: str | None = None,
|
||||
ref_id: int | None = None,
|
||||
) -> PointLedger:
|
||||
last_entry = await db.execute(
|
||||
select(PointLedger)
|
||||
.where(PointLedger.user_id == user_id)
|
||||
.order_by(PointLedger.id.desc())
|
||||
.limit(1)
|
||||
)
|
||||
last = last_entry.scalar_one_or_none()
|
||||
current_balance = last.balance if last else 0
|
||||
|
||||
ledger = PointLedger(
|
||||
user_id=user_id,
|
||||
change=change,
|
||||
balance=current_balance + change,
|
||||
reason=reason,
|
||||
ref_type=ref_type,
|
||||
ref_id=ref_id,
|
||||
)
|
||||
db.add(ledger)
|
||||
await db.flush()
|
||||
return ledger
|
||||
Reference in New Issue
Block a user