File size: 5,820 Bytes
1cff830
bfe88a9
 
 
24dc858
 
4809b28
292f6be
bfe88a9
 
4809b28
bfe88a9
4809b28
465ba2d
4809b28
bfe88a9
24dc858
4809b28
 
 
 
 
292f6be
4809b28
 
 
 
 
bfe88a9
4809b28
 
bfe88a9
 
 
 
 
 
 
 
292f6be
4809b28
1cff830
292f6be
bfe88a9
4809b28
 
 
 
465ba2d
4809b28
 
465ba2d
24dc858
1cff830
292f6be
465ba2d
24dc858
465ba2d
292f6be
 
1cff830
292f6be
4809b28
292f6be
1cff830
24dc858
 
bfe88a9
24dc858
bfe88a9
24dc858
1cff830
24dc858
1cff830
24dc858
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1cff830
 
24dc858
 
 
1cff830
24dc858
bfe88a9
1cff830
 
4809b28
1cff830
 
292f6be
bfe88a9
 
1cff830
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# app/database.py
import os
from databases import Database
from dotenv import load_dotenv
# --- ADD THIS IMPORT ---
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, text, exc as sqlalchemy_exc
import logging
from urllib.parse import urlparse, urlunparse, parse_qs, urlencode

load_dotenv()
logger = logging.getLogger(__name__)

# --- Database URL Configuration ---
DEFAULT_DB_PATH = "/tmp/app.db" # Store DB in the temporary directory
raw_db_url = os.getenv("DATABASE_URL", f"sqlite+aiosqlite:///{DEFAULT_DB_PATH}")

# --- URL Parsing and Async Database setup (remains the same) ---
final_database_url = raw_db_url
if raw_db_url.startswith("sqlite+aiosqlite"):
    parsed_url = urlparse(raw_db_url)
    query_params = parse_qs(parsed_url.query)
    if 'check_same_thread' not in query_params:
        query_params['check_same_thread'] = ['False']
        new_query = urlencode(query_params, doseq=True)
        final_database_url = urlunparse(parsed_url._replace(query=new_query))
    logger.info(f"Using final async DB URL: {final_database_url}")
else:
    logger.info(f"Using non-SQLite async DB URL: {final_database_url}")

database = Database(final_database_url)
metadata = MetaData()
users = Table(
    "users",
    metadata,
    Column("id", Integer, primary_key=True),
    Column("email", String, unique=True, index=True, nullable=False),
    Column("hashed_password", String, nullable=False),
)

# --- Synchronous Engine for Initial Table Creation ---
sync_db_url = final_database_url.replace("+aiosqlite", "")
logger.info(f"Using synchronous DB URL for initial check/create: {sync_db_url}")
engine = create_engine(sync_db_url)

# --- Directory and Table Creation Logic ---
db_file_path = ""
if sync_db_url.startswith("sqlite"):
    path_part = sync_db_url.split("sqlite:///")[-1].split("?")[0]
    db_file_path = path_part # Should be /tmp/app.db

if db_file_path:
    db_dir = os.path.dirname(db_file_path) # Should be /tmp
    logger.info(f"Checking database directory: {db_dir}")
    try:
        if not os.path.exists(db_dir):
             logger.error(f"CRITICAL: Directory {db_dir} does not exist!")
        elif not os.access(db_dir, os.W_OK):
            logger.error(f"CRITICAL: Directory {db_dir} is not writable! Cannot create database.")
        else:
            logger.info(f"Database directory {db_dir} appears writable.")
    except OSError as e:
        logger.error(f"Error accessing database directory {db_dir}: {e}")
    except Exception as e:
        logger.error(f"Unexpected error checking directory {db_dir}: {e}")


# --- Refined Synchronous Table Check/Creation ---
try:
    logger.info("Attempting sync connection to check/create table...")
    with engine.connect() as connection:
        logger.info("Sync engine connection successful.")
        try:
            # Check if table exists
            connection.execute(text("SELECT 1 FROM users LIMIT 1"))
            logger.info("Users table already exists (checked via sync connection).")
        # --- Catch specific SQLAlchemy error ---
        except sqlalchemy_exc.OperationalError as table_missing_err:
             # Check if the error message specifically indicates "no such table"
             if "no such table" in str(table_missing_err).lower():
                 logger.warning("Users table not found (expected), attempting creation...")
                 try:
                     # Begin a transaction explicitly (optional, connect() usually does)
                     # with connection.begin(): # Alternative way to manage transaction
                     # Use the connection object for create_all
                     metadata.create_all(bind=connection) # <-- Bind to connection
                     # --- Explicitly commit ---
                     connection.commit()
                     logger.info("Users table creation attempted and committed via sync connection.")
                     # --- Verify creation immediately ---
                     try:
                          connection.execute(text("SELECT 1 FROM users LIMIT 1"))
                          logger.info("Users table successfully verified immediately after creation.")
                     except Exception as verify_err:
                          logger.error(f"Failed to verify table immediately after creation: {verify_err}")

                 except Exception as creation_err:
                      logger.exception(f"Error during table creation or commit: {creation_err}")
                      # Optionally rollback? connection.rollback()

             else:
                 # Log other OperationalErrors during the check phase
                 logger.error(f"OperationalError during table check (but not 'no such table'): {table_missing_err}")
                 raise # Re-raise unexpected errors

        except Exception as table_check_exc: # Catch other unexpected errors during check
             logger.error(f"Unexpected error during table check: {type(table_check_exc).__name__}: {table_check_exc}")
             raise # Re-raise unexpected errors

except Exception as e:
    # Errors connecting, or unexpected errors during check/create phase
    logger.exception(f"CRITICAL: Failed during sync connection or table setup: {e}")


# --- Async connect/disconnect functions (remain the same) ---
async def connect_db():
    try:
        await database.connect()
        logger.info(f"Database connection established (async): {final_database_url}")
    except Exception as e:
        logger.exception(f"Failed to establish async database connection: {e}")
        raise

async def disconnect_db():
    try:
        await database.disconnect()
        logger.info("Database connection closed (async).")
    except Exception as e:
        logger.exception(f"Error closing async database connection: {e}")