hazo_chat 2.1.0 → 3.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.
- package/README.md +182 -67
- package/SETUP_CHECKLIST.md +773 -67
- package/dist/api/index.d.ts +17 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +16 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/messages.d.ts +34 -1
- package/dist/api/messages.d.ts.map +1 -1
- package/dist/api/messages.js +340 -47
- package/dist/api/messages.js.map +1 -1
- package/dist/api/types.d.ts +50 -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.d.ts.map +1 -1
- package/dist/components/hazo_chat/hazo_chat.js +23 -15
- package/dist/components/hazo_chat/hazo_chat.js.map +1 -1
- package/dist/components/hazo_chat/hazo_chat_header.d.ts.map +1 -1
- package/dist/components/hazo_chat/hazo_chat_header.js +17 -4
- package/dist/components/hazo_chat/hazo_chat_header.js.map +1 -1
- package/dist/components/hazo_chat/hazo_chat_messages.d.ts +5 -4
- package/dist/components/hazo_chat/hazo_chat_messages.d.ts.map +1 -1
- package/dist/components/hazo_chat/hazo_chat_messages.js +48 -8
- package/dist/components/hazo_chat/hazo_chat_messages.js.map +1 -1
- package/dist/hooks/use_chat_messages.d.ts +5 -5
- package/dist/hooks/use_chat_messages.d.ts.map +1 -1
- package/dist/hooks/use_chat_messages.js +247 -148
- package/dist/hooks/use_chat_messages.js.map +1 -1
- package/dist/types/index.d.ts +162 -7
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
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
|
|
|
@@ -56,52 +60,426 @@ npm install react react-dom next
|
|
|
56
60
|
|
|
57
61
|
## 3. Database Setup
|
|
58
62
|
|
|
59
|
-
### Step 3.1:
|
|
63
|
+
### Step 3.1: PostgreSQL Setup (Recommended)
|
|
64
|
+
|
|
65
|
+
For PostgreSQL databases, follow these steps to create the schema with UUID types, enums, and proper defaults.
|
|
66
|
+
|
|
67
|
+
#### Step 3.1.1: Enable UUID Extension
|
|
68
|
+
|
|
69
|
+
First, enable the UUID extension for generating UUIDs:
|
|
70
|
+
|
|
71
|
+
```sql
|
|
72
|
+
-- Enable UUID extension (required for gen_random_uuid())
|
|
73
|
+
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
74
|
+
-- OR for PostgreSQL 13+, use pgcrypto extension
|
|
75
|
+
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### Step 3.1.2: Create Enum Types (Recommended)
|
|
79
|
+
|
|
80
|
+
Create enum types for data integrity. All enums are prefixed with `hazo_enum_`:
|
|
81
|
+
|
|
82
|
+
```sql
|
|
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
|
+
);
|
|
102
|
+
|
|
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';
|
|
116
|
+
```
|
|
117
|
+
|
|
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
|
+
```
|
|
60
165
|
|
|
61
|
-
|
|
166
|
+
**Alternative: Without Enum Type (More Flexible)**
|
|
167
|
+
|
|
168
|
+
If you prefer flexibility over strict enum validation:
|
|
62
169
|
|
|
63
170
|
```sql
|
|
64
|
-
--
|
|
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)
|
|
190
|
+
|
|
191
|
+
Create the chat messages table with UUID types and proper defaults:
|
|
192
|
+
|
|
193
|
+
```sql
|
|
194
|
+
-- hazo_chat table for storing chat messages (PostgreSQL)
|
|
195
|
+
-- MODIFIED in v3.0: receiver_user_id replaced with chat_group_id
|
|
65
196
|
CREATE TABLE IF NOT EXISTS hazo_chat (
|
|
66
|
-
id
|
|
67
|
-
reference_id
|
|
68
|
-
reference_type
|
|
69
|
-
sender_user_id
|
|
70
|
-
|
|
197
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
198
|
+
reference_id UUID NOT NULL,
|
|
199
|
+
reference_type hazo_enum_chat_type DEFAULT 'chat' NOT NULL,
|
|
200
|
+
sender_user_id UUID NOT NULL,
|
|
201
|
+
chat_group_id UUID NOT NULL REFERENCES hazo_chat_group(id), -- CHANGED from receiver_user_id
|
|
71
202
|
message_text TEXT,
|
|
72
|
-
reference_list
|
|
73
|
-
read_at
|
|
74
|
-
deleted_at
|
|
75
|
-
created_at
|
|
76
|
-
changed_at
|
|
203
|
+
reference_list JSONB,
|
|
204
|
+
read_at TIMESTAMPTZ,
|
|
205
|
+
deleted_at TIMESTAMPTZ,
|
|
206
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
207
|
+
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
77
208
|
);
|
|
78
209
|
|
|
79
210
|
-- Performance indexes
|
|
80
211
|
CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_id ON hazo_chat(reference_id);
|
|
81
212
|
CREATE INDEX IF NOT EXISTS idx_hazo_chat_sender ON hazo_chat(sender_user_id);
|
|
82
|
-
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
|
|
83
214
|
CREATE INDEX IF NOT EXISTS idx_hazo_chat_created ON hazo_chat(created_at DESC);
|
|
215
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_type ON hazo_chat(reference_type);
|
|
216
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_read_at ON hazo_chat(read_at) WHERE read_at IS NOT NULL;
|
|
217
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_deleted_at ON hazo_chat(deleted_at) WHERE deleted_at IS NOT NULL;
|
|
84
218
|
```
|
|
85
219
|
|
|
86
|
-
|
|
220
|
+
**Alternative: Without Enum Type (More Flexible)**
|
|
221
|
+
|
|
222
|
+
If you prefer flexibility over strict enum validation:
|
|
223
|
+
|
|
224
|
+
```sql
|
|
225
|
+
-- hazo_chat table with TEXT reference_type (more flexible)
|
|
226
|
+
-- MODIFIED in v3.0: receiver_user_id replaced with chat_group_id
|
|
227
|
+
CREATE TABLE IF NOT EXISTS hazo_chat (
|
|
228
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
229
|
+
reference_id UUID NOT NULL,
|
|
230
|
+
reference_type TEXT DEFAULT 'chat' NOT NULL,
|
|
231
|
+
sender_user_id UUID NOT NULL,
|
|
232
|
+
chat_group_id UUID NOT NULL REFERENCES hazo_chat_group(id), -- CHANGED from receiver_user_id
|
|
233
|
+
message_text TEXT,
|
|
234
|
+
reference_list JSONB,
|
|
235
|
+
read_at TIMESTAMPTZ,
|
|
236
|
+
deleted_at TIMESTAMPTZ,
|
|
237
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
238
|
+
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
-- Performance indexes
|
|
242
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_id ON hazo_chat(reference_id);
|
|
243
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_sender ON hazo_chat(sender_user_id);
|
|
244
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_group ON hazo_chat(chat_group_id); -- CHANGED from receiver index
|
|
245
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_created ON hazo_chat(created_at DESC);
|
|
246
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_type ON hazo_chat(reference_type);
|
|
247
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_read_at ON hazo_chat(read_at) WHERE read_at IS NOT NULL;
|
|
248
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_deleted_at ON hazo_chat(deleted_at) WHERE deleted_at IS NOT NULL;
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### Step 3.1.4: Ensure Users Table Exists (PostgreSQL)
|
|
87
252
|
|
|
88
253
|
You need a users table with at least these fields:
|
|
89
254
|
|
|
90
255
|
```sql
|
|
256
|
+
-- Users table for PostgreSQL
|
|
257
|
+
CREATE TABLE IF NOT EXISTS hazo_users (
|
|
258
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
259
|
+
email_address VARCHAR(255) UNIQUE NOT NULL,
|
|
260
|
+
name VARCHAR(255),
|
|
261
|
+
profile_picture_url TEXT,
|
|
262
|
+
is_active BOOLEAN DEFAULT TRUE NOT NULL,
|
|
263
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
264
|
+
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
-- Indexes for users table
|
|
268
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_users_email ON hazo_users(email_address);
|
|
269
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_users_active ON hazo_users(is_active) WHERE is_active = TRUE;
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### Step 3.1.6: Add Foreign Key Constraints (Optional)
|
|
273
|
+
|
|
274
|
+
Add foreign key constraints for referential integrity:
|
|
275
|
+
|
|
276
|
+
```sql
|
|
277
|
+
-- Add foreign key constraints (optional but recommended)
|
|
278
|
+
-- MODIFIED in v3.0: receiver_user_id constraint replaced with chat_group_id
|
|
279
|
+
ALTER TABLE hazo_chat
|
|
280
|
+
ADD CONSTRAINT fk_hazo_chat_sender
|
|
281
|
+
FOREIGN KEY (sender_user_id)
|
|
282
|
+
REFERENCES hazo_users(id)
|
|
283
|
+
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;
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Step 3.2: SQLite Setup (Alternative)
|
|
291
|
+
|
|
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
|
|
91
298
|
CREATE TABLE IF NOT EXISTS hazo_users (
|
|
92
299
|
id TEXT PRIMARY KEY,
|
|
93
300
|
email_address TEXT UNIQUE NOT NULL,
|
|
94
301
|
name TEXT,
|
|
95
302
|
profile_picture_url TEXT,
|
|
96
303
|
is_active INTEGER DEFAULT 1,
|
|
97
|
-
created_at TEXT NOT NULL,
|
|
98
|
-
changed_at TEXT NOT NULL
|
|
304
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
305
|
+
changed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
99
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)
|
|
366
|
+
|
|
367
|
+
```sql
|
|
368
|
+
-- hazo_chat table for storing chat messages (SQLite)
|
|
369
|
+
-- MODIFIED in v3.0: receiver_user_id replaced with chat_group_id
|
|
370
|
+
CREATE TABLE IF NOT EXISTS hazo_chat (
|
|
371
|
+
id TEXT PRIMARY KEY,
|
|
372
|
+
reference_id TEXT NOT NULL,
|
|
373
|
+
reference_type TEXT DEFAULT 'chat',
|
|
374
|
+
sender_user_id TEXT NOT NULL,
|
|
375
|
+
chat_group_id TEXT NOT NULL,
|
|
376
|
+
message_text TEXT,
|
|
377
|
+
reference_list TEXT,
|
|
378
|
+
read_at TEXT,
|
|
379
|
+
deleted_at TEXT,
|
|
380
|
+
created_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)
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
-- Performance indexes
|
|
387
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_id ON hazo_chat(reference_id);
|
|
388
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_sender ON hazo_chat(sender_user_id);
|
|
389
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_group ON hazo_chat(chat_group_id);
|
|
390
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_created ON hazo_chat(created_at DESC);
|
|
391
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_chat_reference_type ON hazo_chat(reference_type);
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Step 3.3: Key Differences Between PostgreSQL and SQLite
|
|
395
|
+
|
|
396
|
+
| Feature | PostgreSQL | SQLite |
|
|
397
|
+
|---------|-----------|--------|
|
|
398
|
+
| **ID Type** | `UUID` with `gen_random_uuid()` default | `TEXT` (manual UUID generation) |
|
|
399
|
+
| **Boolean** | `BOOLEAN` type with `TRUE`/`FALSE` | `INTEGER` with `0`/`1` |
|
|
400
|
+
| **Timestamp** | `TIMESTAMPTZ` with `NOW()` default | `TEXT` with `datetime('now')` |
|
|
401
|
+
| **JSON** | `JSONB` type (binary JSON) | `TEXT` (JSON string) |
|
|
402
|
+
| **Enum** | Native `ENUM` type available | Not supported (use TEXT) |
|
|
403
|
+
| **Extensions** | Requires UUID extension | No extensions needed |
|
|
404
|
+
|
|
405
|
+
#### Step 3.3.1: Verify PostgreSQL Setup
|
|
406
|
+
|
|
407
|
+
Run these queries to verify your PostgreSQL setup:
|
|
408
|
+
|
|
409
|
+
```sql
|
|
410
|
+
-- Verify UUID extension is enabled
|
|
411
|
+
SELECT extname, extversion FROM pg_extension WHERE extname IN ('uuid-ossp', 'pgcrypto');
|
|
412
|
+
|
|
413
|
+
-- Verify enum type exists (if using enum)
|
|
414
|
+
SELECT typname FROM pg_type WHERE typname = 'hazo_chat_reference_type';
|
|
415
|
+
|
|
416
|
+
-- Verify table structure
|
|
417
|
+
SELECT
|
|
418
|
+
column_name,
|
|
419
|
+
data_type,
|
|
420
|
+
column_default,
|
|
421
|
+
is_nullable
|
|
422
|
+
FROM information_schema.columns
|
|
423
|
+
WHERE table_name = 'hazo_chat'
|
|
424
|
+
ORDER BY ordinal_position;
|
|
425
|
+
|
|
426
|
+
-- Verify indexes
|
|
427
|
+
SELECT indexname, indexdef
|
|
428
|
+
FROM pg_indexes
|
|
429
|
+
WHERE tablename = 'hazo_chat';
|
|
430
|
+
|
|
431
|
+
-- Test UUID generation
|
|
432
|
+
SELECT gen_random_uuid() AS test_uuid;
|
|
433
|
+
|
|
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
|
|
436
|
+
INSERT INTO hazo_chat (
|
|
437
|
+
reference_id,
|
|
438
|
+
sender_user_id,
|
|
439
|
+
chat_group_id,
|
|
440
|
+
message_text
|
|
441
|
+
) VALUES (
|
|
442
|
+
gen_random_uuid(),
|
|
443
|
+
(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
|
|
445
|
+
'Test message'
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
-- Verify auto-generated values
|
|
449
|
+
SELECT
|
|
450
|
+
id,
|
|
451
|
+
created_at,
|
|
452
|
+
changed_at,
|
|
453
|
+
reference_type
|
|
454
|
+
FROM hazo_chat
|
|
455
|
+
WHERE message_text = 'Test message'
|
|
456
|
+
LIMIT 1;
|
|
457
|
+
|
|
458
|
+
-- Clean up test data
|
|
459
|
+
DELETE FROM hazo_chat WHERE message_text = 'Test message';
|
|
100
460
|
```
|
|
101
461
|
|
|
102
462
|
### Verification
|
|
103
|
-
|
|
104
|
-
|
|
463
|
+
|
|
464
|
+
**PostgreSQL:**
|
|
465
|
+
- [ ] UUID extension enabled (`uuid-ossp` or `pgcrypto`)
|
|
466
|
+
- [ ] Enum type created (if using enum approach)
|
|
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)
|
|
471
|
+
- [ ] All indexes created successfully
|
|
472
|
+
- [ ] Can query: `SELECT * FROM hazo_chat LIMIT 1`
|
|
473
|
+
- [ ] UUID default generation works: `INSERT INTO hazo_chat (...) VALUES (...)` generates UUID automatically
|
|
474
|
+
- [ ] Timestamp defaults work: `created_at` and `changed_at` auto-populate
|
|
475
|
+
|
|
476
|
+
**SQLite:**
|
|
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
|
|
105
483
|
- [ ] Can query: `SELECT * FROM hazo_chat LIMIT 1`
|
|
106
484
|
|
|
107
485
|
---
|
|
@@ -276,8 +654,9 @@ This endpoint is optional but useful for displaying unread message counts or bad
|
|
|
276
654
|
|
|
277
655
|
```typescript
|
|
278
656
|
/**
|
|
279
|
-
* API route to get unread message counts grouped by
|
|
657
|
+
* API route to get unread message counts grouped by chat_group_id
|
|
280
658
|
* Uses the exportable library function from hazo_chat
|
|
659
|
+
* MODIFIED in v3.0: Now uses user_id and optional chat_group_ids
|
|
281
660
|
*/
|
|
282
661
|
|
|
283
662
|
export const dynamic = 'force-dynamic';
|
|
@@ -294,36 +673,45 @@ const hazo_chat_get_unread_count = createUnreadCountFunction({
|
|
|
294
673
|
export async function GET(request: NextRequest) {
|
|
295
674
|
try {
|
|
296
675
|
const { searchParams } = new URL(request.url);
|
|
297
|
-
const
|
|
676
|
+
const user_id = searchParams.get('user_id');
|
|
677
|
+
const chat_group_ids_param = searchParams.get('chat_group_ids');
|
|
298
678
|
|
|
299
|
-
if (!
|
|
679
|
+
if (!user_id) {
|
|
300
680
|
return NextResponse.json(
|
|
301
|
-
{
|
|
302
|
-
success: false,
|
|
303
|
-
error: '
|
|
681
|
+
{
|
|
682
|
+
success: false,
|
|
683
|
+
error: 'user_id is required',
|
|
304
684
|
unread_counts: []
|
|
305
685
|
},
|
|
306
686
|
{ status: 400 }
|
|
307
687
|
);
|
|
308
688
|
}
|
|
309
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
|
+
|
|
310
695
|
// Call the library function
|
|
311
|
-
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
|
+
});
|
|
312
700
|
|
|
313
701
|
return NextResponse.json({
|
|
314
702
|
success: true,
|
|
315
|
-
|
|
703
|
+
user_id,
|
|
316
704
|
unread_counts,
|
|
317
|
-
|
|
705
|
+
total_groups: unread_counts.length,
|
|
318
706
|
total_unread: unread_counts.reduce((sum, item) => sum + item.count, 0)
|
|
319
707
|
});
|
|
320
708
|
} catch (error) {
|
|
321
709
|
const error_message = error instanceof Error ? error.message : 'Unknown error';
|
|
322
710
|
console.error('[hazo_chat/unread_count] Error:', error_message, error);
|
|
323
|
-
|
|
711
|
+
|
|
324
712
|
return NextResponse.json(
|
|
325
|
-
{
|
|
326
|
-
success: false,
|
|
713
|
+
{
|
|
714
|
+
success: false,
|
|
327
715
|
error: error_message,
|
|
328
716
|
unread_counts: []
|
|
329
717
|
},
|
|
@@ -333,25 +721,27 @@ export async function GET(request: NextRequest) {
|
|
|
333
721
|
}
|
|
334
722
|
```
|
|
335
723
|
|
|
336
|
-
**What this endpoint does:**
|
|
337
|
-
- Takes `
|
|
338
|
-
-
|
|
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
|
|
339
730
|
- Only counts messages where `read_at` is `null` and `deleted_at` is `null`
|
|
340
|
-
- Groups results by `
|
|
731
|
+
- Groups results by `chat_group_id`
|
|
341
732
|
- Sorts by count (descending - most unread first)
|
|
342
733
|
|
|
343
734
|
**Response format:**
|
|
344
735
|
```json
|
|
345
736
|
{
|
|
346
737
|
"success": true,
|
|
347
|
-
"
|
|
738
|
+
"user_id": "user-123",
|
|
348
739
|
"unread_counts": [
|
|
349
|
-
{ "
|
|
350
|
-
{ "
|
|
351
|
-
{ "reference_id": "", "count": 1 }
|
|
740
|
+
{ "chat_group_id": "group-1", "count": 5 },
|
|
741
|
+
{ "chat_group_id": "group-2", "count": 3 }
|
|
352
742
|
],
|
|
353
|
-
"
|
|
354
|
-
"total_unread":
|
|
743
|
+
"total_groups": 2,
|
|
744
|
+
"total_unread": 8
|
|
355
745
|
}
|
|
356
746
|
```
|
|
357
747
|
|
|
@@ -371,9 +761,9 @@ export async function GET(request: NextRequest) {
|
|
|
371
761
|
- [ ] All API route files exist
|
|
372
762
|
- [ ] `GET /api/hazo_auth/me` returns user data when logged in
|
|
373
763
|
- [ ] `POST /api/hazo_auth/profiles` returns profiles for given IDs
|
|
374
|
-
- [ ] `GET /api/hazo_chat/messages?
|
|
764
|
+
- [ ] `GET /api/hazo_chat/messages?chat_group_id=xxx` works (v3.0)
|
|
375
765
|
- [ ] `PATCH /api/hazo_chat/messages/[message-id]/read` marks message as read
|
|
376
|
-
- [ ] `GET /api/hazo_chat/unread_count?
|
|
766
|
+
- [ ] `GET /api/hazo_chat/unread_count?user_id=xxx` returns unread counts (v3.0, if implemented)
|
|
377
767
|
|
|
378
768
|
---
|
|
379
769
|
|
|
@@ -392,7 +782,7 @@ export default function ChatPage() {
|
|
|
392
782
|
return (
|
|
393
783
|
<div className="h-screen">
|
|
394
784
|
<HazoChat
|
|
395
|
-
|
|
785
|
+
chat_group_id="group-uuid-here"
|
|
396
786
|
title="Chat"
|
|
397
787
|
subtitle="Direct Message"
|
|
398
788
|
/>
|
|
@@ -411,7 +801,7 @@ import { HazoChat } from 'hazo_chat';
|
|
|
411
801
|
export default function ChatPage() {
|
|
412
802
|
return (
|
|
413
803
|
<HazoChat
|
|
414
|
-
|
|
804
|
+
chat_group_id="group-123"
|
|
415
805
|
reference_id="project-456"
|
|
416
806
|
reference_type="project_chat"
|
|
417
807
|
api_base_url="/api/hazo_chat"
|
|
@@ -419,11 +809,11 @@ export default function ChatPage() {
|
|
|
419
809
|
title="Project Discussion"
|
|
420
810
|
subtitle="Design Review"
|
|
421
811
|
additional_references={[
|
|
422
|
-
{
|
|
423
|
-
id: 'doc-1',
|
|
424
|
-
type: 'document',
|
|
812
|
+
{
|
|
813
|
+
id: 'doc-1',
|
|
814
|
+
type: 'document',
|
|
425
815
|
scope: 'field',
|
|
426
|
-
name: 'Design.pdf',
|
|
816
|
+
name: 'Design.pdf',
|
|
427
817
|
url: '/files/design.pdf'
|
|
428
818
|
}
|
|
429
819
|
]}
|
|
@@ -446,18 +836,18 @@ The component uses `h-full`, which requires its parent container to have a **def
|
|
|
446
836
|
|
|
447
837
|
```typescript
|
|
448
838
|
<div className="h-[600px]"> {/* ✅ Required: parent must have height */}
|
|
449
|
-
<HazoChat
|
|
839
|
+
<HazoChat chat_group_id={...} />
|
|
450
840
|
</div>
|
|
451
841
|
|
|
452
842
|
// OR
|
|
453
843
|
|
|
454
844
|
<div className="h-screen"> {/* ✅ Full screen height */}
|
|
455
|
-
<HazoChat
|
|
845
|
+
<HazoChat chat_group_id={...} />
|
|
456
846
|
</div>
|
|
457
847
|
|
|
458
848
|
// ❌ WRONG - will cause layout issues
|
|
459
849
|
<div> {/* No height defined */}
|
|
460
|
-
<HazoChat
|
|
850
|
+
<HazoChat chat_group_id={...} />
|
|
461
851
|
</div>
|
|
462
852
|
```
|
|
463
853
|
|
|
@@ -470,12 +860,12 @@ The component uses `h-full`, which requires its parent container to have a **def
|
|
|
470
860
|
```typescript
|
|
471
861
|
// ✅ Recommended: at least 500px width
|
|
472
862
|
<div className="w-[600px] h-[600px]">
|
|
473
|
-
<HazoChat
|
|
863
|
+
<HazoChat chat_group_id={...} />
|
|
474
864
|
</div>
|
|
475
865
|
|
|
476
866
|
// ✅ Narrow container: documents will open in new tab
|
|
477
867
|
<div className="w-[400px] h-[600px]">
|
|
478
|
-
<HazoChat
|
|
868
|
+
<HazoChat chat_group_id={...} />
|
|
479
869
|
</div>
|
|
480
870
|
```
|
|
481
871
|
|
|
@@ -490,8 +880,8 @@ export default function ChatPage() {
|
|
|
490
880
|
return (
|
|
491
881
|
<div className="w-[600px] h-[600px] flex-shrink-0">
|
|
492
882
|
<div className="rounded-xl border shadow-lg p-0 h-full">
|
|
493
|
-
<HazoChat
|
|
494
|
-
|
|
883
|
+
<HazoChat
|
|
884
|
+
chat_group_id="group-123"
|
|
495
885
|
title="Chat"
|
|
496
886
|
className="h-full"
|
|
497
887
|
/>
|
|
@@ -917,8 +1307,28 @@ For detailed specifications, see the [UI Design Standards](#ui-design-standards)
|
|
|
917
1307
|
- [ ] No TypeScript errors
|
|
918
1308
|
|
|
919
1309
|
### Database Verification
|
|
920
|
-
|
|
921
|
-
|
|
1310
|
+
|
|
1311
|
+
**PostgreSQL:**
|
|
1312
|
+
- [ ] UUID extension enabled (check with: `SELECT * FROM pg_extension WHERE extname = 'uuid-ossp' OR extname = 'pgcrypto';`)
|
|
1313
|
+
- [ ] Enum type created (if using): `SELECT typname FROM pg_type WHERE typname = 'hazo_chat_reference_type';`
|
|
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)
|
|
1318
|
+
- [ ] All indexes created (check with: `SELECT indexname FROM pg_indexes WHERE tablename = 'hazo_chat';`)
|
|
1319
|
+
- [ ] UUID default generation works: Test insert without providing ID
|
|
1320
|
+
- [ ] Timestamp defaults work: Test insert without providing timestamps
|
|
1321
|
+
- [ ] Boolean columns accept TRUE/FALSE values
|
|
1322
|
+
- [ ] Can insert and query messages
|
|
1323
|
+
- [ ] Foreign key constraints work (if enabled)
|
|
1324
|
+
|
|
1325
|
+
**SQLite:**
|
|
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
|
|
922
1332
|
- [ ] Can insert and query messages
|
|
923
1333
|
|
|
924
1334
|
### API Verification
|
|
@@ -934,11 +1344,11 @@ curl -X POST http://localhost:3000/api/hazo_auth/profiles \
|
|
|
934
1344
|
-H "Content-Type: application/json" \
|
|
935
1345
|
-d '{"user_ids": ["user-id-here"]}'
|
|
936
1346
|
|
|
937
|
-
# Test messages endpoint
|
|
938
|
-
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"
|
|
939
1349
|
|
|
940
|
-
# Test unread count endpoint (if implemented)
|
|
941
|
-
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"
|
|
942
1352
|
```
|
|
943
1353
|
|
|
944
1354
|
### UI Verification & Responsive Behavior
|
|
@@ -1009,10 +1419,11 @@ curl "http://localhost:3000/api/hazo_chat/unread_count?receiver_user_id=user-id"
|
|
|
1009
1419
|
|
|
1010
1420
|
**Checklist:**
|
|
1011
1421
|
1. Is user authenticated? Check `/api/hazo_auth/me`
|
|
1012
|
-
2. Is `
|
|
1013
|
-
3.
|
|
1014
|
-
4. Check
|
|
1015
|
-
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
|
|
1016
1427
|
|
|
1017
1428
|
### Issue: Hamburger button visible on desktop
|
|
1018
1429
|
|
|
@@ -1108,6 +1519,301 @@ curl "http://localhost:3000/api/hazo_chat/unread_count?receiver_user_id=user-id"
|
|
|
1108
1519
|
|
|
1109
1520
|
---
|
|
1110
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
|
+
|
|
1111
1817
|
## Quick Setup Summary
|
|
1112
1818
|
|
|
1113
1819
|
```bash
|