Skip to content

Commit f1eb9c9

Browse files
authored
Merge pull request #2166 from Jdubrick/add-saved-prompts-dal
RHIDP-14306: add saved prompts data access layer
2 parents 066c991 + 983d398 commit f1eb9c9

2 files changed

Lines changed: 377 additions & 7 deletions

File tree

src/utils/saved_prompts.py

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,40 @@
1-
"""Validation helpers for saved prompts."""
1+
"""Validation helpers and data access for saved prompts."""
22

3+
from sqlalchemy.exc import IntegrityError
34

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):
519
"""Invalid saved-prompt field values."""
620

721

822
class SavedPromptLimitExceededError(SavedPromptValidationError):
923
"""Per-user saved-prompt count would exceed the configured maximum."""
1024

1125

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+
1238
def validate_saved_prompt_quota(
1339
current_count: int,
1440
max_prompts_per_user: int,
@@ -90,3 +116,122 @@ def validate_saved_prompt_content(
90116
f"Saved prompt content length {len(content)} exceeds maximum "
91117
f"{max_content_length}"
92118
)
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

Comments
 (0)