Mercurial
diff mrjunejune/conversation_store.h @ 264:04fee26ecce0
add authenticated JRPG conversation platform
Add reusable auth/session storage, owned conversation recovery, guest quotas, admin workflows, URL-routed conversation UI, mobile frame support, and parallel browser acceptance.
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Fri, 07 Aug 2026 07:34:12 -0700 |
| parents | 1f9877b637e9 |
| children |
line wrap: on
line diff
--- a/mrjunejune/conversation_store.h Thu Aug 06 11:31:30 2026 -0700 +++ b/mrjunejune/conversation_store.h Fri Aug 07 07:34:12 2026 -0700 @@ -12,6 +12,46 @@ CONVERSATION_STORE_CONFLICT = 2, } Conversation_Store_Result; +/* ------------------------------------------------------------------ */ +/* Ownership types */ +/* ------------------------------------------------------------------ */ + +typedef enum { + CONVERSATION_OWNER_KIND_USER = 0, + CONVERSATION_OWNER_KIND_GUEST = 1, + CONVERSATION_OWNER_KIND_LEGACY = 2, +} Conversation_Owner_Kind; + +/* + * Owner identity for a conversation. + * kind=USER/GUEST: id holds user_id or guest_id (UUID, 36 chars). + * kind=LEGACY: id is empty; owned by no one. + */ +typedef struct { + Conversation_Owner_Kind kind; + char id[37]; /* user_id or guest_id; empty string for legacy */ +} Conversation_Owner; + +/* + * Lightweight summary returned by the listing endpoint. + * All string pointers are arena-allocated. + * last_message_preview is truncated to 200 chars (UTF-8 may split mid-char; + * callers must not rely on it being valid UTF-8 at the boundary). + */ +typedef struct { + char *id; + char *title; + char *status; + int64 created_at; + int64 updated_at; + int64 turn_count; + char *last_message_preview; /* at most 200 bytes + NUL */ +} Conversation_Summary; + +/* ------------------------------------------------------------------ */ +/* Turn and record types */ +/* ------------------------------------------------------------------ */ + typedef struct { int64 id; int64 sequence; @@ -36,10 +76,116 @@ Conversation_Turn *turns; } Conversation_Record; +/* ------------------------------------------------------------------ */ +/* Lifecycle */ +/* ------------------------------------------------------------------ */ + Conversation_Store *Conversation_Store_Create(const char *database_path); void Conversation_Store_Destroy(Conversation_Store *p_store); boolean Conversation_Store_Generate_UUID(char output[37]); +/* ------------------------------------------------------------------ */ +/* Owner-aware APIs (production HTTP handlers must use these) */ +/* ------------------------------------------------------------------ */ + +/* + * Create a conversation assigned to p_owner. + * p_owner->kind must be USER or GUEST; LEGACY is rejected. + */ +Conversation_Store_Result Conversation_Store_Create_Owned( + Conversation_Store *p_store, + const char *title, + const Conversation_Owner *p_owner, + char output_id[37]); + +/* + * Get a conversation; returns NOT_FOUND if the conversation does not belong + * to p_owner (existence leaks prevented). + */ +Conversation_Store_Result Conversation_Store_Get_Owned( + Conversation_Store *p_store, + const char *conversation_id, + const Conversation_Owner *p_owner, + Conversation_Record *p_record, + Dowa_Arena *p_arena); + +Conversation_Store_Result Conversation_Store_Update_Title_Owned( + Conversation_Store *p_store, + const char *conversation_id, + const Conversation_Owner *p_owner, + const char *title); + +Conversation_Store_Result Conversation_Store_Delete_Owned( + Conversation_Store *p_store, + const char *conversation_id, + const Conversation_Owner *p_owner); + +Conversation_Store_Result Conversation_Store_Begin_Turn_Owned( + Conversation_Store *p_store, + const char *conversation_id, + const Conversation_Owner *p_owner, + const char *request_id, + const char *prompt); + +/* + * List conversations for an owner, ordered newest-first. + * Legacy conversations are never listed (returns empty list for LEGACY owner). + * + * cursor_updated_at / cursor_id: pass 0 / NULL for the first page. + * limit: capped to 50 internally; must be >= 1. + * + * *pp_summaries is arena-allocated; *p_count is the number of items. + * A cursor for the next page (if *p_count == limit) can be derived from + * (*pp_summaries)[*p_count - 1].{updated_at, id}. + */ +Conversation_Store_Result Conversation_Store_List( + Conversation_Store *p_store, + const Conversation_Owner *p_owner, + int64 cursor_updated_at, + const char *cursor_id, + int32 limit, + Conversation_Summary **pp_summaries, + int32 *p_count, + Dowa_Arena *p_arena); + +/* + * Claim a legacy conversation: reassign it to user_id. + * Returns NOT_FOUND if the conversation is not a legacy row (or does not + * exist), preventing existence leaks. + * Returns CONFLICT if the conversation is already owned by someone else. + */ +Conversation_Store_Result Conversation_Store_Claim_Legacy( + Conversation_Store *p_store, + const char *conversation_id, + const char *user_id); + +/* + * Atomically transfer all guest conversations to a user. + * Idempotent: re-running with the same (guest_id, user_id) pair is safe. + * Returns CONVERSATION_STORE_OK on success (including the no-op case). + */ +Conversation_Store_Result Conversation_Store_Transfer_Guest_To_User( + Conversation_Store *p_store, + const char *guest_id, + const char *user_id); + +/* + * Like Conversation_Store_Transfer_Guest_To_User but also clears outstanding + * guest quota reservations atomically in the same transaction. + * Use this on login: both the conversation transfer and quota cleanup commit + * or roll back together. The quota tables must reside in the same SQLite + * database file as the conversation tables (the shared mrjunejune.db). + */ +Conversation_Store_Result Conversation_Store_Transfer_Guest_To_User_Atomic( + Conversation_Store *p_store, + const char *guest_id, + const char *user_id); + +/* ------------------------------------------------------------------ */ +/* Legacy/internal APIs (kept only for migration-compatibility tests) */ +/* Production HTTP handlers must not use these. */ +/* ------------------------------------------------------------------ */ + Conversation_Store_Result Conversation_Store_Create_Conversation( Conversation_Store *p_store, const char *title,