File size: 4,765 Bytes
5d6260a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
from __future__ import annotations

import asyncio
import logging
from typing import Any, Dict, List, Optional

from supabase import create_client, Client

from app.config import get_settings

logger = logging.getLogger(__name__)


class SupabaseClient:
    def __init__(self, url: str, service_key: str):
        self._url = url
        self._service_key = service_key
        self._client: Optional[Client] = None

    async def initialize(self):
        self._client = await asyncio.to_thread(create_client, self._url, self._service_key)
        logger.info("Supabase client initialized")

    @property
    def client(self) -> Client:
        if self._client is None:
            raise RuntimeError("Supabase client not initialized. Call initialize() first.")
        return self._client

    async def close(self):
        self._client = None
        set_supabase_client(None)
        logger.info("Supabase client closed")

    # --- Table operations ---

    async def select(
        self, table: str, columns: str = "*",
        eq: Optional[tuple[str, Any]] = None,
        order: Optional[tuple[str, bool]] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
    ) -> List[Dict[str, Any]]:
        query = self.client.table(table).select(columns)
        if eq:
            query = query.eq(eq[0], eq[1])
        if order:
            query = query.order(order[0], desc=order[1])
        if limit:
            query = query.limit(limit)
        if offset:
            query = query.offset(offset)
        result = await asyncio.to_thread(query.execute)
        return result.data if result else []

    async def select_in(
        self, table: str, column: str, values: List[Any],
        columns: str = "*",
    ) -> List[Dict[str, Any]]:
        query = self.client.table(table).select(columns).in_(column, values)
        result = await asyncio.to_thread(query.execute)
        return result.data if result else []

    async def insert(
        self, table: str, data: Dict[str, Any],
        returning: str = "representation",
    ) -> Optional[Dict[str, Any]]:
        query = self.client.table(table).insert(data, returning=returning)
        result = await asyncio.to_thread(query.execute)
        if result and result.data:
            return result.data[0]
        return None

    async def update(
        self, table: str, column: str, value: Any,
        data: Dict[str, Any],
    ) -> List[Dict[str, Any]]:
        query = self.client.table(table).update(data).eq(column, value)
        result = await asyncio.to_thread(query.execute)
        return result.data if result else []

    async def delete(
        self, table: str, column: str, value: Any,
    ) -> List[Dict[str, Any]]:
        query = self.client.table(table).delete().eq(column, value)
        result = await asyncio.to_thread(query.execute)
        return result.data if result else []

    async def upsert(
        self, table: str, data: Dict[str, Any],
        on_conflict: str = "id",
    ) -> Optional[Dict[str, Any]]:
        query = self.client.table(table).upsert(data, on_conflict=on_conflict)
        result = await asyncio.to_thread(query.execute)
        if result and result.data:
            return result.data[0]
        return None

    async def find_one(
        self, table: str, column: str, value: Any,
        columns: str = "*",
    ) -> Optional[Dict[str, Any]]:
        rows = await self.select(table, columns=columns, eq=(column, value), limit=1)
        return rows[0] if rows else None

    async def find_all(
        self, table: str, column: str, value: Any,
        columns: str = "*",
    ) -> List[Dict[str, Any]]:
        return await self.select(table, columns=columns, eq=(column, value))

    async def count(
        self, table: str, column: str, value: Any,
    ) -> int:
        query = self.client.table(table).select("*", count="exact").eq(column, value)
        result = await asyncio.to_thread(query.execute)
        return result.count if hasattr(result, "count") and result.count else 0


_client_instance: Optional[SupabaseClient] = None


def get_supabase_client() -> Optional[SupabaseClient]:
    global _client_instance
    return _client_instance


def set_supabase_client(client: SupabaseClient):
    global _client_instance
    _client_instance = client


async def create_supabase_client() -> SupabaseClient:
    settings = get_settings()
    url = settings.supabase_url
    key = settings.supabase_service_role_key
    if not url or not key:
        logger.warning("Supabase not configured (SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY missing)")
        return None
    client = SupabaseClient(url, key)
    await client.initialize()
    set_supabase_client(client)
    return client