|
1 | | -"""Validation helpers for saved prompts.""" |
| 1 | +"""Validation helpers and data access for saved prompts.""" |
2 | 2 |
|
| 3 | +from sqlalchemy.exc import IntegrityError |
3 | 4 |
|
4 | | -class SavedPromptValidationError(Exception): |
| 5 | +import constants |
| 6 | +from app.database import get_session |
| 7 | +from log import get_logger |
| 8 | +from models.database.saved_prompts import SavedPrompt |
| 9 | +from utils.suid import get_suid |
| 10 | + |
| 11 | +logger = get_logger(__name__) |
| 12 | + |
| 13 | + |
| 14 | +class SavedPromptError(Exception): |
| 15 | + """Base class for saved-prompt domain errors.""" |
| 16 | + |
| 17 | + |
| 18 | +class SavedPromptValidationError(SavedPromptError): |
5 | 19 | """Invalid saved-prompt field values.""" |
6 | 20 |
|
7 | 21 |
|
8 | 22 | class SavedPromptLimitExceededError(SavedPromptValidationError): |
9 | 23 | """Per-user saved-prompt count would exceed the configured maximum.""" |
10 | 24 |
|
11 | 25 |
|
| 26 | +class SavedPromptNotFoundError(SavedPromptError): |
| 27 | + """No saved prompt exists for the given identifier.""" |
| 28 | + |
| 29 | + |
| 30 | +class SavedPromptAccessDeniedError(SavedPromptError): |
| 31 | + """The saved prompt exists but is not owned by the requesting user.""" |
| 32 | + |
| 33 | + |
| 34 | +class SavedPromptConflictError(SavedPromptError): |
| 35 | + """A saved prompt conflicts with an existing unique constraint.""" |
| 36 | + |
| 37 | + |
12 | 38 | def validate_saved_prompt_quota( |
13 | 39 | current_count: int, |
14 | 40 | max_prompts_per_user: int, |
@@ -90,3 +116,122 @@ def validate_saved_prompt_content( |
90 | 116 | f"Saved prompt content length {len(content)} exceeds maximum " |
91 | 117 | f"{max_content_length}" |
92 | 118 | ) |
| 119 | + |
| 120 | + |
| 121 | +def create_saved_prompt( |
| 122 | + user_id: str, |
| 123 | + name: str, |
| 124 | + content: str, |
| 125 | + max_prompts_per_user: int, |
| 126 | +) -> SavedPrompt: |
| 127 | + """Create a saved prompt for a user after enforcing the per-user quota. |
| 128 | +
|
| 129 | + Caller is responsible for validating ``name`` and ``content``. This function |
| 130 | + counts existing prompts for ``user_id``, enforces ``max_prompts_per_user``, |
| 131 | + inserts a new row with a generated id, and returns the persisted entity with |
| 132 | + timestamps loaded. |
| 133 | +
|
| 134 | + Parameters: |
| 135 | + user_id: Owner of the saved prompt. |
| 136 | + name: Display name as provided by the caller (not stripped here). |
| 137 | + content: Prompt body as provided by the caller. |
| 138 | + max_prompts_per_user: Maximum prompts the user may hold. |
| 139 | +
|
| 140 | + Returns: |
| 141 | + The created ``SavedPrompt`` with id and timestamps populated. |
| 142 | +
|
| 143 | + Raises: |
| 144 | + SavedPromptLimitExceededError: If the user is already at the limit. |
| 145 | + SavedPromptConflictError: If insert violates a unique constraint |
| 146 | + (practically always duplicate ``(user_id, name)``). |
| 147 | + """ |
| 148 | + with get_session() as session: |
| 149 | + current_count = session.query(SavedPrompt).filter_by(user_id=user_id).count() |
| 150 | + validate_saved_prompt_quota(current_count, max_prompts_per_user) |
| 151 | + |
| 152 | + saved_prompt = SavedPrompt( |
| 153 | + id=get_suid(), |
| 154 | + user_id=user_id, |
| 155 | + name=name, |
| 156 | + content=content, |
| 157 | + ) |
| 158 | + session.add(saved_prompt) |
| 159 | + try: |
| 160 | + session.commit() |
| 161 | + except IntegrityError as exc: |
| 162 | + logger.debug( |
| 163 | + "Saved prompt create conflict for user_id=%s", |
| 164 | + user_id, |
| 165 | + ) |
| 166 | + raise SavedPromptConflictError("Saved prompt name already exists") from exc |
| 167 | + |
| 168 | + # reload server default timestamps so they remain usable after the session closes |
| 169 | + session.refresh(saved_prompt) |
| 170 | + logger.debug( |
| 171 | + "Created saved prompt id=%s for user_id=%s", |
| 172 | + saved_prompt.id, |
| 173 | + user_id, |
| 174 | + ) |
| 175 | + return saved_prompt |
| 176 | + |
| 177 | + |
| 178 | +def list_saved_prompts_by_user(user_id: str) -> list[SavedPrompt]: |
| 179 | + """List saved prompts for a user ordered by created_at descending. |
| 180 | +
|
| 181 | + Results are capped at ``SAVED_PROMPTS_MAX_PER_USER_UPPER_BOUND`` so a |
| 182 | + misconfigured or out-of-band insert path cannot materialize unbounded rows. |
| 183 | +
|
| 184 | + Parameters: |
| 185 | + user_id: Owner whose prompts should be returned. |
| 186 | +
|
| 187 | + Returns: |
| 188 | + List of ``SavedPrompt`` rows for the user. Empty list if none exist. |
| 189 | + Tie order when ``created_at`` values are equal is database-defined. |
| 190 | + """ |
| 191 | + with get_session() as session: |
| 192 | + return ( |
| 193 | + session.query(SavedPrompt) |
| 194 | + .filter_by(user_id=user_id) |
| 195 | + .order_by(SavedPrompt.created_at.desc()) |
| 196 | + .limit(constants.SAVED_PROMPTS_MAX_PER_USER_UPPER_BOUND) |
| 197 | + .all() |
| 198 | + ) |
| 199 | + |
| 200 | + |
| 201 | +def delete_saved_prompt_by_id_and_user(prompt_id: str, user_id: str) -> None: |
| 202 | + """Delete a saved prompt only if it belongs to the given user. |
| 203 | +
|
| 204 | + Parameters: |
| 205 | + prompt_id: Primary key of the saved prompt. |
| 206 | + user_id: Authenticated user attempting the delete. |
| 207 | +
|
| 208 | + Raises: |
| 209 | + SavedPromptNotFoundError: If no row exists for ``prompt_id``. |
| 210 | + SavedPromptAccessDeniedError: If the row exists but ``user_id`` does not |
| 211 | + match the owner. |
| 212 | + """ |
| 213 | + with get_session() as session: |
| 214 | + saved_prompt = session.query(SavedPrompt).filter_by(id=prompt_id).first() |
| 215 | + if saved_prompt is None: |
| 216 | + logger.debug( |
| 217 | + "Saved prompt not found for delete prompt_id=%s user_id=%s", |
| 218 | + prompt_id, |
| 219 | + user_id, |
| 220 | + ) |
| 221 | + raise SavedPromptNotFoundError("Saved prompt not found") |
| 222 | + |
| 223 | + if saved_prompt.user_id != user_id: |
| 224 | + logger.debug( |
| 225 | + "Saved prompt access denied for delete prompt_id=%s user_id=%s", |
| 226 | + prompt_id, |
| 227 | + user_id, |
| 228 | + ) |
| 229 | + raise SavedPromptAccessDeniedError("Saved prompt access denied") |
| 230 | + |
| 231 | + session.delete(saved_prompt) |
| 232 | + session.commit() |
| 233 | + logger.debug( |
| 234 | + "Deleted saved prompt id=%s for user_id=%s", |
| 235 | + prompt_id, |
| 236 | + user_id, |
| 237 | + ) |
0 commit comments