AI Update: شروع به پیاده سازی کن

This commit is contained in:
Antigravity Bot
2026-08-30 11:54:50 +03:30
parent c2a3cdc085
commit a5342b0850
4 changed files with 648 additions and 23 deletions
+18
View File
@@ -152,17 +152,35 @@ CREATE INDEX IF NOT EXISTS idx_source_websites_active ON source_websites(is_acti
CREATE INDEX IF NOT EXISTS idx_ai_providers_active ON ai_providers(is_active);
CREATE TABLE IF NOT EXISTS admin_channels (
id SERIAL PRIMARY KEY,
channel_id BIGINT UNIQUE NOT NULL,
title VARCHAR(255),
username VARCHAR(255),
is_default BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_admin_channels_active ON admin_channels(is_active);
-- Migration safety for existing tables
ALTER TABLE ai_providers ADD COLUMN IF NOT EXISTS fallback_provider_id BIGINT REFERENCES ai_providers(id) ON DELETE SET NULL;
ALTER TABLE ai_providers ADD COLUMN IF NOT EXISTS supports_vision BOOLEAN DEFAULT FALSE;
ALTER TABLE sources ADD COLUMN IF NOT EXISTS category_id INT REFERENCES channel_categories(id) ON DELETE SET NULL;
ALTER TABLE targets ADD COLUMN IF NOT EXISTS category_id INT REFERENCES channel_categories(id) ON DELETE SET NULL;
ALTER TABLE sources ADD COLUMN IF NOT EXISTS admin_channel_id BIGINT;
ALTER TABLE source_websites ADD COLUMN IF NOT EXISTS admin_channel_id BIGINT;
ALTER TABLE posts ADD COLUMN IF NOT EXISTS review_channel_id BIGINT;
ALTER TABLE sources ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT TRUE;
ALTER TABLE targets ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT TRUE;
ALTER TABLE sources ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP;
ALTER TABLE targets ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP;
CREATE INDEX IF NOT EXISTS idx_sources_category ON sources(category_id);
CREATE INDEX IF NOT EXISTS idx_targets_category ON targets(category_id);
CREATE INDEX IF NOT EXISTS idx_sources_admin_channel ON sources(admin_channel_id);
CREATE INDEX IF NOT EXISTS idx_source_websites_admin_channel ON source_websites(admin_channel_id);
CREATE INDEX IF NOT EXISTS idx_posts_review_channel ON posts(review_channel_id);
ALTER TABLE targets ADD COLUMN IF NOT EXISTS personality TEXT DEFAULT '';
ALTER TABLE targets ADD COLUMN IF NOT EXISTS custom_footer TEXT DEFAULT '';
ALTER TABLE targets ADD COLUMN IF NOT EXISTS sleep_start_hour INT DEFAULT 0;
+13
View File
@@ -1,6 +1,16 @@
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Any
@dataclass
class AdminReviewChannel:
id: Optional[int]
channel_id: int
title: Optional[str]
username: Optional[str] = None
is_default: bool = False
is_active: bool = True
created_at: Optional[str] = None
@dataclass
class ChannelCategory:
id: Optional[int]
@@ -16,6 +26,7 @@ class SourceChannel:
username: Optional[str]
title: Optional[str]
category_id: Optional[int] = None
admin_channel_id: Optional[int] = None
context_message_count: int = 0
is_active: bool = True
created_at: Optional[str] = None
@@ -26,6 +37,7 @@ class SourceWebsite:
name: str
url: str
category_id: Optional[int] = None
admin_channel_id: Optional[int] = None
check_interval_min: int = 30
auto_reanalyze_hours: int = 24
last_reanalyzed_at: Optional[str] = None
@@ -82,6 +94,7 @@ class Post:
rejection_reason: Optional[str] = None
published_to: List[Dict[str, Any]] = field(default_factory=list)
is_deleted: bool = False
review_channel_id: Optional[int] = None
review_message_id: Optional[int] = None
scheduled_at: Optional[str] = None
published_at: Optional[str] = None
+197 -7
View File
@@ -1,11 +1,17 @@
import json
import asyncpg
from datetime import datetime, timezone
from typing import List, Optional, Dict, Any, Tuple
from db.models import SourceChannel, SourceWebsite, TargetChannel, Post, Setting, AILog, AIProviderProfile, ChannelCategory
from db.models import (
SourceChannel, SourceWebsite, TargetChannel, Post, Setting,
AILog, AIProviderProfile, ChannelCategory, AdminReviewChannel
)
from db.database import get_db_pool
def _parse_admin_channel_row(row: asyncpg.Record) -> AdminReviewChannel:
return AdminReviewChannel(**dict(row))
def _parse_post_row(row: asyncpg.Record) -> Post:
data = dict(row)
if isinstance(data.get("published_to"), str):
@@ -566,13 +572,19 @@ class Repository:
)
return [_parse_post_row(r) for r in rows]
async def update_review_message_id(self, post_id: int, review_message_id: int) -> None:
async def update_review_message_id(self, post_id: int, review_message_id: Optional[int], review_channel_id: Optional[int] = None) -> None:
pool = await self._get_pool()
async with pool.acquire() as conn:
await conn.execute(
"UPDATE posts SET review_message_id = $1 WHERE id = $2;",
review_message_id, post_id,
)
if review_channel_id is not None:
await conn.execute(
"UPDATE posts SET review_message_id = $1, review_channel_id = $2 WHERE id = $3;",
review_message_id, review_channel_id, post_id,
)
else:
await conn.execute(
"UPDATE posts SET review_message_id = $1 WHERE id = $2;",
review_message_id, post_id,
)
async def _upsert_published_entry(
self,
@@ -1044,6 +1056,184 @@ class Repository:
description="Global emergency operational pause"
)
# --- Admin Review Channels ---
async def add_admin_channel(
self,
channel_id: int,
title: Optional[str] = None,
username: Optional[str] = None,
is_default: bool = False
) -> int:
pool = await self._get_pool()
async with pool.acquire() as conn:
async with conn.transaction():
if is_default:
await conn.execute("UPDATE admin_channels SET is_default = FALSE;")
row = await conn.fetchrow(
"""
INSERT INTO admin_channels (channel_id, title, username, is_default, is_active)
VALUES ($1, $2, $3, $4, TRUE)
ON CONFLICT(channel_id) DO UPDATE SET
title = COALESCE(EXCLUDED.title, admin_channels.title),
username = COALESCE(EXCLUDED.username, admin_channels.username),
is_default = EXCLUDED.is_default,
is_active = TRUE
RETURNING id;
""",
channel_id, title, username, is_default
)
return row["id"]
async def get_admin_channels(self) -> List[AdminReviewChannel]:
pool = await self._get_pool()
async with pool.acquire() as conn:
rows = await conn.fetch("SELECT * FROM admin_channels WHERE is_active = TRUE ORDER BY is_default DESC, id ASC;")
return [_parse_admin_channel_row(r) for r in rows]
async def get_admin_channel_by_id(self, admin_id: int) -> Optional[AdminReviewChannel]:
pool = await self._get_pool()
async with pool.acquire() as conn:
row = await conn.fetchrow("SELECT * FROM admin_channels WHERE id = $1;", admin_id)
return _parse_admin_channel_row(row) if row else None
async def get_admin_channel_by_telegram_id(self, channel_id: int) -> Optional[AdminReviewChannel]:
pool = await self._get_pool()
async with pool.acquire() as conn:
row = await conn.fetchrow("SELECT * FROM admin_channels WHERE channel_id = $1;", channel_id)
return _parse_admin_channel_row(row) if row else None
async def get_default_admin_channel(self) -> Optional[AdminReviewChannel]:
pool = await self._get_pool()
async with pool.acquire() as conn:
row = await conn.fetchrow("SELECT * FROM admin_channels WHERE is_active = TRUE AND is_default = TRUE LIMIT 1;")
if not row:
row = await conn.fetchrow("SELECT * FROM admin_channels WHERE is_active = TRUE ORDER BY id ASC LIMIT 1;")
return _parse_admin_channel_row(row) if row else None
async def set_default_admin_channel(self, admin_id: int) -> None:
pool = await self._get_pool()
async with pool.acquire() as conn:
async with conn.transaction():
await conn.execute("UPDATE admin_channels SET is_default = FALSE;")
await conn.execute("UPDATE admin_channels SET is_default = TRUE WHERE id = $1;", admin_id)
async def delete_admin_channel(self, admin_id: int) -> None:
pool = await self._get_pool()
async with pool.acquire() as conn:
row = await conn.fetchrow("SELECT channel_id FROM admin_channels WHERE id = $1;", admin_id)
if row:
ch_id = row["channel_id"]
await conn.execute("UPDATE sources SET admin_channel_id = NULL WHERE admin_channel_id = $1;", ch_id)
await conn.execute("UPDATE source_websites SET admin_channel_id = NULL WHERE admin_channel_id = $1;", ch_id)
await conn.execute("DELETE FROM admin_channels WHERE id = $1;", admin_id)
# --- Source & Website Admin Channel Setters ---
async def set_source_admin_channel(self, source_id: int, admin_channel_id: Optional[int]) -> None:
pool = await self._get_pool()
async with pool.acquire() as conn:
await conn.execute("UPDATE sources SET admin_channel_id = $1 WHERE id = $2;", admin_channel_id, source_id)
async def set_website_admin_channel(self, site_id: int, admin_channel_id: Optional[int]) -> None:
pool = await self._get_pool()
async with pool.acquire() as conn:
await conn.execute("UPDATE source_websites SET admin_channel_id = $1 WHERE id = $2;", admin_channel_id, site_id)
# --- Review Purge Helpers ---
async def get_review_posts_by_admin_channel(self, admin_channel_id: int) -> List[Post]:
pool = await self._get_pool()
async with pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT * FROM posts
WHERE review_channel_id = $1
AND review_message_id IS NOT NULL
AND is_deleted = FALSE
ORDER BY id ASC;
""",
admin_channel_id
)
return [_parse_post_row(r) for r in rows]
async def count_review_posts_by_admin_channel(self, admin_channel_id: int) -> int:
pool = await self._get_pool()
async with pool.acquire() as conn:
val = await conn.fetchval(
"""
SELECT COUNT(*) FROM posts
WHERE review_channel_id = $1
AND review_message_id IS NOT NULL
AND is_deleted = FALSE;
""",
admin_channel_id
)
return val or 0
async def clear_review_messages_for_channel(self, admin_channel_id: int) -> List[Post]:
pool = await self._get_pool()
async with pool.acquire() as conn:
async with conn.transaction():
rows = await conn.fetch(
"""
SELECT * FROM posts
WHERE review_channel_id = $1
AND review_message_id IS NOT NULL
AND is_deleted = FALSE;
""",
admin_channel_id
)
posts = [_parse_post_row(r) for r in rows]
await conn.execute(
"""
UPDATE posts
SET review_message_id = NULL,
is_deleted = TRUE,
status = 'deleted'
WHERE review_channel_id = $1
AND review_message_id IS NOT NULL
AND is_deleted = FALSE;
""",
admin_channel_id
)
return posts
async def get_all_review_posts_with_message_id(self) -> List[Post]:
pool = await self._get_pool()
async with pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT * FROM posts
WHERE review_message_id IS NOT NULL
AND is_deleted = FALSE
ORDER BY id ASC;
"""
)
return [_parse_post_row(r) for r in rows]
async def clear_all_review_messages(self) -> List[Post]:
pool = await self._get_pool()
async with pool.acquire() as conn:
async with conn.transaction():
rows = await conn.fetch(
"""
SELECT * FROM posts
WHERE review_message_id IS NOT NULL
AND is_deleted = FALSE;
"""
)
posts = [_parse_post_row(r) for r in rows]
await conn.execute(
"""
UPDATE posts
SET review_message_id = NULL,
is_deleted = TRUE,
status = 'deleted'
WHERE review_message_id IS NOT NULL
AND is_deleted = FALSE;
"""
)
return posts