hazo_admin 0.7.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGE_LOG.md +60 -0
  2. package/README.md +65 -4
  3. package/SETUP_CHECKLIST.md +24 -0
  4. package/dist/api/index.d.ts +6 -1
  5. package/dist/api/index.d.ts.map +1 -1
  6. package/dist/api/index.js +166 -2
  7. package/dist/components/issues_panel/board_columns.d.ts +17 -0
  8. package/dist/components/issues_panel/board_columns.d.ts.map +1 -0
  9. package/dist/components/issues_panel/board_columns.js +37 -0
  10. package/dist/components/issues_panel/card_assignee_control.d.ts +17 -0
  11. package/dist/components/issues_panel/card_assignee_control.d.ts.map +1 -0
  12. package/dist/components/issues_panel/card_assignee_control.js +51 -0
  13. package/dist/components/issues_panel/card_type_control.d.ts +18 -0
  14. package/dist/components/issues_panel/card_type_control.d.ts.map +1 -0
  15. package/dist/components/issues_panel/card_type_control.js +42 -0
  16. package/dist/components/issues_panel/facet_sidebar.d.ts +25 -0
  17. package/dist/components/issues_panel/facet_sidebar.d.ts.map +1 -0
  18. package/dist/components/issues_panel/facet_sidebar.js +72 -0
  19. package/dist/components/issues_panel/facet_topbar.d.ts +20 -0
  20. package/dist/components/issues_panel/facet_topbar.d.ts.map +1 -0
  21. package/dist/components/issues_panel/facet_topbar.js +42 -0
  22. package/dist/components/issues_panel/filter.d.ts +12 -0
  23. package/dist/components/issues_panel/filter.d.ts.map +1 -0
  24. package/dist/components/issues_panel/filter.js +41 -0
  25. package/dist/components/issues_panel/index.d.ts.map +1 -1
  26. package/dist/components/issues_panel/index.js +145 -43
  27. package/dist/components/issues_panel/manage_types_dialog.d.ts +28 -0
  28. package/dist/components/issues_panel/manage_types_dialog.d.ts.map +1 -0
  29. package/dist/components/issues_panel/manage_types_dialog.js +84 -0
  30. package/dist/components/issues_panel/ui_helpers.d.ts +21 -0
  31. package/dist/components/issues_panel/ui_helpers.d.ts.map +1 -0
  32. package/dist/components/issues_panel/ui_helpers.js +136 -0
  33. package/dist/components/issues_panel/use_issue_types.d.ts +30 -0
  34. package/dist/components/issues_panel/use_issue_types.d.ts.map +1 -0
  35. package/dist/components/issues_panel/use_issue_types.js +81 -0
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/index.ui.d.ts +2 -0
  40. package/dist/index.ui.d.ts.map +1 -1
  41. package/dist/index.ui.js +1 -0
  42. package/dist/issues/archive_handler.d.ts +1 -1
  43. package/dist/issues/archive_handler.js +2 -2
  44. package/dist/issues/index.d.ts +6 -0
  45. package/dist/issues/index.d.ts.map +1 -1
  46. package/dist/issues/index.js +4 -0
  47. package/dist/issues/notify_handler.d.ts +26 -0
  48. package/dist/issues/notify_handler.d.ts.map +1 -0
  49. package/dist/issues/notify_handler.js +97 -0
  50. package/dist/issues/raise.d.ts +44 -0
  51. package/dist/issues/raise.d.ts.map +1 -0
  52. package/dist/issues/raise.js +77 -0
  53. package/dist/issues/recipients.d.ts +20 -0
  54. package/dist/issues/recipients.d.ts.map +1 -0
  55. package/dist/issues/recipients.js +61 -0
  56. package/dist/issues/registry.client.d.ts +1 -0
  57. package/dist/issues/registry.client.d.ts.map +1 -1
  58. package/dist/issues/registry.client.js +4 -1
  59. package/dist/issues/registry.d.ts +3 -3
  60. package/dist/issues/registry.d.ts.map +1 -1
  61. package/dist/issues/store.d.ts +10 -5
  62. package/dist/issues/store.d.ts.map +1 -1
  63. package/dist/issues/store.js +58 -28
  64. package/dist/issues/type_catalog.d.ts +48 -0
  65. package/dist/issues/type_catalog.d.ts.map +1 -0
  66. package/dist/issues/type_catalog.js +185 -0
  67. package/package.json +5 -5
