File size: 7,955 Bytes
54eb2ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
"""

Controller for user settings operations.

"""

from typing import Optional
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from src.model import UserSettings
from src.schema.user_settings import UserSettingsUpdate
from fastapi import HTTPException, status
from cryptography.fernet import Fernet, InvalidToken
import os

# Get the same encryption key used by the model
ENCRYPTION_KEY = os.getenv("ENCRYPTION_KEY")
if not ENCRYPTION_KEY:
    raise ValueError("ENCRYPTION_KEY environment variable is not set")

fernet = Fernet(ENCRYPTION_KEY.encode())


class UserSettingsController:
    """Controller for user settings operations."""

    @staticmethod
    def is_api_key_encrypted(api_key: str) -> bool:
        """

        Check if an API key is already encrypted (Fernet token) or plaintext.



        Args:

            api_key: The API key string to check



        Returns:

            True if encrypted, False if plaintext

        """
        if not api_key:
            return False
        if len(api_key) < 80:
            return False
        try:
            decrypted = fernet.decrypt(api_key.encode()).decode()
            return True
        except (InvalidToken, Exception) as e:
            return False

    @staticmethod
    async def get_user_settings(

        session: AsyncSession, user_id: int

    ) -> Optional[UserSettings]:
        """

        Get user settings by user ID.



        Args:

            session: Database session

            user_id: User ID



        Returns:

            UserSettings object or None if not found

        """
        result = await session.execute(
            select(UserSettings).where(UserSettings.user_id == user_id)
        )
        return result.scalar_one_or_none()

    @staticmethod
    async def create_user_settings(

        session: AsyncSession,

        user_id: int,

        location: Optional[str] = None,

        custom_summary_instructions: Optional[str] = None,

        usability_analysis_instructions: Optional[str] = None,

        summary_model: Optional[str] = None,

        usability_model: Optional[str] = None,

        api_keys: Optional[list[dict]] = None,

    ) -> UserSettings:
        """

        Create user settings.



        Args:

            session: Database session

            user_id: User ID

            location: User location

            custom_summary_instructions: Custom summary instructions

            usability_analysis_instructions: Usability analysis instructions

            api_keys: List of API key dictionaries



        Returns:

            Created UserSettings object

        """
        settings = UserSettings(
            user_id=user_id,
            location=location,
            custom_summary_instructions=custom_summary_instructions,
            usability_analysis_instructions=usability_analysis_instructions,
            summary_model=summary_model,
            usability_model=usability_model,
        )

        if api_keys:
            # Process each key: encrypt only if not already encrypted
            processed_keys = []
            for key_item in api_keys:
                processed_item = key_item.copy()
                api_key_value = processed_item.get("api_key", "")
                slug = processed_item.get("slug", "unknown")

                # Only encrypt if the key is plaintext
                if api_key_value and not UserSettingsController.is_api_key_encrypted(
                    api_key_value
                ):
                    encrypted_value = fernet.encrypt(api_key_value.encode()).decode()
                    processed_item["api_key"] = encrypted_value
                processed_keys.append(processed_item)

            settings.api_keys_encrypted = processed_keys

        session.add(settings)
        await session.commit()
        await session.refresh(settings)
        return settings

    @staticmethod
    async def update_user_settings(

        session: AsyncSession, user_id: int, settings_data: UserSettingsUpdate

    ) -> UserSettings:
        """

        Update user settings.



        Args:

            session: Database session

            user_id: User ID

            settings_data: Settings data to update



        Returns:

            Updated UserSettings object



        Raises:

            HTTPException: If settings not found

        """
        # Get existing settings
        settings = await UserSettingsController.get_user_settings(session, user_id)

        if not settings:
            # Create new settings if they don't exist
            api_keys = None
            if settings_data.api_keys_encrypted:
                api_keys = [
                    item.model_dump() for item in settings_data.api_keys_encrypted
                ]

            settings = await UserSettingsController.create_user_settings(
                session=session,
                user_id=user_id,
                location=settings_data.location,
                custom_summary_instructions=settings_data.custom_summary_instructions,
                usability_analysis_instructions=settings_data.usability_analysis_instructions,
                summary_model=settings_data.summary_model,
                usability_model=settings_data.usability_model,
                api_keys=api_keys,
            )
            return settings

        # Update existing settings
        update_data = settings_data.model_dump(exclude_unset=True)

        # Handle api_keys_encrypted separately
        if "api_keys_encrypted" in update_data:
            api_keys = update_data.pop("api_keys_encrypted")
            if api_keys is not None:
                # Handle empty array to clear all keys
                if len(api_keys) == 0:
                    settings.api_keys_encrypted = []
                else:
                    # Process each key: encrypt only if not already encrypted
                    processed_keys = []
                    for item in api_keys:
                        key_dict = (
                            item.model_dump() if hasattr(item, "model_dump") else item
                        )
                        processed_item = key_dict.copy()
                        api_key_value = processed_item.get("api_key", "")
                        slug = processed_item.get("slug", "unknown")

                        if (
                            api_key_value
                            and not UserSettingsController.is_api_key_encrypted(
                                api_key_value
                            )
                        ):
                            encrypted_value = fernet.encrypt(
                                api_key_value.encode()
                            ).decode()
                            processed_item["api_key"] = encrypted_value

                        processed_keys.append(processed_item)

                    settings.api_keys_encrypted = processed_keys

        # Update other fields
        for field, value in update_data.items():
            setattr(settings, field, value)

        await session.commit()
        await session.refresh(settings)
        return settings

    @staticmethod
    async def get_or_create_user_settings(

        session: AsyncSession, user_id: int

    ) -> UserSettings:
        """

        Get user settings, creating them if they don't exist.



        Args:

            session: Database session

            user_id: User ID



        Returns:

            UserSettings object

        """
        settings = await UserSettingsController.get_user_settings(session, user_id)

        if not settings:
            settings = await UserSettingsController.create_user_settings(
                session, user_id
            )

        return settings