hazo_chat 5.2.3 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/api/messages.d.ts.map +1 -1
  2. package/dist/api/messages.js +440 -438
  3. package/dist/api/messages.js.map +1 -1
  4. package/dist/api/types.d.ts +3 -1
  5. package/dist/api/types.d.ts.map +1 -1
  6. package/dist/api/unread_count.d.ts.map +1 -1
  7. package/dist/api/unread_count.js +88 -84
  8. package/dist/api/unread_count.js.map +1 -1
  9. package/dist/components/hazo_chat/hazo_chat.js +1 -1
  10. package/dist/components/hazo_chat/hazo_chat.js.map +1 -1
  11. package/dist/components/hazo_chat/hazo_chat_attachment_preview.js +1 -1
  12. package/dist/components/hazo_chat/hazo_chat_attachment_preview.js.map +1 -1
  13. package/dist/components/hazo_chat/hazo_chat_context.d.ts.map +1 -1
  14. package/dist/components/hazo_chat/hazo_chat_context.js +2 -1
  15. package/dist/components/hazo_chat/hazo_chat_context.js.map +1 -1
  16. package/dist/components/hazo_chat/hazo_chat_document_viewer.js +1 -1
  17. package/dist/components/hazo_chat/hazo_chat_document_viewer.js.map +1 -1
  18. package/dist/components/hazo_chat/hazo_chat_header.js +1 -1
  19. package/dist/components/hazo_chat/hazo_chat_header.js.map +1 -1
  20. package/dist/components/hazo_chat/hazo_chat_input.js +1 -1
  21. package/dist/components/hazo_chat/hazo_chat_input.js.map +1 -1
  22. package/dist/components/hazo_chat/hazo_chat_messages.js +1 -1
  23. package/dist/components/hazo_chat/hazo_chat_messages.js.map +1 -1
  24. package/dist/components/hazo_chat/hazo_chat_reference_list.js +1 -1
  25. package/dist/components/hazo_chat/hazo_chat_reference_list.js.map +1 -1
  26. package/dist/components/hazo_chat/hazo_chat_sidebar.js +1 -1
  27. package/dist/components/hazo_chat/hazo_chat_sidebar.js.map +1 -1
  28. package/dist/components/ui/avatar.js +1 -1
  29. package/dist/components/ui/avatar.js.map +1 -1
  30. package/dist/components/ui/badge.js +1 -1
  31. package/dist/components/ui/badge.js.map +1 -1
  32. package/dist/components/ui/button.js +1 -1
  33. package/dist/components/ui/button.js.map +1 -1
  34. package/dist/components/ui/chat_bubble.js +1 -1
  35. package/dist/components/ui/chat_bubble.js.map +1 -1
  36. package/dist/components/ui/hover-card.js +1 -1
  37. package/dist/components/ui/hover-card.js.map +1 -1
  38. package/dist/components/ui/input.js +1 -1
  39. package/dist/components/ui/input.js.map +1 -1
  40. package/dist/components/ui/loading_skeleton.js +1 -1
  41. package/dist/components/ui/loading_skeleton.js.map +1 -1
  42. package/dist/components/ui/scroll-area.js +1 -1
  43. package/dist/components/ui/scroll-area.js.map +1 -1
  44. package/dist/components/ui/separator.js +1 -1
  45. package/dist/components/ui/separator.js.map +1 -1
  46. package/dist/components/ui/skeleton.js +1 -1
  47. package/dist/components/ui/skeleton.js.map +1 -1
  48. package/dist/components/ui/textarea.js +1 -1
  49. package/dist/components/ui/textarea.js.map +1 -1
  50. package/dist/components/ui/tooltip.js +1 -1
  51. package/dist/components/ui/tooltip.js.map +1 -1
  52. package/dist/hooks/use_chat_messages.d.ts.map +1 -1
  53. package/dist/hooks/use_chat_messages.js +8 -7
  54. package/dist/hooks/use_chat_messages.js.map +1 -1
  55. package/dist/hooks/use_file_upload.d.ts.map +1 -1
  56. package/dist/hooks/use_file_upload.js +2 -1
  57. package/dist/hooks/use_file_upload.js.map +1 -1
  58. package/dist/lib/config.d.ts +24 -40
  59. package/dist/lib/config.d.ts.map +1 -1
  60. package/dist/lib/config.js +50 -89
  61. package/dist/lib/config.js.map +1 -1
  62. package/dist/lib/index.d.ts +1 -1
  63. package/dist/lib/index.d.ts.map +1 -1
  64. package/dist/lib/index.js +1 -1
  65. package/dist/lib/index.js.map +1 -1
  66. package/dist/lib/utils.d.ts +7 -8
  67. package/dist/lib/utils.d.ts.map +1 -1
  68. package/dist/lib/utils.js +24 -10
  69. package/dist/lib/utils.js.map +1 -1
  70. package/package.json +20 -11
@@ -23,6 +23,8 @@ import { NextResponse } from 'next/server';
23
23
  import { cookies } from 'next/headers';
24
24
  import { createCrudService } from 'hazo_connect/server';
25
25
  import { is_valid_uuid } from './validation.js';
26
+ import { serialize_error } from '../lib/utils.js';
27
+ import { generateRequestId, withContext } from 'hazo_core';
26
28
  // ============================================================================
27
29
  // Constants for validation
28
30
  // ============================================================================
@@ -39,14 +41,6 @@ const MAX_LIMIT = 100;
39
41
  // ============================================================================
40
42
  // Helper Functions
41
43
  // ============================================================================
42
- /** Generate a UUID v4 */
43
- function generateUUID() {
44
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
45
- const r = (Math.random() * 16) | 0;
46
- const v = c === 'x' ? r : (r & 0x3) | 0x8;
47
- return v.toString(16);
48
- });
49
- }
50
44
  /** Create a standardized error response */