@@ -0,0 +1,185 @@
1
+ import 'server-only';
2
+ import { createLogger } from 'hazo_core';
3
+ const log = createLogger('hazo_admin:issues');
4
+ const TABLE = 'hazo_admin_issue_types';
5
+ export function toIssueTypeCatalogRecord(raw) {
6
+ return {
7
+ type_key: raw.type_key,
8
+ label: raw.label,
9
+ color: raw.color ?? null,
10
+ description: raw.description ?? null,
11
+ scope_id: raw.scope_id ?? null,
12
+ sort_order: raw.sort_order ?? 0,
13
+ created_at: raw.created_at,
14
+ updated_at: raw.updated_at,
15
+ };
16
+ }
17
+ /** Columns updateType is allowed to write. Guards against attacker-controlled
18
+ * identifiers in the dynamic SET clause / CRUD patch (keys arrive from the
19
+ * PATCH request body). Unknown keys are dropped, never interpolated into SQL. */
20
+ const UPDATABLE_COLUMNS = ['label', 'color', 'description', 'sort_order'];
21
+ function pickUpdatableFields(patch) {
22
+ const out = {};
23
+ for (const k of UPDATABLE_COLUMNS) {
24
+ if (Object.prototype.hasOwnProperty.call(patch, k)) {
25
+ out[k] = patch[k];
26
+ }
27
+ }
28
+ return out;
29
+ }
30
+ function sortTypes(rows) {
31
+ return [...rows].sort((a, b) => {
32
+ if (a.sort_order !== b.sort_order)
33
+ return a.sort_order - b.sort_order;
34
+ return a.label.localeCompare(b.label);
35
+ });
36
+ }
37
+ export function createIssueTypeCatalogStore(adapter) {
38
+ function now() {
39
+ return new Date().toISOString();
40
+ }
41
+ async function raw(sql, params = []) {
42
+ return adapter.raw(sql, params);
43
+ }
44
+ async function listTypes(opts) {
45
+ const { scopeIds, isGlobalAdmin = false } = opts;
46
+ if (isGlobalAdmin || !scopeIds || scopeIds.length === 0) {
47
+ const rows = await raw(`SELECT * FROM ${TABLE} ORDER BY sort_order ASC, label ASC`);
48
+ return (rows ?? []).map(toIssueTypeCatalogRecord);
49
+ }
50
+ const placeholders = scopeIds.map((_, i) => `$${i + 1}`).join(', ');
51
+ const rows = await raw(`SELECT * FROM ${TABLE} WHERE scope_id IN (${placeholders}) OR scope_id IS NULL
52
+ ORDER BY sort_order ASC, label ASC`, scopeIds);
53
+ return (rows ?? []).map(toIssueTypeCatalogRecord);
54
+ }
55
+ async function getType(type_key) {
56
+ const rows = await raw(`SELECT * FROM ${TABLE} WHERE type_key = $1 LIMIT 1`, [type_key]);
57
+ if (!rows || rows.length === 0)
58
+ return null;
59
+ return toIssueTypeCatalogRecord(rows[0]);
60
+ }
61
+ async function createType(input) {
62
+ log.debug('createType', { type_key: input.type_key });
63
+ const ts = now();
64
+ const inserted = await raw(`INSERT INTO ${TABLE} (
65
+ type_key, label, color, description, scope_id, sort_order, created_at, updated_at
66
+ ) VALUES (
67
+ $1, $2, $3, $4, $5, $6, $7, $8
68
+ ) RETURNING *`, [
69
+ input.type_key,
70
+ input.label,
71
+ input.color ?? null,
72
+ input.description ?? null,
73
+ input.scope_id ?? null,
74
+ input.sort_order ?? 0,
75
+ ts,
76
+ ts,
77
+ ]);
78
+ const row = inserted && inserted.length > 0 ? inserted[0] : null;
79
+ if (!row)
80
+ throw new Error('hazo_admin:issues — INSERT returned no row for issue type');
81
+ return toIssueTypeCatalogRecord(row);
82
+ }
83
+ async function updateType(type_key, patch) {
84
+ const ts = now();
85
+ const fullPatch = { ...pickUpdatableFields(patch), updated_at: ts };
86
+ const setClauses = Object.keys(fullPatch)
87
+ .map((k, i) => `${k} = $${i + 2}`)
88
+ .join(', ');
89
+ const updated = await raw(`UPDATE ${TABLE} SET ${setClauses} WHERE type_key = $1 RETURNING *`, [type_key, ...Object.values(fullPatch)]);
90
+ const row = updated && updated.length > 0 ? updated[0] : null;
91
+ if (!row)
92
+ throw new Error(`hazo_admin:issues — issue type ${type_key} not found`);
93
+ return toIssueTypeCatalogRecord(row);
94
+ }
95
+ async function deleteType(type_key) {
96
+ log.debug('deleteType', { type_key });
97
+ await raw(`DELETE FROM ${TABLE} WHERE type_key = $1`, [type_key]);
98
+ }
99
+ return {
100
+ listTypes,
101
+ getType,
102
+ createType,
103
+ updateType,
104
+ deleteType,
105
+ };
106
+ }
107
+ /**
108
+ * PostgREST-native implementation of IssueTypeCatalogStore.
109
+ * Uses createCrudService so no raw SQL is issued.
110
+ * Falls back to createIssueTypeCatalogStore when the adapter exposes a raw()
111
+ * method (i.e. it is a direct-DB adapter that already handles SQL).
112
+ *
113
+ * Returns a Promise because it lazily imports hazo_connect/server on the
114
+ * CRUD path (avoiding loading the full hazo_connect stack for raw adapters).
115
+ */
116
+ export async function createIssueTypeCatalogStoreFromConnect(adapter) {
117
+ // Capability sniff: direct-DB adapters have a raw() method — use the raw store.
118
+ if (typeof adapter.raw === 'function') {
119
+ return createIssueTypeCatalogStore(adapter);
120
+ }
121
+ // Lazy import to avoid pulling hazo_connect into server-only environments
122
+ // that don't have it installed (e.g., direct-SQL test adapters).
123
+ const { createCrudService } = await import('hazo_connect/server');
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ const crud = createCrudService(adapter, TABLE);
126
+ function now() {
127
+ return new Date().toISOString();
128
+ }
129
+ async function listTypes(opts) {
130
+ const { scopeIds, isGlobalAdmin = false } = opts;
131
+ // The query builder cannot express `scope_id IN (...) OR scope_id IS NULL`,
132
+ // so fetch broadly and filter/merge in-memory when scoped.
133
+ const qb = crud.query();
134
+ const rows = await qb.execute('GET');
135
+ const all = (Array.isArray(rows) ? rows : []).map((r) => toIssueTypeCatalogRecord(r));
136
+ if (isGlobalAdmin || !scopeIds || scopeIds.length === 0) {
137
+ return sortTypes(all);
138
+ }
139
+ const filtered = all.filter((t) => t.scope_id === null || scopeIds.includes(t.scope_id));
140
+ return sortTypes(filtered);
141
+ }
142
+ async function getType(type_key) {
143
+ const row = await crud.findById(type_key);
144
+ if (!row)
145
+ return null;
146
+ return toIssueTypeCatalogRecord(row);
147
+ }
148
+ async function createType(input) {
149
+ log.debug('createIssueTypeCatalogStoreFromConnect:createType', { type_key: input.type_key });
150
+ const ts = now();
151
+ const inserted = await crud.insert({
152
+ type_key: input.type_key,
153
+ label: input.label,
154
+ color: input.color ?? null,
155
+ description: input.description ?? null,
156
+ scope_id: input.scope_id ?? null,
157
+ sort_order: input.sort_order ?? 0,
158
+ created_at: ts,
159
+ updated_at: ts,
160
+ });
161
+ const row = Array.isArray(inserted) ? inserted[0] : inserted;
162
+ if (!row)
163
+ throw new Error('hazo_admin:issues — INSERT returned no row for issue type');
164
+ return toIssueTypeCatalogRecord(row);
165
+ }
166
+ async function updateType(type_key, patch) {
167
+ const ts = now();
168
+ const updated = await crud.updateById(type_key, { ...pickUpdatableFields(patch), updated_at: ts });
169
+ const row = Array.isArray(updated) ? updated[0] : updated;
170
+ if (!row)
171
+ throw new Error(`hazo_admin:issues — issue type ${type_key} not found`);
172
+ return toIssueTypeCatalogRecord(row);
173
+ }
174
+ async function deleteType(type_key) {
175
+ log.debug('createIssueTypeCatalogStoreFromConnect:deleteType', { type_key });
176
+ await crud.deleteById(type_key);
177
+ }
178
+ return {
179
+ listTypes,
180
+ getType,
181
+ createType,
182
+ updateType,
183
+ deleteType,
184
+ };
185
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_admin",
3
- "version": "0.7.0",
3
+ "version": "0.11.0",
4
4
  "description": "Standard site-admin package — auth-gated admin shell + panel kit + drop-in /admin preset",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
@@ -56,7 +56,7 @@
56
56
  "react": "^18.0.0 || ^19.0.0",
57
57
  "react-dom": "^18.0.0 || ^19.0.0",
58
58
  "next": "^14.0.0 || ^16.0.0",
59
- "hazo_auth": "^10.4.2",
59
+ "hazo_auth": "^10.5.0",
60
60
  "hazo_logs": "^2.0.2",
61
61
  "hazo_debug": "^3.1.1",
62
62
  "hazo_connect": "^3.7.0",
@@ -64,7 +64,7 @@
64
64
  "hazo_api": "^2.3.1",
65
65
  "hazo_files": "^3.0.0",
66
66
  "hazo_jobs": "^0.12.0",
67
- "hazo_env": "^0.2.0",
67
+ "hazo_env": "^0.6.0",
68
68
  "hazo_audit": "^2.1.1",
69
69
  "hazo_feedback": "^2.2.0",
70
70
  "hazo_secure": "^1.2.0",
@@ -131,8 +131,8 @@
131
131
  "@types/react": "^19.0.0",
132
132
  "@types/react-dom": "^19.0.0",
133
133
  "hazo_core": "^1.2.1",
134
- "hazo_ui": "^4.6.2",
135
- "hazo_auth": "^10.4.2",
134
+ "hazo_ui": "^4.7.0",
135
+ "hazo_auth": "^10.5.0",
136
136
  "hazo_testing": "^0.3.1",
137
137
  "react": "^19.0.0",
138
138
  "react-dom": "^19.0.0",