|
1 | 1 | """Database storage layer for Flow2API""" |
| 2 | +from pathlib import Path |
| 3 | + |
2 | 4 | import aiosqlite |
3 | 5 | import json |
4 | | -from datetime import datetime |
5 | 6 | from typing import Optional, List, Dict, Any |
6 | | -from pathlib import Path |
7 | | -from .models import Token, TokenStats, Task, RequestLog, AdminConfig, ProxyConfig, GenerationConfig, CacheConfig, Project, CaptchaConfig, PluginConfig, CallLogicConfig |
| 7 | + |
| 8 | +from .models import Token, TokenStats, Task, RequestLog, AdminConfig, ProxyConfig, GenerationConfig, CacheConfig, \ |
| 9 | + Project, CaptchaConfig, PluginConfig, CallLogicConfig |
8 | 10 |
|
9 | 11 |
|
10 | 12 | class Database: |
@@ -325,6 +327,25 @@ async def check_and_migrate_db(self, config_dict: dict = None): |
325 | 327 | ) |
326 | 328 | """) |
327 | 329 |
|
| 330 | + # Check and create uploaded_image_cache table if missing |
| 331 | + if not await self._table_exists(db, "uploaded_image_cache"): |
| 332 | + print(" ✓ Creating missing table: uploaded_image_cache") |
| 333 | + await db.execute(""" |
| 334 | + CREATE TABLE uploaded_image_cache |
| 335 | + ( |
| 336 | + id INTEGER PRIMARY KEY AUTOINCREMENT, |
| 337 | + email TEXT NOT NULL, |
| 338 | + project_id TEXT NOT NULL, |
| 339 | + image_hash TEXT NOT NULL, |
| 340 | + aspect_ratio TEXT NOT NULL, |
| 341 | + media_id TEXT NOT NULL, |
| 342 | + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 343 | + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 344 | + last_used_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 345 | + UNIQUE (email, project_id, image_hash, aspect_ratio) |
| 346 | + ) |
| 347 | + """) |
| 348 | + |
328 | 349 | # ========== Step 2: Add missing columns to existing tables ========== |
329 | 350 | # Check and add missing columns to tokens table |
330 | 351 | if await self._table_exists(db, "tokens"): |
@@ -543,6 +564,23 @@ async def init_db(self): |
543 | 564 | ) |
544 | 565 | """) |
545 | 566 |
|
| 567 | + # Uploaded image media cache table |
| 568 | + await db.execute(""" |
| 569 | + CREATE TABLE IF NOT EXISTS uploaded_image_cache |
| 570 | + ( |
| 571 | + id INTEGER PRIMARY KEY AUTOINCREMENT, |
| 572 | + email TEXT NOT NULL, |
| 573 | + project_id TEXT NOT NULL, |
| 574 | + image_hash TEXT NOT NULL, |
| 575 | + aspect_ratio TEXT NOT NULL, |
| 576 | + media_id TEXT NOT NULL, |
| 577 | + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 578 | + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 579 | + last_used_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 580 | + UNIQUE (email, project_id, image_hash, aspect_ratio) |
| 581 | + ) |
| 582 | + """) |
| 583 | + |
546 | 584 | # Admin config table |
547 | 585 | await db.execute(""" |
548 | 586 | CREATE TABLE IF NOT EXISTS admin_config ( |
@@ -656,6 +694,8 @@ async def init_db(self): |
656 | 694 | await db.execute("CREATE INDEX IF NOT EXISTS idx_project_id ON projects(project_id)") |
657 | 695 | await db.execute("CREATE INDEX IF NOT EXISTS idx_tokens_email ON tokens(email)") |
658 | 696 | await db.execute("CREATE INDEX IF NOT EXISTS idx_tokens_is_active_last_used_at ON tokens(is_active, last_used_at)") |
| 697 | + await db.execute( |
| 698 | + "CREATE INDEX IF NOT EXISTS idx_uploaded_image_cache_lookup ON uploaded_image_cache(email, project_id, image_hash, aspect_ratio)") |
659 | 699 |
|
660 | 700 | # Migrate request_logs table if needed |
661 | 701 | await self._migrate_request_logs(db) |
@@ -944,6 +984,101 @@ async def delete_project(self, project_id: str): |
944 | 984 | await db.execute("DELETE FROM projects WHERE project_id = ?", (project_id,)) |
945 | 985 | await db.commit() |
946 | 986 |
|
| 987 | + # Uploaded image cache operations |
| 988 | + async def get_uploaded_image_cache( |
| 989 | + self, |
| 990 | + email: str, |
| 991 | + project_id: str, |
| 992 | + image_hash: str, |
| 993 | + aspect_ratio: str, |
| 994 | + ) -> Optional[Dict[str, Any]]: |
| 995 | + """Get cached uploaded media metadata by cache key.""" |
| 996 | + async with aiosqlite.connect(self.db_path) as db: |
| 997 | + db.row_factory = aiosqlite.Row |
| 998 | + cursor = await db.execute( |
| 999 | + """ |
| 1000 | + SELECT * |
| 1001 | + FROM uploaded_image_cache |
| 1002 | + WHERE email = ? |
| 1003 | + AND project_id = ? |
| 1004 | + AND image_hash = ? |
| 1005 | + AND aspect_ratio = ? |
| 1006 | + """, |
| 1007 | + (email, project_id, image_hash, aspect_ratio), |
| 1008 | + ) |
| 1009 | + row = await cursor.fetchone() |
| 1010 | + if row: |
| 1011 | + return dict(row) |
| 1012 | + return None |
| 1013 | + |
| 1014 | + async def upsert_uploaded_image_cache( |
| 1015 | + self, |
| 1016 | + email: str, |
| 1017 | + project_id: str, |
| 1018 | + image_hash: str, |
| 1019 | + aspect_ratio: str, |
| 1020 | + media_id: str, |
| 1021 | + ): |
| 1022 | + """Insert or update cached uploaded media metadata.""" |
| 1023 | + async with aiosqlite.connect(self.db_path) as db: |
| 1024 | + await db.execute( |
| 1025 | + """ |
| 1026 | + INSERT INTO uploaded_image_cache (email, project_id, image_hash, aspect_ratio, media_id) |
| 1027 | + VALUES (?, ?, ?, ?, ?) |
| 1028 | + ON CONFLICT(email, project_id, image_hash, aspect_ratio) |
| 1029 | + DO UPDATE SET media_id = excluded.media_id, |
| 1030 | + updated_at = CURRENT_TIMESTAMP, |
| 1031 | + last_used_at = CURRENT_TIMESTAMP |
| 1032 | + """, |
| 1033 | + (email, project_id, image_hash, aspect_ratio, media_id), |
| 1034 | + ) |
| 1035 | + await db.commit() |
| 1036 | + |
| 1037 | + async def touch_uploaded_image_cache( |
| 1038 | + self, |
| 1039 | + email: str, |
| 1040 | + project_id: str, |
| 1041 | + image_hash: str, |
| 1042 | + aspect_ratio: str, |
| 1043 | + ): |
| 1044 | + """Update cache entry usage timestamps.""" |
| 1045 | + async with aiosqlite.connect(self.db_path) as db: |
| 1046 | + await db.execute( |
| 1047 | + """ |
| 1048 | + UPDATE uploaded_image_cache |
| 1049 | + SET updated_at = CURRENT_TIMESTAMP, |
| 1050 | + last_used_at = CURRENT_TIMESTAMP |
| 1051 | + WHERE email = ? |
| 1052 | + AND project_id = ? |
| 1053 | + AND image_hash = ? |
| 1054 | + AND aspect_ratio = ? |
| 1055 | + """, |
| 1056 | + (email, project_id, image_hash, aspect_ratio), |
| 1057 | + ) |
| 1058 | + await db.commit() |
| 1059 | + |
| 1060 | + async def delete_uploaded_image_cache( |
| 1061 | + self, |
| 1062 | + email: str, |
| 1063 | + project_id: str, |
| 1064 | + image_hash: str, |
| 1065 | + aspect_ratio: str, |
| 1066 | + ): |
| 1067 | + """Delete cached uploaded media metadata by cache key.""" |
| 1068 | + async with aiosqlite.connect(self.db_path) as db: |
| 1069 | + await db.execute( |
| 1070 | + """ |
| 1071 | + DELETE |
| 1072 | + FROM uploaded_image_cache |
| 1073 | + WHERE email = ? |
| 1074 | + AND project_id = ? |
| 1075 | + AND image_hash = ? |
| 1076 | + AND aspect_ratio = ? |
| 1077 | + """, |
| 1078 | + (email, project_id, image_hash, aspect_ratio), |
| 1079 | + ) |
| 1080 | + await db.commit() |
| 1081 | + |
947 | 1082 | # Task operations |
948 | 1083 | async def create_task(self, task: Task) -> int: |
949 | 1084 | """Create a new task""" |
|
0 commit comments