| | import sqlite3
|
| | import json
|
| | from datetime import datetime, timedelta
|
| | from typing import List, Optional, Dict
|
| | import pandas as pd
|
| |
|
| | class Database:
|
| | def __init__(self, db_path: str = "data/indiscan.db"):
|
| | self.db_path = db_path
|
| | self.init_db()
|
| |
|
| | def init_db(self):
|
| | conn = sqlite3.connect(self.db_path)
|
| | c = conn.cursor()
|
| |
|
| |
|
| | c.execute('''
|
| | CREATE TABLE IF NOT EXISTS products (
|
| | id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| | barcode TEXT UNIQUE,
|
| | name TEXT,
|
| | brand TEXT,
|
| | category TEXT,
|
| | ingredients TEXT,
|
| | nutrition_info TEXT,
|
| | health_score INTEGER,
|
| | last_updated TIMESTAMP,
|
| | image_url TEXT,
|
| | product_type TEXT,
|
| | added_by TEXT,
|
| | is_verified BOOLEAN DEFAULT 0
|
| | )
|
| | ''')
|
| |
|
| |
|
| | c.execute('''
|
| | CREATE TABLE IF NOT EXISTS ingredients (
|
| | id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| | name TEXT UNIQUE,
|
| | risk_score INTEGER,
|
| | description TEXT,
|
| | category TEXT,
|
| | concerns TEXT
|
| | )
|
| | ''')
|
| |
|
| |
|
| | c.execute('''
|
| | CREATE TABLE IF NOT EXISTS users (
|
| | id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| | username TEXT UNIQUE,
|
| | password_hash TEXT,
|
| | is_admin BOOLEAN DEFAULT 0
|
| | )
|
| | ''')
|
| |
|
| |
|
| | c.execute('''
|
| | CREATE TABLE IF NOT EXISTS price_tracking (
|
| | id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| | product_id INTEGER,
|
| | platform TEXT,
|
| | price REAL,
|
| | timestamp TIMESTAMP,
|
| | url TEXT,
|
| | FOREIGN KEY (product_id) REFERENCES products (id)
|
| | )
|
| | ''')
|
| |
|
| | conn.commit()
|
| | conn.close()
|
| |
|
| | def add_product(self, product_data: Dict) -> int:
|
| | conn = sqlite3.connect(self.db_path)
|
| | c = conn.cursor()
|
| |
|
| | product_data['last_updated'] = datetime.now().isoformat()
|
| | if 'ingredients' in product_data and isinstance(product_data['ingredients'], list):
|
| | product_data['ingredients'] = json.dumps(product_data['ingredients'])
|
| | if 'nutrition_info' in product_data and isinstance(product_data['nutrition_info'], dict):
|
| | product_data['nutrition_info'] = json.dumps(product_data['nutrition_info'])
|
| |
|
| | columns = ', '.join(product_data.keys())
|
| | placeholders = ', '.join(['?' for _ in product_data])
|
| | values = tuple(product_data.values())
|
| |
|
| | try:
|
| | c.execute(f"INSERT INTO products ({columns}) VALUES ({placeholders})", values)
|
| | product_id = c.lastrowid
|
| | conn.commit()
|
| | return product_id
|
| | except sqlite3.IntegrityError:
|
| |
|
| | update_cols = ', '.join([f"{k}=?" for k in product_data.keys()])
|
| | c.execute(f"UPDATE products SET {update_cols} WHERE barcode=?",
|
| | (*values, product_data['barcode']))
|
| | conn.commit()
|
| | return c.lastrowid
|
| | finally:
|
| | conn.close()
|
| |
|
| | def get_product(self, barcode: str) -> Optional[Dict]:
|
| | conn = sqlite3.connect(self.db_path)
|
| | c = conn.cursor()
|
| |
|
| | c.execute("SELECT * FROM products WHERE barcode=?", (barcode,))
|
| | result = c.fetchone()
|
| |
|
| | if result:
|
| | columns = [description[0] for description in c.description]
|
| | product = dict(zip(columns, result))
|
| |
|
| |
|
| | if product['ingredients']:
|
| | product['ingredients'] = json.loads(product['ingredients'])
|
| | if product['nutrition_info']:
|
| | product['nutrition_info'] = json.loads(product['nutrition_info'])
|
| |
|
| | conn.close()
|
| | return product
|
| |
|
| | conn.close()
|
| | return None
|
| |
|
| | def update_prices(self, product_id: int, prices: List[Dict]):
|
| | conn = sqlite3.connect(self.db_path)
|
| | c = conn.cursor()
|
| |
|
| | timestamp = datetime.now().isoformat()
|
| |
|
| | for price_data in prices:
|
| | c.execute("""
|
| | INSERT INTO price_tracking (product_id, platform, price, timestamp, url)
|
| | VALUES (?, ?, ?, ?, ?)
|
| | """, (product_id, price_data['platform'], price_data['price'], timestamp, price_data['url']))
|
| |
|
| | conn.commit()
|
| | conn.close()
|
| |
|
| | def get_products_for_update(self) -> List[str]:
|
| | """Get products that haven't been updated in 60 days"""
|
| | conn = sqlite3.connect(self.db_path)
|
| | c = conn.cursor()
|
| |
|
| | sixty_days_ago = (datetime.now() - timedelta(days=60)).isoformat()
|
| |
|
| | c.execute("""
|
| | SELECT barcode FROM products
|
| | WHERE last_updated < ? OR last_updated IS NULL
|
| | """, (sixty_days_ago,))
|
| |
|
| | barcodes = [row[0] for row in c.fetchall()]
|
| | conn.close()
|
| | return barcodes
|
| |
|
| | def export_to_csv(self, filepath: str):
|
| | """Export the database to CSV files"""
|
| | conn = sqlite3.connect(self.db_path)
|
| |
|
| |
|
| | pd.read_sql_query("SELECT * FROM products", conn).to_csv(f"{filepath}/products.csv", index=False)
|
| |
|
| |
|
| | pd.read_sql_query("SELECT * FROM ingredients", conn).to_csv(f"{filepath}/ingredients.csv", index=False)
|
| |
|
| |
|
| | pd.read_sql_query("SELECT * FROM price_tracking", conn).to_csv(f"{filepath}/price_tracking.csv", index=False)
|
| |
|
| | conn.close()
|
| |
|
| | def import_from_csv(self, filepath: str):
|
| | """Import data from CSV files"""
|
| | conn = sqlite3.connect(self.db_path)
|
| |
|
| |
|
| | products_df = pd.read_csv(f"{filepath}/products.csv")
|
| | products_df.to_sql('products', conn, if_exists='append', index=False)
|
| |
|
| |
|
| | ingredients_df = pd.read_csv(f"{filepath}/ingredients.csv")
|
| | ingredients_df.to_sql('ingredients', conn, if_exists='append', index=False)
|
| |
|
| |
|
| | price_df = pd.read_csv(f"{filepath}/price_tracking.csv")
|
| | price_df.to_sql('price_tracking', conn, if_exists='append', index=False)
|
| |
|
| | conn.commit()
|
| | conn.close()
|
| |
|
| | def verify_admin(self, username: str, password_hash: str) -> bool:
|
| | conn = sqlite3.connect(self.db_path)
|
| | c = conn.cursor()
|
| |
|
| | c.execute("SELECT is_admin FROM users WHERE username=? AND password_hash=?",
|
| | (username, password_hash))
|
| | result = c.fetchone()
|
| |
|
| | conn.close()
|
| | return bool(result and result[0]) |