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.
- package/README.md +467 -67
- package/SETUP_CHECKLIST.md +585 -98
- package/dist/api/messages.d.ts.map +1 -1
- package/dist/api/messages.js +83 -32
- package/dist/api/messages.js.map +1 -1
- package/dist/api/types.d.ts +25 -2
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/unread_count.d.ts +19 -10
- package/dist/api/unread_count.d.ts.map +1 -1
- package/dist/api/unread_count.js +54 -30
- package/dist/api/unread_count.js.map +1 -1
- package/dist/components/hazo_chat/hazo_chat.js +6 -6
- package/dist/components/hazo_chat/hazo_chat.js.map +1 -1
- package/dist/components/hazo_chat/hazo_chat_messages.js +3 -3
- package/dist/components/hazo_chat/hazo_chat_messages.js.map +1 -1
- package/dist/hooks/use_chat_messages.d.ts +3 -3
- package/dist/hooks/use_chat_messages.d.ts.map +1 -1
- package/dist/hooks/use_chat_messages.js +15 -20
- package/dist/hooks/use_chat_messages.js.map +1 -1
- package/dist/types/index.d.ts +57 -7
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
package/SETUP_CHECKLIST.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
# hazo_chat Setup Checklist (
|
|
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 (
|
|
78
|
+
#### Step 3.1.2: Create Enum Types (Recommended)
|
|
75
79
|
|
|
76
|
-
Create enum types for
|
|
80
|
+
Create enum types for data integrity. All enums are prefixed with `hazo_enum_`:
|
|
77
81
|
|
|
78
82
|
```sql
|
|
79
|
-
--
|
|
80
|
-
|
|
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
|
-
--
|
|
83
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
183
|
-
FOREIGN KEY (
|
|
184
|
-
REFERENCES
|
|
185
|
-
ON DELETE
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
- [ ] `
|
|
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
|
-
- [ ] `
|
|
310
|
-
- [ ]
|
|
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
|
|
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
|
|
676
|
+
const user_id = searchParams.get('user_id');
|
|
677
|
+
const chat_group_ids_param = searchParams.get('chat_group_ids');
|
|
504
678
|
|
|
505
|
-
if (!
|
|
679
|
+
if (!user_id) {
|
|
506
680
|
return NextResponse.json(
|
|
507
|
-
{
|
|
508
|
-
success: false,
|
|
509
|
-
error: '
|
|
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(
|
|
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
|
-
|
|
703
|
+
user_id,
|
|
522
704
|
unread_counts,
|
|
523
|
-
|
|
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 `
|
|
544
|
-
-
|
|
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 `
|
|
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
|
-
"
|
|
738
|
+
"user_id": "user-123",
|
|
554
739
|
"unread_counts": [
|
|
555
|
-
{ "
|
|
556
|
-
{ "
|
|
557
|
-
{ "reference_id": "", "count": 1 }
|
|
740
|
+
{ "chat_group_id": "group-1", "count": 5 },
|
|
741
|
+
{ "chat_group_id": "group-2", "count": 3 }
|
|
558
742
|
],
|
|
559
|
-
"
|
|
560
|
-
"total_unread":
|
|
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?
|
|
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?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
- [ ] `
|
|
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
|
-
- [ ] `
|
|
1140
|
-
- [ ]
|
|
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?
|
|
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?
|
|
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 `
|
|
1232
|
-
3.
|
|
1233
|
-
4. Check
|
|
1234
|
-
5. Check
|
|
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
|