Lukeetah commited on
Commit
3dc9da2
·
verified ·
1 Parent(s): 3f9545f

Create db.py

Browse files
Files changed (1) hide show
  1. db.py +164 -0
db.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # db.py
2
+ import aiosqlite
3
+ import uuid
4
+ import os
5
+ import colorlog
6
+ from datetime import datetime
7
+ from typing import List, Optional, Dict, Any
8
+
9
+ from app import AuditLog, Soul, DatabaseIntegrityError # Imports circulares corregidos en app.py
10
+
11
+ # Se usa el logger y settings del main, pero por simplicidad para la ejecución se incluyen aquí de manera dummy.
12
+ # En un proyecto real, se importarían. Aquí se definen para que el archivo sea ejecutable/testeable.
13
+ logger = colorlog.getLogger("OMEGA_DB")
14
+ logger.setLevel(colorlog.logging.INFO)
15
+ DB_PATH = "data/omega_modular_argentina.db"
16
+
17
+ class AsyncDatabase:
18
+ """
19
+ Clase central para interactuar con SQLite. Modularizada para PRD.
20
+ """
21
+ def __init__(self, db_path: str = DB_PATH):
22
+ self.db_path = db_path
23
+ os.makedirs(os.path.dirname(db_path), exist_ok=True)
24
+
25
+ async def init(self):
26
+ """Inicializa la DB y crea tablas."""
27
+ logger.info("🛠️ Base de Datos: Verificando tablas y consistencia.")
28
+ async with aiosqlite.connect(self.db_path) as db:
29
+ await db.execute("PRAGMA journal_mode=WAL;")
30
+
31
+ # Tabla Souls
32
+ await db.execute("""
33
+ CREATE TABLE IF NOT EXISTS souls (
34
+ uuid TEXT PRIMARY KEY,
35
+ username TEXT UNIQUE,
36
+ password_hash TEXT,
37
+ role TEXT DEFAULT 'INICIADO',
38
+ karma INTEGER DEFAULT 100,
39
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
40
+ is_banned BOOLEAN DEFAULT 0
41
+ )
42
+ """)
43
+ # Tabla Codex
44
+ await db.execute("""
45
+ CREATE TABLE IF NOT EXISTS codex (
46
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
47
+ uuid TEXT UNIQUE,
48
+ title TEXT,
49
+ content TEXT,
50
+ principle TEXT,
51
+ author_id TEXT,
52
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
53
+ )
54
+ """)
55
+ # Tabla Audit Logs
56
+ await db.execute("""
57
+ CREATE TABLE IF NOT EXISTS audit_logs (
58
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
59
+ user_id TEXT,
60
+ action TEXT,
61
+ details TEXT,
62
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
63
+ )
64
+ """)
65
+ await db.commit()
66
+ await self.log_event(None, "SYSTEM_DB_INIT", "Base de Datos en línea. ¡Fierro!")
67
+
68
+ # --- Métodos CRUD Específicos (Altamente detallados) ---
69
+ async def create_soul_transactional(self, username: str, pass_hash: str, role: str) -> str:
70
+ """Crea un alma nueva asegurando la atomicidad de la transacción."""
71
+ soul_uuid = str(uuid.uuid4())
72
+ try:
73
+ async with aiosqlite.connect(self.db_path) as db:
74
+ await db.execute(
75
+ "INSERT INTO souls (uuid, username, password_hash, role) VALUES (?, ?, ?, ?)",
76
+ (soul_uuid, username, pass_hash, role)
77
+ )
78
+ await db.commit()
79
+ return soul_uuid
80
+ except aiosqlite.IntegrityError:
81
+ raise DatabaseIntegrityError("Ese usuario ya existe.")
82
+ except Exception as e:
83
+ logger.error(f"Quilombo al crear alma: {e}")
84
+ raise
85
+
86
+ async def get_soul_by_username(self, username: str) -> Optional['Soul']:
87
+ """Busca un usuario por nombre. Retorna el modelo validado."""
88
+ async with aiosqlite.connect(self.db_path) as db:
89
+ db.row_factory = aiosqlite.Row
90
+ async with db.execute("SELECT * FROM souls WHERE username = ?", (username,)) as cursor:
91
+ row = await cursor.fetchone()
92
+ # Nota: La validación Pydantic se hace en el controlador principal
93
+ if row:
94
+ # Simulación de retorno de dict para evitar el import circular aquí
95
+ return dict(row)
96
+ return None
97
+
98
+ async def get_all_souls_data(self) -> List[Dict[str, Any]]:
99
+ """Trae todos los datos de usuario para el panel de Arquitecto."""
100
+ async with aiosqlite.connect(self.db_path) as db:
101
+ db.row_factory = aiosqlite.Row
102
+ async with db.execute("SELECT * FROM souls ORDER BY karma DESC") as cursor:
103
+ rows = await cursor.fetchall()
104
+ return [dict(r) for r in rows]
105
+
106
+ async def update_ban_status_db(self, username: str, is_banned: bool):
107
+ """Actualiza el flag de banneo."""
108
+ async with aiosqlite.connect(self.db_path) as db:
109
+ await db.execute("UPDATE souls SET is_banned = ? WHERE username = ?", (1 if is_banned else 0, username))
110
+ await db.commit()
111
+
112
+ async def write_law_and_reward(self, title: str, content: str, principle: str, author_uuid: str):
113
+ """Escribe una Ley y actualiza el karma del autor."""
114
+ law_uuid = str(uuid.uuid4())
115
+ async with aiosqlite.connect(self.db_path) as db:
116
+ await db.execute(
117
+ "INSERT INTO codex (uuid, title, content, principle, author_id) VALUES (?, ?, ?, ?, ?)",
118
+ (law_uuid, title, content, principle, author_uuid)
119
+ )
120
+ # Premio de Karma: 20 puntos. ¡Posta!
121
+ await db.execute("UPDATE souls SET karma = karma + 20 WHERE uuid = ?", (author_uuid,))
122
+ await db.commit()
123
+
124
+ async def get_codex_feed_data(self, limit: int = 25) -> List[dict]:
125
+ """Trae las leyes más recientes para el feed."""
126
+ async with aiosqlite.connect(self.db_path) as db:
127
+ db.row_factory = aiosqlite.Row
128
+ query = """
129
+ SELECT c.*, s.username as author_name, s.role
130
+ FROM codex c
131
+ LEFT JOIN souls s ON c.author_id = s.uuid
132
+ ORDER BY c.created_at DESC LIMIT ?
133
+ """
134
+ async with db.execute(query, (limit,)) as cursor:
135
+ rows = await cursor.fetchall()
136
+ return [dict(r) for r in rows]
137
+
138
+ async def search_context_rag(self, query: str) -> str:
139
+ """Busca contexto para la IA (simulación RAG)."""
140
+ async with aiosqlite.connect(self.db_path) as db:
141
+ db.row_factory = aiosqlite.Row
142
+ async with db.execute("SELECT title, content FROM codex ORDER BY RANDOM() LIMIT 2") as cursor:
143
+ rows = await cursor.fetchall()
144
+ context = "\n".join([f"PRECEDENTE [{r['title']}]: {r['content']}" for r in rows])
145
+ return context if context else "No hay precedentes cargados."
146
+
147
+ async def log_event(self, user_id: Optional[str], action: str, details: str):
148
+ """Registra cada acción. Es la prueba de que el sistema anda re-bien."""
149
+ async with aiosqlite.connect(self.db_path) as db:
150
+ await db.execute(
151
+ "INSERT INTO audit_logs (user_id, action, details) VALUES (?, ?, ?)",
152
+ (user_id, action, details)
153
+ )
154
+ await db.commit()
155
+
156
+ async def get_recent_audit_logs(self, limit: int = 20) -> List[Dict[str, Any]]:
157
+ """Trae los últimos logs para el Arquitecto."""
158
+ async with aiosqlite.connect(self.db_path) as db:
159
+ db.row_factory = aiosqlite.Row
160
+ async with db.execute("SELECT * FROM audit_logs ORDER BY timestamp DESC LIMIT ?", (limit,)) as cursor:
161
+ rows = await cursor.fetchall()
162
+ return [dict(r) for r in rows]
163
+
164
+ DB = AsyncDatabase()