AI Update: شروع به پیاده سازی کن
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user