forge-openclaw-plugin 0.2.4 → 0.2.10
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 +186 -6
- package/dist/assets/board-C_m78kvK.js +6 -0
- package/dist/assets/board-C_m78kvK.js.map +1 -0
- package/dist/assets/favicon-BCHm9dUV.ico +0 -0
- package/dist/assets/index-BWtLtXwb.js +36 -0
- package/dist/assets/index-BWtLtXwb.js.map +1 -0
- package/dist/assets/index-Dp5GXY_z.css +1 -0
- package/dist/assets/motion-CpZvZumD.js +10 -0
- package/dist/assets/motion-CpZvZumD.js.map +1 -0
- package/dist/assets/plus-jakarta-sans-latin-ext-wght-normal-DmpS2jIq.woff2 +0 -0
- package/dist/assets/plus-jakarta-sans-latin-wght-normal-eXO_dkmS.woff2 +0 -0
- package/dist/assets/plus-jakarta-sans-vietnamese-wght-normal-qRpaaN48.woff2 +0 -0
- package/dist/assets/sora-latin-ext-wght-normal-CawQDOvP.woff2 +0 -0
- package/dist/assets/sora-latin-wght-normal-DdqRvwsR.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-500-normal-CNSSEhBt.woff +0 -0
- package/dist/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
- package/dist/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-ext-500-normal-3dgZTiw9.woff +0 -0
- package/dist/assets/space-grotesk-latin-ext-500-normal-DUe3BAxM.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
- package/dist/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
- package/dist/assets/space-grotesk-vietnamese-500-normal-BTqKIpxg.woff +0 -0
- package/dist/assets/space-grotesk-vietnamese-500-normal-BmEvtly_.woff2 +0 -0
- package/dist/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
- package/dist/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
- package/dist/assets/table-DtyXTw03.js +23 -0
- package/dist/assets/table-DtyXTw03.js.map +1 -0
- package/dist/assets/ui-BXbpiKyS.js +46 -0
- package/dist/assets/ui-BXbpiKyS.js.map +1 -0
- package/dist/assets/vendor-CRS-psbw.css +1 -0
- package/dist/assets/vendor-QBH6qVEe.js +433 -0
- package/dist/assets/vendor-QBH6qVEe.js.map +1 -0
- package/dist/assets/viz-w-IMeueL.js +34 -0
- package/dist/assets/viz-w-IMeueL.js.map +1 -0
- package/dist/favicon.ico +0 -0
- package/dist/favicon.png +0 -0
- package/dist/index.html +29 -0
- package/dist/openclaw/api-client.d.ts +9 -0
- package/dist/openclaw/api-client.js +31 -4
- package/dist/openclaw/local-runtime.d.ts +3 -0
- package/dist/openclaw/local-runtime.js +136 -0
- package/dist/openclaw/parity.d.ts +4 -4
- package/dist/openclaw/parity.js +23 -33
- package/dist/openclaw/plugin-entry-shared.d.ts +4 -2
- package/dist/openclaw/plugin-entry-shared.js +63 -9
- package/dist/openclaw/routes.d.ts +12 -3
- package/dist/openclaw/routes.js +156 -924
- package/dist/openclaw/tools.js +242 -1100
- package/dist/server/app.js +2487 -0
- package/dist/server/db.js +313 -0
- package/dist/server/demo-data.js +49 -0
- package/dist/server/e2e-server.js +20 -0
- package/dist/server/errors.js +15 -0
- package/dist/server/index.js +16 -0
- package/dist/server/managers/base.js +17 -0
- package/dist/server/managers/contracts.js +47 -0
- package/dist/server/managers/platform/api-gateway-manager.js +11 -0
- package/dist/server/managers/platform/audit-manager.js +15 -0
- package/dist/server/managers/platform/authentication-manager.js +56 -0
- package/dist/server/managers/platform/authorization-manager.js +56 -0
- package/dist/server/managers/platform/background-job-manager.js +10 -0
- package/dist/server/managers/platform/configuration-manager.js +33 -0
- package/dist/server/managers/platform/database-manager.js +14 -0
- package/dist/server/managers/platform/event-bus-manager.js +7 -0
- package/dist/server/managers/platform/external-service-manager.js +11 -0
- package/dist/server/managers/platform/health-manager.js +7 -0
- package/dist/server/managers/platform/migration-manager.js +8 -0
- package/dist/server/managers/platform/search-index-manager.js +4 -0
- package/dist/server/managers/platform/secrets-manager.js +19 -0
- package/dist/server/managers/platform/session-manager.js +121 -0
- package/dist/server/managers/platform/storage-manager.js +16 -0
- package/dist/server/managers/platform/token-manager.js +37 -0
- package/dist/server/managers/platform/transaction-manager.js +8 -0
- package/dist/server/managers/platform/trusted-network.js +39 -0
- package/dist/server/managers/runtime.js +56 -0
- package/dist/server/managers/type-guards.js +4 -0
- package/dist/server/openapi.js +3553 -0
- package/dist/server/psyche-types.js +366 -0
- package/dist/server/repositories/activity-events.js +157 -0
- package/dist/server/repositories/collaboration.js +497 -0
- package/dist/server/repositories/deleted-entities.js +226 -0
- package/dist/server/repositories/domains.js +30 -0
- package/dist/server/repositories/event-log.js +64 -0
- package/dist/server/repositories/goals.js +156 -0
- package/dist/server/repositories/notes.js +359 -0
- package/dist/server/repositories/projects.js +211 -0
- package/dist/server/repositories/psyche.js +1353 -0
- package/dist/server/repositories/rewards.js +675 -0
- package/dist/server/repositories/settings.js +399 -0
- package/dist/server/repositories/tags.js +160 -0
- package/dist/server/repositories/task-runs.js +490 -0
- package/dist/server/repositories/tasks.js +424 -0
- package/dist/server/seed-demo.js +11 -0
- package/dist/server/services/context.js +214 -0
- package/dist/server/services/dashboard.js +173 -0
- package/dist/server/services/entity-crud.js +573 -0
- package/dist/server/services/gamification.js +215 -0
- package/dist/server/services/insights.js +91 -0
- package/dist/server/services/projects.js +77 -0
- package/dist/server/services/psyche.js +63 -0
- package/dist/server/services/relations.js +28 -0
- package/dist/server/services/reviews.js +88 -0
- package/dist/server/services/run-recovery.js +13 -0
- package/dist/server/services/tagging.js +49 -0
- package/dist/server/services/task-run-watchdog.js +92 -0
- package/dist/server/services/work-time.js +176 -0
- package/dist/server/types.js +1058 -0
- package/dist/server/web.js +91 -0
- package/openclaw.plugin.json +32 -9
- package/package.json +17 -4
- package/server/migrations/001_core.sql +411 -0
- package/server/migrations/002_psyche.sql +392 -0
- package/skills/forge-openclaw/SKILL.md +197 -271
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { getDatabase } from "../db.js";
|
|
2
|
+
import { activitySourceSchema, crudEntityTypeSchema, deletedEntityRecordSchema, settingsBinPayloadSchema } from "../types.js";
|
|
3
|
+
function mapDeletedEntity(row) {
|
|
4
|
+
return deletedEntityRecordSchema.parse({
|
|
5
|
+
entityType: row.entity_type,
|
|
6
|
+
entityId: row.entity_id,
|
|
7
|
+
title: row.title,
|
|
8
|
+
subtitle: row.subtitle,
|
|
9
|
+
deletedAt: row.deleted_at,
|
|
10
|
+
deletedByActor: row.deleted_by_actor,
|
|
11
|
+
deletedSource: row.deleted_source,
|
|
12
|
+
deleteReason: row.delete_reason,
|
|
13
|
+
snapshot: JSON.parse(row.snapshot_json)
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function listDeletedEntityRows(entityType) {
|
|
17
|
+
if (entityType) {
|
|
18
|
+
return getDatabase()
|
|
19
|
+
.prepare(`SELECT entity_type, entity_id, title, subtitle, deleted_at, deleted_by_actor, deleted_source, delete_reason, snapshot_json
|
|
20
|
+
FROM deleted_entities
|
|
21
|
+
WHERE entity_type = ?
|
|
22
|
+
ORDER BY deleted_at DESC`)
|
|
23
|
+
.all(entityType);
|
|
24
|
+
}
|
|
25
|
+
return getDatabase()
|
|
26
|
+
.prepare(`SELECT entity_type, entity_id, title, subtitle, deleted_at, deleted_by_actor, deleted_source, delete_reason, snapshot_json
|
|
27
|
+
FROM deleted_entities
|
|
28
|
+
ORDER BY deleted_at DESC`)
|
|
29
|
+
.all();
|
|
30
|
+
}
|
|
31
|
+
export function getDeletedEntityRecord(entityType, entityId) {
|
|
32
|
+
const row = getDatabase()
|
|
33
|
+
.prepare(`SELECT entity_type, entity_id, title, subtitle, deleted_at, deleted_by_actor, deleted_source, delete_reason, snapshot_json
|
|
34
|
+
FROM deleted_entities
|
|
35
|
+
WHERE entity_type = ? AND entity_id = ?`)
|
|
36
|
+
.get(entityType, entityId);
|
|
37
|
+
return row ? mapDeletedEntity(row) : undefined;
|
|
38
|
+
}
|
|
39
|
+
export function listDeletedEntities() {
|
|
40
|
+
return listDeletedEntityRows().map(mapDeletedEntity);
|
|
41
|
+
}
|
|
42
|
+
export function getDeletedEntityIdSet(entityType) {
|
|
43
|
+
const rows = getDatabase()
|
|
44
|
+
.prepare(`SELECT entity_id FROM deleted_entities WHERE entity_type = ?`)
|
|
45
|
+
.all(entityType);
|
|
46
|
+
return new Set(rows.map((row) => row.entity_id));
|
|
47
|
+
}
|
|
48
|
+
export function isEntityDeleted(entityType, entityId) {
|
|
49
|
+
const row = getDatabase()
|
|
50
|
+
.prepare(`SELECT 1
|
|
51
|
+
FROM deleted_entities
|
|
52
|
+
WHERE entity_type = ? AND entity_id = ?
|
|
53
|
+
LIMIT 1`)
|
|
54
|
+
.get(entityType, entityId);
|
|
55
|
+
return Boolean(row);
|
|
56
|
+
}
|
|
57
|
+
export function filterDeletedEntities(entityType, items) {
|
|
58
|
+
if (items.length === 0) {
|
|
59
|
+
return items;
|
|
60
|
+
}
|
|
61
|
+
const deletedIds = getDeletedEntityIdSet(entityType);
|
|
62
|
+
if (deletedIds.size === 0) {
|
|
63
|
+
return items;
|
|
64
|
+
}
|
|
65
|
+
return items.filter((item) => !deletedIds.has(item.id));
|
|
66
|
+
}
|
|
67
|
+
export function filterDeletedIds(entityType, ids) {
|
|
68
|
+
if (ids.length === 0) {
|
|
69
|
+
return ids;
|
|
70
|
+
}
|
|
71
|
+
const deletedIds = getDeletedEntityIdSet(entityType);
|
|
72
|
+
if (deletedIds.size === 0) {
|
|
73
|
+
return ids;
|
|
74
|
+
}
|
|
75
|
+
return ids.filter((id) => !deletedIds.has(id));
|
|
76
|
+
}
|
|
77
|
+
export function upsertDeletedEntityRecord(input) {
|
|
78
|
+
const entityType = crudEntityTypeSchema.parse(input.entityType);
|
|
79
|
+
const deletedAt = new Date().toISOString();
|
|
80
|
+
getDatabase()
|
|
81
|
+
.prepare(`INSERT INTO deleted_entities (
|
|
82
|
+
entity_type, entity_id, title, subtitle, deleted_at, deleted_by_actor, deleted_source, delete_reason, snapshot_json
|
|
83
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
84
|
+
ON CONFLICT(entity_type, entity_id) DO UPDATE SET
|
|
85
|
+
title = excluded.title,
|
|
86
|
+
subtitle = excluded.subtitle,
|
|
87
|
+
deleted_at = excluded.deleted_at,
|
|
88
|
+
deleted_by_actor = excluded.deleted_by_actor,
|
|
89
|
+
deleted_source = excluded.deleted_source,
|
|
90
|
+
delete_reason = excluded.delete_reason,
|
|
91
|
+
snapshot_json = excluded.snapshot_json`)
|
|
92
|
+
.run(entityType, input.entityId, input.title, input.subtitle ?? "", deletedAt, input.context.actor ?? null, activitySourceSchema.parse(input.context.source), input.deleteReason ?? "", JSON.stringify(input.snapshot));
|
|
93
|
+
}
|
|
94
|
+
export function restoreDeletedEntityRecord(entityType, entityId) {
|
|
95
|
+
const existing = getDeletedEntityRecord(entityType, entityId);
|
|
96
|
+
if (!existing) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
getDatabase()
|
|
100
|
+
.prepare(`DELETE FROM deleted_entities WHERE entity_type = ? AND entity_id = ?`)
|
|
101
|
+
.run(entityType, entityId);
|
|
102
|
+
return existing;
|
|
103
|
+
}
|
|
104
|
+
export function clearDeletedEntityRecord(entityType, entityId) {
|
|
105
|
+
getDatabase()
|
|
106
|
+
.prepare(`DELETE FROM deleted_entities WHERE entity_type = ? AND entity_id = ?`)
|
|
107
|
+
.run(entityType, entityId);
|
|
108
|
+
}
|
|
109
|
+
export function cascadeSoftDeleteAnchoredCollaboration(parentEntityType, parentEntityId, context, deleteReason = "") {
|
|
110
|
+
const noteRows = getDatabase()
|
|
111
|
+
.prepare(`SELECT DISTINCT
|
|
112
|
+
notes.id AS id,
|
|
113
|
+
notes.content_markdown AS content_markdown,
|
|
114
|
+
notes.content_plain AS content_plain,
|
|
115
|
+
notes.author AS author,
|
|
116
|
+
notes.source AS source,
|
|
117
|
+
notes.created_at AS created_at,
|
|
118
|
+
notes.updated_at AS updated_at
|
|
119
|
+
FROM notes
|
|
120
|
+
INNER JOIN note_links
|
|
121
|
+
ON note_links.note_id = notes.id
|
|
122
|
+
LEFT JOIN deleted_entities
|
|
123
|
+
ON deleted_entities.entity_type = 'note'
|
|
124
|
+
AND deleted_entities.entity_id = notes.id
|
|
125
|
+
WHERE note_links.entity_type = ?
|
|
126
|
+
AND note_links.entity_id = ?
|
|
127
|
+
AND deleted_entities.entity_id IS NULL`)
|
|
128
|
+
.all(parentEntityType, parentEntityId);
|
|
129
|
+
if (noteRows.length > 0) {
|
|
130
|
+
const placeholders = noteRows.map(() => "?").join(", ");
|
|
131
|
+
const linkRows = getDatabase()
|
|
132
|
+
.prepare(`SELECT note_id, entity_type, entity_id, anchor_key
|
|
133
|
+
FROM note_links
|
|
134
|
+
WHERE note_id IN (${placeholders})
|
|
135
|
+
ORDER BY created_at ASC`)
|
|
136
|
+
.all(...noteRows.map((row) => row.id));
|
|
137
|
+
const linksByNoteId = new Map();
|
|
138
|
+
for (const link of linkRows) {
|
|
139
|
+
const current = linksByNoteId.get(link.note_id) ?? [];
|
|
140
|
+
current.push({
|
|
141
|
+
entityType: link.entity_type,
|
|
142
|
+
entityId: link.entity_id,
|
|
143
|
+
anchorKey: link.anchor_key.trim().length > 0 ? link.anchor_key : null
|
|
144
|
+
});
|
|
145
|
+
linksByNoteId.set(link.note_id, current);
|
|
146
|
+
}
|
|
147
|
+
for (const row of noteRows) {
|
|
148
|
+
const compact = (row.content_plain || row.content_markdown).replace(/\s+/g, " ").trim();
|
|
149
|
+
upsertDeletedEntityRecord({
|
|
150
|
+
entityType: "note",
|
|
151
|
+
entityId: row.id,
|
|
152
|
+
title: compact.slice(0, 72) || "Note",
|
|
153
|
+
subtitle: compact.length > 72 ? compact.slice(72, 168).trim() : `Linked to ${parentEntityType.replaceAll("_", " ")}`,
|
|
154
|
+
snapshot: {
|
|
155
|
+
id: row.id,
|
|
156
|
+
contentMarkdown: row.content_markdown,
|
|
157
|
+
contentPlain: row.content_plain,
|
|
158
|
+
author: row.author,
|
|
159
|
+
source: row.source,
|
|
160
|
+
createdAt: row.created_at,
|
|
161
|
+
updatedAt: row.updated_at,
|
|
162
|
+
links: linksByNoteId.get(row.id) ?? []
|
|
163
|
+
},
|
|
164
|
+
deleteReason,
|
|
165
|
+
context
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const insightRows = getDatabase()
|
|
170
|
+
.prepare(`SELECT id, title, summary, created_at, updated_at
|
|
171
|
+
FROM insights
|
|
172
|
+
WHERE entity_type = ? AND entity_id = ?`)
|
|
173
|
+
.all(parentEntityType, parentEntityId);
|
|
174
|
+
for (const row of insightRows) {
|
|
175
|
+
upsertDeletedEntityRecord({
|
|
176
|
+
entityType: "insight",
|
|
177
|
+
entityId: row.id,
|
|
178
|
+
title: row.title,
|
|
179
|
+
subtitle: row.summary,
|
|
180
|
+
snapshot: {
|
|
181
|
+
id: row.id,
|
|
182
|
+
entityType: parentEntityType,
|
|
183
|
+
entityId: parentEntityId,
|
|
184
|
+
title: row.title,
|
|
185
|
+
summary: row.summary,
|
|
186
|
+
createdAt: row.created_at,
|
|
187
|
+
updatedAt: row.updated_at
|
|
188
|
+
},
|
|
189
|
+
deleteReason,
|
|
190
|
+
context
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
export function restoreAnchoredCollaboration(parentEntityType, parentEntityId) {
|
|
195
|
+
getDatabase()
|
|
196
|
+
.prepare(`DELETE FROM deleted_entities
|
|
197
|
+
WHERE entity_type = 'note'
|
|
198
|
+
AND entity_id IN (
|
|
199
|
+
SELECT DISTINCT note_id
|
|
200
|
+
FROM note_links
|
|
201
|
+
WHERE entity_type = ? AND entity_id = ?
|
|
202
|
+
)`)
|
|
203
|
+
.run(parentEntityType, parentEntityId);
|
|
204
|
+
getDatabase()
|
|
205
|
+
.prepare(`DELETE FROM deleted_entities
|
|
206
|
+
WHERE entity_type = 'insight'
|
|
207
|
+
AND entity_id IN (
|
|
208
|
+
SELECT id
|
|
209
|
+
FROM insights
|
|
210
|
+
WHERE entity_type = ? AND entity_id = ?
|
|
211
|
+
)`)
|
|
212
|
+
.run(parentEntityType, parentEntityId);
|
|
213
|
+
}
|
|
214
|
+
export function buildSettingsBinPayload() {
|
|
215
|
+
const items = listDeletedEntities();
|
|
216
|
+
const counts = items.reduce((acc, item) => {
|
|
217
|
+
acc[item.entityType] = (acc[item.entityType] ?? 0) + 1;
|
|
218
|
+
return acc;
|
|
219
|
+
}, {});
|
|
220
|
+
return settingsBinPayloadSchema.parse({
|
|
221
|
+
generatedAt: new Date().toISOString(),
|
|
222
|
+
totalCount: items.length,
|
|
223
|
+
countsByEntityType: counts,
|
|
224
|
+
records: items
|
|
225
|
+
});
|
|
226
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getDatabase } from "../db.js";
|
|
2
|
+
import { domainSchema } from "../psyche-types.js";
|
|
3
|
+
function mapDomain(row) {
|
|
4
|
+
return domainSchema.parse({
|
|
5
|
+
id: row.id,
|
|
6
|
+
slug: row.slug,
|
|
7
|
+
title: row.title,
|
|
8
|
+
description: row.description,
|
|
9
|
+
themeColor: row.theme_color,
|
|
10
|
+
sensitive: row.sensitive === 1,
|
|
11
|
+
createdAt: row.created_at,
|
|
12
|
+
updatedAt: row.updated_at
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export function listDomains() {
|
|
16
|
+
const rows = getDatabase()
|
|
17
|
+
.prepare(`SELECT id, slug, title, description, theme_color, sensitive, created_at, updated_at
|
|
18
|
+
FROM domains
|
|
19
|
+
ORDER BY sensitive DESC, title`)
|
|
20
|
+
.all();
|
|
21
|
+
return rows.map(mapDomain);
|
|
22
|
+
}
|
|
23
|
+
export function getDomainBySlug(slug) {
|
|
24
|
+
const row = getDatabase()
|
|
25
|
+
.prepare(`SELECT id, slug, title, description, theme_color, sensitive, created_at, updated_at
|
|
26
|
+
FROM domains
|
|
27
|
+
WHERE slug = ?`)
|
|
28
|
+
.get(slug);
|
|
29
|
+
return row ? mapDomain(row) : undefined;
|
|
30
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getDatabase } from "../db.js";
|
|
3
|
+
import { eventLogEntrySchema } from "../types.js";
|
|
4
|
+
function mapEvent(row) {
|
|
5
|
+
return eventLogEntrySchema.parse({
|
|
6
|
+
id: row.id,
|
|
7
|
+
eventKind: row.event_kind,
|
|
8
|
+
entityType: row.entity_type,
|
|
9
|
+
entityId: row.entity_id,
|
|
10
|
+
actor: row.actor,
|
|
11
|
+
source: row.source,
|
|
12
|
+
causedByEventId: row.caused_by_event_id,
|
|
13
|
+
metadata: JSON.parse(row.metadata_json),
|
|
14
|
+
createdAt: row.created_at
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function recordEventLog(input, now = new Date()) {
|
|
18
|
+
const event = eventLogEntrySchema.parse({
|
|
19
|
+
id: `log_${randomUUID().replaceAll("-", "").slice(0, 10)}`,
|
|
20
|
+
eventKind: input.eventKind,
|
|
21
|
+
entityType: input.entityType,
|
|
22
|
+
entityId: input.entityId,
|
|
23
|
+
actor: input.actor ?? null,
|
|
24
|
+
source: input.source,
|
|
25
|
+
causedByEventId: input.causedByEventId ?? null,
|
|
26
|
+
metadata: input.metadata ?? {},
|
|
27
|
+
createdAt: now.toISOString()
|
|
28
|
+
});
|
|
29
|
+
getDatabase()
|
|
30
|
+
.prepare(`INSERT INTO event_log (
|
|
31
|
+
id, event_kind, entity_type, entity_id, actor, source, caused_by_event_id, metadata_json, created_at
|
|
32
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
33
|
+
.run(event.id, event.eventKind, event.entityType, event.entityId, event.actor, event.source, event.causedByEventId, JSON.stringify(event.metadata), event.createdAt);
|
|
34
|
+
return event;
|
|
35
|
+
}
|
|
36
|
+
export function listEventLog(filters = {}) {
|
|
37
|
+
const whereClauses = [];
|
|
38
|
+
const params = [];
|
|
39
|
+
if (filters.entityType) {
|
|
40
|
+
whereClauses.push("entity_type = ?");
|
|
41
|
+
params.push(filters.entityType);
|
|
42
|
+
}
|
|
43
|
+
if (filters.entityId) {
|
|
44
|
+
whereClauses.push("entity_id = ?");
|
|
45
|
+
params.push(filters.entityId);
|
|
46
|
+
}
|
|
47
|
+
if (filters.eventKind) {
|
|
48
|
+
whereClauses.push("event_kind = ?");
|
|
49
|
+
params.push(filters.eventKind);
|
|
50
|
+
}
|
|
51
|
+
const whereSql = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
52
|
+
const limitSql = filters.limit ? "LIMIT ?" : "";
|
|
53
|
+
if (filters.limit) {
|
|
54
|
+
params.push(filters.limit);
|
|
55
|
+
}
|
|
56
|
+
const rows = getDatabase()
|
|
57
|
+
.prepare(`SELECT id, event_kind, entity_type, entity_id, actor, source, caused_by_event_id, metadata_json, created_at
|
|
58
|
+
FROM event_log
|
|
59
|
+
${whereSql}
|
|
60
|
+
ORDER BY created_at DESC
|
|
61
|
+
${limitSql}`)
|
|
62
|
+
.all(...params);
|
|
63
|
+
return rows.map(mapEvent);
|
|
64
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getDatabase, runInTransaction } from "../db.js";
|
|
3
|
+
import { recordActivityEvent } from "./activity-events.js";
|
|
4
|
+
import { filterDeletedEntities, filterDeletedIds, isEntityDeleted } from "./deleted-entities.js";
|
|
5
|
+
import { createLinkedNotes } from "./notes.js";
|
|
6
|
+
import { assertGoalRelations } from "../services/relations.js";
|
|
7
|
+
import { pruneLinkedEntityReferences } from "./psyche.js";
|
|
8
|
+
import { goalSchema } from "../types.js";
|
|
9
|
+
function readGoalTagIds(goalId) {
|
|
10
|
+
const rows = getDatabase()
|
|
11
|
+
.prepare(`SELECT tag_id FROM goal_tags WHERE goal_id = ? ORDER BY tag_id`)
|
|
12
|
+
.all(goalId);
|
|
13
|
+
return filterDeletedIds("tag", rows.map((row) => row.tag_id));
|
|
14
|
+
}
|
|
15
|
+
function mapGoal(row) {
|
|
16
|
+
return goalSchema.parse({
|
|
17
|
+
id: row.id,
|
|
18
|
+
title: row.title,
|
|
19
|
+
description: row.description,
|
|
20
|
+
horizon: row.horizon,
|
|
21
|
+
status: row.status,
|
|
22
|
+
targetPoints: row.target_points,
|
|
23
|
+
themeColor: row.theme_color,
|
|
24
|
+
createdAt: row.created_at,
|
|
25
|
+
updatedAt: row.updated_at,
|
|
26
|
+
tagIds: readGoalTagIds(row.id)
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function replaceGoalTags(goalId, tagIds) {
|
|
30
|
+
const database = getDatabase();
|
|
31
|
+
database.prepare(`DELETE FROM goal_tags WHERE goal_id = ?`).run(goalId);
|
|
32
|
+
const insert = database.prepare(`INSERT INTO goal_tags (goal_id, tag_id) VALUES (?, ?)`);
|
|
33
|
+
for (const tagId of tagIds) {
|
|
34
|
+
insert.run(goalId, tagId);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function listGoals() {
|
|
38
|
+
const rows = getDatabase()
|
|
39
|
+
.prepare(`SELECT id, title, description, horizon, status, target_points, theme_color, created_at, updated_at
|
|
40
|
+
FROM goals
|
|
41
|
+
ORDER BY created_at`)
|
|
42
|
+
.all();
|
|
43
|
+
return filterDeletedEntities("goal", rows.map(mapGoal));
|
|
44
|
+
}
|
|
45
|
+
export function createGoal(input, activity) {
|
|
46
|
+
return runInTransaction(() => {
|
|
47
|
+
assertGoalRelations(input);
|
|
48
|
+
const now = new Date().toISOString();
|
|
49
|
+
const id = `goal_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
|
|
50
|
+
getDatabase()
|
|
51
|
+
.prepare(`INSERT INTO goals (id, title, description, horizon, status, target_points, theme_color, created_at, updated_at)
|
|
52
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
53
|
+
.run(id, input.title, input.description, input.horizon, input.status, input.targetPoints, input.themeColor, now, now);
|
|
54
|
+
replaceGoalTags(id, input.tagIds);
|
|
55
|
+
const goal = getGoalById(id);
|
|
56
|
+
createLinkedNotes(input.notes, { entityType: "goal", entityId: goal.id, anchorKey: null }, activity ?? { source: "ui", actor: null });
|
|
57
|
+
if (activity) {
|
|
58
|
+
recordActivityEvent({
|
|
59
|
+
entityType: "goal",
|
|
60
|
+
entityId: goal.id,
|
|
61
|
+
eventType: "goal_created",
|
|
62
|
+
title: `Goal created: ${goal.title}`,
|
|
63
|
+
description: `Target set to ${goal.targetPoints} points.`,
|
|
64
|
+
actor: activity.actor ?? null,
|
|
65
|
+
source: activity.source,
|
|
66
|
+
metadata: {
|
|
67
|
+
horizon: goal.horizon,
|
|
68
|
+
status: goal.status,
|
|
69
|
+
targetPoints: goal.targetPoints
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return goal;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
export function updateGoal(goalId, input, activity) {
|
|
77
|
+
const current = getGoalById(goalId);
|
|
78
|
+
if (!current) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
return runInTransaction(() => {
|
|
82
|
+
const next = {
|
|
83
|
+
...current,
|
|
84
|
+
...input,
|
|
85
|
+
updatedAt: new Date().toISOString(),
|
|
86
|
+
tagIds: input.tagIds ?? current.tagIds
|
|
87
|
+
};
|
|
88
|
+
assertGoalRelations(next);
|
|
89
|
+
getDatabase()
|
|
90
|
+
.prepare(`UPDATE goals
|
|
91
|
+
SET title = ?, description = ?, horizon = ?, status = ?, target_points = ?, theme_color = ?, updated_at = ?
|
|
92
|
+
WHERE id = ?`)
|
|
93
|
+
.run(next.title, next.description, next.horizon, next.status, next.targetPoints, next.themeColor, next.updatedAt, goalId);
|
|
94
|
+
replaceGoalTags(goalId, next.tagIds);
|
|
95
|
+
const goal = getGoalById(goalId);
|
|
96
|
+
if (goal && activity) {
|
|
97
|
+
const statusChanged = current.status !== goal.status;
|
|
98
|
+
recordActivityEvent({
|
|
99
|
+
entityType: "goal",
|
|
100
|
+
entityId: goal.id,
|
|
101
|
+
eventType: statusChanged ? "goal_status_changed" : "goal_updated",
|
|
102
|
+
title: statusChanged ? `Goal ${goal.status}: ${goal.title}` : `Goal updated: ${goal.title}`,
|
|
103
|
+
description: statusChanged ? `Goal status moved from ${current.status} to ${goal.status}.` : "Goal details were edited.",
|
|
104
|
+
actor: activity.actor ?? null,
|
|
105
|
+
source: activity.source,
|
|
106
|
+
metadata: {
|
|
107
|
+
previousStatus: current.status,
|
|
108
|
+
status: goal.status,
|
|
109
|
+
targetPoints: goal.targetPoints,
|
|
110
|
+
previousTargetPoints: current.targetPoints
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return goal;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
export function getGoalById(goalId) {
|
|
118
|
+
if (isEntityDeleted("goal", goalId)) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
const row = getDatabase()
|
|
122
|
+
.prepare(`SELECT id, title, description, horizon, status, target_points, theme_color, created_at, updated_at
|
|
123
|
+
FROM goals
|
|
124
|
+
WHERE id = ?`)
|
|
125
|
+
.get(goalId);
|
|
126
|
+
return row ? mapGoal(row) : undefined;
|
|
127
|
+
}
|
|
128
|
+
export function deleteGoal(goalId, activity) {
|
|
129
|
+
const current = getGoalById(goalId);
|
|
130
|
+
if (!current) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
return runInTransaction(() => {
|
|
134
|
+
pruneLinkedEntityReferences("goal", goalId);
|
|
135
|
+
getDatabase()
|
|
136
|
+
.prepare(`DELETE FROM goals WHERE id = ?`)
|
|
137
|
+
.run(goalId);
|
|
138
|
+
if (activity) {
|
|
139
|
+
recordActivityEvent({
|
|
140
|
+
entityType: "goal",
|
|
141
|
+
entityId: current.id,
|
|
142
|
+
eventType: "goal_deleted",
|
|
143
|
+
title: `Goal deleted: ${current.title}`,
|
|
144
|
+
description: "Goal removed from the system.",
|
|
145
|
+
actor: activity.actor ?? null,
|
|
146
|
+
source: activity.source,
|
|
147
|
+
metadata: {
|
|
148
|
+
horizon: current.horizon,
|
|
149
|
+
status: current.status,
|
|
150
|
+
targetPoints: current.targetPoints
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return current;
|
|
155
|
+
});
|
|
156
|
+
}
|