data_service.py 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393
  1. import json
  2. import os
  3. import re
  4. import psycopg2
  5. from config.settings import POSTGRESQL_DB_CONFIG
  6. from typing import List, Dict, Optional
  7. from abc import ABC
  8. from config.settings import BG_DATA_PATH, IMAGE_PATH, PRODUCT_DATA_PATH, CURRENT_URL
  9. from toteat.toteat import get_all_products, get_category_dict
  10. from logging import getLogger
  11. from datetime import datetime
  12. from cryptography.fernet import Fernet
  13. from rich import print
  14. from config.settings import PIN_KEY
  15. import base64 as b64
  16. from time import time
  17. # Import models
  18. from models.user import User
  19. from models.items import Product
  20. from models.sales import Sale
  21. from models.blacklist import Blacklist
  22. fernet = Fernet(PIN_KEY.encode())
  23. logger = getLogger(__name__)
  24. """
  25. ESQUEMA DE BASE DE DATOS SQLITE (data.db)
  26. 1. Tabla: users
  27. -----------------------------------
  28. - id SERIAL PRIMARY KEY
  29. - email VARCHAR(255) UNIQUE NOT NULL
  30. - name VARCHAR(255) NOT NULL
  31. - rut VARCHAR(20) UNIQUE NOT NULL
  32. - pin_hash TEXT NOT NULL (encriptado)
  33. - kleincoins TEXT NOT NULL (encriptado, valor por defecto "0")
  34. - created_at TEXT NOT NULL (fecha de creación en formato ISO 8601)
  35. - reward_progress INTEGER DEFAULT 0 NOT NULL CHECK (reward_progress >= 0 AND reward_progress <= 100)
  36. (Guarda la información del usuario con su pin hasheado y kleincoins encriptadas)
  37. 2. Tabla: products
  38. -----------------------------------
  39. - id INTEGER PRIMARY KEY
  40. - name VARCHAR(255) NOT NULL
  41. - type VARCHAR(100) NOT NULL
  42. - description TEXT NOT NULL
  43. - price INTEGER NOT NULL
  44. - image TEXT (URL de la imagen)
  45. - status INTEGER DEFAULT 1 NOT NULL CHECK (status IN (0, 1)) -- 0: Inactivo, 1: Activo
  46. - promo_id INTEGER (ID de la promoción asociada, si existe)(puede ser null)
  47. - promo_price INTEGER (Precio promocional, si existe)(puede ser null)
  48. - promo_day INTEGER (Día de la semana para la promoción, 1-7)(puede ser null)
  49. (Guarda los productos disponibles para venta con su estado activo/inactivo)
  50. 3. Tabla: sales
  51. -----------------------------------
  52. - id SERIAL PRIMARY KEY
  53. - user_id INTEGER NOT NULL REFERENCES users.id
  54. - total INTEGER NOT NULL (precio total de la venta)
  55. - fudo_id TEXT UNIQUE NOT NULL (ID string único por venta)
  56. - date TEXT NOT NULL (fecha y hora en formato ISO 8601)
  57. - table INTEGER NOT NULL (número de mesa)
  58. (Guarda cada venta, asociada a un usuario y mesa)
  59. 4. Tabla: sale_products
  60. -----------------------------------
  61. - sale_id INTEGER NOT NULL (relación a sales.id)
  62. - product_id INTEGER NOT NULL (relación a products.id)
  63. - quantity INTEGER NOT NULL DEFAULT 1 (cantidad del producto)
  64. (Relación muchos a muchos entre ventas y productos con cantidad)
  65. 5. Tabla: blacklist
  66. -----------------------------------
  67. - id SERIAL PRIMARY KEY
  68. - user_id INTEGER NOT NULL (relación a users.id)
  69. (Usuarios bloqueados o no autorizados para ciertas acciones)
  70. RELACIONES:
  71. -----------------------------------
  72. - users puede tener muchas sales
  73. - sales puede tener muchos productos (y viceversa), por eso se usa una tabla intermedia (sale_products)
  74. - products pueden repetirse en múltiples sales
  75. """
  76. # Base abstract class for data access
  77. class BaseDataService(ABC):
  78. """Abstract base class for data services"""
  79. def __init__(self):
  80. self.db_config = POSTGRESQL_DB_CONFIG
  81. def _get_connection(self):
  82. """Get database connection"""
  83. return psycopg2.connect(
  84. dbname=self.db_config['dbname'],
  85. user=self.db_config['user'],
  86. password=self.db_config['password'],
  87. host=self.db_config['host'],
  88. port=self.db_config['port']
  89. )
  90. # User Data Service
  91. class UserDataService(BaseDataService):
  92. """Service for managing user data"""
  93. #region Create
  94. def create(self, name: str, email: str, rut: str, pin_hash: str) -> int:
  95. """Add a new user to the database"""
  96. conn = self._get_connection()
  97. cursor = conn.cursor()
  98. try:
  99. cursor.execute(
  100. "INSERT INTO users (name, email, rut, pin_hash, kleincoins, created_at) VALUES (%s, %s, %s, %s, %s, %s) RETURNING id",
  101. (name, email, rut, fernet.encrypt(pin_hash.encode()).decode(), fernet.encrypt(b"0").decode(), datetime.now().isoformat())
  102. )
  103. conn.commit()
  104. user_id = cursor.fetchone()
  105. if user_id:
  106. logger.info(f"User added with ID: {user_id[0]}")
  107. return user_id[0]
  108. else:
  109. logger.error("Failed to add user.")
  110. return -1
  111. except psycopg2.IntegrityError as e:
  112. logger.error(f"Failed to add user: {e}")
  113. return -1
  114. finally:
  115. conn.close()
  116. #endregion
  117. #region Read
  118. def get_all(self) -> List[User]:
  119. """Get all users from the database"""
  120. conn = self._get_connection()
  121. cursor = conn.cursor()
  122. cursor.execute("SELECT * FROM users")
  123. users = cursor.fetchall()
  124. conn.close()
  125. return [
  126. User(
  127. id=user[0],
  128. email=user[1],
  129. name=user[2],
  130. rut=user[3],
  131. pin_hash=fernet.decrypt(user[4].encode()).decode(),
  132. kleincoins=fernet.decrypt(user[5].encode()).decode(),
  133. created_at=user[6],
  134. permissions=user[7],
  135. reward_progress=user[8]
  136. ) for user in users
  137. ]
  138. def get_by_id(self, user_id: int) -> Optional[User]:
  139. """Get user data from the database"""
  140. conn = self._get_connection()
  141. cursor = conn.cursor()
  142. cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
  143. user = cursor.fetchone()
  144. conn.close()
  145. if user:
  146. return User(
  147. id=user[0],
  148. email=user[1],
  149. name=user[2],
  150. rut=user[3],
  151. pin_hash=fernet.decrypt(user[4].encode()).decode(),
  152. kleincoins=fernet.decrypt(user[5].encode()).decode(),
  153. created_at=user[6],
  154. permissions=user[7],
  155. reward_progress=user[8]
  156. )
  157. return None
  158. def get_by_email(self, email: str) -> Optional[User]:
  159. """Get user by email"""
  160. conn = self._get_connection()
  161. cursor = conn.cursor()
  162. cursor.execute("SELECT * FROM users WHERE email = %s", (email,))
  163. user = cursor.fetchone()
  164. conn.close()
  165. logger.debug(f"get_by_email: {email}, user: {user}")
  166. if user:
  167. return User(
  168. id=user[0],
  169. email=user[1],
  170. name=user[2],
  171. rut=user[3],
  172. pin_hash=user[4],
  173. kleincoins=fernet.decrypt(user[5].encode()).decode(),
  174. created_at=user[6],
  175. permissions=user[7],
  176. reward_progress=user[8]
  177. )
  178. return None
  179. def login(self, email: str, pin_hashed: str) -> Optional[User]:
  180. """Login user by email and pin hash"""
  181. user = self.get_by_email(email)
  182. if user:
  183. logger.info(f"comparing {fernet.decrypt(user.pin_hash.encode()).decode()} with {pin_hashed}")
  184. if user and fernet.decrypt(user.pin_hash.encode()).decode() == pin_hashed:
  185. logger.info(f"User {user.email} logged in successfully.")
  186. return user
  187. else:
  188. logger.error("Login failed: Invalid email or pin.")
  189. return None
  190. def permissions(self, user_id: int) -> int:
  191. """Get user permissions"""
  192. conn = self._get_connection()
  193. cursor = conn.cursor()
  194. cursor.execute("SELECT permissions FROM users WHERE id = %s", (user_id,))
  195. result = cursor.fetchone()
  196. if not result:
  197. logger.error(f"User with ID {user_id} not found.")
  198. return 0
  199. logger.info(f"userID: {user_id}, permissions: {result[0]}")
  200. result = result[0]
  201. conn.close()
  202. if result:
  203. return result
  204. return 0
  205. def get_by_rut(self, rut: str) -> Optional[User]:
  206. """Get user by RUT"""
  207. conn = self._get_connection()
  208. cursor = conn.cursor()
  209. cursor.execute("SELECT * FROM users WHERE rut = %s", (rut,))
  210. user = cursor.fetchone()
  211. if not user:
  212. logger.error(f"User with RUT {rut} not found.")
  213. conn.close()
  214. return None
  215. user = user[0]
  216. conn.close()
  217. if user:
  218. return User(
  219. id=user[0],
  220. email=user[1],
  221. name=user[2],
  222. rut=user[3],
  223. pin_hash=fernet.decrypt(user[4].encode()).decode(),
  224. kleincoins=fernet.decrypt(user[5].encode()).decode(),
  225. created_at=user[6],
  226. permissions=user[7],
  227. reward_progress=user[8]
  228. )
  229. return None
  230. def get_next_id(self) -> int:
  231. """Get the next user ID"""
  232. conn = self._get_connection()
  233. cursor = conn.cursor()
  234. cursor.execute("SELECT last_value FROM users_id_seq")
  235. result = cursor.fetchone()
  236. conn.close()
  237. if result and result[0]:
  238. return result[0]
  239. return 1
  240. #endregion
  241. #region Update
  242. def update(self, user_id: int, email=None, name=None, rut=None, pin_hash=None, kleincoins=None) -> bool:
  243. """Update user information in the database"""
  244. conn = self._get_connection()
  245. cursor = conn.cursor()
  246. updates = []
  247. params = []
  248. if email:
  249. updates.append("email = %s")
  250. params.append(email)
  251. if name:
  252. updates.append("name = %s")
  253. params.append(name)
  254. if rut:
  255. updates.append("rut = %s")
  256. params.append(rut)
  257. if pin_hash:
  258. updates.append("pin_hash = %s")
  259. params.append(fernet.encrypt(pin_hash.encode()).decode())
  260. if kleincoins is not None:
  261. updates.append("kleincoins = %s")
  262. params.append(fernet.encrypt(str(kleincoins).encode()).decode())
  263. if not updates:
  264. conn.close()
  265. return False
  266. try:
  267. cursor.execute(f"UPDATE users SET {', '.join(updates)} WHERE id = %s", (*params, user_id))
  268. conn.commit()
  269. success = cursor.rowcount > 0
  270. if success:
  271. logger.info(f"User with ID {user_id} updated.")
  272. return success
  273. except psycopg2.IntegrityError as e:
  274. logger.error(f"Failed to update user: {e}")
  275. return False
  276. finally:
  277. conn.close()
  278. #endregion
  279. def set_reward_progress(self, user_id: int, progress: int) -> bool:
  280. """Add progress to user's reward"""
  281. conn = self._get_connection()
  282. cursor = conn.cursor()
  283. try:
  284. cursor.execute("UPDATE users SET reward_progress = %s WHERE id = %s", (progress, user_id))
  285. conn.commit()
  286. success = cursor.rowcount > 0
  287. if success:
  288. logger.info(f"Reward progress updated for user {user_id}: {progress}")
  289. return success
  290. except psycopg2.IntegrityError as e:
  291. logger.error(f"Failed to update reward progress: {e}")
  292. return False
  293. finally:
  294. conn.close()
  295. #region Delete
  296. def delete(self, user_id: int) -> bool:
  297. """Delete a user from the database"""
  298. conn = self._get_connection()
  299. cursor = conn.cursor()
  300. cursor.execute("DELETE FROM users WHERE id = %s", (user_id,))
  301. conn.commit()
  302. conn.close()
  303. if cursor.rowcount > 0:
  304. logger.info(f"User with ID {user_id} deleted.")
  305. return True
  306. else:
  307. logger.error(f"Failed to delete user with ID {user_id}.")
  308. return False
  309. def update_kleincoins(self, user_id: int, kleincoins: int) -> bool:
  310. """Update user's kleincoins"""
  311. conn = self._get_connection()
  312. cursor = conn.cursor()
  313. try:
  314. cursor.execute(
  315. "UPDATE users SET kleincoins = %s WHERE id = %s",
  316. (fernet.encrypt(str(kleincoins).encode()).decode(), user_id)
  317. )
  318. conn.commit()
  319. success = cursor.rowcount > 0
  320. if success:
  321. logger.info(f"Kleincoins updated for user {user_id}: {kleincoins}")
  322. return success
  323. except psycopg2.IntegrityError as e:
  324. logger.error(f"Failed to update kleincoins: {e}")
  325. return False
  326. finally:
  327. conn.close()
  328. def get_kleincoins(self, user_id: int) -> Optional[int]:
  329. """Get user's kleincoins"""
  330. conn = self._get_connection()
  331. cursor = conn.cursor()
  332. cursor.execute("SELECT kleincoins FROM users WHERE id = %s", (user_id,))
  333. result = cursor.fetchone()
  334. conn.close()
  335. if result:
  336. try:
  337. return int(fernet.decrypt(result[0].encode()).decode())
  338. except Exception as e:
  339. logger.error(f"Failed to decrypt kleincoins for user {user_id}: {e}")
  340. return None
  341. return None
  342. #endregion
  343. # Blacklist Data Service
  344. class BlacklistDataService(BaseDataService):
  345. """Service for managing blacklisted users"""
  346. #region Create
  347. def create(self, user_id: int) -> int:
  348. """Add a user to the blacklist"""
  349. conn = self._get_connection()
  350. cursor = conn.cursor()
  351. try:
  352. cursor.execute("INSERT INTO blacklist (user_id) VALUES (%s) RETURNING id", (user_id,))
  353. conn.commit()
  354. blacklist_id = cursor.fetchone()
  355. if blacklist_id:
  356. logger.info(f"User with ID {blacklist_id[0]} added to blacklist.")
  357. return blacklist_id[0]
  358. else:
  359. logger.error(f"Failed to add user with ID {user_id} to blacklist.")
  360. return -1
  361. except psycopg2.IntegrityError as e:
  362. logger.error(f"Failed to add user to blacklist: {e}")
  363. return -1
  364. finally:
  365. conn.close()
  366. #endregion
  367. #region Read
  368. def get_all(self) -> List[Blacklist]:
  369. """Get all blacklisted users"""
  370. conn = self._get_connection()
  371. cursor = conn.cursor()
  372. cursor.execute("""
  373. SELECT b.id, b.user_id, u.email, u.name, u.rut
  374. FROM blacklist b
  375. LEFT JOIN users u ON b.user_id = u.id
  376. """)
  377. blacklisted = cursor.fetchall()
  378. conn.close()
  379. return [
  380. Blacklist(
  381. id=row[0],
  382. user_id=row[1],
  383. email=row[2],
  384. name=row[3],
  385. rut=row[4]
  386. ) for row in blacklisted
  387. ]
  388. def get_by_id(self, id: int) -> Optional[Blacklist]:
  389. """Get blacklist entry by ID"""
  390. conn = self._get_connection()
  391. cursor = conn.cursor()
  392. cursor.execute("""
  393. SELECT b.id, b.user_id, u.email, u.name, u.rut
  394. FROM blacklist b
  395. LEFT JOIN users u ON b.user_id = u.id
  396. WHERE b.id = %s
  397. """, (id,))
  398. row = cursor.fetchone()
  399. conn.close()
  400. if row:
  401. return Blacklist(
  402. id=row[0],
  403. user_id=row[1],
  404. email=row[2],
  405. name=row[3],
  406. rut=row[4]
  407. )
  408. return None
  409. def get_blacklisted_user_ids(self) -> List[int]:
  410. """Get a list of blacklisted user IDs"""
  411. conn = self._get_connection()
  412. cursor = conn.cursor()
  413. cursor.execute("SELECT user_id FROM blacklist")
  414. blacklisted_users = [row[0] for row in cursor.fetchall()]
  415. conn.close()
  416. return blacklisted_users
  417. def is_user_blacklisted(self, user_id: int) -> bool:
  418. """Check if a user is blacklisted"""
  419. conn = self._get_connection()
  420. cursor = conn.cursor()
  421. cursor.execute("SELECT * FROM blacklist WHERE user_id = %s", (user_id,))
  422. blacklisted = cursor.fetchone() is not None
  423. conn.close()
  424. return blacklisted
  425. #endregion
  426. #region Update
  427. def update(self, id: int, **kwargs) -> bool:
  428. """Update blacklist entry (not commonly used)"""
  429. # Blacklist entries typically don't need updates
  430. return False
  431. #endregion
  432. #region Delete
  433. def delete(self, id: int) -> bool:
  434. """Remove a blacklist entry by ID"""
  435. conn = self._get_connection()
  436. cursor = conn.cursor()
  437. cursor.execute("DELETE FROM blacklist WHERE id = %s", (id,))
  438. conn.commit()
  439. success = cursor.rowcount > 0
  440. conn.close()
  441. if success:
  442. logger.info(f"Blacklist entry with ID {id} removed.")
  443. else:
  444. logger.error(f"Failed to remove blacklist entry with ID {id}.")
  445. return success
  446. def remove_user_from_blacklist(self, user_id: int) -> bool:
  447. """Remove a user from the blacklist"""
  448. conn = self._get_connection()
  449. cursor = conn.cursor()
  450. cursor.execute("DELETE FROM blacklist WHERE user_id = %s", (user_id,))
  451. conn.commit()
  452. success = cursor.rowcount > 0
  453. conn.close()
  454. if success:
  455. logger.info(f"User with ID {user_id} removed from blacklist.")
  456. else:
  457. logger.error(f"Failed to remove user with ID {user_id} from blacklist.")
  458. return success
  459. #endregion
  460. # Product Data Service
  461. class ProductDataService(BaseDataService):
  462. category_dict = {}
  463. product_cache = {
  464. "products": [],
  465. "expires": 0
  466. }
  467. """Service for managing products"""
  468. #region Create
  469. def create(self, id: str, name: str, price: float, type: Optional[str] = None, description: Optional[str] = None, image: Optional[str] = None, status: int = 1) -> Optional[str]:
  470. """Add a new product to the database"""
  471. conn = self._get_connection()
  472. cursor = conn.cursor()
  473. if image and "base64" in image:
  474. extension = re.search(r'data:image/(.*%s);base64,', image)
  475. image_name = f"{id}_img"
  476. if extension:
  477. image_name += f".{extension.group(1)}"
  478. image = self._image_process(image_name, image)
  479. try:
  480. cursor.execute(
  481. "INSERT INTO products (id, name, type, description, price, image, status) VALUES (%s, %s, %s, %s, %s, %s, %s) RETURNING id",
  482. (id, name, type, description, price, image, status)
  483. )
  484. conn.commit()
  485. product_id = cursor.fetchone()
  486. if product_id:
  487. logger.info(f"Product added with ID: {product_id[0]}")
  488. return product_id[0]
  489. else:
  490. logger.error("Failed to add product.")
  491. return None
  492. except psycopg2.Error as e:
  493. logger.error(f"Failed to add product: {e}")
  494. return None
  495. finally:
  496. conn.close()
  497. def load_data(self, products: List[Dict[str, str]]) -> None:
  498. """Load multiple products from a list of dictionaries"""
  499. conn = self._get_connection()
  500. cursor = conn.cursor()
  501. for product in products:
  502. try:
  503. self.create(
  504. id=str(product['id']),
  505. name=product['name'],
  506. price=int(product['price']),
  507. type=product.get('type'),
  508. description=product.get('description'),
  509. image=product.get('image'),
  510. status=int(product.get('status', 1)) # Default to active (1)
  511. )
  512. except Exception as e:
  513. logger.error(f"Failed to load product {product['name']}: {e}")
  514. conn.close()
  515. #endregion
  516. #region Read
  517. async def get_all(self) -> List[Product]:
  518. """Get all products from the database"""
  519. return_list = []
  520. beUpdate = False
  521. # se ve si la cache expiro o si esta vacia
  522. actual_time = time()
  523. if self.product_cache['expires'] < actual_time or not self.product_cache['products']:
  524. self.product_cache['expires'] = actual_time # Por ahora la cache no expira
  525. beUpdate = True
  526. else:
  527. logger.debug("Product cache not expired")
  528. return self.product_cache['products']
  529. products = await get_all_products()
  530. for product in products:
  531. categoryID = product['relationships']['productCategory']['data']['id']
  532. # en caso de que la categoria no este en el diccionario, se actualiza
  533. if categoryID not in self.category_dict:
  534. self.category_dict = get_category_dict()
  535. try:
  536. return_list.append(
  537. Product(
  538. id=str(product['id']),
  539. name=product['attributes']['name'],
  540. type=self.category_dict[categoryID],
  541. description=product['attributes'].get('description'),
  542. price=int(product['attributes'].get('price', 0)),
  543. image=product['attributes'].get('imageUrl'),
  544. kitchen_id=product['relationships']['kitchen']['data']['id'] or None,
  545. status=1 if product['attributes'].get('active') else 0,
  546. promo_id=None,
  547. promo_price=None,
  548. promo_day=None
  549. ))
  550. except Exception as e:
  551. print(e)
  552. continue
  553. if beUpdate:
  554. self.product_cache['products'] = return_list
  555. return return_list
  556. def get_by_id(self, product_id: str) -> Optional[Product]:
  557. """Get product by ID"""
  558. conn = self._get_connection()
  559. cursor = conn.cursor()
  560. cursor.execute("SELECT * FROM products WHERE id = %s", (product_id,))
  561. product = cursor.fetchone()
  562. conn.close()
  563. if product:
  564. return Product(
  565. id=product[0],
  566. name=product[1],
  567. type=product[2],
  568. description=product[3],
  569. price=product[4],
  570. image=product[5],
  571. status=product[6],
  572. promo_id=product[7],
  573. promo_price=product[8],
  574. promo_day=product[9]
  575. )
  576. return None
  577. def get_by_type(self, product_type: str) -> List[Product]:
  578. """Get products by type"""
  579. conn = self._get_connection()
  580. cursor = conn.cursor()
  581. cursor.execute("SELECT * FROM products WHERE type = %s", (product_type,))
  582. products = cursor.fetchall()
  583. conn.close()
  584. return [
  585. Product(
  586. id=product[0],
  587. name=product[1],
  588. type=product[2],
  589. description=product[3],
  590. price=product[4],
  591. image=product[5],
  592. status=product[6],
  593. promo_id=product[7],
  594. promo_price=product[8],
  595. promo_day=product[9]
  596. ) for product in products
  597. ]
  598. def search_by_name(self, name: str) -> List[Product]:
  599. """Search products by name"""
  600. conn = self._get_connection()
  601. cursor = conn.cursor()
  602. cursor.execute("SELECT * FROM products WHERE name LIKE %s", (f"%{name}%",))
  603. products = cursor.fetchall()
  604. conn.close()
  605. return [
  606. Product(
  607. id=product[0],
  608. name=product[1],
  609. type=product[2],
  610. description=product[3],
  611. price=product[4],
  612. image=product[5],
  613. status=product[6],
  614. promo_id=product[7],
  615. promo_price=product[8],
  616. promo_day=product[9]
  617. ) for product in products
  618. ]
  619. async def get_products(self, product_ids: List[str]) -> List[Product]:
  620. """Get multiple products by their IDs"""
  621. products = await self.get_all()
  622. products = list(filter(lambda x: x.id in product_ids, products))
  623. return products
  624. #endregion
  625. #region Update
  626. def _image_process(self, image: str, base64: str) -> str:
  627. """Process image for storage"""
  628. if not image or not base64:
  629. raise ValueError("Image and base64 data must be provided")
  630. image_path = os.path.join(IMAGE_PATH, image)
  631. if not os.path.exists(IMAGE_PATH):
  632. os.makedirs(IMAGE_PATH)
  633. with open(image_path, 'wb') as img_file:
  634. img_file.write(b64.b64decode(base64.split(',')[1]))
  635. return CURRENT_URL+"/images/" + image # Return url
  636. def update(self, product_id: str, name=None, type=None, description=None, price=None, image=None, status=None) -> bool:
  637. """Update product information"""
  638. conn = self._get_connection()
  639. cursor = conn.cursor()
  640. product = self.get_by_id(product_id)
  641. if not product:
  642. logger.error(f"Product with ID {product_id} not found.")
  643. return False
  644. updates = []
  645. params = []
  646. if name is not None:
  647. updates.append("name = %s")
  648. params.append(name)
  649. if type is not None:
  650. updates.append("type = %s")
  651. params.append(type)
  652. if description is not None:
  653. updates.append("description = %s")
  654. params.append(description)
  655. if price is not None:
  656. updates.append("price = %s")
  657. params.append(price)
  658. if image is not None:
  659. if product.image and product.image != image:
  660. if os.path.exists(os.path.join(IMAGE_PATH, product.image)):
  661. os.remove(os.path.join(IMAGE_PATH, product.image))
  662. try:
  663. logger.info(f"Processing image: {image[:40]}... for product {product_id}")
  664. logger.info(f"updates: {updates}, params: {params}")
  665. extension = re.search(r'data:image/(.*?);base64,', image[:40])
  666. if not extension:
  667. raise ValueError("Invalid image format")
  668. extension = extension.group(1)
  669. image = self._image_process(f"{product.id}_img.{extension}", image)
  670. except ValueError as e:
  671. logger.error(f"Failed to process image: {e}")
  672. return False
  673. updates.append("image = %s")
  674. params.append(image)
  675. if status is not None:
  676. updates.append("status = %s")
  677. params.append(status)
  678. if not updates:
  679. conn.close()
  680. return False
  681. try:
  682. cursor.execute(f"UPDATE products SET {', '.join(updates)} WHERE id = %s", (*params, product_id))
  683. conn.commit()
  684. success = cursor.rowcount > 0
  685. if success:
  686. logger.info(f"Product with ID {product_id} updated.")
  687. return success
  688. except psycopg2.Error as e:
  689. logger.error(f"Failed to update product: {e}")
  690. return False
  691. finally:
  692. conn.close()
  693. def get_active_products(self) -> List[Product]:
  694. """Get only active products (status = 1)"""
  695. conn = self._get_connection()
  696. cursor = conn.cursor()
  697. cursor.execute("SELECT * FROM products WHERE status = 1")
  698. products = cursor.fetchall()
  699. conn.close()
  700. return [
  701. Product(
  702. id=product[0],
  703. name=product[1],
  704. type=product[2],
  705. description=product[3],
  706. price=product[4],
  707. image=product[5],
  708. status=product[6],
  709. promo_id=product[7],
  710. promo_price=product[8],
  711. promo_day=product[9]
  712. ) for product in products
  713. ]
  714. def get_inactive_products(self) -> List[Product]:
  715. """Get only inactive products (status = 0)"""
  716. conn = self._get_connection()
  717. cursor = conn.cursor()
  718. cursor.execute("SELECT * FROM products WHERE status = 0")
  719. products = cursor.fetchall()
  720. conn.close()
  721. return [
  722. Product(
  723. id=product[0],
  724. name=product[1],
  725. type=product[2],
  726. description=product[3],
  727. price=product[4],
  728. image=product[5],
  729. status=product[6],
  730. promo_id=product[7],
  731. promo_price=product[8],
  732. promo_day=product[9]
  733. ) for product in products
  734. ]
  735. def activate_product(self, product_id: str) -> bool:
  736. """Activate a product (set status to 1)"""
  737. return self.update(product_id, status=1)
  738. def deactivate_product(self, product_id: str) -> bool:
  739. """Deactivate a product (set status to 0)"""
  740. return self.update(product_id, status=0)
  741. def is_product_active(self, product_id: str) -> Optional[bool]:
  742. """Check if a product is active"""
  743. product = self.get_by_id(product_id)
  744. if product:
  745. return product.status == 1
  746. return None
  747. def update_cache(self):
  748. self.product_cache['expires'] = time() + 60 * 15 # 15 minutos
  749. self.product_cache['products'] = self.get_all()
  750. #endregion
  751. #region Delete
  752. def delete(self, product_id: str) -> bool:
  753. """Delete a product from the database"""
  754. conn = self._get_connection()
  755. cursor = conn.cursor()
  756. product = self.get_by_id(product_id)
  757. if product and product.image and os.path.exists(os.path.join(IMAGE_PATH, product.image.removeprefix(CURRENT_URL + "/images/"))):
  758. os.remove(os.path.join(IMAGE_PATH, product.image.removeprefix(CURRENT_URL + "/images/")))
  759. cursor.execute("DELETE FROM products WHERE id = %s", (product_id,))
  760. conn.commit()
  761. success = cursor.rowcount > 0
  762. conn.close()
  763. if success:
  764. logger.info(f"Product with ID {product_id} deleted.")
  765. else:
  766. logger.error(f"Failed to delete product with ID {product_id}.")
  767. return success
  768. #endregion
  769. # Sales Data Service
  770. class SalesDataService(BaseDataService):
  771. """Service for managing sales"""
  772. #region C
  773. def create(self, user_id: int, fudo_id: str, total: float, table: int, product_ids: List[str], quantities: Optional[List[int]] = None) -> int:
  774. """Create a new sale with products and quantities"""
  775. conn = self._get_connection()
  776. cursor = conn.cursor()
  777. try:
  778. fecha = datetime.now().isoformat()
  779. # Insert sale
  780. cursor.execute(
  781. "INSERT INTO sales (user_id, total, fudo_id, date, table_number) VALUES (%s, %s, %s, %s, %s) RETURNING id",
  782. (user_id, total, fudo_id, fecha, table)
  783. )
  784. last_sale = cursor.fetchone()
  785. sale_id = last_sale[0] if last_sale else None
  786. if sale_id and product_ids:
  787. # Insert sale-product relationships with quantities
  788. if quantities is None:
  789. quantities = [1] * len(product_ids) # Default quantity 1
  790. for i, product_id in enumerate(product_ids):
  791. quantity = quantities[i] if i < len(quantities) else 1
  792. cursor.execute(
  793. "INSERT INTO sale_products (sale_id, product_id, quantity) VALUES (%s, %s, %s)",
  794. (sale_id, product_id, quantity)
  795. )
  796. conn.commit()
  797. if sale_id:
  798. logger.info(f"Sale created with ID: {sale_id}, fudo_id: {fudo_id}")
  799. return sale_id
  800. else:
  801. logger.error("Failed to create sale.")
  802. return -1
  803. except psycopg2.Error as e:
  804. logger.error(f"Failed to create sale: {e}")
  805. conn.rollback()
  806. return -1
  807. finally:
  808. conn.close()
  809. def add_product_to_sale(self, sale_id: int, product_id: str, quantity: int = 1) -> bool:
  810. """Add a product to an existing sale with quantity"""
  811. conn = self._get_connection()
  812. cursor = conn.cursor()
  813. try:
  814. cursor.execute(
  815. "INSERT INTO sale_products (sale_id, product_id, quantity) VALUES (%s, %s, %s)",
  816. (sale_id, product_id, quantity)
  817. )
  818. conn.commit()
  819. success = cursor.rowcount > 0
  820. if success:
  821. logger.info(f"Product {product_id} (qty: {quantity}) added to sale {sale_id}.")
  822. return success
  823. except psycopg2.IntegrityError as e:
  824. logger.error(f"Failed to add product to sale: {e}")
  825. return False
  826. finally:
  827. conn.close()
  828. def add_products_to_sale(self, sale_id: int, product_ids: List[str], quantities: Optional[List[int]] = None) -> bool:
  829. """Add multiple products to an existing sale with quantities"""
  830. conn = self._get_connection()
  831. cursor = conn.cursor()
  832. try:
  833. if quantities is None:
  834. quantities = [1] * len(product_ids) # Default quantity 1
  835. for i, product_id in enumerate(product_ids):
  836. quantity = quantities[i] if i < len(quantities) else 1
  837. cursor.execute(
  838. "INSERT INTO sale_products (sale_id, product_id, quantity) VALUES (%s, %s, %s)",
  839. (sale_id, product_id, quantity)
  840. )
  841. conn.commit()
  842. success = cursor.rowcount > 0
  843. if success:
  844. logger.info(f"Products added to sale {sale_id}.")
  845. return success
  846. except psycopg2.IntegrityError as e:
  847. logger.error(f"Failed to add products to sale: {e}")
  848. return False
  849. finally:
  850. conn.close()
  851. #endregion
  852. #region R
  853. def get_all(self) -> List[Sale]:
  854. """Get all sales from the database"""
  855. conn = self._get_connection()
  856. cursor = conn.cursor()
  857. cursor.execute("""
  858. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table, u.name, u.email
  859. FROM sales s
  860. LEFT JOIN users u ON s.user_id = u.id
  861. ORDER BY s.date DESC
  862. """)
  863. sales = cursor.fetchall()
  864. conn.close()
  865. return [
  866. Sale(
  867. id=sale[0],
  868. user_id=sale[1],
  869. total=sale[2],
  870. fudo_id=sale[3],
  871. date=sale[4],
  872. table=sale[5],
  873. username=sale[6],
  874. user_email=sale[7],
  875. products=self.get_sale_products(sale[0])
  876. ) for sale in sales
  877. ]
  878. def get_by_id(self, sale_id: int) -> Optional[Sale]:
  879. """Get sale by ID"""
  880. conn = self._get_connection()
  881. cursor = conn.cursor()
  882. cursor.execute("""
  883. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table, u.name, u.email
  884. FROM sales s
  885. LEFT JOIN users u ON s.user_id = u.id
  886. WHERE s.id = %s
  887. """, (sale_id,))
  888. sale = cursor.fetchone()
  889. conn.close()
  890. if sale:
  891. return Sale(
  892. id=sale[0],
  893. user_id=sale[1],
  894. total=sale[2],
  895. fudo_id=sale[3],
  896. date=sale[4],
  897. table=sale[5],
  898. username=sale[6],
  899. user_email=sale[7]
  900. )
  901. return None
  902. def get_by_fudo_id(self, fudo_id: str) -> Optional[Sale]:
  903. """Get sale by fudo_id"""
  904. conn = self._get_connection()
  905. cursor = conn.cursor()
  906. cursor.execute("""
  907. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table, u.name, u.email
  908. FROM sales s
  909. LEFT JOIN users u ON s.user_id = u.id
  910. WHERE s.fudo_id = %s
  911. """, (fudo_id,))
  912. sale = cursor.fetchone()
  913. conn.close()
  914. if sale:
  915. return Sale(
  916. id=sale[0],
  917. user_id=sale[1],
  918. total=sale[2],
  919. fudo_id=sale[3],
  920. date=sale[4],
  921. table=sale[5],
  922. username=sale[6],
  923. user_email=sale[7]
  924. )
  925. return None
  926. def get_by_user(self, user_id: int) -> List[Sale]:
  927. """Get sales by user ID"""
  928. conn = self._get_connection()
  929. cursor = conn.cursor()
  930. cursor.execute(
  931. """
  932. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table_number, u.name, u.email
  933. FROM sales s
  934. LEFT JOIN users u ON s.user_id = u.id
  935. WHERE s.user_id = %s
  936. ORDER BY s.date DESC
  937. """,
  938. (user_id,)
  939. )
  940. sales = cursor.fetchall()
  941. conn.close()
  942. return [
  943. Sale(
  944. id=sale[0],
  945. user_id=sale[1],
  946. total=sale[2],
  947. fudo_id=sale[3],
  948. date=datetime.fromisoformat(sale[4]),
  949. table=sale[5],
  950. username=sale[6],
  951. user_email=sale[7],
  952. products=self.get_sale_products(sale[0])
  953. ) for sale in sales
  954. ]
  955. def get_sale_products_ids(self, sale_id: int) -> List[int]:
  956. """Get product IDs for a specific sale"""
  957. conn = self._get_connection()
  958. cursor = conn.cursor()
  959. cursor.execute("""
  960. SELECT product_id FROM sale_products WHERE sale_id = %s
  961. """, (sale_id,))
  962. products = cursor.fetchall()
  963. conn.close()
  964. return [product[0] for product in products]
  965. def get_sale_products(self, sale_id: int) -> List[Product]:
  966. """Get products for a specific sale with quantities"""
  967. conn = self._get_connection()
  968. cursor = conn.cursor()
  969. cursor.execute("""
  970. SELECT p.id, p.name, p.type, p.description, p.price, p.image, p.status, sp.quantity
  971. FROM sale_products sp
  972. JOIN products p ON sp.product_id = p.id
  973. WHERE sp.sale_id = %s
  974. """, (sale_id,))
  975. products = cursor.fetchall()
  976. conn.close()
  977. return [
  978. Product(
  979. id=product[0],
  980. name=product[1],
  981. type=product[2],
  982. description=product[3],
  983. price=product[4],
  984. image=product[5],
  985. status=product[6],
  986. quantity=product[7],
  987. promo_id=None, # Not applicable for sale products
  988. promo_price=None,
  989. promo_day=None
  990. ) for product in products
  991. ]
  992. def get_by_table(self, table: int) -> List[Sale]:
  993. """Get sales by table number"""
  994. conn = self._get_connection()
  995. cursor = conn.cursor()
  996. cursor.execute(
  997. """
  998. SELECT s.id, s.user_id, s.total, s.fudo_id, s.date, s.table_number, u.name, u.email
  999. FROM sales s
  1000. LEFT JOIN users u ON s.user_id = u.id
  1001. WHERE s.table_number = %s
  1002. ORDER BY s.date DESC
  1003. """,
  1004. (table,)
  1005. )
  1006. sales = cursor.fetchall()
  1007. conn.close()
  1008. return [
  1009. Sale(
  1010. id=sale[0],
  1011. user_id=sale[1],
  1012. total=sale[2],
  1013. fudo_id=sale[3],
  1014. date=sale[4],
  1015. table=sale[5],
  1016. username=sale[6],
  1017. user_email=sale[7]
  1018. ) for sale in sales
  1019. ]
  1020. def update_product_quantity(self, sale_id: int, product_id: str, new_quantity: int) -> bool:
  1021. """Update quantity of a product in a sale"""
  1022. conn = self._get_connection()
  1023. cursor = conn.cursor()
  1024. try:
  1025. cursor.execute(
  1026. "UPDATE sale_products SET quantity = %s WHERE sale_id = %s AND product_id = %s",
  1027. (new_quantity, sale_id, product_id)
  1028. )
  1029. conn.commit()
  1030. success = cursor.rowcount > 0
  1031. if success:
  1032. logger.info(f"Updated quantity to {new_quantity} for product {product_id} in sale {sale_id}.")
  1033. return success
  1034. except psycopg2.Error as e:
  1035. logger.error(f"Failed to update product quantity: {e}")
  1036. return False
  1037. finally:
  1038. conn.close()
  1039. def get_product_quantity_in_sale(self, sale_id: int, product_id: str) -> Optional[int]:
  1040. """Get quantity of a specific product in a sale"""
  1041. conn = self._get_connection()
  1042. cursor = conn.cursor()
  1043. cursor.execute(
  1044. "SELECT quantity FROM sale_products WHERE sale_id = %s AND product_id = %s",
  1045. (sale_id, product_id)
  1046. )
  1047. result = cursor.fetchone()
  1048. conn.close()
  1049. return result[0] if result else None
  1050. #endregion
  1051. #region U
  1052. def update(self, sale_id: int, user_id=None, total=None, table=None) -> bool:
  1053. """Update sale information (products should be updated via specific methods)"""
  1054. conn = self._get_connection()
  1055. cursor = conn.cursor()
  1056. updates = []
  1057. params = []
  1058. if user_id is not None:
  1059. updates.append("user_id = %s")
  1060. params.append(user_id)
  1061. if total is not None:
  1062. updates.append("total = %s")
  1063. params.append(total)
  1064. if table is not None:
  1065. updates.append("table = %s")
  1066. params.append(table)
  1067. if not updates:
  1068. conn.close()
  1069. return False
  1070. try:
  1071. cursor.execute(f"UPDATE sales SET {', '.join(updates)} WHERE id = %s", (*params, sale_id))
  1072. conn.commit()
  1073. success = cursor.rowcount > 0
  1074. if success:
  1075. logger.info(f"Sale with ID {sale_id} updated.")
  1076. return success
  1077. except psycopg2.Error as e:
  1078. logger.error(f"Failed to update sale: {e}")
  1079. return False
  1080. finally:
  1081. conn.close()
  1082. #endregion
  1083. #region D
  1084. def delete(self, sale_id: int) -> bool:
  1085. """Delete a sale and its product relationships"""
  1086. conn = self._get_connection()
  1087. cursor = conn.cursor()
  1088. try:
  1089. # Delete sale-product relationships first
  1090. cursor.execute("DELETE FROM sale_products WHERE sale_id = %s", (sale_id,))
  1091. # Delete the sale
  1092. cursor.execute("DELETE FROM sales WHERE id = %s", (sale_id,))
  1093. conn.commit()
  1094. success = cursor.rowcount > 0
  1095. if success:
  1096. logger.info(f"Sale with ID {sale_id} deleted.")
  1097. else:
  1098. logger.error(f"Failed to delete sale with ID {sale_id}.")
  1099. return success
  1100. except psycopg2.Error as e:
  1101. logger.error(f"Failed to delete sale: {e}")
  1102. return False
  1103. finally:
  1104. conn.close()
  1105. def remove_product_from_sale(self, sale_id: int, product_id: str) -> bool:
  1106. """Remove a product from a sale (removes all quantity)"""
  1107. conn = self._get_connection()
  1108. cursor = conn.cursor()
  1109. cursor.execute(
  1110. "DELETE FROM sale_products WHERE sale_id = %s AND product_id = %s",
  1111. (sale_id, product_id)
  1112. )
  1113. conn.commit()
  1114. success = cursor.rowcount > 0
  1115. conn.close()
  1116. if success:
  1117. logger.info(f"Product {product_id} removed from sale {sale_id}.")
  1118. else:
  1119. logger.error(f"Failed to remove product {product_id} from sale {sale_id}.")
  1120. return success
  1121. def decrease_product_quantity(self, sale_id: int, product_id: str, decrease_by: int = 1) -> bool:
  1122. """Decrease quantity of a product in a sale, removes if quantity becomes 0 or less"""
  1123. conn = self._get_connection()
  1124. cursor = conn.cursor()
  1125. try:
  1126. # Get current quantity
  1127. cursor.execute(
  1128. "SELECT quantity FROM sale_products WHERE sale_id = %s AND product_id = %s",
  1129. (sale_id, product_id)
  1130. )
  1131. result = cursor.fetchone()
  1132. if not result:
  1133. return False
  1134. current_quantity = result[0]
  1135. new_quantity = current_quantity - decrease_by
  1136. if new_quantity <= 0:
  1137. # Remove the product completely
  1138. cursor.execute(
  1139. "DELETE FROM sale_products WHERE sale_id = %s AND product_id = %s",
  1140. (sale_id, product_id)
  1141. )
  1142. logger.info(f"Product {product_id} removed from sale {sale_id} (quantity reached 0).")
  1143. else:
  1144. # Update with new quantity
  1145. cursor.execute(
  1146. "UPDATE sale_products SET quantity = %s WHERE sale_id = %s AND product_id = %s",
  1147. (new_quantity, sale_id, product_id)
  1148. )
  1149. logger.info(f"Product {product_id} quantity decreased to {new_quantity} in sale {sale_id}.")
  1150. conn.commit()
  1151. return True
  1152. except psycopg2.Error as e:
  1153. logger.error(f"Failed to decrease product quantity: {e}")
  1154. return False
  1155. finally:
  1156. conn.close()
  1157. #endregion
  1158. # Factory class to get service instances
  1159. class DataServiceFactory:
  1160. """Factory class to create data service instances"""
  1161. @staticmethod
  1162. def get_user_service() -> UserDataService:
  1163. """Get user data service instance"""
  1164. return UserDataService()
  1165. @staticmethod
  1166. def get_blacklist_service() -> BlacklistDataService:
  1167. """Get blacklist data service instance"""
  1168. return BlacklistDataService()
  1169. @staticmethod
  1170. def get_product_service() -> ProductDataService:
  1171. """Get product data service instance"""
  1172. return ProductDataService()
  1173. @staticmethod
  1174. def get_sales_service() -> SalesDataService:
  1175. """Get sales data service instance"""
  1176. return SalesDataService()
  1177. # Helper functions for background data
  1178. def load_bg_data() -> List[Dict[str, str]]:
  1179. """Load background data for AI assistant"""
  1180. try:
  1181. with open(BG_DATA_PATH, 'r', encoding='utf-8') as f:
  1182. return json.load(f)
  1183. except FileNotFoundError:
  1184. logger.error(f"Data file not found at {BG_DATA_PATH}. Serving with empty data.")
  1185. return []
  1186. except json.JSONDecodeError:
  1187. logger.error(f"Could not decode JSON from {BG_DATA_PATH}. Serving with empty data.")
  1188. return []
  1189. def initialize_db():
  1190. # En PostgreSQL, no se crea el archivo de base de datos, así que verificamos si las tablas existen.
  1191. try:
  1192. conn = psycopg2.connect(
  1193. dbname=POSTGRESQL_DB_CONFIG['dbname'],
  1194. user=POSTGRESQL_DB_CONFIG['user'],
  1195. password=POSTGRESQL_DB_CONFIG['password'],
  1196. host=POSTGRESQL_DB_CONFIG['host'],
  1197. port=POSTGRESQL_DB_CONFIG['port']
  1198. )
  1199. cursor = conn.cursor()
  1200. # Verificar si la tabla 'users' ya existe
  1201. cursor.execute("""
  1202. SELECT EXISTS (
  1203. SELECT FROM information_schema.tables
  1204. WHERE table_name = 'users'
  1205. );
  1206. """)
  1207. exists = cursor.fetchone()
  1208. if exists:
  1209. logger.info("La base de datos ya está inicializada, no se necesita inicializar.")
  1210. conn.close()
  1211. return
  1212. conn.close()
  1213. except Exception as e:
  1214. logger.error(f"Error comprobando la existencia de tablas: {e}")
  1215. # Si hay error, continuamos con la inicialización
  1216. conn = psycopg2.connect(
  1217. dbname=POSTGRESQL_DB_CONFIG['dbname'],
  1218. user=POSTGRESQL_DB_CONFIG['user'],
  1219. password=POSTGRESQL_DB_CONFIG['password'],
  1220. host=POSTGRESQL_DB_CONFIG['host'],
  1221. port=POSTGRESQL_DB_CONFIG['port']
  1222. )
  1223. cursor = conn.cursor()
  1224. # Crear tabla de usuarios
  1225. logger.info("Inicializando base de datos...")
  1226. logger.info("Creando tabla de usuarios...")
  1227. cursor.execute("""
  1228. CREATE TABLE IF NOT EXISTS users (
  1229. id SERIAL PRIMARY KEY,
  1230. email VARCHAR(255) UNIQUE NOT NULL,
  1231. name VARCHAR(255) NOT NULL,
  1232. rut VARCHAR(20) UNIQUE NOT NULL,
  1233. pin_hash TEXT NOT NULL,
  1234. kleincoins TEXT NOT NULL,
  1235. created_at TEXT NOT NULL,
  1236. permissions INTEGER DEFAULT 0 NOT NULL CHECK (permissions IN (0, 1, 2)) -- 0: Usuario normal, 1: Administrador, 2: Superusuario
  1237. reward_progress INTEGER DEFAULT 0 NOT NULL CHECK (reward_progress >= 0 AND reward_progress <= 100) -- Progreso de recompensas
  1238. );
  1239. """)
  1240. # Crear tabla de productos
  1241. logger.info("Creando tabla de products...")
  1242. cursor.execute("""
  1243. CREATE TABLE IF NOT EXISTS products (
  1244. id TEXT PRIMARY KEY,
  1245. name VARCHAR(255) NOT NULL,
  1246. type VARCHAR(100) NOT NULL,
  1247. description TEXT NOT NULL,
  1248. price INTEGER NOT NULL,
  1249. image TEXT,
  1250. status INTEGER DEFAULT 1 NOT NULL CHECK (status IN (0, 1)) -- 0: Inactivo, 1: Activo,
  1251. promo_id INTEGER,
  1252. promo_price INTEGER,
  1253. promo_day INTEGER CHECK (promo_day >= 1 AND promo_day <= 7)
  1254. );
  1255. """)
  1256. # Crear tabla de ventas
  1257. logger.info("Creando tabla de sales...")
  1258. cursor.execute("""
  1259. CREATE TABLE IF NOT EXISTS sales (
  1260. id SERIAL PRIMARY KEY,
  1261. user_id INTEGER NOT NULL,
  1262. total INTEGER NOT NULL,
  1263. fudo_id TEXT UNIQUE NOT NULL,
  1264. date TEXT NOT NULL,
  1265. table_number INTEGER NOT NULL,
  1266. FOREIGN KEY (user_id) REFERENCES users(id)
  1267. );
  1268. """)
  1269. # Crear tabla intermedia para ventas y productos
  1270. logger.info("Creando tabla intermedia de sale_products...")
  1271. cursor.execute("""
  1272. CREATE TABLE IF NOT EXISTS sale_products (
  1273. sale_id INTEGER NOT NULL,
  1274. product_id TEXT NOT NULL,
  1275. quantity INTEGER NOT NULL DEFAULT 1,
  1276. FOREIGN KEY (sale_id) REFERENCES sales(id),
  1277. FOREIGN KEY (product_id) REFERENCES products(id)
  1278. );
  1279. """)
  1280. # Crear tabla de blacklist
  1281. logger.info("Creando tabla de blacklist...")
  1282. cursor.execute("""
  1283. CREATE TABLE IF NOT EXISTS blacklist (
  1284. id SERIAL PRIMARY KEY,
  1285. user_id INTEGER NOT NULL,
  1286. FOREIGN KEY (user_id) REFERENCES users(id)
  1287. );
  1288. """)
  1289. logger.info("Todas las tablas creadas correctamente.")
  1290. logger.info("Cargando datos de productos desde el archivo JSON...")
  1291. products_json = json.loads(open(PRODUCT_DATA_PATH, 'r', encoding='utf-8').read())
  1292. product_service = ProductDataService()
  1293. product_service.load_data(products_json)
  1294. conn.commit()
  1295. conn.close()
  1296. logger.info("Base de datos inicializada correctamente.")
  1297. data_bg_loaded = load_bg_data()
  1298. if __name__ == "__main__":
  1299. import asyncio
  1300. data_service = ProductDataService()
  1301. products = asyncio.run(data_service.get_all())
  1302. print(products[0])