async def create(conn): # Users core table await conn.execute( """ CREATE TABLE IF NOT EXISTS User ( id INTEGER PRIMARY KEY, email TEXT NOT NULL UNIQUE, display_name TEXT NOT NULL, profile_photo_url TEXT ); """ ) # Local credential storage for password auth await conn.execute( """ CREATE TABLE IF NOT EXISTS LocalCredentials ( user_id INTEGER PRIMARY KEY, hashed_password TEXT NOT NULL, FOREIGN KEY(user_id) REFERENCES User(id) ON DELETE CASCADE ); """ ) # OAuth provider links (e.g., Google) await conn.execute( """ CREATE TABLE IF NOT EXISTS OAuthCredentials ( user_id INTEGER NOT NULL, provider TEXT NOT NULL, provider_user_id TEXT NOT NULL, PRIMARY KEY (provider, provider_user_id), FOREIGN KEY(user_id) REFERENCES User(id) ON DELETE CASCADE ); """ ) # Helpful indices await conn.execute("CREATE INDEX IF NOT EXISTS idx_user_email ON User(email);") async def get_by_email(conn, email: str): async with conn.execute( "SELECT id, email, display_name, profile_photo_url FROM User WHERE email = ? LIMIT 1", (email,), ) as c: row = await c.fetchone() if not row: return None from users.models import User return User(id=int(row[0]), email=row[1], display_name=row[2], profile_photo_url=row[3]) async def get_by_id(conn, user_id: int): async with conn.execute( "SELECT id, email, display_name, profile_photo_url FROM User WHERE id = ? LIMIT 1", (user_id,), ) as c: row = await c.fetchone() if not row: return None from users.models import User return User(id=int(row[0]), email=row[1], display_name=row[2], profile_photo_url=row[3]) async def get_by_ids(conn, ids: list[int]): if not ids: return {} placeholders = ",".join(["?"] * len(ids)) results: dict[int, "User"] = {} async with conn.execute( f"SELECT id, email, display_name, profile_photo_url FROM User WHERE id IN ({placeholders})", list(ids), ) as c: async for row in c: from users.models import User u = User(id=int(row[0]), email=row[1], display_name=row[2], profile_photo_url=row[3]) results[u.id] = u return results async def insert_user(conn, email: str, display_name: str, profile_photo_url: str | None = None): async with conn.execute( "INSERT INTO User (email, display_name, profile_photo_url) VALUES (?, ?, ?)", (email, display_name, profile_photo_url), ) as cur: user_id = cur.lastrowid return user_id async def set_local_credentials(conn, user_id: int, hashed_password: str): await conn.execute( "INSERT OR REPLACE INTO LocalCredentials (user_id, hashed_password) VALUES (?, ?)", (user_id, hashed_password), ) async def get_local_password_hash(conn, user_id: int) -> str | None: async with conn.execute( "SELECT hashed_password FROM LocalCredentials WHERE user_id = ?", (user_id,), ) as c: row = await c.fetchone() return row[0] if row else None