comparison 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
comparison
equal deleted inserted replaced
263:ee04e4e69fed 264:04fee26ecce0
9 CONVERSATION_STORE_ERROR = -1, 9 CONVERSATION_STORE_ERROR = -1,
10 CONVERSATION_STORE_OK = 0, 10 CONVERSATION_STORE_OK = 0,
11 CONVERSATION_STORE_NOT_FOUND = 1, 11 CONVERSATION_STORE_NOT_FOUND = 1,
12 CONVERSATION_STORE_CONFLICT = 2, 12 CONVERSATION_STORE_CONFLICT = 2,
13 } Conversation_Store_Result; 13 } Conversation_Store_Result;
14
15 /* ------------------------------------------------------------------ */
16 /* Ownership types */
17 /* ------------------------------------------------------------------ */
18
19 typedef enum {
20 CONVERSATION_OWNER_KIND_USER = 0,
21 CONVERSATION_OWNER_KIND_GUEST = 1,
22 CONVERSATION_OWNER_KIND_LEGACY = 2,
23 } Conversation_Owner_Kind;
24
25 /*
26 * Owner identity for a conversation.
27 * kind=USER/GUEST: id holds user_id or guest_id (UUID, 36 chars).
28 * kind=LEGACY: id is empty; owned by no one.
29 */
30 typedef struct {
31 Conversation_Owner_Kind kind;
32 char id[37]; /* user_id or guest_id; empty string for legacy */
33 } Conversation_Owner;
34
35 /*
36 * Lightweight summary returned by the listing endpoint.
37 * All string pointers are arena-allocated.
38 * last_message_preview is truncated to 200 chars (UTF-8 may split mid-char;
39 * callers must not rely on it being valid UTF-8 at the boundary).
40 */
41 typedef struct {
42 char *id;
43 char *title;
44 char *status;
45 int64 created_at;
46 int64 updated_at;
47 int64 turn_count;
48 char *last_message_preview; /* at most 200 bytes + NUL */
49 } Conversation_Summary;
50
51 /* ------------------------------------------------------------------ */
52 /* Turn and record types */
53 /* ------------------------------------------------------------------ */
14 54
15 typedef struct { 55 typedef struct {
16 int64 id; 56 int64 id;
17 int64 sequence; 57 int64 sequence;
18 char *role; 58 char *role;
34 int64 created_at; 74 int64 created_at;
35 int64 updated_at; 75 int64 updated_at;
36 Conversation_Turn *turns; 76 Conversation_Turn *turns;
37 } Conversation_Record; 77 } Conversation_Record;
38 78
79 /* ------------------------------------------------------------------ */
80 /* Lifecycle */
81 /* ------------------------------------------------------------------ */
82
39 Conversation_Store *Conversation_Store_Create(const char *database_path); 83 Conversation_Store *Conversation_Store_Create(const char *database_path);
40 void Conversation_Store_Destroy(Conversation_Store *p_store); 84 void Conversation_Store_Destroy(Conversation_Store *p_store);
41 boolean Conversation_Store_Generate_UUID(char output[37]); 85 boolean Conversation_Store_Generate_UUID(char output[37]);
42 86
87 /* ------------------------------------------------------------------ */
88 /* Owner-aware APIs (production HTTP handlers must use these) */
89 /* ------------------------------------------------------------------ */
90
91 /*
92 * Create a conversation assigned to p_owner.
93 * p_owner->kind must be USER or GUEST; LEGACY is rejected.
94 */
95 Conversation_Store_Result Conversation_Store_Create_Owned(
96 Conversation_Store *p_store,
97 const char *title,
98 const Conversation_Owner *p_owner,
99 char output_id[37]);
100
101 /*
102 * Get a conversation; returns NOT_FOUND if the conversation does not belong
103 * to p_owner (existence leaks prevented).
104 */
105 Conversation_Store_Result Conversation_Store_Get_Owned(
106 Conversation_Store *p_store,
107 const char *conversation_id,
108 const Conversation_Owner *p_owner,
109 Conversation_Record *p_record,
110 Dowa_Arena *p_arena);
111
112 Conversation_Store_Result Conversation_Store_Update_Title_Owned(
113 Conversation_Store *p_store,
114 const char *conversation_id,
115 const Conversation_Owner *p_owner,
116 const char *title);
117
118 Conversation_Store_Result Conversation_Store_Delete_Owned(
119 Conversation_Store *p_store,
120 const char *conversation_id,
121 const Conversation_Owner *p_owner);
122
123 Conversation_Store_Result Conversation_Store_Begin_Turn_Owned(
124 Conversation_Store *p_store,
125 const char *conversation_id,
126 const Conversation_Owner *p_owner,
127 const char *request_id,
128 const char *prompt);
129
130 /*
131 * List conversations for an owner, ordered newest-first.
132 * Legacy conversations are never listed (returns empty list for LEGACY owner).
133 *
134 * cursor_updated_at / cursor_id: pass 0 / NULL for the first page.
135 * limit: capped to 50 internally; must be >= 1.
136 *
137 * *pp_summaries is arena-allocated; *p_count is the number of items.
138 * A cursor for the next page (if *p_count == limit) can be derived from
139 * (*pp_summaries)[*p_count - 1].{updated_at, id}.
140 */
141 Conversation_Store_Result Conversation_Store_List(
142 Conversation_Store *p_store,
143 const Conversation_Owner *p_owner,
144 int64 cursor_updated_at,
145 const char *cursor_id,
146 int32 limit,
147 Conversation_Summary **pp_summaries,
148 int32 *p_count,
149 Dowa_Arena *p_arena);
150
151 /*
152 * Claim a legacy conversation: reassign it to user_id.
153 * Returns NOT_FOUND if the conversation is not a legacy row (or does not
154 * exist), preventing existence leaks.
155 * Returns CONFLICT if the conversation is already owned by someone else.
156 */
157 Conversation_Store_Result Conversation_Store_Claim_Legacy(
158 Conversation_Store *p_store,
159 const char *conversation_id,
160 const char *user_id);
161
162 /*
163 * Atomically transfer all guest conversations to a user.
164 * Idempotent: re-running with the same (guest_id, user_id) pair is safe.
165 * Returns CONVERSATION_STORE_OK on success (including the no-op case).
166 */
167 Conversation_Store_Result Conversation_Store_Transfer_Guest_To_User(
168 Conversation_Store *p_store,
169 const char *guest_id,
170 const char *user_id);
171
172 /*
173 * Like Conversation_Store_Transfer_Guest_To_User but also clears outstanding
174 * guest quota reservations atomically in the same transaction.
175 * Use this on login: both the conversation transfer and quota cleanup commit
176 * or roll back together. The quota tables must reside in the same SQLite
177 * database file as the conversation tables (the shared mrjunejune.db).
178 */
179 Conversation_Store_Result Conversation_Store_Transfer_Guest_To_User_Atomic(
180 Conversation_Store *p_store,
181 const char *guest_id,
182 const char *user_id);
183
184 /* ------------------------------------------------------------------ */
185 /* Legacy/internal APIs (kept only for migration-compatibility tests) */
186 /* Production HTTP handlers must not use these. */
187 /* ------------------------------------------------------------------ */
188
43 Conversation_Store_Result Conversation_Store_Create_Conversation( 189 Conversation_Store_Result Conversation_Store_Create_Conversation(
44 Conversation_Store *p_store, 190 Conversation_Store *p_store,
45 const char *title, 191 const char *title,
46 char output_id[37]); 192 char output_id[37]);
47 193