51
45
  function createErrorResponse(error, status, error_code) {
52
46
  return NextResponse.json({
@@ -82,7 +76,7 @@ async function verifyGroupMembership(hazoConnect, user_id, chat_group_id, logger
82
76
  return memberships[0] || null;
83
77
  }
84
78
  catch (error) {
85
- logger.error('[hazo_chat] Error verifying group membership:', { error });
79
+ logger.error('[hazo_chat] Error verifying group membership:', { error: serialize_error(error) });
86
80
  return null;
87
81
  }
88
82
  }
@@ -107,145 +101,147 @@ export function createMessagesHandler(options) {
107
101
  * - direction (optional): 'older' or 'newer' relative to cursor (default: 'older')
108
102
  */
109
103
  async function GET(request) {
110
- try {
111
- // Get current user ID
112
- const current_user_id = getUserIdFromRequest
113
- ? await getUserIdFromRequest(request)
114
- : await defaultGetUserIdFromRequest();
115
- if (!current_user_id) {
116
- logger.error('[hazo_chat/messages GET] No user ID - not authenticated');
117
- return createErrorResponse('User not authenticated', 401, 'UNAUTHENTICATED');
118
- }
119
- // Get query params
120
- const { searchParams } = new URL(request.url);
121
- const chat_group_id = searchParams.get('chat_group_id');
122
- const reference_id = searchParams.get('reference_id') || '';
123
- const reference_type = searchParams.get('reference_type') || '';
124
- const cursor = searchParams.get('cursor') || '';
125
- const direction = searchParams.get('direction') || 'older';
126
- const limit_param = searchParams.get('limit');
127
- // Validate required params
128
- if (!chat_group_id) {
129
- logger.error('[hazo_chat/messages GET] Missing chat_group_id');
130
- return createErrorResponse('chat_group_id is required', 400, 'MISSING_CHAT_GROUP');
131
- }
132
- // Validate UUID formats before database queries
133
- if (!is_valid_uuid(chat_group_id)) {
134
- logger.debug('[hazo_chat/messages GET] Invalid chat_group_id format:', { chat_group_id });
135
- return createErrorResponse('chat_group_id must be a valid UUID', 400, 'INVALID_UUID_FORMAT');
136
- }
137
- if (!is_valid_uuid(current_user_id)) {
138
- logger.debug('[hazo_chat/messages GET] Invalid user_id format:', { current_user_id });
139
- return createErrorResponse('Invalid user ID format', 400, 'INVALID_UUID_FORMAT');
140
- }
141
- // Get hazo_connect instance early for membership check
142
- const hazoConnect = getHazoConnect();
143
- // Verify user is a member of the chat group
144
- const membership = await verifyGroupMembership(hazoConnect, current_user_id, chat_group_id, logger);
145
- if (!membership) {
146
- logger.warn('[hazo_chat/messages GET] User is not a member of chat group:', {
104
+ return withContext({}, async () => {
105
+ try {
106
+ // Get current user ID
107
+ const current_user_id = getUserIdFromRequest
108
+ ? await getUserIdFromRequest(request)
109
+ : await defaultGetUserIdFromRequest();
110
+ if (!current_user_id) {
111
+ logger.error('[hazo_chat/messages GET] No user ID - not authenticated');
112
+ return createErrorResponse('User not authenticated', 401, 'UNAUTHENTICATED');
113
+ }
114
+ // Get query params
115
+ const { searchParams } = new URL(request.url);
116
+ const chat_group_id = searchParams.get('chat_group_id');
117
+ const reference_id = searchParams.get('reference_id') || '';
118
+ const reference_type = searchParams.get('reference_type') || '';
119
+ const cursor = searchParams.get('cursor') || '';
120
+ const direction = searchParams.get('direction') || 'older';
121
+ const limit_param = searchParams.get('limit');
122
+ // Validate required params
123
+ if (!chat_group_id) {
124
+ logger.error('[hazo_chat/messages GET] Missing chat_group_id');
125
+ return createErrorResponse('chat_group_id is required', 400, 'MISSING_CHAT_GROUP');
126
+ }
127
+ // Validate UUID formats before database queries
128
+ if (!is_valid_uuid(chat_group_id)) {
129
+ logger.debug('[hazo_chat/messages GET] Invalid chat_group_id format:', { chat_group_id });
130
+ return createErrorResponse('chat_group_id must be a valid UUID', 400, 'INVALID_UUID_FORMAT');
131
+ }
132
+ if (!is_valid_uuid(current_user_id)) {
133
+ logger.debug('[hazo_chat/messages GET] Invalid user_id format:', { current_user_id });
134
+ return createErrorResponse('Invalid user ID format', 400, 'INVALID_UUID_FORMAT');
135
+ }
136
+ // Get hazo_connect instance early for membership check
137
+ const hazoConnect = getHazoConnect();
138
+ // Verify user is a member of the chat group
139
+ const membership = await verifyGroupMembership(hazoConnect, current_user_id, chat_group_id, logger);
140
+ if (!membership) {
141
+ logger.warn('[hazo_chat/messages GET] User is not a member of chat group:', {
142
+ current_user_id,
143
+ chat_group_id,
144
+ });
145
+ return createErrorResponse('Access denied - not a member of this chat group', 403, 'FORBIDDEN');
146
+ }
147
+ // Validate input lengths
148
+ if (reference_id && reference_id.length > MAX_REFERENCE_ID_LENGTH) {
149
+ return createErrorResponse(`reference_id exceeds maximum length of ${MAX_REFERENCE_ID_LENGTH}`, 400, 'INVALID_REFERENCE_ID');
150
+ }
151
+ if (reference_type && reference_type.length > MAX_REFERENCE_TYPE_LENGTH) {
152
+ return createErrorResponse(`reference_type exceeds maximum length of ${MAX_REFERENCE_TYPE_LENGTH}`, 400, 'INVALID_REFERENCE_TYPE');
153
+ }
154
+ // Parse and validate limit
155
+ let limit = DEFAULT_LIMIT;
156
+ if (limit_param) {
157
+ const parsed_limit = parseInt(limit_param, 10);
158
+ if (isNaN(parsed_limit) || parsed_limit < 1) {
159
+ return createErrorResponse('limit must be a positive integer', 400, 'INVALID_LIMIT');
160
+ }
161
+ limit = Math.min(parsed_limit, MAX_LIMIT);
162
+ }
163
+ logger.debug('[hazo_chat/messages GET] Fetching messages:', {
147
164
  current_user_id,
148
165
  chat_group_id,
166
+ reference_id,
167
+ reference_type,
168
+ cursor,
169
+ direction,
170
+ limit,
149
171
  });
150
- return createErrorResponse('Access denied - not a member of this chat group', 403, 'FORBIDDEN');
151
- }
152
- // Validate input lengths
153
- if (reference_id && reference_id.length > MAX_REFERENCE_ID_LENGTH) {
154
- return createErrorResponse(`reference_id exceeds maximum length of ${MAX_REFERENCE_ID_LENGTH}`, 400, 'INVALID_REFERENCE_ID');
155
- }
156
- if (reference_type && reference_type.length > MAX_REFERENCE_TYPE_LENGTH) {
157
- return createErrorResponse(`reference_type exceeds maximum length of ${MAX_REFERENCE_TYPE_LENGTH}`, 400, 'INVALID_REFERENCE_TYPE');
158
- }
159
- // Parse and validate limit
160
- let limit = DEFAULT_LIMIT;
161
- if (limit_param) {
162
- const parsed_limit = parseInt(limit_param, 10);
163
- if (isNaN(parsed_limit) || parsed_limit < 1) {
164
- return createErrorResponse('limit must be a positive integer', 400, 'INVALID_LIMIT');
165
- }
166
- limit = Math.min(parsed_limit, MAX_LIMIT);
167
- }
168
- logger.debug('[hazo_chat/messages GET] Fetching messages:', {
169
- current_user_id,
170
- chat_group_id,
171
- reference_id,
172
- reference_type,
173
- cursor,
174
- direction,
175
- limit,
176
- });
177
- // Create CRUD service (hazoConnect already obtained above)
178
- const chatService = createCrudService(hazoConnect, 'hazo_chat');
179
- let messages = [];
180
- try {
181
- // Build query with proper filtering
182
- const all_messages = await chatService.list((qb) => {
183
- let builder = qb.select('*');
184
- // Filter by chat group - this is the primary filter
185
- builder = builder.where('chat_group_id', 'eq', chat_group_id);
186
- // Exclude soft-deleted messages at the database level
187
- builder = builder.where('deleted_at', 'is', null);
188
- // Filter by reference if provided
189
- if (reference_id) {
190
- builder = builder.where('reference_id', 'eq', reference_id);
191
- }
192
- if (reference_type) {
193
- builder = builder.where('reference_type', 'eq', reference_type);
194
- }
195
- // Apply cursor-based pagination
196
- if (cursor) {
197
- if (direction === 'newer') {
198
- builder = builder.where('created_at', 'gt', cursor);
172
+ // Create CRUD service (hazoConnect already obtained above)
173
+ const chatService = createCrudService(hazoConnect, 'hazo_chat');
174
+ let messages = [];
175
+ try {
176
+ // Build query with proper filtering
177
+ const all_messages = await chatService.list((qb) => {
178
+ let builder = qb.select('*');
179
+ // Filter by chat group - this is the primary filter
180
+ builder = builder.where('chat_group_id', 'eq', chat_group_id);
181
+ // Exclude soft-deleted messages at the database level
182
+ builder = builder.where('deleted_at', 'is', null);
183
+ // Filter by reference if provided
184
+ if (reference_id) {
185
+ builder = builder.where('reference_id', 'eq', reference_id);
186
+ }
187
+ if (reference_type) {
188
+ builder = builder.where('reference_type', 'eq', reference_type);
189
+ }
190
+ // Apply cursor-based pagination
191
+ if (cursor) {
192
+ if (direction === 'newer') {
193
+ builder = builder.where('created_at', 'gt', cursor);
194
+ }
195
+ else {
196
+ builder = builder.where('created_at', 'lt', cursor);
197
+ }
198
+ }
199
+ // Order by created_at
200
+ // For 'older' direction, we want desc to get the most recent first before cursor
201
+ // For 'newer' or initial load, we want asc
202
+ if (direction === 'older' && cursor) {
203
+ builder = builder.order('created_at', 'desc');
199
204
  }
200
205
  else {
201
- builder = builder.where('created_at', 'lt', cursor);
206
+ builder = builder.order('created_at', 'asc');
202
207
  }
203
- }
204
- // Order by created_at
205
- // For 'older' direction, we want desc to get the most recent first before cursor
206
- // For 'newer' or initial load, we want asc
208
+ return builder;
209
+ });
210
+ // Apply limit (deleted messages already filtered at DB level)
211
+ messages = all_messages.slice(0, limit);
212
+ // If we fetched in desc order, reverse to return in asc order
207
213
  if (direction === 'older' && cursor) {
208
- builder = builder.order('created_at', 'desc');
214
+ messages.reverse();
209
215
  }
210
- else {
211
- builder = builder.order('created_at', 'asc');
212
- }
213
- return builder;
214
- });
215
- // Apply limit (deleted messages already filtered at DB level)
216
- messages = all_messages.slice(0, limit);
217
- // If we fetched in desc order, reverse to return in asc order
218
- if (direction === 'older' && cursor) {
219
- messages.reverse();
220
216
  }
217
+ catch (dbError) {
218
+ logger.error('[hazo_chat/messages GET] Database error:', { dbError: serialize_error(dbError) });
219
+ throw dbError;
220
+ }
221
+ logger.debug('[hazo_chat/messages GET] Found messages:', { count: messages.length });
222
+ // Determine if there are more messages
223
+ const has_more = messages.length === limit;
224
+ // Get cursors for next/prev page
225
+ const next_cursor = messages.length > 0 ? messages[messages.length - 1].created_at : null;
226
+ const prev_cursor = messages.length > 0 ? messages[0].created_at : null;
227
+ return NextResponse.json({
228
+ success: true,
229
+ messages,
230
+ current_user_id,
231
+ pagination: {
232
+ limit,
233
+ has_more,
234
+ next_cursor,
235
+ prev_cursor,
236
+ },
237
+ });
221
238
  }
222
- catch (dbError) {
223
- logger.error('[hazo_chat/messages GET] Database error:', { dbError });
224
- throw dbError;
239
+ catch (error) {
240
+ const error_message = error instanceof Error ? error.message : 'Unknown error';
241
+ logger.error('[hazo_chat/messages GET] Error:', { error_message, error: serialize_error(error) });
242
+ return createErrorResponse('Failed to fetch messages', 500, 'INTERNAL_ERROR');
225
243
  }
226
- logger.debug('[hazo_chat/messages GET] Found messages:', { count: messages.length });
227
- // Determine if there are more messages
228
- const has_more = messages.length === limit;
229
- // Get cursors for next/prev page
230
- const next_cursor = messages.length > 0 ? messages[messages.length - 1].created_at : null;
231
- const prev_cursor = messages.length > 0 ? messages[0].created_at : null;
232
- return NextResponse.json({
233
- success: true,
234
- messages,
235
- current_user_id,
236
- pagination: {
237
- limit,
238
- has_more,
239
- next_cursor,
240
- prev_cursor,
241
- },
242
- });
243
- }
244
- catch (error) {
245
- const error_message = error instanceof Error ? error.message : 'Unknown error';
246
- logger.error('[hazo_chat/messages GET] Error:', { error_message, error });
247
- return createErrorResponse('Failed to fetch messages', 500, 'INTERNAL_ERROR');
248
- }
244
+ }); // end withContext
249
245
  }
250
246
  /**
251
247
  * POST handler - Create a new chat message
@@ -257,123 +253,125 @@ export function createMessagesHandler(options) {
257
253
  * - reference_type (optional): Reference type (default: 'chat')
258
254
  */
259
255
  async function POST(request) {
260
- try {
261
- // Get current user ID (sender)
262
- const sender_user_id = getUserIdFromRequest
263
- ? await getUserIdFromRequest(request)
264
- : await defaultGetUserIdFromRequest();
265
- if (!sender_user_id) {
266
- logger.error('[hazo_chat/messages POST] No user ID - not authenticated');
267
- return createErrorResponse('User not authenticated', 401, 'UNAUTHENTICATED');
268
- }
269
- // Parse request body
270
- const body = await request.json();
271
- const { chat_group_id, message_text, reference_id, reference_type } = body;
272
- // Validate required fields
273
- if (!chat_group_id) {
274
- logger.error('[hazo_chat/messages POST] Missing chat_group_id');
275
- return createErrorResponse('chat_group_id is required', 400, 'MISSING_CHAT_GROUP');
276
- }
277
- // Validate UUID formats before database queries
278
- if (!is_valid_uuid(chat_group_id)) {
279
- logger.debug('[hazo_chat/messages POST] Invalid chat_group_id format:', { chat_group_id });
280
- return createErrorResponse('chat_group_id must be a valid UUID', 400, 'INVALID_UUID_FORMAT');
281
- }
282
- if (!is_valid_uuid(sender_user_id)) {
283
- logger.debug('[hazo_chat/messages POST] Invalid user_id format:', { sender_user_id });
284
- return createErrorResponse('Invalid user ID format', 400, 'INVALID_UUID_FORMAT');
285
- }
286
- // Get hazo_connect instance early for membership check
287
- const hazoConnect = getHazoConnect();
288
- // Verify user is a member of the chat group
289
- const membership = await verifyGroupMembership(hazoConnect, sender_user_id, chat_group_id, logger);
290
- if (!membership) {
291
- logger.warn('[hazo_chat/messages POST] User is not a member of chat group:', {
292
- sender_user_id,
293
- chat_group_id,
294
- });
295
- return createErrorResponse('Access denied - not a member of this chat group', 403, 'FORBIDDEN');
296
- }
297
- // Validate message_text
298
- if (!message_text || typeof message_text !== 'string') {
299
- logger.error('[hazo_chat/messages POST] Missing message_text');
300
- return createErrorResponse('message_text is required', 400, 'MISSING_MESSAGE');
301
- }
302
- const trimmed_message = message_text.trim();
303
- if (trimmed_message === '') {
304
- logger.error('[hazo_chat/messages POST] Empty message_text');
305
- return createErrorResponse('message_text cannot be empty or whitespace-only', 400, 'EMPTY_MESSAGE');
306
- }
307
- if (trimmed_message.length > MAX_MESSAGE_LENGTH) {
308
- logger.error('[hazo_chat/messages POST] Message too long:', { length: trimmed_message.length });
309
- return createErrorResponse(`Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`, 400, 'MESSAGE_TOO_LONG');
310
- }
311
- // Validate reference_id length
312
- if (reference_id && reference_id.length > MAX_REFERENCE_ID_LENGTH) {
313
- return createErrorResponse(`reference_id exceeds maximum length of ${MAX_REFERENCE_ID_LENGTH}`, 400, 'INVALID_REFERENCE_ID');
314
- }
315
- // Validate reference_type length
316
- if (reference_type && reference_type.length > MAX_REFERENCE_TYPE_LENGTH) {
317
- return createErrorResponse(`reference_type exceeds maximum length of ${MAX_REFERENCE_TYPE_LENGTH}`, 400, 'INVALID_REFERENCE_TYPE');
318
- }
319
- // Create CRUD service (hazoConnect already obtained above)
320
- const chatService = createCrudService(hazoConnect, 'hazo_chat');
321
- // Generate message ID and timestamps
322
- const message_id = generateUUID();
323
- const now = new Date().toISOString();
324
- // Create message record
325
- const message_record = {
326
- id: message_id,
327
- reference_id: reference_id || '',
328
- reference_type: reference_type || 'chat',
329
- sender_user_id,
330
- chat_group_id,
331
- message_text: trimmed_message,
332
- reference_list: null,
333
- read_at: null,
334
- deleted_at: null,
335
- created_at: now,
336
- changed_at: now,
337
- };
338
- logger.info('[hazo_chat/messages POST] Saving message:', {
339
- id: message_id,
340
- sender_user_id,
341
- chat_group_id,
342
- reference_id: reference_id || '',
343
- reference_type: reference_type || 'chat',
344
- message_length: trimmed_message.length,
345
- });
346
- // Save to database
256
+ return withContext({}, async () => {
347
257
  try {
348
- await chatService.insert(message_record);
349
- }
350
- catch (dbError) {
351
- logger.error('[hazo_chat/messages POST] Database error:', { dbError });
352
- throw dbError;
353
- }
354
- logger.info('[hazo_chat/messages POST] Message saved successfully:', { message_id });
355
- return NextResponse.json({
356
- success: true,
357
- message: {
258
+ // Get current user ID (sender)
259
+ const sender_user_id = getUserIdFromRequest
260
+ ? await getUserIdFromRequest(request)
261
+ : await defaultGetUserIdFromRequest();
262
+ if (!sender_user_id) {
263
+ logger.error('[hazo_chat/messages POST] No user ID - not authenticated');
264
+ return createErrorResponse('User not authenticated', 401, 'UNAUTHENTICATED');
265
+ }
266
+ // Parse request body
267
+ const body = await request.json();
268
+ const { chat_group_id, message_text, reference_id, reference_type } = body;
269
+ // Validate required fields
270
+ if (!chat_group_id) {
271
+ logger.error('[hazo_chat/messages POST] Missing chat_group_id');
272
+ return createErrorResponse('chat_group_id is required', 400, 'MISSING_CHAT_GROUP');
273
+ }
274
+ // Validate UUID formats before database queries
275
+ if (!is_valid_uuid(chat_group_id)) {
276
+ logger.debug('[hazo_chat/messages POST] Invalid chat_group_id format:', { chat_group_id });
277
+ return createErrorResponse('chat_group_id must be a valid UUID', 400, 'INVALID_UUID_FORMAT');
278
+ }
279
+ if (!is_valid_uuid(sender_user_id)) {
280
+ logger.debug('[hazo_chat/messages POST] Invalid user_id format:', { sender_user_id });
281
+ return createErrorResponse('Invalid user ID format', 400, 'INVALID_UUID_FORMAT');
282
+ }
283
+ // Get hazo_connect instance early for membership check
284
+ const hazoConnect = getHazoConnect();
285
+ // Verify user is a member of the chat group
286
+ const membership = await verifyGroupMembership(hazoConnect, sender_user_id, chat_group_id, logger);
287
+ if (!membership) {
288
+ logger.warn('[hazo_chat/messages POST] User is not a member of chat group:', {
289
+ sender_user_id,
290
+ chat_group_id,
291
+ });
292
+ return createErrorResponse('Access denied - not a member of this chat group', 403, 'FORBIDDEN');
293
+ }
294
+ // Validate message_text
295
+ if (!message_text || typeof message_text !== 'string') {
296
+ logger.error('[hazo_chat/messages POST] Missing message_text');
297
+ return createErrorResponse('message_text is required', 400, 'MISSING_MESSAGE');
298
+ }
299
+ const trimmed_message = message_text.trim();
300
+ if (trimmed_message === '') {
301
+ logger.error('[hazo_chat/messages POST] Empty message_text');
302
+ return createErrorResponse('message_text cannot be empty or whitespace-only', 400, 'EMPTY_MESSAGE');
303
+ }
304
+ if (trimmed_message.length > MAX_MESSAGE_LENGTH) {
305
+ logger.error('[hazo_chat/messages POST] Message too long:', { length: trimmed_message.length });
306
+ return createErrorResponse(`Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`, 400, 'MESSAGE_TOO_LONG');
307
+ }
308
+ // Validate reference_id length
309
+ if (reference_id && reference_id.length > MAX_REFERENCE_ID_LENGTH) {
310
+ return createErrorResponse(`reference_id exceeds maximum length of ${MAX_REFERENCE_ID_LENGTH}`, 400, 'INVALID_REFERENCE_ID');
311
+ }
312
+ // Validate reference_type length
313
+ if (reference_type && reference_type.length > MAX_REFERENCE_TYPE_LENGTH) {
314
+ return createErrorResponse(`reference_type exceeds maximum length of ${MAX_REFERENCE_TYPE_LENGTH}`, 400, 'INVALID_REFERENCE_TYPE');
315
+ }
316
+ // Create CRUD service (hazoConnect already obtained above)
317
+ const chatService = createCrudService(hazoConnect, 'hazo_chat');
318
+ // Generate message ID and timestamps
319
+ const message_id = generateRequestId().slice(4); // strips 'req_' prefix for UUID-like storage
320
+ const now = new Date().toISOString();
321
+ // Create message record
322
+ const message_record = {
358
323
  id: message_id,
359
- sender_user_id,
360
- chat_group_id,
361
324
  reference_id: reference_id || '',
362
325
  reference_type: reference_type || 'chat',
326
+ sender_user_id,
327
+ chat_group_id,
363
328
  message_text: trimmed_message,
364
329
  reference_list: null,
365
330
  read_at: null,
366
331
  deleted_at: null,
367
332
  created_at: now,
368
333
  changed_at: now,
369
- },
370
- });
371
- }
372
- catch (error) {
373
- const error_message = error instanceof Error ? error.message : 'Unknown error';
374
- logger.error('[hazo_chat/messages POST] Error:', { error_message, error });
375
- return createErrorResponse('Failed to save message', 500, 'INTERNAL_ERROR');
376
- }
334
+ };
335
+ logger.info('[hazo_chat/messages POST] Saving message:', {
336
+ id: message_id,
337
+ sender_user_id,
338
+ chat_group_id,
339
+ reference_id: reference_id || '',
340
+ reference_type: reference_type || 'chat',
341
+ message_length: trimmed_message.length,
342
+ });
343
+ // Save to database
344
+ try {
345
+ await chatService.insert(message_record);
346
+ }
347
+ catch (dbError) {
348
+ logger.error('[hazo_chat/messages POST] Database error:', { dbError: serialize_error(dbError) });
349
+ throw dbError;
350
+ }
351
+ logger.info('[hazo_chat/messages POST] Message saved successfully:', { message_id });
352
+ return NextResponse.json({
353
+ success: true,
354
+ message: {
355
+ id: message_id,
356
+ sender_user_id,
357
+ chat_group_id,
358
+ reference_id: reference_id || '',
359
+ reference_type: reference_type || 'chat',
360
+ message_text: trimmed_message,
361
+ reference_list: null,
362
+ read_at: null,
363
+ deleted_at: null,
364
+ created_at: now,
365
+ changed_at: now,
366
+ },
367
+ });
368
+ }
369
+ catch (error) {
370
+ const error_message = error instanceof Error ? error.message : 'Unknown error';
371
+ logger.error('[hazo_chat/messages POST] Error:', { error_message, error: serialize_error(error) });
372
+ return createErrorResponse('Failed to save message', 500, 'INTERNAL_ERROR');
373
+ }
374
+ }); // end withContext
377
375
  }
378
376
  return { GET, POST };
379
377
  }
@@ -398,114 +396,116 @@ export function createMarkAsReadHandler(options) {
398
396
  * Note: In Next.js 13+ App Router, params may be a Promise
399
397
  */
400
398
  async function PATCH(request, context) {
401
- try {
402
- // Get current user ID
403
- const current_user_id = getUserIdFromRequest
404
- ? await getUserIdFromRequest(request)
405
- : await defaultGetUserIdFromRequest();
406
- if (!current_user_id) {
407
- logger.error('[hazo_chat/messages/[id]/read PATCH] No user ID - not authenticated');
408
- return NextResponse.json({ success: false, error: 'User not authenticated' }, { status: 401 });
409
- }
410
- // Handle params as Promise (Next.js 15+) or direct object (Next.js 13-14)
411
- const params = context.params instanceof Promise ? await context.params : context.params;
412
- const message_id = params.id;
413
- if (!message_id) {
414
- logger.error('[hazo_chat/messages/[id]/read PATCH] Missing message ID');
415
- return NextResponse.json({ success: false, error: 'Message ID is required' }, { status: 400 });
416
- }
417
- // Validate UUID formats before database queries
418
- if (!is_valid_uuid(message_id)) {
419
- logger.debug('[hazo_chat/messages/[id]/read PATCH] Invalid message_id format:', { message_id });
420
- return NextResponse.json({ success: false, error: 'message_id must be a valid UUID', error_code: 'INVALID_UUID_FORMAT' }, { status: 400 });
421
- }
422
- if (!is_valid_uuid(current_user_id)) {
423
- logger.debug('[hazo_chat/messages/[id]/read PATCH] Invalid user_id format:', { current_user_id });
424
- return NextResponse.json({ success: false, error: 'Invalid user ID format', error_code: 'INVALID_UUID_FORMAT' }, { status: 400 });
425
- }
426
- logger.info('[hazo_chat/messages/[id]/read PATCH] Marking message as read:', {
427
- message_id,
428
- current_user_id,
429
- });
430
- // Get hazo_connect instance and create CRUD service
431
- const hazoConnect = getHazoConnect();
432
- const chatService = createCrudService(hazoConnect, 'hazo_chat');
433
- // First, fetch the message to verify ownership
434
- let message = null;
399
+ return withContext({}, async () => {
435
400
  try {
436
- const messages = await chatService.list((qb) => qb.select('*').where('id', 'eq', message_id));
437
- message = messages[0] || null;
438
- }
439
- catch (dbError) {
440
- logger.error('[hazo_chat/messages/[id]/read PATCH] Database error fetching message:', { dbError });
441
- throw dbError;
442
- }
443
- if (!message) {
444
- logger.error('[hazo_chat/messages/[id]/read PATCH] Message not found:', { message_id });
445
- return NextResponse.json({ success: false, error: 'Message not found' }, { status: 404 });
446
- }
447
- // Verify that the current user is a member of the chat group
448
- const membership = await verifyGroupMembership(hazoConnect, current_user_id, message.chat_group_id, logger);
449
- if (!membership) {
450
- logger.error('[hazo_chat/messages/[id]/read PATCH] User is not a member of chat group:', {
451
- message_id,
452
- current_user_id,
453
- chat_group_id: message.chat_group_id,
454
- });
455
- return NextResponse.json({ success: false, error: 'Unauthorized - not a member of this chat group' }, { status: 403 });
456
- }
457
- // Cannot mark your own messages as read
458
- if (message.sender_user_id === current_user_id) {
459
- logger.error('[hazo_chat/messages/[id]/read PATCH] User cannot mark own message as read:', {
401
+ // Get current user ID
402
+ const current_user_id = getUserIdFromRequest
403
+ ? await getUserIdFromRequest(request)
404
+ : await defaultGetUserIdFromRequest();
405
+ if (!current_user_id) {
406
+ logger.error('[hazo_chat/messages/[id]/read PATCH] No user ID - not authenticated');
407
+ return NextResponse.json({ success: false, error: 'User not authenticated' }, { status: 401 });
408
+ }
409
+ // Handle params as Promise (Next.js 15+) or direct object (Next.js 13-14)
410
+ const params = context.params instanceof Promise ? await context.params : context.params;
411
+ const message_id = params.id;
412
+ if (!message_id) {
413
+ logger.error('[hazo_chat/messages/[id]/read PATCH] Missing message ID');
414
+ return NextResponse.json({ success: false, error: 'Message ID is required' }, { status: 400 });
415
+ }
416
+ // Validate UUID formats before database queries
417
+ if (!is_valid_uuid(message_id)) {
418
+ logger.debug('[hazo_chat/messages/[id]/read PATCH] Invalid message_id format:', { message_id });
419
+ return NextResponse.json({ success: false, error: 'message_id must be a valid UUID', error_code: 'INVALID_UUID_FORMAT' }, { status: 400 });
420
+ }
421
+ if (!is_valid_uuid(current_user_id)) {
422
+ logger.debug('[hazo_chat/messages/[id]/read PATCH] Invalid user_id format:', { current_user_id });
423
+ return NextResponse.json({ success: false, error: 'Invalid user ID format', error_code: 'INVALID_UUID_FORMAT' }, { status: 400 });
424
+ }
425
+ logger.info('[hazo_chat/messages/[id]/read PATCH] Marking message as read:', {
460
426
  message_id,
461
427
  current_user_id,
462
428
  });
463
- return NextResponse.json({ success: false, error: 'Cannot mark your own messages as read' }, { status: 400 });
464
- }
465
- // Don't update if already read
466
- if (message.read_at) {
467
- logger.info('[hazo_chat/messages/[id]/read PATCH] Message already read:', { message_id });
429
+ // Get hazo_connect instance and create CRUD service
430
+ const hazoConnect = getHazoConnect();
431
+ const chatService = createCrudService(hazoConnect, 'hazo_chat');
432
+ // First, fetch the message to verify ownership
433
+ let message = null;
434
+ try {
435
+ const messages = await chatService.list((qb) => qb.select('*').where('id', 'eq', message_id));
436
+ message = messages[0] || null;
437
+ }
438
+ catch (dbError) {
439
+ logger.error('[hazo_chat/messages/[id]/read PATCH] Database error fetching message:', { dbError: serialize_error(dbError) });
440
+ throw dbError;
441
+ }
442
+ if (!message) {
443
+ logger.error('[hazo_chat/messages/[id]/read PATCH] Message not found:', { message_id });
444
+ return NextResponse.json({ success: false, error: 'Message not found' }, { status: 404 });
445
+ }
446
+ // Verify that the current user is a member of the chat group
447
+ const membership = await verifyGroupMembership(hazoConnect, current_user_id, message.chat_group_id, logger);
448
+ if (!membership) {
449
+ logger.error('[hazo_chat/messages/[id]/read PATCH] User is not a member of chat group:', {
450
+ message_id,
451
+ current_user_id,
452
+ chat_group_id: message.chat_group_id,
453
+ });
454
+ return NextResponse.json({ success: false, error: 'Unauthorized - not a member of this chat group' }, { status: 403 });
455
+ }
456
+ // Cannot mark your own messages as read
457
+ if (message.sender_user_id === current_user_id) {
458
+ logger.error('[hazo_chat/messages/[id]/read PATCH] User cannot mark own message as read:', {
459
+ message_id,
460
+ current_user_id,
461
+ });
462
+ return NextResponse.json({ success: false, error: 'Cannot mark your own messages as read' }, { status: 400 });
463
+ }
464
+ // Don't update if already read
465
+ if (message.read_at) {
466
+ logger.debug('[hazo_chat/messages/[id]/read PATCH] Message already read:', { message_id });
467
+ return NextResponse.json({
468
+ success: true,
469
+ message: {
470
+ ...message,
471
+ read_at: message.read_at,
472
+ },
473
+ });
474
+ }
475
+ // Update the read_at timestamp using the adapter-agnostic CRUD service
476
+ const now = new Date().toISOString();
477
+ try {
478
+ const updated_rows = await chatService.updateById(message_id, {
479
+ read_at: now,
480
+ changed_at: now,
481
+ });
482
+ if (updated_rows.length === 0) {
483
+ logger.warn('[hazo_chat/messages/[id]/read PATCH] No rows updated - message may not exist:', { message_id });
484
+ }
485
+ else {
486
+ logger.debug('[hazo_chat/messages/[id]/read PATCH] Successfully updated', { rows: updated_rows.length });
487
+ }
488
+ }
489
+ catch (dbError) {
490
+ logger.error('[hazo_chat/messages/[id]/read PATCH] Database error updating message:', { dbError: serialize_error(dbError) });
491
+ throw dbError;
492
+ }
493
+ logger.debug('[hazo_chat/messages/[id]/read PATCH] Message marked as read successfully:', { message_id });
468
494
  return NextResponse.json({
469
495
  success: true,
470
496
  message: {
471
497
  ...message,
472
- read_at: message.read_at,
498
+ read_at: now,
499
+ changed_at: now,
473
500
  },
474
501
  });
475
502
  }
476
- // Update the read_at timestamp using the adapter-agnostic CRUD service
477
- const now = new Date().toISOString();
478
- try {
479
- const updated_rows = await chatService.updateById(message_id, {
480
- read_at: now,
481
- changed_at: now,
482
- });
483
- if (updated_rows.length === 0) {
484
- logger.warn('[hazo_chat/messages/[id]/read PATCH] No rows updated - message may not exist:', { message_id });
485
- }
486
- else {
487
- logger.info('[hazo_chat/messages/[id]/read PATCH] Successfully updated', { rows: updated_rows.length });
488
- }
503
+ catch (error) {
504
+ const error_message = error instanceof Error ? error.message : 'Unknown error';
505
+ logger.error('[hazo_chat/messages/[id]/read PATCH] Error:', { error_message, error: serialize_error(error) });
506
+ return NextResponse.json({ success: false, error: 'Failed to mark message as read' }, { status: 500 });
489
507
  }
490
- catch (dbError) {
491
- logger.error('[hazo_chat/messages/[id]/read PATCH] Database error updating message:', { dbError });
492
- throw dbError;
493
- }
494
- logger.info('[hazo_chat/messages/[id]/read PATCH] Message marked as read successfully:', { message_id });
495
- return NextResponse.json({
496
- success: true,
497
- message: {
498
- ...message,
499
- read_at: now,
500
- changed_at: now,
501
- },
502
- });
503
- }
504
- catch (error) {
505
- const error_message = error instanceof Error ? error.message : 'Unknown error';
506
- logger.error('[hazo_chat/messages/[id]/read PATCH] Error:', { error_message, error });
507
- return NextResponse.json({ success: false, error: 'Failed to mark message as read' }, { status: 500 });
508
- }
508
+ }); // end withContext
509
509
  }
510
510
  return { PATCH };
511
511
  }
@@ -546,117 +546,119 @@ export function createDeleteHandler(options) {
546
546
  * Only the sender can delete their own messages.
547
547
  */
548
548
  async function DELETE(request, context) {
549
- try {
550
- // Get current user ID
551
- const current_user_id = getUserIdFromRequest
552
- ? await getUserIdFromRequest(request)
553
- : await defaultGetUserIdFromRequest();
554
- if (!current_user_id) {
555
- logger.error('[hazo_chat/messages/[id] DELETE] No user ID - not authenticated');
556
- return createErrorResponse('User not authenticated', 401, 'UNAUTHENTICATED');
557
- }
558
- // Handle params as Promise (Next.js 15+) or direct object (Next.js 13-14)
559
- const params = context.params instanceof Promise ? await context.params : context.params;
560
- const message_id = params.id;
561
- if (!message_id) {
562
- logger.error('[hazo_chat/messages/[id] DELETE] Missing message ID');
563
- return createErrorResponse('Message ID is required', 400, 'MISSING_MESSAGE_ID');
564
- }
565
- // Validate UUID formats before database queries
566
- if (!is_valid_uuid(message_id)) {
567
- logger.debug('[hazo_chat/messages/[id] DELETE] Invalid message_id format:', { message_id });
568
- return createErrorResponse('message_id must be a valid UUID', 400, 'INVALID_UUID_FORMAT');
569
- }
570
- if (!is_valid_uuid(current_user_id)) {
571
- logger.debug('[hazo_chat/messages/[id] DELETE] Invalid user_id format:', { current_user_id });
572
- return createErrorResponse('Invalid user ID format', 400, 'INVALID_UUID_FORMAT');
573
- }
574
- logger.info('[hazo_chat/messages/[id] DELETE] Deleting message:', {
575
- message_id,
576
- current_user_id,
577
- });
578
- // Get hazo_connect instance and create CRUD service
579
- const hazoConnect = getHazoConnect();
580
- const chatService = createCrudService(hazoConnect, 'hazo_chat');
581
- // Fetch the message to verify ownership
582
- let message = null;
549
+ return withContext({}, async () => {
583
550
  try {
584
- const messages = await chatService.list((qb) => qb.select('*').where('id', 'eq', message_id));
585
- message = messages[0] || null;
586
- }
587
- catch (dbError) {
588
- logger.error('[hazo_chat/messages/[id] DELETE] Database error fetching message:', { dbError });
589
- throw dbError;
590
- }
591
- if (!message) {
592
- logger.error('[hazo_chat/messages/[id] DELETE] Message not found:', { message_id });
593
- return createErrorResponse('Message not found', 404, 'MESSAGE_NOT_FOUND');
594
- }
595
- // Verify that the current user is a member of the chat group
596
- const membership = await verifyGroupMembership(hazoConnect, current_user_id, message.chat_group_id, logger);
597
- if (!membership) {
598
- logger.error('[hazo_chat/messages/[id] DELETE] User is not a member of chat group:', {
599
- message_id,
600
- current_user_id,
601
- chat_group_id: message.chat_group_id,
602
- });
603
- return createErrorResponse('Unauthorized - not a member of this chat group', 403, 'FORBIDDEN');
604
- }
605
- // Verify that the current user is the sender (only senders can delete their messages)
606
- if (message.sender_user_id !== current_user_id) {
607
- logger.error('[hazo_chat/messages/[id] DELETE] User is not the sender:', {
551
+ // Get current user ID
552
+ const current_user_id = getUserIdFromRequest
553
+ ? await getUserIdFromRequest(request)
554
+ : await defaultGetUserIdFromRequest();
555
+ if (!current_user_id) {
556
+ logger.error('[hazo_chat/messages/[id] DELETE] No user ID - not authenticated');
557
+ return createErrorResponse('User not authenticated', 401, 'UNAUTHENTICATED');
558
+ }
559
+ // Handle params as Promise (Next.js 15+) or direct object (Next.js 13-14)
560
+ const params = context.params instanceof Promise ? await context.params : context.params;
561
+ const message_id = params.id;
562
+ if (!message_id) {
563
+ logger.error('[hazo_chat/messages/[id] DELETE] Missing message ID');
564
+ return createErrorResponse('Message ID is required', 400, 'MISSING_MESSAGE_ID');
565
+ }
566
+ // Validate UUID formats before database queries
567
+ if (!is_valid_uuid(message_id)) {
568
+ logger.debug('[hazo_chat/messages/[id] DELETE] Invalid message_id format:', { message_id });
569
+ return createErrorResponse('message_id must be a valid UUID', 400, 'INVALID_UUID_FORMAT');
570
+ }
571
+ if (!is_valid_uuid(current_user_id)) {
572
+ logger.debug('[hazo_chat/messages/[id] DELETE] Invalid user_id format:', { current_user_id });
573
+ return createErrorResponse('Invalid user ID format', 400, 'INVALID_UUID_FORMAT');
574
+ }
575
+ logger.info('[hazo_chat/messages/[id] DELETE] Deleting message:', {
608
576
  message_id,
609
577
  current_user_id,
610
- sender_user_id: message.sender_user_id,
611
578
  });
612
- return createErrorResponse('Unauthorized - only the sender can delete their messages', 403, 'UNAUTHORIZED');
613
- }
614
- // Check if already deleted
615
- if (message.deleted_at) {
616
- logger.info('[hazo_chat/messages/[id] DELETE] Message already deleted:', { message_id });
579
+ // Get hazo_connect instance and create CRUD service
580
+ const hazoConnect = getHazoConnect();
581
+ const chatService = createCrudService(hazoConnect, 'hazo_chat');
582
+ // Fetch the message to verify ownership
583
+ let message = null;
584
+ try {
585
+ const messages = await chatService.list((qb) => qb.select('*').where('id', 'eq', message_id));
586
+ message = messages[0] || null;
587
+ }
588
+ catch (dbError) {
589
+ logger.error('[hazo_chat/messages/[id] DELETE] Database error fetching message:', { dbError: serialize_error(dbError) });
590
+ throw dbError;
591
+ }
592
+ if (!message) {
593
+ logger.error('[hazo_chat/messages/[id] DELETE] Message not found:', { message_id });
594
+ return createErrorResponse('Message not found', 404, 'MESSAGE_NOT_FOUND');
595
+ }
596
+ // Verify that the current user is a member of the chat group
597
+ const membership = await verifyGroupMembership(hazoConnect, current_user_id, message.chat_group_id, logger);
598
+ if (!membership) {
599
+ logger.error('[hazo_chat/messages/[id] DELETE] User is not a member of chat group:', {
600
+ message_id,
601
+ current_user_id,
602
+ chat_group_id: message.chat_group_id,
603
+ });
604
+ return createErrorResponse('Unauthorized - not a member of this chat group', 403, 'FORBIDDEN');
605
+ }
606
+ // Verify that the current user is the sender (only senders can delete their messages)
607
+ if (message.sender_user_id !== current_user_id) {
608
+ logger.error('[hazo_chat/messages/[id] DELETE] User is not the sender:', {
609
+ message_id,
610
+ current_user_id,
611
+ sender_user_id: message.sender_user_id,
612
+ });
613
+ return createErrorResponse('Unauthorized - only the sender can delete their messages', 403, 'UNAUTHORIZED');
614
+ }
615
+ // Check if already deleted
616
+ if (message.deleted_at) {
617
+ logger.debug('[hazo_chat/messages/[id] DELETE] Message already deleted:', { message_id });
618
+ return NextResponse.json({
619
+ success: true,
620
+ message: {
621
+ ...message,
622
+ message_text: null,
623
+ },
624
+ });
625
+ }
626
+ // Soft delete: set deleted_at timestamp and clear message_text using adapter-agnostic CRUD service
627
+ const now = new Date().toISOString();
628
+ try {
629
+ const updated_rows = await chatService.updateById(message_id, {
630
+ deleted_at: now,
631
+ message_text: null,
632
+ changed_at: now,
633
+ });
634
+ if (updated_rows.length === 0) {
635
+ logger.warn('[hazo_chat/messages/[id] DELETE] No rows updated - message may not exist:', { message_id });
636
+ }
637
+ else {
638
+ logger.debug('[hazo_chat/messages/[id] DELETE] Successfully deleted', { rows: updated_rows.length });
639
+ }
640
+ }
641
+ catch (dbError) {
642
+ logger.error('[hazo_chat/messages/[id] DELETE] Database error deleting message:', { dbError: serialize_error(dbError) });
643
+ throw dbError;
644
+ }
645
+ logger.debug('[hazo_chat/messages/[id] DELETE] Message deleted successfully:', { message_id });
617
646
  return NextResponse.json({
618
647
  success: true,
619
648
  message: {
620
649
  ...message,
650
+ deleted_at: now,
621
651
  message_text: null,
652
+ changed_at: now,
622
653
  },
623
654
  });
624
655
  }
625
- // Soft delete: set deleted_at timestamp and clear message_text using adapter-agnostic CRUD service
626
- const now = new Date().toISOString();
627
- try {
628
- const updated_rows = await chatService.updateById(message_id, {
629
- deleted_at: now,
630
- message_text: null,
631
- changed_at: now,
632
- });
633
- if (updated_rows.length === 0) {
634
- logger.warn('[hazo_chat/messages/[id] DELETE] No rows updated - message may not exist:', { message_id });
635
- }
636
- else {
637
- logger.info('[hazo_chat/messages/[id] DELETE] Successfully deleted', { rows: updated_rows.length });
638
- }
656
+ catch (error) {
657
+ const error_message = error instanceof Error ? error.message : 'Unknown error';
658
+ logger.error('[hazo_chat/messages/[id] DELETE] Error:', { error_message, error: serialize_error(error) });
659
+ return createErrorResponse('Failed to delete message', 500, 'INTERNAL_ERROR');
639
660
  }
640
- catch (dbError) {
641
- logger.error('[hazo_chat/messages/[id] DELETE] Database error deleting message:', { dbError });
642
- throw dbError;
643
- }
644
- logger.info('[hazo_chat/messages/[id] DELETE] Message deleted successfully:', { message_id });
645
- return NextResponse.json({
646
- success: true,
647
- message: {
648
- ...message,
649
- deleted_at: now,
650
- message_text: null,
651
- changed_at: now,
652
- },
653
- });
654
- }
655
- catch (error) {
656
- const error_message = error instanceof Error ? error.message : 'Unknown error';
657
- logger.error('[hazo_chat/messages/[id] DELETE] Error:', { error_message, error });
658
- return createErrorResponse('Failed to delete message', 500, 'INTERNAL_ERROR');
659
- }
661
+ }); // end withContext
660
662
  }
661
663
  return { DELETE };
662
664
  }