hazo_chat 2.1.1 → 3.2.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.
@@ -1,7 +1,9 @@
1
- # hazo_chat Setup Checklist (v2.0)
1
+ # hazo_chat Setup Checklist (v3.0)
2
2
 
3
3
  A comprehensive, step-by-step guide for setting up hazo_chat in a Next.js project. This checklist is designed for both AI assistants and human developers.
4
4
 
5
+ **Version 3.0 Changes:** This version introduces group-based chat architecture. See [Migration from v2.x](#migration-from-v2x-to-v30) section for upgrade instructions.
6
+
5
7
  ---
6
8
 
7
9
  ## Table of Contents
@@ -16,6 +18,8 @@ A comprehensive, step-by-step guide for setting up hazo_chat in a Next.js projec
16
18
  8. [UI Design Standards Compliance](#8-ui-design-standards-compliance)
17
19
  9. [Verification Checklist](#9-verification-checklist)
18
20
  10. [Troubleshooting](#10-troubleshooting)
21
+ 11. [Migration from v2.x to v3.0](#migration-from-v2x-to-v30)
22
+ 12. [Migration from v3.0 to v3.1](#migration-from-v30-to-v31-generic-schema)
19
23
 
20
24
  ---
21
25
 
@@ -71,30 +75,130 @@ CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
71
75
  CREATE EXTENSION IF NOT EXISTS "pgcrypto";
72
76
  ```
73
77
 
74
- #### Step 3.1.2: Create Enum Types (Optional but Recommended)
78
+ #### Step 3.1.2: Create Enum Types (Recommended)
75
79
 
76
- Create enum types for reference types to ensure data integrity:
80
+ Create enum types for data integrity. All enums are prefixed with `hazo_enum_`:
77
81
 
78
82
  ```sql
79
- -- Create enum for reference types
80
- CREATE TYPE hazo_enum_chat_type AS ENUM ('chat', 'field', 'project', 'support', 'general');
83
+ -- ============================================================================
84
+ -- ENUM TYPE DEFINITIONS (PostgreSQL)
85
+ -- ============================================================================
86
+
87
+ -- 1. Reference type enum - for categorizing chat contexts
88
+ CREATE TYPE hazo_enum_chat_type AS ENUM (
89
+ 'chat', -- General chat
90
+ 'field', -- Form field reference
91
+ 'project', -- Project-related
92
+ 'support', -- Support ticket
93
+ 'general' -- General purpose
94
+ );
95
+
96
+ -- 2. Group type enum (v3.1) - for identifying conversation patterns
97
+ CREATE TYPE hazo_enum_group_type AS ENUM (
98
+ 'support', -- Client-to-staff support conversation
99
+ 'peer', -- Peer-to-peer direct message (1:1)
100
+ 'group' -- Multi-user group conversation
101
+ );
81
102
 
82
- -- Note: If you need to add more enum values later, use:
83
- -- ALTER TYPE hazo_chat_reference_type ADD VALUE 'new_type';
103
+ -- 3. Group user role enum (v3.1) - for membership roles
104
+ CREATE TYPE hazo_enum_group_role AS ENUM (
105
+ 'client', -- Customer/end-user in support scenarios
106
+ 'staff', -- Support personnel in support scenarios
107
+ 'owner', -- Creator of peer/group chats
108
+ 'admin', -- Delegated administrator in group chats
109
+ 'member' -- Standard participant in peer/group chats
110
+ );
111
+
112
+ -- Note: To add new enum values later, use:
113
+ -- ALTER TYPE hazo_enum_chat_type ADD VALUE 'new_value';
114
+ -- ALTER TYPE hazo_enum_group_type ADD VALUE 'new_value';
115
+ -- ALTER TYPE hazo_enum_group_role ADD VALUE 'new_value';
84
116
  ```
85
117
 
86
- #### Step 3.1.3: Create hazo_chat Table
118
+ #### Step 3.1.3: Create hazo_chat_group Table (UPDATED in v3.1)
119
+
120
+ Create the chat group container table:
121
+
122
+ ```sql
123
+
124
+ -- hazo_chat_group table for group-based chat (PostgreSQL)
125
+ -- UPDATED in v3.1: client_user_id is now nullable, added group_type
126
+ CREATE TABLE IF NOT EXISTS hazo_chat_group (
127
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
128
+ client_user_id UUID REFERENCES hazo_users(id), -- Nullable for peer/group chats
129
+ group_type hazo_enum_group_type DEFAULT 'support', -- Type of conversation
130
+ name VARCHAR(255),
131
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
132
+ changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
133
+ );
134
+
135
+ -- Performance indexes
136
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group_client ON hazo_chat_group(client_user_id);
137
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group_type ON hazo_chat_group(group_type);
138
+ ```
139
+
140
+ **Group Types:**
141
+ - `'support'`: Client-to-staff support conversation (has designated `client_user_id`)
142
+ - `'peer'`: Peer-to-peer direct message (1:1, no `client_user_id`)
143
+ - `'group'`: Multi-user group conversation (no `client_user_id`)
144
+
145
+ #### Step 3.1.4: Create hazo_chat_group_users Table (UPDATED in v3.1)
146
+
147
+ Create the group membership table:
148
+
149
+ ```sql
150
+ -- hazo_chat_group_users table for group membership (PostgreSQL)
151
+ -- UPDATED in v3.1: uses hazo_enum_group_role enum type
152
+ CREATE TABLE IF NOT EXISTS hazo_chat_group_users (
153
+ chat_group_id UUID NOT NULL REFERENCES hazo_chat_group(id) ON DELETE CASCADE,
154
+ user_id UUID NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,
155
+ role hazo_enum_group_role NOT NULL DEFAULT 'member',
156
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
157
+ changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
158
+ PRIMARY KEY (chat_group_id, user_id)
159
+ );
160
+
161
+ -- Performance indexes
162
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group_users_user ON hazo_chat_group_users(user_id);
163
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group_users_group ON hazo_chat_group_users(chat_group_id);
164
+ ```
165
+
166
+ **Alternative: Without Enum Type (More Flexible)**
167
+
168
+ If you prefer flexibility over strict enum validation:
169
+
170
+ ```sql
171
+ -- hazo_chat_group_users table with VARCHAR role (more flexible)
172
+ CREATE TABLE IF NOT EXISTS hazo_chat_group_users (
173
+ chat_group_id UUID NOT NULL REFERENCES hazo_chat_group(id) ON DELETE CASCADE,
174
+ user_id UUID NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,
175
+ role VARCHAR(20) NOT NULL CHECK (role IN ('client', 'staff', 'owner', 'admin', 'member')),
176
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
177
+ changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
178
+ PRIMARY KEY (chat_group_id, user_id)
179
+ );
180
+ ```
181
+
182
+ **Role Types:**
183
+ - `'client'`: Customer/end-user in support scenarios
184
+ - `'staff'`: Support personnel in support scenarios
185
+ - `'owner'`: Creator of peer/group chats
186
+ - `'admin'`: Delegated administrator in group chats
187
+ - `'member'`: Standard participant in peer/group chats
188
+
189
+ #### Step 3.1.5: Create hazo_chat Table (MODIFIED in v3.0)
87
190
 
88
191
  Create the chat messages table with UUID types and proper defaults:
89
192
 
90
193
  ```sql
91
194
  -- hazo_chat table for storing chat messages (PostgreSQL)
195
+ -- MODIFIED in v3.0: receiver_user_id replaced with chat_group_id
92
196
  CREATE TABLE IF NOT EXISTS hazo_chat (
93
197
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
94
198
  reference_id UUID NOT NULL,
95
199
  reference_type hazo_enum_chat_type DEFAULT 'chat' NOT NULL,
96
200
  sender_user_id UUID NOT NULL,
97
- receiver_user_id UUID NOT NULL,
201
+ chat_group_id UUID NOT NULL REFERENCES hazo_chat_group(id), -- CHANGED from receiver_user_id
98
202
  message_text TEXT,
99
203
  reference_list JSONB,
100
204
  read_at TIMESTAMPTZ,
@@ -106,14 +210,11 @@ CREATE TABLE IF NOT EXISTS hazo_chat (
106
210
  -- Performance indexes
107
211
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_id ON hazo_chat(reference_id);
108
212
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_sender ON hazo_chat(sender_user_id);
109
- CREATE INDEX IF NOT EXISTS idx_hazo_chat_receiver ON hazo_chat(receiver_user_id);
213
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group ON hazo_chat(chat_group_id); -- CHANGED from receiver index
110
214
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_created ON hazo_chat(created_at DESC);
111
215
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_type ON hazo_chat(reference_type);
112
216
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_read_at ON hazo_chat(read_at) WHERE read_at IS NOT NULL;
113
217
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_deleted_at ON hazo_chat(deleted_at) WHERE deleted_at IS NOT NULL;
114
-
115
- -- Composite index for common query pattern
116
- CREATE INDEX IF NOT EXISTS idx_hazo_chat_receiver_reference ON hazo_chat(receiver_user_id, reference_id);
117
218
  ```
118
219
 
119
220
  **Alternative: Without Enum Type (More Flexible)**
@@ -122,12 +223,13 @@ If you prefer flexibility over strict enum validation:
122
223
 
123
224
  ```sql
124
225
  -- hazo_chat table with TEXT reference_type (more flexible)
226
+ -- MODIFIED in v3.0: receiver_user_id replaced with chat_group_id
125
227
  CREATE TABLE IF NOT EXISTS hazo_chat (
126
228
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
127
229
  reference_id UUID NOT NULL,
128
230
  reference_type TEXT DEFAULT 'chat' NOT NULL,
129
231
  sender_user_id UUID NOT NULL,
130
- receiver_user_id UUID NOT NULL,
232
+ chat_group_id UUID NOT NULL REFERENCES hazo_chat_group(id), -- CHANGED from receiver_user_id
131
233
  message_text TEXT,
132
234
  reference_list JSONB,
133
235
  read_at TIMESTAMPTZ,
@@ -136,15 +238,14 @@ CREATE TABLE IF NOT EXISTS hazo_chat (
136
238
  changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
137
239
  );
138
240
 
139
- -- Performance indexes (same as above)
241
+ -- Performance indexes
140
242
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_id ON hazo_chat(reference_id);
141
243
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_sender ON hazo_chat(sender_user_id);
142
- CREATE INDEX IF NOT EXISTS idx_hazo_chat_receiver ON hazo_chat(receiver_user_id);
244
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group ON hazo_chat(chat_group_id); -- CHANGED from receiver index
143
245
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_created ON hazo_chat(created_at DESC);
144
246
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_type ON hazo_chat(reference_type);
145
247
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_read_at ON hazo_chat(read_at) WHERE read_at IS NOT NULL;
146
248
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_deleted_at ON hazo_chat(deleted_at) WHERE deleted_at IS NOT NULL;
147
- CREATE INDEX IF NOT EXISTS idx_hazo_chat_receiver_reference ON hazo_chat(receiver_user_id, reference_id);
148
249
  ```
149
250
 
150
251
  #### Step 3.1.4: Ensure Users Table Exists (PostgreSQL)
@@ -168,62 +269,126 @@ CREATE INDEX IF NOT EXISTS idx_hazo_users_email ON hazo_users(email_address);
168
269
  CREATE INDEX IF NOT EXISTS idx_hazo_users_active ON hazo_users(is_active) WHERE is_active = TRUE;
169
270
  ```
170
271
 
171
- #### Step 3.1.5: Add Foreign Key Constraints (Optional)
272
+ #### Step 3.1.6: Add Foreign Key Constraints (Optional)
172
273
 
173
274
  Add foreign key constraints for referential integrity:
174
275
 
175
276
  ```sql
176
277
  -- Add foreign key constraints (optional but recommended)
278
+ -- MODIFIED in v3.0: receiver_user_id constraint replaced with chat_group_id
177
279
  ALTER TABLE hazo_chat
178
- ADD CONSTRAINT fk_hazo_chat_sender
179
- FOREIGN KEY (sender_user_id)
180
- REFERENCES hazo_users(id)
280
+ ADD CONSTRAINT fk_hazo_chat_sender
281
+ FOREIGN KEY (sender_user_id)
282
+ REFERENCES hazo_users(id)
181
283
  ON DELETE RESTRICT,
182
- ADD CONSTRAINT fk_hazo_chat_receiver
183
- FOREIGN KEY (receiver_user_id)
184
- REFERENCES hazo_users(id)
185
- ON DELETE RESTRICT;
284
+ ADD CONSTRAINT fk_hazo_chat_group
285
+ FOREIGN KEY (chat_group_id)
286
+ REFERENCES hazo_chat_group(id)
287
+ ON DELETE CASCADE;
186
288
  ```
187
289
 
188
290
  ### Step 3.2: SQLite Setup (Alternative)
189
291
 
190
- For SQLite databases (development/testing), use this simplified schema:
292
+ For SQLite databases (development/testing), use this simplified schema.
293
+
294
+ #### Step 3.2.1: Create hazo_users Table (SQLite)
295
+
296
+ ```sql
297
+ -- Users table for SQLite
298
+ CREATE TABLE IF NOT EXISTS hazo_users (
299
+ id TEXT PRIMARY KEY,
300
+ email_address TEXT UNIQUE NOT NULL,
301
+ name TEXT,
302
+ profile_picture_url TEXT,
303
+ is_active INTEGER DEFAULT 1,
304
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
305
+ changed_at TEXT NOT NULL DEFAULT (datetime('now'))
306
+ );
307
+
308
+ -- Indexes for users table
309
+ CREATE INDEX IF NOT EXISTS idx_hazo_users_email ON hazo_users(email_address);
310
+ ```
311
+
312
+ #### Step 3.2.2: Create hazo_chat_group Table (SQLite - UPDATED in v3.1)
313
+
314
+ ```sql
315
+ -- hazo_chat_group table for group-based chat (SQLite)
316
+ -- UPDATED in v3.1: client_user_id is now nullable, added group_type
317
+ CREATE TABLE IF NOT EXISTS hazo_chat_group (
318
+ id TEXT PRIMARY KEY,
319
+ client_user_id TEXT, -- Nullable for peer/group chats
320
+ group_type TEXT DEFAULT 'support' CHECK (group_type IN ('support', 'peer', 'group')),
321
+ name TEXT,
322
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
323
+ changed_at TEXT NOT NULL DEFAULT (datetime('now')),
324
+ FOREIGN KEY (client_user_id) REFERENCES hazo_users(id)
325
+ );
326
+
327
+ -- Performance indexes
328
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group_client ON hazo_chat_group(client_user_id);
329
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group_type ON hazo_chat_group(group_type);
330
+ ```
331
+
332
+ **Group Types:**
333
+ - `'support'`: Client-to-staff support conversation
334
+ - `'peer'`: Peer-to-peer direct message (1:1)
335
+ - `'group'`: Multi-user group conversation
336
+
337
+ #### Step 3.2.3: Create hazo_chat_group_users Table (SQLite - UPDATED in v3.1)
338
+
339
+ ```sql
340
+ -- hazo_chat_group_users table for group membership (SQLite)
341
+ -- UPDATED in v3.1: expanded role options
342
+ CREATE TABLE IF NOT EXISTS hazo_chat_group_users (
343
+ chat_group_id TEXT NOT NULL,
344
+ user_id TEXT NOT NULL,
345
+ role TEXT NOT NULL CHECK (role IN ('client', 'staff', 'owner', 'admin', 'member')),
346
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
347
+ changed_at TEXT NOT NULL DEFAULT (datetime('now')),
348
+ PRIMARY KEY (chat_group_id, user_id),
349
+ FOREIGN KEY (chat_group_id) REFERENCES hazo_chat_group(id) ON DELETE CASCADE,
350
+ FOREIGN KEY (user_id) REFERENCES hazo_users(id) ON DELETE CASCADE
351
+ );
352
+
353
+ -- Performance indexes
354
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group_users_user ON hazo_chat_group_users(user_id);
355
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group_users_group ON hazo_chat_group_users(chat_group_id);
356
+ ```
357
+
358
+ **Role Types:**
359
+ - `'client'`: Customer/end-user in support scenarios
360
+ - `'staff'`: Support personnel in support scenarios
361
+ - `'owner'`: Creator of peer/group chats
362
+ - `'admin'`: Delegated administrator in group chats
363
+ - `'member'`: Standard participant in peer/group chats
364
+
365
+ #### Step 3.2.4: Create hazo_chat Table (SQLite - MODIFIED in v3.0)
191
366
 
192
367
  ```sql
193
368
  -- hazo_chat table for storing chat messages (SQLite)
369
+ -- MODIFIED in v3.0: receiver_user_id replaced with chat_group_id
194
370
  CREATE TABLE IF NOT EXISTS hazo_chat (
195
371
  id TEXT PRIMARY KEY,
196
372
  reference_id TEXT NOT NULL,
197
373
  reference_type TEXT DEFAULT 'chat',
198
374
  sender_user_id TEXT NOT NULL,
199
- receiver_user_id TEXT NOT NULL,
375
+ chat_group_id TEXT NOT NULL,
200
376
  message_text TEXT,
201
377
  reference_list TEXT,
202
378
  read_at TEXT,
203
379
  deleted_at TEXT,
204
380
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
205
- changed_at TEXT NOT NULL DEFAULT (datetime('now'))
381
+ changed_at TEXT NOT NULL DEFAULT (datetime('now')),
382
+ FOREIGN KEY (sender_user_id) REFERENCES hazo_users(id),
383
+ FOREIGN KEY (chat_group_id) REFERENCES hazo_chat_group(id)
206
384
  );
207
385
 
208
386
  -- Performance indexes
209
387
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_id ON hazo_chat(reference_id);
210
388
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_sender ON hazo_chat(sender_user_id);
211
- CREATE INDEX IF NOT EXISTS idx_hazo_chat_receiver ON hazo_chat(receiver_user_id);
389
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group ON hazo_chat(chat_group_id);
212
390
  CREATE INDEX IF NOT EXISTS idx_hazo_chat_created ON hazo_chat(created_at DESC);
213
- ```
214
-
215
- **SQLite Users Table:**
216
-
217
- ```sql
218
- CREATE TABLE IF NOT EXISTS hazo_users (
219
- id TEXT PRIMARY KEY,
220
- email_address TEXT UNIQUE NOT NULL,
221
- name TEXT,
222
- profile_picture_url TEXT,
223
- is_active INTEGER DEFAULT 1,
224
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
225
- changed_at TEXT NOT NULL DEFAULT (datetime('now'))
226
- );
391
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_type ON hazo_chat(reference_type);
227
392
  ```
228
393
 
229
394
  ### Step 3.3: Key Differences Between PostgreSQL and SQLite
@@ -266,16 +431,17 @@ WHERE tablename = 'hazo_chat';
266
431
  -- Test UUID generation
267
432
  SELECT gen_random_uuid() AS test_uuid;
268
433
 
269
- -- Test table insert with defaults (replace UUIDs with valid user IDs)
434
+ -- Test table insert with defaults (v3.0 - uses chat_group_id)
435
+ -- First ensure you have a chat group and the user is a member
270
436
  INSERT INTO hazo_chat (
271
437
  reference_id,
272
438
  sender_user_id,
273
- receiver_user_id,
439
+ chat_group_id,
274
440
  message_text
275
441
  ) VALUES (
276
442
  gen_random_uuid(),
277
443
  (SELECT id FROM hazo_users LIMIT 1), -- Use existing user ID
278
- (SELECT id FROM hazo_users LIMIT 1), -- Use existing user ID
444
+ (SELECT id FROM hazo_chat_group LIMIT 1), -- Use existing chat group ID
279
445
  'Test message'
280
446
  );
281
447
 
@@ -298,16 +464,22 @@ DELETE FROM hazo_chat WHERE message_text = 'Test message';
298
464
  **PostgreSQL:**
299
465
  - [ ] UUID extension enabled (`uuid-ossp` or `pgcrypto`)
300
466
  - [ ] Enum type created (if using enum approach)
301
- - [ ] `hazo_chat` table exists with UUID columns
467
+ - [ ] `hazo_users` table exists with UUID primary key
468
+ - [ ] `hazo_chat_group` table exists (v3.0)
469
+ - [ ] `hazo_chat_group_users` table exists (v3.0)
470
+ - [ ] `hazo_chat` table exists with `chat_group_id` column (v3.0)
302
471
  - [ ] All indexes created successfully
303
- - [ ] Users table exists with UUID primary key
304
472
  - [ ] Can query: `SELECT * FROM hazo_chat LIMIT 1`
305
473
  - [ ] UUID default generation works: `INSERT INTO hazo_chat (...) VALUES (...)` generates UUID automatically
306
474
  - [ ] Timestamp defaults work: `created_at` and `changed_at` auto-populate
307
475
 
308
476
  **SQLite:**
309
- - [ ] `hazo_chat` table exists in database
310
- - [ ] Users table exists with at least one user
477
+ - [ ] `hazo_users` table exists
478
+ - [ ] `hazo_chat_group` table exists (v3.0)
479
+ - [ ] `hazo_chat_group_users` table exists (v3.0)
480
+ - [ ] `hazo_chat` table exists with `chat_group_id` column (v3.0)
481
+ - [ ] At least one user exists in database
482
+ - [ ] At least one chat group exists with memberships
311
483
  - [ ] Can query: `SELECT * FROM hazo_chat LIMIT 1`
312
484
 
313
485
  ---
@@ -482,8 +654,9 @@ This endpoint is optional but useful for displaying unread message counts or bad
482
654
 
483
655
  ```typescript
484
656
  /**
485
- * API route to get unread message counts grouped by reference_id
657
+ * API route to get unread message counts grouped by chat_group_id
486
658
  * Uses the exportable library function from hazo_chat
659
+ * MODIFIED in v3.0: Now uses user_id and optional chat_group_ids
487
660
  */
488
661
 
489
662
  export const dynamic = 'force-dynamic';
@@ -500,36 +673,45 @@ const hazo_chat_get_unread_count = createUnreadCountFunction({
500
673
  export async function GET(request: NextRequest) {
501
674
  try {
502
675
  const { searchParams } = new URL(request.url);
503
- const receiver_user_id = searchParams.get('receiver_user_id');
676
+ const user_id = searchParams.get('user_id');
677
+ const chat_group_ids_param = searchParams.get('chat_group_ids');
504
678
 
505
- if (!receiver_user_id) {
679
+ if (!user_id) {
506
680
  return NextResponse.json(
507
- {
508
- success: false,
509
- error: 'receiver_user_id is required',
681
+ {
682
+ success: false,
683
+ error: 'user_id is required',
510
684
  unread_counts: []
511
685
  },
512
686
  { status: 400 }
513
687
  );
514
688
  }
515
689
 
690
+ // Parse optional chat_group_ids (comma-separated)
691
+ const chat_group_ids = chat_group_ids_param
692
+ ? chat_group_ids_param.split(',').filter(Boolean)
693
+ : undefined;
694
+
516
695
  // Call the library function
517
- const unread_counts = await hazo_chat_get_unread_count(receiver_user_id);
696
+ const unread_counts = await hazo_chat_get_unread_count({
697
+ user_id,
698
+ chat_group_ids
699
+ });
518
700
 
519
701
  return NextResponse.json({
520
702
  success: true,
521
- receiver_user_id,
703
+ user_id,
522
704
  unread_counts,
523
- total_references: unread_counts.length,
705
+ total_groups: unread_counts.length,
524
706
  total_unread: unread_counts.reduce((sum, item) => sum + item.count, 0)
525
707
  });
526
708
  } catch (error) {
527
709
  const error_message = error instanceof Error ? error.message : 'Unknown error';
528
710
  console.error('[hazo_chat/unread_count] Error:', error_message, error);
529
-
711
+
530
712
  return NextResponse.json(
531
- {
532
- success: false,
713
+ {
714
+ success: false,
533
715
  error: error_message,
534
716
  unread_counts: []
535
717
  },
@@ -539,25 +721,27 @@ export async function GET(request: NextRequest) {
539
721
  }
540
722
  ```
541
723
 
542
- **What this endpoint does:**
543
- - Takes `receiver_user_id` as a query parameter
544
- - Returns an array of objects with `reference_id` and `count` of unread messages
724
+ **What this endpoint does (v3.0):**
725
+ - Takes `user_id` as a required query parameter
726
+ - Takes optional `chat_group_ids` as a comma-separated list to filter specific groups
727
+ - Returns an array of objects with `chat_group_id` and `count` of unread messages
728
+ - Only counts messages in groups where the user is a member
729
+ - Excludes messages sent by the user themselves
545
730
  - Only counts messages where `read_at` is `null` and `deleted_at` is `null`
546
- - Groups results by `reference_id`
731
+ - Groups results by `chat_group_id`
547
732
  - Sorts by count (descending - most unread first)
548
733
 
549
734
  **Response format:**
550
735
  ```json
551
736
  {
552
737
  "success": true,
553
- "receiver_user_id": "user-123",
738
+ "user_id": "user-123",
554
739
  "unread_counts": [
555
- { "reference_id": "ref-1", "count": 5 },
556
- { "reference_id": "ref-2", "count": 3 },
557
- { "reference_id": "", "count": 1 }
740
+ { "chat_group_id": "group-1", "count": 5 },
741
+ { "chat_group_id": "group-2", "count": 3 }
558
742
  ],
559
- "total_references": 3,
560
- "total_unread": 9
743
+ "total_groups": 2,
744
+ "total_unread": 8
561
745
  }
562
746
  ```
563
747
 
@@ -577,9 +761,9 @@ export async function GET(request: NextRequest) {
577
761
  - [ ] All API route files exist
578
762
  - [ ] `GET /api/hazo_auth/me` returns user data when logged in
579
763
  - [ ] `POST /api/hazo_auth/profiles` returns profiles for given IDs
580
- - [ ] `GET /api/hazo_chat/messages?receiver_user_id=xxx` works
764
+ - [ ] `GET /api/hazo_chat/messages?chat_group_id=xxx` works (v3.0)
581
765
  - [ ] `PATCH /api/hazo_chat/messages/[message-id]/read` marks message as read
582
- - [ ] `GET /api/hazo_chat/unread_count?receiver_user_id=xxx` returns unread counts (if implemented)
766
+ - [ ] `GET /api/hazo_chat/unread_count?user_id=xxx` returns unread counts (v3.0, if implemented)
583
767
 
584
768
  ---
585
769
 
@@ -598,7 +782,7 @@ export default function ChatPage() {
598
782
  return (
599
783
  <div className="h-screen">
600
784
  <HazoChat
601
- receiver_user_id="recipient-uuid-here"
785
+ chat_group_id="group-uuid-here"
602
786
  title="Chat"
603
787
  subtitle="Direct Message"
604
788
  />
@@ -617,7 +801,7 @@ import { HazoChat } from 'hazo_chat';
617
801
  export default function ChatPage() {
618
802
  return (
619
803
  <HazoChat
620
- receiver_user_id="user-123"
804
+ chat_group_id="group-123"
621
805
  reference_id="project-456"
622
806
  reference_type="project_chat"
623
807
  api_base_url="/api/hazo_chat"
@@ -625,11 +809,11 @@ export default function ChatPage() {
625
809
  title="Project Discussion"
626
810
  subtitle="Design Review"
627
811
  additional_references={[
628
- {
629
- id: 'doc-1',
630
- type: 'document',
812
+ {
813
+ id: 'doc-1',
814
+ type: 'document',
631
815
  scope: 'field',
632
- name: 'Design.pdf',
816
+ name: 'Design.pdf',
633
817
  url: '/files/design.pdf'
634
818
  }
635
819
  ]}
@@ -652,18 +836,18 @@ The component uses `h-full`, which requires its parent container to have a **def
652
836
 
653
837
  ```typescript
654
838
  <div className="h-[600px]"> {/* ✅ Required: parent must have height */}
655
- <HazoChat receiver_user_id={...} />
839
+ <HazoChat chat_group_id={...} />
656
840
  </div>
657
841
 
658
842
  // OR
659
843
 
660
844
  <div className="h-screen"> {/* ✅ Full screen height */}
661
- <HazoChat receiver_user_id={...} />
845
+ <HazoChat chat_group_id={...} />
662
846
  </div>
663
847
 
664
848
  // ❌ WRONG - will cause layout issues
665
849
  <div> {/* No height defined */}
666
- <HazoChat receiver_user_id={...} />
850
+ <HazoChat chat_group_id={...} />
667
851
  </div>
668
852
  ```
669
853
 
@@ -676,12 +860,12 @@ The component uses `h-full`, which requires its parent container to have a **def
676
860
  ```typescript
677
861
  // ✅ Recommended: at least 500px width
678
862
  <div className="w-[600px] h-[600px]">
679
- <HazoChat receiver_user_id={...} />
863
+ <HazoChat chat_group_id={...} />
680
864
  </div>
681
865
 
682
866
  // ✅ Narrow container: documents will open in new tab
683
867
  <div className="w-[400px] h-[600px]">
684
- <HazoChat receiver_user_id={...} />
868
+ <HazoChat chat_group_id={...} />
685
869
  </div>
686
870
  ```
687
871
 
@@ -696,8 +880,8 @@ export default function ChatPage() {
696
880
  return (
697
881
  <div className="w-[600px] h-[600px] flex-shrink-0">
698
882
  <div className="rounded-xl border shadow-lg p-0 h-full">
699
- <HazoChat
700
- receiver_user_id="user-123"
883
+ <HazoChat
884
+ chat_group_id="group-123"
701
885
  title="Chat"
702
886
  className="h-full"
703
887
  />
@@ -1127,7 +1311,10 @@ For detailed specifications, see the [UI Design Standards](#ui-design-standards)
1127
1311
  **PostgreSQL:**
1128
1312
  - [ ] UUID extension enabled (check with: `SELECT * FROM pg_extension WHERE extname = 'uuid-ossp' OR extname = 'pgcrypto';`)
1129
1313
  - [ ] Enum type created (if using): `SELECT typname FROM pg_type WHERE typname = 'hazo_chat_reference_type';`
1130
- - [ ] `hazo_chat` table exists with correct column types
1314
+ - [ ] `hazo_users` table exists
1315
+ - [ ] `hazo_chat_group` table exists (v3.0)
1316
+ - [ ] `hazo_chat_group_users` table exists (v3.0)
1317
+ - [ ] `hazo_chat` table exists with `chat_group_id` column (v3.0)
1131
1318
  - [ ] All indexes created (check with: `SELECT indexname FROM pg_indexes WHERE tablename = 'hazo_chat';`)
1132
1319
  - [ ] UUID default generation works: Test insert without providing ID
1133
1320
  - [ ] Timestamp defaults work: Test insert without providing timestamps
@@ -1136,8 +1323,12 @@ For detailed specifications, see the [UI Design Standards](#ui-design-standards)
1136
1323
  - [ ] Foreign key constraints work (if enabled)
1137
1324
 
1138
1325
  **SQLite:**
1139
- - [ ] `hazo_chat` table exists
1140
- - [ ] Users table exists with test users
1326
+ - [ ] `hazo_users` table exists
1327
+ - [ ] `hazo_chat_group` table exists (v3.0)
1328
+ - [ ] `hazo_chat_group_users` table exists (v3.0)
1329
+ - [ ] `hazo_chat` table exists with `chat_group_id` column (v3.0)
1330
+ - [ ] At least one user exists in database
1331
+ - [ ] At least one chat group exists with memberships
1141
1332
  - [ ] Can insert and query messages
1142
1333
 
1143
1334
  ### API Verification
@@ -1153,11 +1344,11 @@ curl -X POST http://localhost:3000/api/hazo_auth/profiles \
1153
1344
  -H "Content-Type: application/json" \
1154
1345
  -d '{"user_ids": ["user-id-here"]}'
1155
1346
 
1156
- # Test messages endpoint
1157
- curl "http://localhost:3000/api/hazo_chat/messages?receiver_user_id=user-id"
1347
+ # Test messages endpoint (v3.0 - uses chat_group_id)
1348
+ curl "http://localhost:3000/api/hazo_chat/messages?chat_group_id=group-id"
1158
1349
 
1159
- # Test unread count endpoint (if implemented)
1160
- curl "http://localhost:3000/api/hazo_chat/unread_count?receiver_user_id=user-id"
1350
+ # Test unread count endpoint (v3.0 - uses user_id, if implemented)
1351
+ curl "http://localhost:3000/api/hazo_chat/unread_count?user_id=user-id"
1161
1352
  ```
1162
1353
 
1163
1354
  ### UI Verification & Responsive Behavior
@@ -1228,10 +1419,11 @@ curl "http://localhost:3000/api/hazo_chat/unread_count?receiver_user_id=user-id"
1228
1419
 
1229
1420
  **Checklist:**
1230
1421
  1. Is user authenticated? Check `/api/hazo_auth/me`
1231
- 2. Is `receiver_user_id` provided?
1232
- 3. Check browser console for errors
1233
- 4. Check network tab for API responses
1234
- 5. Check server logs for API errors
1422
+ 2. Is `chat_group_id` provided? (v3.0)
1423
+ 3. Is user a member of the chat group?
1424
+ 4. Check browser console for errors
1425
+ 5. Check network tab for API responses
1426
+ 6. Check server logs for API errors
1235
1427
 
1236
1428
  ### Issue: Hamburger button visible on desktop
1237
1429
 
@@ -1327,6 +1519,301 @@ curl "http://localhost:3000/api/hazo_chat/unread_count?receiver_user_id=user-id"
1327
1519
 
1328
1520
  ---
1329
1521
 
1522
+ ## Migration from v2.x to v3.0
1523
+
1524
+ This section provides instructions for upgrading from hazo_chat v2.x (1-to-1 chat with `receiver_user_id`) to v3.0 (group-based chat with `chat_group_id`).
1525
+
1526
+ ### Breaking Changes
1527
+
1528
+ 1. **Database Schema:**
1529
+ - `hazo_chat.receiver_user_id` column replaced with `chat_group_id`
1530
+ - New tables: `hazo_chat_group` and `hazo_chat_group_users`
1531
+
1532
+ 2. **Component Props:**
1533
+ - `receiver_user_id` prop replaced with `chat_group_id`
1534
+
1535
+ 3. **API Endpoints:**
1536
+ - GET/POST `/api/hazo_chat/messages` now uses `chat_group_id` query param
1537
+ - GET `/api/hazo_chat/unread_count` now uses `user_id` param instead of `receiver_user_id`
1538
+
1539
+ 4. **Types:**
1540
+ - `ChatMessage.receiver_profile` removed
1541
+ - `CreateMessagePayload.receiver_user_id` replaced with `chat_group_id`
1542
+
1543
+ ### Migration Steps (PostgreSQL)
1544
+
1545
+ ```sql
1546
+ -- Step 1: Create new tables
1547
+ CREATE TABLE IF NOT EXISTS hazo_chat_group (
1548
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
1549
+ client_user_id UUID NOT NULL REFERENCES hazo_users(id),
1550
+ name VARCHAR(255),
1551
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
1552
+ changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
1553
+ );
1554
+
1555
+ CREATE TABLE IF NOT EXISTS hazo_chat_group_users (
1556
+ chat_group_id UUID NOT NULL REFERENCES hazo_chat_group(id) ON DELETE CASCADE,
1557
+ user_id UUID NOT NULL REFERENCES hazo_users(id) ON DELETE CASCADE,
1558
+ role VARCHAR(20) NOT NULL CHECK (role IN ('client', 'staff')),
1559
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
1560
+ changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
1561
+ PRIMARY KEY (chat_group_id, user_id)
1562
+ );
1563
+
1564
+ -- Step 2: Create groups for existing 1-to-1 conversations
1565
+ -- This example creates one group per unique receiver_user_id
1566
+ INSERT INTO hazo_chat_group (id, client_user_id, name, created_at, changed_at)
1567
+ SELECT DISTINCT
1568
+ gen_random_uuid(),
1569
+ receiver_user_id,
1570
+ 'Migrated Chat',
1571
+ NOW(),
1572
+ NOW()
1573
+ FROM hazo_chat
1574
+ WHERE receiver_user_id IS NOT NULL;
1575
+
1576
+ -- Step 3: Add sender as staff member to each group
1577
+ INSERT INTO hazo_chat_group_users (chat_group_id, user_id, role, created_at, changed_at)
1578
+ SELECT DISTINCT
1579
+ g.id,
1580
+ c.sender_user_id,
1581
+ 'staff',
1582
+ NOW(),
1583
+ NOW()
1584
+ FROM hazo_chat c
1585
+ JOIN hazo_chat_group g ON g.client_user_id = c.receiver_user_id
1586
+ WHERE c.sender_user_id != c.receiver_user_id
1587
+ ON CONFLICT (chat_group_id, user_id) DO NOTHING;
1588
+
1589
+ -- Step 4: Add client as client member to each group
1590
+ INSERT INTO hazo_chat_group_users (chat_group_id, user_id, role, created_at, changed_at)
1591
+ SELECT
1592
+ id,
1593
+ client_user_id,
1594
+ 'client',
1595
+ NOW(),
1596
+ NOW()
1597
+ FROM hazo_chat_group
1598
+ ON CONFLICT (chat_group_id, user_id) DO NOTHING;
1599
+
1600
+ -- Step 5: Add chat_group_id column to hazo_chat
1601
+ ALTER TABLE hazo_chat ADD COLUMN chat_group_id UUID;
1602
+
1603
+ -- Step 6: Populate chat_group_id from receiver_user_id
1604
+ UPDATE hazo_chat c
1605
+ SET chat_group_id = g.id
1606
+ FROM hazo_chat_group g
1607
+ WHERE g.client_user_id = c.receiver_user_id;
1608
+
1609
+ -- Step 7: Make chat_group_id NOT NULL and add foreign key
1610
+ ALTER TABLE hazo_chat
1611
+ ALTER COLUMN chat_group_id SET NOT NULL,
1612
+ ADD CONSTRAINT fk_hazo_chat_group
1613
+ FOREIGN KEY (chat_group_id)
1614
+ REFERENCES hazo_chat_group(id);
1615
+
1616
+ -- Step 8: Drop old column and index
1617
+ DROP INDEX IF EXISTS idx_hazo_chat_receiver;
1618
+ ALTER TABLE hazo_chat DROP COLUMN receiver_user_id;
1619
+
1620
+ -- Step 9: Create new index
1621
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group ON hazo_chat(chat_group_id);
1622
+ ```
1623
+
1624
+ ### Migration Steps (SQLite)
1625
+
1626
+ ```sql
1627
+ -- Step 1: Create new tables
1628
+ CREATE TABLE IF NOT EXISTS hazo_chat_group (
1629
+ id TEXT PRIMARY KEY,
1630
+ client_user_id TEXT NOT NULL,
1631
+ name TEXT,
1632
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1633
+ changed_at TEXT NOT NULL DEFAULT (datetime('now')),
1634
+ FOREIGN KEY (client_user_id) REFERENCES hazo_users(id)
1635
+ );
1636
+
1637
+ CREATE TABLE IF NOT EXISTS hazo_chat_group_users (
1638
+ chat_group_id TEXT NOT NULL,
1639
+ user_id TEXT NOT NULL,
1640
+ role TEXT NOT NULL CHECK (role IN ('client', 'staff')),
1641
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1642
+ changed_at TEXT NOT NULL DEFAULT (datetime('now')),
1643
+ PRIMARY KEY (chat_group_id, user_id),
1644
+ FOREIGN KEY (chat_group_id) REFERENCES hazo_chat_group(id) ON DELETE CASCADE,
1645
+ FOREIGN KEY (user_id) REFERENCES hazo_users(id) ON DELETE CASCADE
1646
+ );
1647
+
1648
+ -- Step 2: Create groups for existing conversations (manually generate UUIDs)
1649
+ -- Note: SQLite doesn't have gen_random_uuid(), use your app or external tool
1650
+
1651
+ -- Step 3: Recreate hazo_chat table with new schema (SQLite doesn't support ALTER COLUMN)
1652
+ ALTER TABLE hazo_chat RENAME TO hazo_chat_old;
1653
+
1654
+ CREATE TABLE hazo_chat (
1655
+ id TEXT PRIMARY KEY,
1656
+ reference_id TEXT NOT NULL,
1657
+ reference_type TEXT DEFAULT 'chat',
1658
+ sender_user_id TEXT NOT NULL,
1659
+ chat_group_id TEXT NOT NULL,
1660
+ message_text TEXT,
1661
+ reference_list TEXT,
1662
+ read_at TEXT,
1663
+ deleted_at TEXT,
1664
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1665
+ changed_at TEXT NOT NULL DEFAULT (datetime('now')),
1666
+ FOREIGN KEY (sender_user_id) REFERENCES hazo_users(id),
1667
+ FOREIGN KEY (chat_group_id) REFERENCES hazo_chat_group(id)
1668
+ );
1669
+
1670
+ -- Step 4: Migrate data (you'll need to map receiver_user_id to chat_group_id)
1671
+ INSERT INTO hazo_chat (id, reference_id, reference_type, sender_user_id, chat_group_id,
1672
+ message_text, reference_list, read_at, deleted_at, created_at, changed_at)
1673
+ SELECT o.id, o.reference_id, o.reference_type, o.sender_user_id, g.id,
1674
+ o.message_text, o.reference_list, o.read_at, o.deleted_at, o.created_at, o.changed_at
1675
+ FROM hazo_chat_old o
1676
+ JOIN hazo_chat_group g ON g.client_user_id = o.receiver_user_id;
1677
+
1678
+ -- Step 5: Drop old table
1679
+ DROP TABLE hazo_chat_old;
1680
+
1681
+ -- Step 6: Create indexes
1682
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_id ON hazo_chat(reference_id);
1683
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_sender ON hazo_chat(sender_user_id);
1684
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group ON hazo_chat(chat_group_id);
1685
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_created ON hazo_chat(created_at DESC);
1686
+ ```
1687
+
1688
+ ### Code Changes Required
1689
+
1690
+ 1. **Update HazoChat component usage:**
1691
+ ```typescript
1692
+ // Before (v2.x)
1693
+ <HazoChat receiver_user_id="user-123" />
1694
+
1695
+ // After (v3.0)
1696
+ <HazoChat chat_group_id="group-123" />
1697
+ ```
1698
+
1699
+ 2. **Update API route for unread count:**
1700
+ ```typescript
1701
+ // Before (v2.x)
1702
+ const unread = await hazo_chat_get_unread_count(receiver_user_id);
1703
+
1704
+ // After (v3.0)
1705
+ const unread = await hazo_chat_get_unread_count({ user_id, chat_group_ids });
1706
+ ```
1707
+
1708
+ 3. **Update any direct database queries** to use `chat_group_id` instead of `receiver_user_id`.
1709
+
1710
+ ---
1711
+
1712
+ ## Migration from v3.0 to v3.1 (Generic Schema)
1713
+
1714
+ This section provides instructions for upgrading from hazo_chat v3.0 to v3.1 to support flexible chat patterns (support, peer, and group conversations).
1715
+
1716
+ ### What Changed in v3.1
1717
+
1718
+ 1. **`client_user_id`**: Now nullable (optional) for peer/group chats
1719
+ 2. **`group_type`**: New field to identify conversation type ('support', 'peer', 'group')
1720
+ 3. **Expanded roles**: 'owner', 'admin', 'member' added alongside 'client' and 'staff'
1721
+
1722
+ ### PostgreSQL Migration
1723
+
1724
+ ```sql
1725
+ -- Step 1: Create enum types (if not already created)
1726
+ CREATE TYPE hazo_enum_group_type AS ENUM ('support', 'peer', 'group');
1727
+ CREATE TYPE hazo_enum_group_role AS ENUM ('client', 'staff', 'owner', 'admin', 'member');
1728
+
1729
+ -- Step 2: Add group_type column with default 'support' for existing groups
1730
+ ALTER TABLE hazo_chat_group
1731
+ ADD COLUMN group_type hazo_enum_group_type DEFAULT 'support';
1732
+
1733
+ -- Step 3: Make client_user_id nullable
1734
+ ALTER TABLE hazo_chat_group
1735
+ ALTER COLUMN client_user_id DROP NOT NULL;
1736
+
1737
+ -- Step 4: Update role column to use enum type (if using enum approach)
1738
+ -- Option A: Convert to enum type (recommended)
1739
+ ALTER TABLE hazo_chat_group_users
1740
+ DROP CONSTRAINT IF EXISTS hazo_chat_group_users_role_check;
1741
+
1742
+ ALTER TABLE hazo_chat_group_users
1743
+ ALTER COLUMN role TYPE hazo_enum_group_role
1744
+ USING role::hazo_enum_group_role;
1745
+
1746
+ -- Option B: Keep VARCHAR with expanded CHECK constraint
1747
+ -- ALTER TABLE hazo_chat_group_users
1748
+ -- DROP CONSTRAINT IF EXISTS hazo_chat_group_users_role_check;
1749
+ --
1750
+ -- ALTER TABLE hazo_chat_group_users
1751
+ -- ADD CONSTRAINT hazo_chat_group_users_role_check
1752
+ -- CHECK (role IN ('client', 'staff', 'owner', 'admin', 'member'));
1753
+
1754
+ -- Step 5: Create index for group_type
1755
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group_type ON hazo_chat_group(group_type);
1756
+ ```
1757
+
1758
+ ### SQLite Migration
1759
+
1760
+ SQLite doesn't support ALTER COLUMN, so table recreation is needed:
1761
+
1762
+ ```sql
1763
+ -- Step 1: Rename existing tables
1764
+ ALTER TABLE hazo_chat_group RENAME TO hazo_chat_group_old;
1765
+ ALTER TABLE hazo_chat_group_users RENAME TO hazo_chat_group_users_old;
1766
+
1767
+ -- Step 2: Create new hazo_chat_group table with updated schema
1768
+ CREATE TABLE hazo_chat_group (
1769
+ id TEXT PRIMARY KEY,
1770
+ client_user_id TEXT, -- Now nullable
1771
+ group_type TEXT DEFAULT 'support' CHECK (group_type IN ('support', 'peer', 'group')),
1772
+ name TEXT,
1773
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1774
+ changed_at TEXT NOT NULL DEFAULT (datetime('now')),
1775
+ FOREIGN KEY (client_user_id) REFERENCES hazo_users(id)
1776
+ );
1777
+
1778
+ -- Step 3: Migrate group data (existing groups become 'support' type)
1779
+ INSERT INTO hazo_chat_group (id, client_user_id, group_type, name, created_at, changed_at)
1780
+ SELECT id, client_user_id, 'support', name, created_at, changed_at
1781
+ FROM hazo_chat_group_old;
1782
+
1783
+ -- Step 4: Drop old group table
1784
+ DROP TABLE hazo_chat_group_old;
1785
+
1786
+ -- Step 5: Create new hazo_chat_group_users table with expanded roles
1787
+ CREATE TABLE hazo_chat_group_users (
1788
+ chat_group_id TEXT NOT NULL,
1789
+ user_id TEXT NOT NULL,
1790
+ role TEXT NOT NULL CHECK (role IN ('client', 'staff', 'owner', 'admin', 'member')),
1791
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1792
+ changed_at TEXT NOT NULL DEFAULT (datetime('now')),
1793
+ PRIMARY KEY (chat_group_id, user_id),
1794
+ FOREIGN KEY (chat_group_id) REFERENCES hazo_chat_group(id) ON DELETE CASCADE,
1795
+ FOREIGN KEY (user_id) REFERENCES hazo_users(id) ON DELETE CASCADE
1796
+ );
1797
+
1798
+ -- Step 6: Migrate membership data
1799
+ INSERT INTO hazo_chat_group_users SELECT * FROM hazo_chat_group_users_old;
1800
+
1801
+ -- Step 7: Drop old membership table
1802
+ DROP TABLE hazo_chat_group_users_old;
1803
+
1804
+ -- Step 8: Recreate indexes
1805
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group_client ON hazo_chat_group(client_user_id);
1806
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group_type ON hazo_chat_group(group_type);
1807
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group_users_user ON hazo_chat_group_users(user_id);
1808
+ CREATE INDEX IF NOT EXISTS idx_hazo_chat_group_users_group ON hazo_chat_group_users(chat_group_id);
1809
+ ```
1810
+
1811
+ ### No Code Changes Required
1812
+
1813
+ The TypeScript types have been updated, but the API handlers do not enforce role-based permissions. Existing code will continue to work without modification.
1814
+
1815
+ ---
1816
+
1330
1817
  ## Quick Setup Summary
1331
1818
 
1332
1819
  ```bash