hazo_notes 1.1.6 → 2.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 +54 -11
- package/SETUP_CHECKLIST.md +28 -12
- package/config/hazo_notes_config.ini +48 -0
- package/db_setup_postgres.sql +49 -0
- package/db_setup_sqlite.sql +22 -0
- package/dist/api/create_files_handler.d.ts +1 -1
- package/dist/api/create_files_handler.d.ts.map +1 -1
- package/dist/api/create_files_handler.js +196 -139
- package/dist/api/create_files_handler.js.map +1 -1
- package/dist/api/create_notes_handler.d.ts.map +1 -1
- package/dist/api/create_notes_handler.js +246 -160
- package/dist/api/create_notes_handler.js.map +1 -1
- package/dist/components/hazo_notes_entry.js +1 -1
- package/dist/components/hazo_notes_entry.js.map +1 -1
- package/dist/components/hazo_notes_file_preview.js +1 -1
- package/dist/components/hazo_notes_file_preview.js.map +1 -1
- package/dist/components/hazo_notes_icon.js +1 -1
- package/dist/components/hazo_notes_icon.js.map +1 -1
- package/dist/components/hazo_notes_panel.js +1 -1
- package/dist/components/hazo_notes_panel.js.map +1 -1
- package/dist/components/internal/sheet.js +1 -1
- package/dist/components/internal/sheet.js.map +1 -1
- package/dist/index.client.d.ts +0 -1
- package/dist/index.client.d.ts.map +1 -1
- package/dist/index.client.js +2 -2
- package/dist/index.client.js.map +1 -1
- package/dist/lib/config/hazo_notes_config.d.ts +125 -0
- package/dist/lib/config/hazo_notes_config.d.ts.map +1 -0
- package/dist/lib/config/hazo_notes_config.js +76 -0
- package/dist/lib/config/hazo_notes_config.js.map +1 -0
- package/dist/lib/config/index.d.ts +3 -0
- package/dist/lib/config/index.d.ts.map +1 -0
- package/dist/lib/config/index.js +2 -0
- package/dist/lib/config/index.js.map +1 -0
- package/dist/lib/config.d.ts +14 -15
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +45 -93
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/logger/server.d.ts +13 -9
- package/dist/logger/server.d.ts.map +1 -1
- package/dist/logger/server.js +21 -15
- package/dist/logger/server.js.map +1 -1
- package/dist/logger/types.d.ts +3 -2
- package/dist/logger/types.d.ts.map +1 -1
- package/dist/logger/types.js +2 -1
- package/dist/logger/types.js.map +1 -1
- package/dist/types/index.d.ts +31 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +30 -15
- package/dist/utils/cn.d.ts +0 -16
- package/dist/utils/cn.d.ts.map +0 -1
- package/dist/utils/cn.js +0 -19
- package/dist/utils/cn.js.map +0 -1
- package/migrations/001_create_hazo_notes_table.sql +0 -77
package/README.md
CHANGED
|
@@ -70,21 +70,24 @@ Create `app/api/hazo_notes/[ref_id]/route.ts`:
|
|
|
70
70
|
```typescript
|
|
71
71
|
import { createNotesHandler } from 'hazo_notes/api';
|
|
72
72
|
import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
|
|
73
|
-
|
|
74
|
-
import { getSession } from '@/lib/auth'; // Replace with your auth
|
|
73
|
+
import { hazo_get_tenant_auth } from 'hazo_auth/server-lib';
|
|
75
74
|
import { getUserById } from '@/lib/users'; // Replace with your user lookup
|
|
75
|
+
import type { NextRequest } from 'next/server';
|
|
76
76
|
|
|
77
77
|
export const dynamic = 'force-dynamic';
|
|
78
78
|
|
|
79
79
|
const { GET, POST } = createNotesHandler({
|
|
80
80
|
getHazoConnect: () => getHazoConnectSingleton(),
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
81
|
+
// Preferred: hazo_auth@6.x tenant auth. Used by both GET and POST.
|
|
82
|
+
getAuth: async (req) => {
|
|
83
|
+
const auth = await hazo_get_tenant_auth(req as NextRequest);
|
|
84
|
+
if (!auth.authenticated || !auth.user?.id) return null;
|
|
85
|
+
return {
|
|
86
|
+
user_id: auth.user.id,
|
|
87
|
+
scope_id: auth.selected_scope_id ?? null,
|
|
88
|
+
};
|
|
85
89
|
},
|
|
86
90
|
getUserProfile: async (userId) => {
|
|
87
|
-
// IMPORTANT: Replace with your user profile lookup
|
|
88
91
|
const user = await getUserById(userId);
|
|
89
92
|
return {
|
|
90
93
|
id: userId,
|
|
@@ -98,6 +101,15 @@ const { GET, POST } = createNotesHandler({
|
|
|
98
101
|
export { GET, POST };
|
|
99
102
|
```
|
|
100
103
|
|
|
104
|
+
> **GET requires auth.** Starting in `1.2.0`, GET responds `401` when the auth
|
|
105
|
+
> resolver returns `null`, matching POST. Older releases left GET anonymous.
|
|
106
|
+
> If you're still on `hazo_auth@5.x`, use `getUserIdFromRequest` instead of
|
|
107
|
+
> `getAuth` — the package falls back to it for backward compatibility.
|
|
108
|
+
|
|
109
|
+
> **Error detail.** On `500` responses the underlying error message is
|
|
110
|
+
> included as `cause` in dev (when `NODE_ENV !== 'production'`). Set
|
|
111
|
+
> `expose_error_cause: false` to suppress, or `true` to force it on in prod.
|
|
112
|
+
|
|
101
113
|
### 3. Set Up Database
|
|
102
114
|
|
|
103
115
|
Run the migration:
|
|
@@ -114,6 +126,11 @@ CREATE TABLE hazo_notes (
|
|
|
114
126
|
);
|
|
115
127
|
|
|
116
128
|
CREATE INDEX idx_hazo_notes_ref_id ON hazo_notes(ref_id);
|
|
129
|
+
|
|
130
|
+
-- REQUIRED for PostgREST consumers — without this, GET/POST return 500 with
|
|
131
|
+
-- cause "permission denied for table hazo_notes" (PostgREST error 42501).
|
|
132
|
+
-- Shipped as migrations/002_grant_api_user_hazo_notes.sql.
|
|
133
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON hazo_notes TO api_user;
|
|
117
134
|
```
|
|
118
135
|
|
|
119
136
|
**SQLite:**
|
|
@@ -672,17 +689,43 @@ getUserProfile: async (userId) => {
|
|
|
672
689
|
|
|
673
690
|
### Authentication errors
|
|
674
691
|
|
|
675
|
-
**Problem**:
|
|
692
|
+
**Problem**: `401 Unauthorized` on GET or POST.
|
|
693
|
+
|
|
694
|
+
**Solution**: Wire `getAuth` (preferred, hazo_auth@6.x) or `getUserIdFromRequest`
|
|
695
|
+
(hazo_auth@5.x). GET and POST both require a non-null user; both return `401`
|
|
696
|
+
if the resolver returns `null`.
|
|
676
697
|
|
|
677
|
-
**Solution**: Implement `getUserIdFromRequest` to return authenticated user ID:
|
|
678
698
|
```typescript
|
|
699
|
+
// hazo_auth@6.x — preferred
|
|
700
|
+
getAuth: async (req) => {
|
|
701
|
+
const auth = await hazo_get_tenant_auth(req as NextRequest);
|
|
702
|
+
if (!auth.authenticated || !auth.user?.id) return null;
|
|
703
|
+
return { user_id: auth.user.id, scope_id: auth.selected_scope_id ?? null };
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// hazo_auth@5.x — backward-compatible fallback
|
|
679
707
|
getUserIdFromRequest: async (req) => {
|
|
680
708
|
const session = await getSession(req);
|
|
681
|
-
|
|
682
|
-
return session.user.id;
|
|
709
|
+
return session?.user?.id ?? null;
|
|
683
710
|
}
|
|
684
711
|
```
|
|
685
712
|
|
|
713
|
+
### PostgREST `permission denied for table hazo_notes` (500 with cause 42501)
|
|
714
|
+
|
|
715
|
+
**Problem**: GET/POST return 500. Response includes
|
|
716
|
+
`"cause": "permission denied for table hazo_notes"` (when `expose_error_cause`
|
|
717
|
+
is enabled).
|
|
718
|
+
|
|
719
|
+
**Solution**: Apply `migrations/002_grant_api_user_hazo_notes.sql`. It grants
|
|
720
|
+
`SELECT, INSERT, UPDATE, DELETE` on `hazo_notes` to the `api_user` role used
|
|
721
|
+
by PostgREST. Reproducing the underlying error directly:
|
|
722
|
+
|
|
723
|
+
```bash
|
|
724
|
+
curl "$POSTGREST_URL/hazo_notes?limit=1"
|
|
725
|
+
# Before grant: 401 / "permission denied for table hazo_notes"
|
|
726
|
+
# After grant: [] or rows
|
|
727
|
+
```
|
|
728
|
+
|
|
686
729
|
## Examples
|
|
687
730
|
|
|
688
731
|
See the `test-app/` directory for complete working examples:
|
package/SETUP_CHECKLIST.md
CHANGED
|
@@ -66,15 +66,25 @@ CREATE TABLE IF NOT EXISTS hazo_notes (
|
|
|
66
66
|
CREATE INDEX IF NOT EXISTS idx_hazo_notes_ref_id ON hazo_notes(ref_id);
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
**Alternative**: Use the provided migration
|
|
69
|
+
**Alternative**: Use the provided migration files:
|
|
70
70
|
```bash
|
|
71
|
-
# PostgreSQL
|
|
71
|
+
# PostgreSQL — apply both files in order
|
|
72
72
|
psql -d your_database -f node_modules/hazo_notes/migrations/001_create_hazo_notes_table.sql
|
|
73
|
+
psql -d your_database -f node_modules/hazo_notes/migrations/002_grant_api_user_hazo_notes.sql
|
|
73
74
|
|
|
74
|
-
# SQLite
|
|
75
|
+
# SQLite — only 001 (SQLite has no role system, skip 002)
|
|
75
76
|
sqlite3 your_database.db < node_modules/hazo_notes/migrations/001_create_hazo_notes_table.sql
|
|
76
77
|
```
|
|
77
78
|
|
|
79
|
+
> **PostgREST consumers MUST apply migration 002.** Without it, PostgREST
|
|
80
|
+
> returns `42501 / permission denied for table hazo_notes` and the API
|
|
81
|
+
> surfaces a generic 500. Migration 002 grants
|
|
82
|
+
> `SELECT, INSERT, UPDATE, DELETE` on `hazo_notes` to `api_user`. Verify with:
|
|
83
|
+
>
|
|
84
|
+
> ```bash
|
|
85
|
+
> curl "$POSTGREST_URL/hazo_notes?limit=1" # should return [] not 401
|
|
86
|
+
> ```
|
|
87
|
+
|
|
78
88
|
## API Routes
|
|
79
89
|
|
|
80
90
|
### 4. Create the notes API route
|
|
@@ -84,25 +94,27 @@ Create `app/api/hazo_notes/[ref_id]/route.ts`:
|
|
|
84
94
|
```typescript
|
|
85
95
|
import { createNotesHandler } from 'hazo_notes/api';
|
|
86
96
|
import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup';
|
|
87
|
-
|
|
88
|
-
import { getSession } from '@/lib/auth';
|
|
97
|
+
import { hazo_get_tenant_auth } from 'hazo_auth/server-lib';
|
|
89
98
|
import { getUserById } from '@/lib/users';
|
|
99
|
+
import type { NextRequest } from 'next/server';
|
|
90
100
|
|
|
91
101
|
export const dynamic = 'force-dynamic';
|
|
92
102
|
|
|
93
103
|
const { GET, POST } = createNotesHandler({
|
|
94
104
|
getHazoConnect: () => getHazoConnectSingleton(),
|
|
95
105
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return
|
|
106
|
+
// Preferred (hazo_auth@6.x). Used by BOTH GET and POST — both require auth.
|
|
107
|
+
getAuth: async (req) => {
|
|
108
|
+
const auth = await hazo_get_tenant_auth(req as NextRequest);
|
|
109
|
+
if (!auth.authenticated || !auth.user?.id) return null;
|
|
110
|
+
return {
|
|
111
|
+
user_id: auth.user.id,
|
|
112
|
+
scope_id: auth.selected_scope_id ?? null,
|
|
113
|
+
};
|
|
101
114
|
},
|
|
102
115
|
|
|
103
116
|
getUserProfile: async (userId) => {
|
|
104
117
|
// IMPORTANT: Replace this with your actual user profile lookup
|
|
105
|
-
// This function fetches user details for attribution
|
|
106
118
|
const user = await getUserById(userId);
|
|
107
119
|
return {
|
|
108
120
|
id: userId,
|
|
@@ -117,9 +129,13 @@ export { GET, POST };
|
|
|
117
129
|
```
|
|
118
130
|
|
|
119
131
|
**Important Notes**:
|
|
120
|
-
-
|
|
132
|
+
- Either `getAuth` (hazo_auth@6.x) or `getUserIdFromRequest` (hazo_auth@5.x)
|
|
133
|
+
MUST be provided. GET and POST both return `401` if the resolver returns
|
|
134
|
+
`null`. GET-anonymous behavior from `1.1.x` is gone in `1.2.0`.
|
|
121
135
|
- The `getUserProfile` function enriches notes with user information
|
|
122
136
|
- If you don't have user profiles, you can omit `getUserProfile`
|
|
137
|
+
- On a 500 response, the underlying error message is included as `cause`
|
|
138
|
+
in non-production. Set `expose_error_cause: false` to suppress.
|
|
123
139
|
|
|
124
140
|
### 5. (Optional) Create the files API route for file attachments
|
|
125
141
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# hazo_notes Configuration
|
|
2
|
+
# This file configures the notes system behavior and storage
|
|
3
|
+
#
|
|
4
|
+
# Env-var overrides follow the pattern HAZO_NOTES_<SECTION>_<KEY> (uppercase).
|
|
5
|
+
# Per-env overlays: place hazo_notes_config.<HAZO_ENV>.ini next to this file.
|
|
6
|
+
|
|
7
|
+
[general]
|
|
8
|
+
# Optional override for the deployment environment. Defaults to
|
|
9
|
+
# HAZO_ENV → NODE_ENV → 'development' (resolved by hazo_core).
|
|
10
|
+
# env = production
|
|
11
|
+
|
|
12
|
+
[log.overrides]
|
|
13
|
+
# Per-namespace log level overrides (per D-015). Format:
|
|
14
|
+
# <namespace> = <level>
|
|
15
|
+
# Examples:
|
|
16
|
+
# hazo_notes/poll = warn
|
|
17
|
+
# hazo_notes = debug
|
|
18
|
+
|
|
19
|
+
[ui]
|
|
20
|
+
# Background color for notes panel (Tailwind CSS class)
|
|
21
|
+
background_color = bg-yellow-100
|
|
22
|
+
|
|
23
|
+
# Panel presentation style: popover | slide_panel
|
|
24
|
+
panel_style = popover
|
|
25
|
+
|
|
26
|
+
# Save behavior: explicit (save/cancel buttons) | auto (save on blur)
|
|
27
|
+
save_mode = explicit
|
|
28
|
+
|
|
29
|
+
[storage]
|
|
30
|
+
# File storage mode: jsonb (in database) | filesystem (on server)
|
|
31
|
+
file_storage_mode = jsonb
|
|
32
|
+
|
|
33
|
+
# Path for filesystem storage (only used when file_storage_mode = filesystem)
|
|
34
|
+
file_storage_path = /uploads/notes
|
|
35
|
+
|
|
36
|
+
[files]
|
|
37
|
+
# Maximum file size in MB
|
|
38
|
+
max_file_size_mb = 10
|
|
39
|
+
|
|
40
|
+
# Allowed file types (comma-separated extensions)
|
|
41
|
+
allowed_file_types = pdf,png,jpg,jpeg,gif,doc,docx
|
|
42
|
+
|
|
43
|
+
# Maximum files per single note entry
|
|
44
|
+
max_files_per_note = 5
|
|
45
|
+
|
|
46
|
+
[logging]
|
|
47
|
+
# Log file path
|
|
48
|
+
logfile = logs/hazo_notes.log
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- hazo_notes database schema
|
|
2
|
+
-- PostgreSQL -- idempotent (safe to run multiple times)
|
|
3
|
+
-- Run this file to set up all tables required by hazo_notes
|
|
4
|
+
--
|
|
5
|
+
-- Tables: hazo_notes
|
|
6
|
+
|
|
7
|
+
-- ============================================================
|
|
8
|
+
-- hazo_notes
|
|
9
|
+
-- Database-backed notes linked to any entity via ref_id.
|
|
10
|
+
-- Notes are stored as a JSONB array with user attribution and
|
|
11
|
+
-- optional file attachments.
|
|
12
|
+
-- ============================================================
|
|
13
|
+
|
|
14
|
+
CREATE TABLE IF NOT EXISTS hazo_notes (
|
|
15
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
16
|
+
ref_id UUID NOT NULL,
|
|
17
|
+
note JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
18
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
19
|
+
changed_at TIMESTAMPTZ NULL,
|
|
20
|
+
note_count INTEGER NOT NULL DEFAULT 0
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_notes_ref_id ON hazo_notes(ref_id);
|
|
24
|
+
|
|
25
|
+
COMMENT ON TABLE hazo_notes IS 'Notes linked to any entity via ref_id. note column is a JSONB array of {userid, created_at, note_text, note_files}.';
|
|
26
|
+
COMMENT ON COLUMN hazo_notes.id IS 'Primary key UUID';
|
|
27
|
+
COMMENT ON COLUMN hazo_notes.ref_id IS 'UUID reference to the parent entity (e.g. form field, document, task)';
|
|
28
|
+
COMMENT ON COLUMN hazo_notes.note IS 'JSONB array of note entries';
|
|
29
|
+
COMMENT ON COLUMN hazo_notes.created_at IS 'Timestamp when the notes row was first created';
|
|
30
|
+
COMMENT ON COLUMN hazo_notes.changed_at IS 'Timestamp of the last modification to the notes';
|
|
31
|
+
COMMENT ON COLUMN hazo_notes.note_count IS 'Denormalised count of notes, kept in sync on write to avoid parsing JSONB on read';
|
|
32
|
+
|
|
33
|
+
-- ============================================================
|
|
34
|
+
-- PostgREST role grants
|
|
35
|
+
-- Required for the bundled API handlers, which talk to PostgREST.
|
|
36
|
+
-- The api_user role must exist beforehand (created by hazo_connect setup).
|
|
37
|
+
-- ============================================================
|
|
38
|
+
|
|
39
|
+
DO $$ BEGIN
|
|
40
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'api_user') THEN
|
|
41
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON hazo_notes TO api_user;
|
|
42
|
+
END IF;
|
|
43
|
+
END $$;
|
|
44
|
+
|
|
45
|
+
-- ============================================================
|
|
46
|
+
-- Notify PostgREST to reload the schema cache
|
|
47
|
+
-- ============================================================
|
|
48
|
+
|
|
49
|
+
SELECT pg_notify('pgrst', 'reload schema');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
-- hazo_notes database schema
|
|
2
|
+
-- SQLite -- idempotent (safe to run multiple times)
|
|
3
|
+
-- Run this file to set up all tables required by hazo_notes
|
|
4
|
+
--
|
|
5
|
+
-- Tables: hazo_notes
|
|
6
|
+
|
|
7
|
+
-- ============================================================
|
|
8
|
+
-- hazo_notes
|
|
9
|
+
-- Notes linked to any entity via ref_id. The note column is a
|
|
10
|
+
-- JSON-encoded array stored as TEXT (SQLite has no native JSONB).
|
|
11
|
+
-- ============================================================
|
|
12
|
+
|
|
13
|
+
CREATE TABLE IF NOT EXISTS hazo_notes (
|
|
14
|
+
id TEXT PRIMARY KEY,
|
|
15
|
+
ref_id TEXT NOT NULL,
|
|
16
|
+
note TEXT NOT NULL DEFAULT '[]',
|
|
17
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
18
|
+
changed_at TEXT,
|
|
19
|
+
note_count INTEGER NOT NULL DEFAULT 0
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_notes_ref_id ON hazo_notes(ref_id);
|
|
@@ -32,7 +32,7 @@ import type { CreateFilesHandlerOptions, FileUploadApiResponse } from '../types/
|
|
|
32
32
|
* @returns Object with POST (upload) and GET (download) handlers
|
|
33
33
|
*/
|
|
34
34
|
export declare function createFilesHandler(options: CreateFilesHandlerOptions): {
|
|
35
|
-
GET: (request: Request,
|
|
35
|
+
GET: (request: Request, _context: {
|
|
36
36
|
params: Promise<{
|
|
37
37
|
file_id?: string;
|
|
38
38
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create_files_handler.d.ts","sourceRoot":"","sources":["../../src/api/create_files_handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"create_files_handler.d.ts","sourceRoot":"","sources":["../../src/api/create_files_handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAU3C,OAAO,KAAK,EACV,yBAAyB,EAEzB,qBAAqB,EAEtB,MAAM,mBAAmB,CAAC;AAqC3B;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB;mBAgKxD,OAAO,YACN;QAAE,MAAM,EAAE,OAAO,CAAC;YAAE,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,KAClD,OAAO,CAAC,YAAY,CAAC;oBA5IK,OAAO,KAAG,OAAO,CAAC,YAAY,CAAC,qBAAqB,CAAC,CAAC;EA0OpF"}
|