40 Commits

Author SHA1 Message Date
Classic298 189c14fc4d fix: match both JSON text spellings when searching serialised JSON columns (#28399)
Three searches LIKE against cast(json_col AS text), which means they have to match
bytes a JSON encoder wrote. Encoders disagree on non-ASCII: stdlib escapes it to
\uXXXX, orjson writes it raw. Which one produced a row depends on the codec in force
when it was written, so any single pattern finds only half the table.

models.py hard-codes the stdlib spelling, with a comment asserting SQLite stores
JSON via json.dumps(ensure_ascii=True). Model.meta is a JSONField, which has
serialised through JSONCodec since ENABLE_ORJSON was introduced, so on that setting
it stores raw UTF-8 and the escaped pattern matches nothing: non-ASCII workspace
model tag search is broken today. prompts.py and automations.py hard-code the
opposite spelling and miss rows written the other way.

json_text_variants returns both spellings a string can take inside serialised JSON,
collapsing to one for ASCII, and the three call sites OR over them. Rows written
under either setting are now found under either setting, which also covers a
database holding a mix of the two.

Case handling is unchanged. models.py keeps matching non-ASCII tags case-sensitively
on SQLite, whose LOWER() is ASCII-only and would not fold the stored text the way
str.lower() folds the tag. ASCII tags collapse to a single variant and take exactly
the query they took before.

Verified on SQLite across every combination of codec-that-wrote-the-row and
codec-the-app-is-running, for an ASCII and a CJK tag, over all three call sites: 24
of 24 match, against 12 of 24 before. Quoting still bounds whole-tag matches, so
searching "weather" does not match a row tagged "weathervane".

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-17 01:24:05 -06:00
Timothy Jaeryang Baek bb0f898b43 refac 2026-07-31 17:41:14 -04:00
Timothy Jaeryang Baek f1409266fe refac 2026-07-27 02:44:21 -04:00
Classic298 699d512e2f perf: drop redundant session.refresh calls after commit across the model layer (#27381)
Both session factories run with expire_on_commit=False, so ORM objects keep their attribute values after commit. Every session.refresh issued right after a commit therefore re-SELECTed a row whose values the session already held, including full chat JSON blobs and user settings, purely to overwrite identical data. Fifty such calls existed across the model layer, covering nearly every write path in the app (chat inserts, title updates, pin/archive toggles, user role and settings updates, tool, prompt, function, model, file, tag, feedback, memory, automation and grant writes).

All fifty are removed. The only refreshes with an actual job were the two update-then-reload paths in tools and skills, where a Core UPDATE statement bypasses the identity map; those now use session.get(..., populate_existing=True), which guarantees a fresh row in one SELECT whether or not the row was already present in the session (the previous code issued get plus refresh, two SELECTs, on the default configuration).

Benchmark (real SQLite DB, per write):

| write path | before | after |
| --- | --- | --- |
| chat title update, ~600 KB chat blob | 2.08 ms | 1.24 ms |
| user role update, small row | 1.21 ms | 0.68 ms |

On Postgres each removed refresh is additionally a network round trip. The chat-blob case also skips re-parsing the entire JSON document per write.

Functionally verified against a fresh database: user insert, role and settings updates, chat insert (including the server-default meta column, which is always provided client-side), title update and pin toggle, tool insert and the Core-update reload path, tag insert and the prompt insert flow that pins version_id after history creation all return correct values and persist correctly.
2026-07-23 18:08:00 -05:00
Timothy Jaeryang Baek 6fce92aa12 chore: format 2026-06-01 13:56:55 -07:00
Classic298 9a3eea6448 fix: bind prompt history/version ops to the authorized prompt (#25056)
The history diff, delete, and version-restore routes authorize the URL
prompt_id but then act on a caller-supplied history/version id without
checking it belongs to that prompt (IDOR). Filter by prompt_id in
compute_diff and delete_history_entry, and reject a cross-prompt version_id
in update_prompt_version.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 12:07:52 -07:00
G30 48ea043f82 fix(prompts): resolve undefined session variable in _get_access_grants and _to_prompt_model (#25129)
Both _get_access_grants and _to_prompt_model referenced an undefined
local variable 'session' instead of the 'db' parameter passed to each
method. Because these helpers are called outside of any
'async with get_async_db_context()' block, 'session' did not exist in
their scope, causing a NameError on every prompt fetch.

The NameError was silently swallowed by the broad 'except Exception'
clause in get_prompt_by_id, which returned None — causing the frontend
[id]/+page.svelte to immediately redirect back to /workspace/prompts
rather than rendering the prompt editor.

Also adds the missing 'logging' import and module-level 'log' logger,
which was referenced (but never imported) in insert_new_prompt,
update_prompt_version, and delete_prompt_by_id.
2026-05-28 16:23:25 -05:00
Timothy Jaeryang Baek d8b5b9fa79 refac 2026-05-21 15:29:49 +04:00
Timothy Jaeryang Baek 260ead64da refac 2026-05-21 14:01:57 +04:00
Timothy Jaeryang Baek 6d0295588e refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
Timothy Jaeryang Baek 7bcc0e2e5c chore: format 2026-05-09 15:25:27 +09:00
Classic298 1a3e5ef4c1 perf(prompts): make /tags fetch only the tags column with SQL access filter (#24287)
Non-admin GET /api/v1/prompts/tags went through get_prompts_by_user_id,
which loaded every active prompt with its full content/data/meta plus
owner records and all access grants, then ran one has_access query per
prompt that wasn't owned by the caller - all so the endpoint could
collapse the result to a sorted tag list. With 600 prompts this took
several seconds while the admin path (a single SELECT) returned in <1s.

Add Prompts.get_tags_by_user_id which selects only the tags column and
applies the same EXISTS-based access filter used by /list. Also tighten
the admin get_tags to project just the tags column instead of full rows.
The endpoint is now one DB query (plus one for groups), no row hydration,
no N+1.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-09 05:20:13 +09:00
Classic298 41107a34ca perf(prompts): filter prompt list in SQL instead of N+1 has_access loop (#24288)
get_prompts_by_user_id used to fetch every active prompt (with users +
all access grants), then call AccessGrants.has_access() once per prompt
that the user did not own. With 600+ prompts this issued ~600 extra
round-trips per request and explained the multi-second delay reported in
the GET /api/v1/prompts and /api/v1/prompts/tags endpoints for non-admin
users.

Push the access check into a single SQL query via the existing
AccessGrants.has_permission_filter (EXISTS subquery), so only accessible
rows come back from the DB. Users and access grants for the surviving
rows are still batch-fetched, no N+1 anywhere on this path.

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-09 05:12:51 +09:00
Timothy Jaeryang Baek 4113b15a60 chore: format 2026-04-17 14:28:18 +09:00
Timothy Jaeryang Baek e7e752f8e7 refac 2026-04-17 14:09:35 +09:00
Timothy Jaeryang Baek 57784706e4 refac 2026-04-12 19:34:45 -05:00
Timothy Jaeryang Baek 25898116ea chore: format 2026-04-12 18:12:59 -05:00
Timothy Jaeryang Baek 27169124f2 refac: async db 2026-04-12 14:22:11 -05:00
Timothy Jaeryang Baek ade617efa8 refac 2026-03-24 04:49:48 -05:00
Timothy Jaeryang Baek de3317e26b refac 2026-03-17 17:58:01 -05:00
Timothy Jaeryang Baek 631e30e22d refac 2026-02-21 15:35:34 -06:00
Timothy Jaeryang Baek 8c485b260f refac 2026-02-19 16:53:21 -06:00
Timothy Jaeryang Baek 094ed0b48c fix: prompts delete 2026-02-18 14:58:39 -06:00
Timothy Jaeryang Baek f376d4f378 chore: format 2026-02-11 16:24:11 -06:00
Timothy Jaeryang Baek 4aedfdc547 refac 2026-02-10 15:47:21 -06:00
Timothy Jaeryang Baek c259c87806 refac 2026-02-10 15:30:16 -06:00
Timothy Jaeryang Baek 0f78451c2b refac 2026-02-08 21:24:20 -06:00
Timothy Jaeryang Baek 36766f157d refac: prompts pagination 2026-01-27 23:01:56 +04:00
Timothy Jaeryang Baek 4c8d4e6dbd enh: prompt tags 2026-01-26 16:11:10 +04:00
Timothy Jaeryang Baek 32810b4152 refac 2026-01-24 04:13:19 +04:00
Timothy Jaeryang Baek 5ad593e465 refac: prompt endpoints 2026-01-24 03:08:48 +04:00
Timothy Jaeryang Baek 6ab7d54982 refac 2026-01-24 02:51:44 +04:00
Timothy Jaeryang Baek 66daa15722 wip: prompt history models 2026-01-24 02:39:29 +04:00
Timothy Jaeryang Baek 700349064d chore: format 2026-01-08 01:55:56 +04:00
Classic298 1f059fe730 feat: Prompts Atomic PR of #20243 (#20368)
* feat: Add read-only access support for Prompts

- Backend: Add write_access field to PromptAccessResponse
- Backend: Update /prompts/list to return prompts with write_access
- Frontend: Display Read Only badge in Prompts list
- Frontend: Disable inputs and save button when no write access

* feat: Add read-only visual indicators for Prompts workspace

* fix: Return write_access from getPromptByCommand endpoint

- Backend returns write_access directly in response
- Frontend extracts write_access from getPromptByCommand response
- Remove inefficient getPromptList call in edit page

* fix: Align Read Only badge to right in Prompts.svelte

- Title and command stay on left
- Badge pushed to right by justify-between

* fix: Use PromptAccessResponse in get_prompt_by_command endpoint

fix: Use PromptAccessResponse in get_prompt_by_command endpoint

- Return PromptAccessResponse Pydantic model instead of raw dict
- Properly type the response with response_model
2026-01-05 04:36:13 +04:00
Timothy Jaeryang Baek 2041ab483e refac/enh: db session sharing 2025-12-28 22:00:44 +04:00
Tim Jaeryang Baek 472b71f331 Merge pull request #17166 from sihyeonn/perf/sh-model-layer
perf: fix N+1 query issues in user group access control validation
2025-09-03 13:36:01 +04:00
Sihyeon Jang eff06538a6 perf: fix N+1 query issues in user group access control validation
- Pre-fetch user group IDs in get_*_by_user_id methods across models layer
- Pass user_group_ids to has_access to avoid repeated group queries
- Reduce query count from 1+N to 1+1 pattern for access control validation
- Apply consistent optimization across knowledge, models, notes, prompts, and tools

Signed-off-by: Sihyeon Jang <sihyeon.jang@navercorp.com>
2025-09-03 05:56:48 +09:00
Sihyeon Jang c45201a8a2 perf: fix N+1 query issue in get_prompts method
- Replace individual user queries with batch fetching
- Use single query to fetch all required users at once
- Implement O(1) user lookup with dictionary mapping
- Reduce query count from 1+N to 1+1 pattern

Signed-off-by: Sihyeon Jang <sihyeon.jang@navercorp.com>
2025-09-03 05:25:26 +09:00
Timothy Jaeryang Baek d3d161f723 wip 2024-12-10 00:54:13 -08:00