hazo_admin 0.5.0 → 0.6.1
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/CHANGE_LOG.md +14 -0
- package/README.md +18 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/issues/index.d.ts +1 -1
- package/dist/issues/index.d.ts.map +1 -1
- package/dist/issues/index.js +1 -1
- package/dist/issues/store.d.ts +11 -0
- package/dist/issues/store.d.ts.map +1 -1
- package/dist/issues/store.js +175 -1
- package/package.json +7 -7
package/CHANGE_LOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# hazo_admin Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.0] - 2026-06-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **`createIssueStoreFromConnect(adapter)`** — PostgREST-native `IssueStore` implementation that uses `createCrudService` + `adapter.claimRows` instead of raw SQL. Enables the issue store to operate over PostgREST without a direct DB connection.
|
|
7
|
+
- Capability sniff: if the adapter exposes a `.raw()` method (direct-DB adapter), delegates to the existing `createIssueStore` transparently.
|
|
8
|
+
- `createOrBumpIssue`: atomic CAS bump via `claimRows`; falls back to `crud.insert` on no-match.
|
|
9
|
+
- `listIssues`: uses `crud.query()` builder with `.where()` / `.order()` / `.limit()` / `.offset()`.
|
|
10
|
+
- `getIssue`: `crud.findById`.
|
|
11
|
+
- `transitionStatus` / `setAssignee` / `resolveIssue`: `crud.updateById` with the same legal-transition guard as `createIssueStore`.
|
|
12
|
+
- `archiveClosedOlderThan`: batch `claimRows` with `status: closed` + `resolved_at: { lte: cutoff }`.
|
|
13
|
+
- Lazily imports `hazo_connect/server` (avoids loading the full hazo_connect stack for raw adapters in test environments).
|
|
14
|
+
- Exported from `hazo_admin`, `hazo_admin/issues`.
|
|
15
|
+
- 20 new Jest tests in `src/issues/__tests__/store_connect.test.ts`.
|
|
16
|
+
|
|
3
17
|
## [0.5.0] - 2026-06-19
|
|
4
18
|
|
|
5
19
|
### Added
|
package/README.md
CHANGED
|
@@ -234,6 +234,23 @@ import { createJobQueue } from 'hazo_jobs';
|
|
|
234
234
|
registerIssueArchiveJob(jobQueue, { getHazoConnect, archiveAfterMonths: 3 });
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
+
### `createIssueStoreFromConnect(adapter)` — PostgREST-native store (v0.6.0)
|
|
238
|
+
|
|
239
|
+
Use this factory when the consuming app connects via a PostgREST adapter (no direct SQL access). It builds the same `IssueStore` interface using `createCrudService` + `adapter.claimRows` so no raw SQL is issued.
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
import { createIssueStoreFromConnect } from 'hazo_admin';
|
|
243
|
+
|
|
244
|
+
// In your route handler or service bootstrap:
|
|
245
|
+
const store = await createIssueStoreFromConnect(adapter);
|
|
246
|
+
// store implements the full IssueStore interface
|
|
247
|
+
const { issue, isNew } = await store.createOrBumpIssue({ ... });
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Capability sniff:** if the adapter exposes a `.raw()` method (i.e. it is a direct-DB adapter), `createIssueStoreFromConnect` transparently delegates to `createIssueStore` instead. This lets callers use a single factory regardless of adapter type.
|
|
251
|
+
|
|
252
|
+
**Async:** returns a `Promise<IssueStore>` (lazy `hazo_connect/server` import on the CRUD path).
|
|
253
|
+
|
|
237
254
|
---
|
|
238
255
|
|
|
239
256
|
## v0.2.0 Breaking changes
|
|
@@ -258,7 +275,7 @@ Consumers of this endpoint (e.g. `EnvMigrationPanel`) must read the `role` field
|
|
|
258
275
|
|
|
259
276
|
| Import path | Contents | Server-safe |
|
|
260
277
|
|---|---|---|
|
|
261
|
-
| `hazo_admin` | `adminGate`, `withAdminGate`, `HAZO_ADMIN_PERMISSIONS`, `AdminGateResult`, `createIssueStore`, `registerIssueType`, `getIssueType`, `listIssueTypes`, `loadIssueRoutingConfig`, `registerIssueArchiveJob`, `ADMIN_ISSUE_ARCHIVE_JOB_TYPE` | Server only |
|
|
278
|
+
| `hazo_admin` | `adminGate`, `withAdminGate`, `HAZO_ADMIN_PERMISSIONS`, `AdminGateResult`, `createIssueStore`, `createIssueStoreFromConnect`, `registerIssueType`, `getIssueType`, `listIssueTypes`, `loadIssueRoutingConfig`, `registerIssueArchiveJob`, `ADMIN_ISSUE_ARCHIVE_JOB_TYPE` | Server only |
|
|
262
279
|
| `hazo_admin/client` | `HAZO_ADMIN_PERMISSIONS`, `registerIssueCardRenderer`, `getIssueCardRenderer`, `listIssueCardRenderers`, `DefaultIssueCard`, types | Yes |
|
|
263
280
|
| `hazo_admin/api` | `createAdminPresetRoutes` | Server only |
|
|
264
281
|
| `hazo_admin/jobs` | `ENV_MIGRATE_JOB_TYPE`, `envMigrateJobHandler` | Server only (no React) |
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { adminGate, withAdminGate } from './lib/admin_gate.server.js';
|
|
2
2
|
export type { AdminGateResult } from './lib/admin_gate.server.js';
|
|
3
3
|
export { HAZO_ADMIN_PERMISSIONS } from './index.client.js';
|
|
4
|
-
export { createIssueStore, registerIssueType, getIssueType, listIssueTypes, loadIssueRoutingConfig, getRecipientStrategy, issueArchiveJobHandler, registerIssueArchiveJob, ADMIN_ISSUE_ARCHIVE_JOB_TYPE, } from './issues/index.js';
|
|
4
|
+
export { createIssueStore, createIssueStoreFromConnect, registerIssueType, getIssueType, listIssueTypes, loadIssueRoutingConfig, getRecipientStrategy, issueArchiveJobHandler, registerIssueArchiveJob, ADMIN_ISSUE_ARCHIVE_JOB_TYPE, } from './issues/index.js';
|
|
5
5
|
export type { IssueRecord, IssueStore, CreateIssueInput, IssueTypeDef, IssueActionDef, IssueActionCtx, IssueActionResult, RecipientResult, NotifyPayload, IssueRoutingConfig, RecipientStrategy, } from './issues/index.js';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACtE,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACtE,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EACL,gBAAgB,EAChB,2BAA2B,EAC3B,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { adminGate, withAdminGate } from './lib/admin_gate.server.js';
|
|
2
2
|
export { HAZO_ADMIN_PERMISSIONS } from './index.client.js';
|
|
3
|
-
export { createIssueStore, registerIssueType, getIssueType, listIssueTypes, loadIssueRoutingConfig, getRecipientStrategy, issueArchiveJobHandler, registerIssueArchiveJob, ADMIN_ISSUE_ARCHIVE_JOB_TYPE, } from './issues/index.js';
|
|
3
|
+
export { createIssueStore, createIssueStoreFromConnect, registerIssueType, getIssueType, listIssueTypes, loadIssueRoutingConfig, getRecipientStrategy, issueArchiveJobHandler, registerIssueArchiveJob, ADMIN_ISSUE_ARCHIVE_JOB_TYPE, } from './issues/index.js';
|
package/dist/issues/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createIssueStore } from './store.js';
|
|
1
|
+
export { createIssueStore, createIssueStoreFromConnect } from './store.js';
|
|
2
2
|
export type { IssueRecord, IssueStore, CreateIssueInput } from './store.js';
|
|
3
3
|
export { registerIssueType, getIssueType, listIssueTypes } from './registry.js';
|
|
4
4
|
export type { IssueTypeDef, IssueActionDef, IssueActionCtx, IssueActionResult, RecipientResult, NotifyPayload, } from './registry.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/issues/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/issues/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAC3E,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAChF,YAAY,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAC5E,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC"}
|
package/dist/issues/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createIssueStore } from './store.js';
|
|
1
|
+
export { createIssueStore, createIssueStoreFromConnect } from './store.js';
|
|
2
2
|
export { registerIssueType, getIssueType, listIssueTypes } from './registry.js';
|
|
3
3
|
export { loadIssueRoutingConfig, getRecipientStrategy } from './routing.js';
|
|
4
4
|
export { issueArchiveJobHandler, registerIssueArchiveJob, ADMIN_ISSUE_ARCHIVE_JOB_TYPE, } from './archive_handler.js';
|
package/dist/issues/store.d.ts
CHANGED
|
@@ -57,5 +57,16 @@ export interface IssueStore {
|
|
|
57
57
|
}): Promise<IssueRecord>;
|
|
58
58
|
archiveClosedOlderThan(cutoffDate: Date): Promise<number>;
|
|
59
59
|
}
|
|
60
|
+
export declare function toIssueRecord(raw: Record<string, unknown>): IssueRecord;
|
|
60
61
|
export declare function createIssueStore(adapter: any): IssueStore;
|
|
62
|
+
/**
|
|
63
|
+
* PostgREST-native implementation of IssueStore.
|
|
64
|
+
* Uses createCrudService + adapter.claimRows so no raw SQL is issued.
|
|
65
|
+
* Falls back to createIssueStore when the adapter exposes a raw() method
|
|
66
|
+
* (i.e. it is a direct-DB adapter that already handles SQL).
|
|
67
|
+
*
|
|
68
|
+
* Returns a Promise because it lazily imports hazo_connect/server on the
|
|
69
|
+
* CRUD path (avoiding loading the full hazo_connect stack for raw adapters).
|
|
70
|
+
*/
|
|
71
|
+
export declare function createIssueStoreFromConnect(adapter: any): Promise<IssueStore>;
|
|
61
72
|
//# sourceMappingURL=store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/issues/store.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAOrB,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,UAAU,CAAC;IAC9C,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;IACxC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC5F,UAAU,CAAC,IAAI,EAAE;QACf,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;QACzB,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,MAAM,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,GAAG,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzD,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAClD,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAG,QAAQ,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAClG,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACrE,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;QAC7B,UAAU,EAAE,SAAS,GAAG,QAAQ,CAAC;QACjC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACzB,sBAAsB,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3D;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/issues/store.ts"],"names":[],"mappings":"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"}
|
package/dist/issues/store.js
CHANGED
|
@@ -2,7 +2,7 @@ import 'server-only';
|
|
|
2
2
|
import { createLogger } from 'hazo_core';
|
|
3
3
|
const log = createLogger('hazo_admin:issues');
|
|
4
4
|
const TABLE = 'hazo_admin_issues';
|
|
5
|
-
function toIssueRecord(raw) {
|
|
5
|
+
export function toIssueRecord(raw) {
|
|
6
6
|
return {
|
|
7
7
|
id: raw.id,
|
|
8
8
|
scope_id: raw.scope_id,
|
|
@@ -225,3 +225,177 @@ export function createIssueStore(adapter) {
|
|
|
225
225
|
archiveClosedOlderThan,
|
|
226
226
|
};
|
|
227
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* PostgREST-native implementation of IssueStore.
|
|
230
|
+
* Uses createCrudService + adapter.claimRows so no raw SQL is issued.
|
|
231
|
+
* Falls back to createIssueStore when the adapter exposes a raw() method
|
|
232
|
+
* (i.e. it is a direct-DB adapter that already handles SQL).
|
|
233
|
+
*
|
|
234
|
+
* Returns a Promise because it lazily imports hazo_connect/server on the
|
|
235
|
+
* CRUD path (avoiding loading the full hazo_connect stack for raw adapters).
|
|
236
|
+
*/
|
|
237
|
+
export async function createIssueStoreFromConnect(adapter) {
|
|
238
|
+
// Capability sniff: direct-DB adapters have a raw() method — use the raw store.
|
|
239
|
+
if (typeof adapter.raw === 'function') {
|
|
240
|
+
return createIssueStore(adapter);
|
|
241
|
+
}
|
|
242
|
+
// Lazy import to avoid pulling hazo_connect into server-only environments
|
|
243
|
+
// that don't have it installed (e.g., direct-SQL test adapters).
|
|
244
|
+
const { createCrudService } = await import('hazo_connect/server');
|
|
245
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
246
|
+
const crud = createCrudService(adapter, TABLE);
|
|
247
|
+
function now() {
|
|
248
|
+
return new Date().toISOString();
|
|
249
|
+
}
|
|
250
|
+
async function createOrBumpIssue(input) {
|
|
251
|
+
log.debug('createIssueStoreFromConnect:createOrBumpIssue', { dedupe_key: input.dedupe_key });
|
|
252
|
+
// Atomically bump an existing open issue via claimRows.
|
|
253
|
+
const bumped = await adapter.claimRows({
|
|
254
|
+
table: TABLE,
|
|
255
|
+
where: {
|
|
256
|
+
dedupe_key: input.dedupe_key,
|
|
257
|
+
status: { in: ['new', 'wip'] },
|
|
258
|
+
},
|
|
259
|
+
set: {
|
|
260
|
+
occurrence_count: { increment: 1 },
|
|
261
|
+
last_seen_at: { $now: true },
|
|
262
|
+
updated_at: { $now: true },
|
|
263
|
+
},
|
|
264
|
+
returning: ['*'],
|
|
265
|
+
});
|
|
266
|
+
if (bumped && bumped.length > 0) {
|
|
267
|
+
return { issue: toIssueRecord(bumped[0]), isNew: false };
|
|
268
|
+
}
|
|
269
|
+
// No open issue found — insert a new one.
|
|
270
|
+
const ts = now();
|
|
271
|
+
const inserted = await crud.insert({
|
|
272
|
+
scope_id: input.scope_id,
|
|
273
|
+
type: input.type,
|
|
274
|
+
status: 'new',
|
|
275
|
+
subject_user_id: input.subject_user_id,
|
|
276
|
+
assigned_to: null,
|
|
277
|
+
recipient_scope_id: input.recipient_scope_id ?? null,
|
|
278
|
+
title: input.title,
|
|
279
|
+
summary: input.summary,
|
|
280
|
+
payload: input.payload,
|
|
281
|
+
dedupe_key: input.dedupe_key,
|
|
282
|
+
occurrence_count: 1,
|
|
283
|
+
first_seen_at: ts,
|
|
284
|
+
last_seen_at: ts,
|
|
285
|
+
resolution: null,
|
|
286
|
+
resolution_role_id: null,
|
|
287
|
+
resolution_reason: null,
|
|
288
|
+
resolved_by: null,
|
|
289
|
+
resolved_at: null,
|
|
290
|
+
created_at: ts,
|
|
291
|
+
updated_at: ts,
|
|
292
|
+
});
|
|
293
|
+
const row = Array.isArray(inserted) ? inserted[0] : inserted;
|
|
294
|
+
if (!row)
|
|
295
|
+
throw new Error('hazo_admin:issues — INSERT returned no row');
|
|
296
|
+
return { issue: toIssueRecord(row), isNew: true };
|
|
297
|
+
}
|
|
298
|
+
async function listIssues(opts) {
|
|
299
|
+
const { adminScopeIds, isGlobalAdmin = false, status, assignedTo, type, limit = 50, offset = 0, } = opts;
|
|
300
|
+
if (!isGlobalAdmin && (!adminScopeIds || adminScopeIds.length === 0)) {
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
const qb = crud.query();
|
|
304
|
+
if (!isGlobalAdmin && adminScopeIds && adminScopeIds.length > 0) {
|
|
305
|
+
qb.where('scope_id', 'in', adminScopeIds);
|
|
306
|
+
}
|
|
307
|
+
if (status !== undefined) {
|
|
308
|
+
const statuses = Array.isArray(status) ? status : [status];
|
|
309
|
+
qb.where('status', 'in', statuses);
|
|
310
|
+
}
|
|
311
|
+
if (assignedTo !== undefined) {
|
|
312
|
+
if (assignedTo === null) {
|
|
313
|
+
qb.where('assigned_to', 'is', null);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
qb.where('assigned_to', 'eq', assignedTo);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (type !== undefined) {
|
|
320
|
+
qb.where('type', 'eq', type);
|
|
321
|
+
}
|
|
322
|
+
qb.order('last_seen_at', 'desc').limit(limit).offset(offset);
|
|
323
|
+
const rows = await qb.execute('GET');
|
|
324
|
+
return (Array.isArray(rows) ? rows : []).map((r) => toIssueRecord(r));
|
|
325
|
+
}
|
|
326
|
+
async function getIssue(id) {
|
|
327
|
+
const row = await crud.findById(id);
|
|
328
|
+
if (!row)
|
|
329
|
+
return null;
|
|
330
|
+
return toIssueRecord(row);
|
|
331
|
+
}
|
|
332
|
+
async function transitionStatus(id, status, actorUserId) {
|
|
333
|
+
const issue = await getIssue(id);
|
|
334
|
+
if (!issue)
|
|
335
|
+
throw new Error(`hazo_admin:issues — issue ${id} not found`);
|
|
336
|
+
assertTransition(issue.status, status);
|
|
337
|
+
const ts = now();
|
|
338
|
+
const patch = { status, updated_at: ts };
|
|
339
|
+
if (status === 'wip' && issue.assigned_to === null) {
|
|
340
|
+
patch.assigned_to = actorUserId;
|
|
341
|
+
}
|
|
342
|
+
const updated = await crud.updateById(id, patch);
|
|
343
|
+
const row = Array.isArray(updated) ? updated[0] : updated;
|
|
344
|
+
if (!row)
|
|
345
|
+
throw new Error(`hazo_admin:issues — UPDATE returned no row for ${id}`);
|
|
346
|
+
return toIssueRecord(row);
|
|
347
|
+
}
|
|
348
|
+
async function setAssignee(id, userId) {
|
|
349
|
+
const ts = now();
|
|
350
|
+
const updated = await crud.updateById(id, { assigned_to: userId, updated_at: ts });
|
|
351
|
+
const row = Array.isArray(updated) ? updated[0] : updated;
|
|
352
|
+
if (!row)
|
|
353
|
+
throw new Error(`hazo_admin:issues — issue ${id} not found`);
|
|
354
|
+
return toIssueRecord(row);
|
|
355
|
+
}
|
|
356
|
+
async function resolveIssue(id, opts) {
|
|
357
|
+
const ts = now();
|
|
358
|
+
const updated = await crud.updateById(id, {
|
|
359
|
+
status: 'closed',
|
|
360
|
+
resolution: opts.resolution,
|
|
361
|
+
resolution_role_id: opts.role_id ?? null,
|
|
362
|
+
resolution_reason: opts.reason ?? null,
|
|
363
|
+
resolved_by: opts.resolved_by,
|
|
364
|
+
resolved_at: ts,
|
|
365
|
+
updated_at: ts,
|
|
366
|
+
});
|
|
367
|
+
const row = Array.isArray(updated) ? updated[0] : updated;
|
|
368
|
+
if (!row)
|
|
369
|
+
throw new Error(`hazo_admin:issues — issue ${id} not found`);
|
|
370
|
+
return toIssueRecord(row);
|
|
371
|
+
}
|
|
372
|
+
async function archiveClosedOlderThan(cutoffDate) {
|
|
373
|
+
const cutoff = cutoffDate.toISOString();
|
|
374
|
+
log.debug('createIssueStoreFromConnect:archiveClosedOlderThan', { cutoff });
|
|
375
|
+
const archived = await adapter.claimRows({
|
|
376
|
+
table: TABLE,
|
|
377
|
+
where: {
|
|
378
|
+
status: 'closed',
|
|
379
|
+
resolved_at: { lte: cutoff },
|
|
380
|
+
},
|
|
381
|
+
set: {
|
|
382
|
+
status: 'archived',
|
|
383
|
+
updated_at: { $now: true },
|
|
384
|
+
},
|
|
385
|
+
returning: ['id'],
|
|
386
|
+
limit: 1000,
|
|
387
|
+
});
|
|
388
|
+
const count = (archived ?? []).length;
|
|
389
|
+
log.debug('createIssueStoreFromConnect:archiveClosedOlderThan — archived', { count });
|
|
390
|
+
return count;
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
createOrBumpIssue,
|
|
394
|
+
listIssues,
|
|
395
|
+
getIssue,
|
|
396
|
+
transitionStatus,
|
|
397
|
+
setAssignee,
|
|
398
|
+
resolveIssue,
|
|
399
|
+
archiveClosedOlderThan,
|
|
400
|
+
};
|
|
401
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hazo_admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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",
|
|
@@ -52,15 +52,15 @@
|
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"hazo_core": "^1.2.0",
|
|
55
|
-
"hazo_ui": "^4.
|
|
55
|
+
"hazo_ui": "^4.4.0",
|
|
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.
|
|
59
|
+
"hazo_auth": "^10.4.1",
|
|
60
60
|
"hazo_logs": "^2.0.2",
|
|
61
61
|
"hazo_debug": "^3.1.1",
|
|
62
62
|
"hazo_connect": "^3.7.0",
|
|
63
|
-
"hazo_config": "^2.
|
|
63
|
+
"hazo_config": "^2.3.0",
|
|
64
64
|
"hazo_api": "^2.3.1",
|
|
65
65
|
"hazo_files": "^3.0.0",
|
|
66
66
|
"hazo_jobs": "^0.12.0",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"hazo_secure": "^1.2.0",
|
|
71
71
|
"hazo_blog": "^0.2.0",
|
|
72
72
|
"hazo_testing": "^0.3.1",
|
|
73
|
-
"hazo_notify": "^6.1.
|
|
73
|
+
"hazo_notify": "^6.1.4",
|
|
74
74
|
"lucide-react": "^0.553.0"
|
|
75
75
|
},
|
|
76
76
|
"peerDependenciesMeta": {
|
|
@@ -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.0",
|
|
134
|
-
"hazo_ui": "^4.
|
|
135
|
-
"hazo_auth": "^10.
|
|
134
|
+
"hazo_ui": "^4.4.0",
|
|
135
|
+
"hazo_auth": "^10.4.1",
|
|
136
136
|
"hazo_testing": "^0.3.1",
|
|
137
137
|
"react": "^19.0.0",
|
|
138
138
|
"react-dom": "^19.0.0",
|