File size: 4,862 Bytes
9aab9db
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
"""
Supabase database client
"""
from typing import Any, Dict, List, Optional
from contextlib import asynccontextmanager
from supabase import create_client, Client
from postgrest.exceptions import APIError
from app.config import settings
from app.utils.logging import get_logger

logger = get_logger("database")


class DatabaseClient:
    """Supabase database client wrapper"""
    
    _instance = None
    _client: Optional[Client] = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def connect(self) -> Client:
        """Initialize Supabase client"""
        if self._client is None:
            try:
                self._client = create_client(
                    settings.supabase_url,
                    settings.supabase_service_key or settings.supabase_key
                )
                logger.info("Supabase client initialized")
            except Exception as e:
                logger.error("Failed to initialize Supabase client", error=str(e))
                raise
        return self._client
    
    @property
    def client(self) -> Client:
        """Get Supabase client"""
        if self._client is None:
            return self.connect()
        return self._client
    
    @property
    def table(self):
        """Get table builder"""
        return self.client.table
    
    async def fetch_one(
        self,
        table: str,
        filters: Optional[Dict[str, Any]] = None
    ) -> Optional[Dict[str, Any]]:
        """Fetch single record"""
        try:
            query = self.table(table)
            if filters:
                for key, value in filters.items():
                    query = query.eq(key, value)
            
            result = await query.execute()
            if result.data and len(result.data) > 0:
                return result.data[0]
            return None
        except Exception as e:
            logger.error(f"Error fetching from {table}", error=str(e))
            return None
    
    async def fetch_many(
        self,
        table: str,
        filters: Optional[Dict[str, Any]] = None,
        order_by: Optional[str] = None,
        ascending: bool = True,
        limit: int = 100,
        offset: int = 0
    ) -> List[Dict[str, Any]]:
        """Fetch multiple records"""
        try:
            query = self.table(table)
            
            if filters:
                for key, value in filters.items():
                    query = query.eq(key, value)
            
            if order_by:
                query = query.order(order_by, desc=not ascending)
            
            result = await query.limit(limit).offset(offset).execute()
            return result.data or []
        except Exception as e:
            logger.error(f"Error fetching from {table}", error=str(e))
            return []
    
    async def insert(
        self,
        table: str,
        data: Dict[str, Any] | List[Dict[str, Any]]
    ) -> Optional[Dict[str, Any] | List[Dict[str, Any]]]:
        """Insert record(s)"""
        try:
            result = await self.table(table).insert(data).execute()
            return result.data
        except Exception as e:
            logger.error(f"Error inserting into {table}", error=str(e))
            return None
    
    async def update(
        self,
        table: str,
        data: Dict[str, Any],
        filters: Dict[str, Any]
    ) -> Optional[List[Dict[str, Any]]]:
        """Update record(s)"""
        try:
            query = self.table(table)
            for key, value in filters.items():
                query = query.eq(key, value)
            
            result = await query.update(data).execute()
            return result.data
        except Exception as e:
            logger.error(f"Error updating {table}", error=str(e))
            return None
    
    async def delete(
        self,
        table: str,
        filters: Dict[str, Any]
    ) -> bool:
        """Delete record(s)"""
        try:
            query = self.table(table)
            for key, value in filters.items():
                query = query.eq(key, value)
            
            await query.delete().execute()
            return True
        except Exception as e:
            logger.error(f"Error deleting from {table}", error=str(e))
            return False
    
    async def rpc(
        self,
        function_name: str,
        params: Optional[Dict[str, Any]] = None
    ) -> Any:
        """Call stored procedure"""
        try:
            result = await self.client.rpc(function_name, params or {}).execute()
            return result.data
        except Exception as e:
            logger.error(f"Error calling RPC {function_name}", error=str(e))
            return None


# Global database instance
db = DatabaseClient()