@crombolo/data 0.0.0-stage → 0.9.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/LICENSE +21 -0
- package/NOTICE +69 -0
- package/package.json +31 -4
- package/src/change-report.ts +166 -0
- package/src/cluster.ts +108 -0
- package/src/database.ts +121 -0
- package/src/dialect.ts +128 -0
- package/src/index.ts +60 -0
- package/src/locks.ts +103 -0
- package/src/maintenance.ts +51 -0
- package/src/migrations.ts +431 -0
- package/src/node-ref.ts +107 -0
- package/src/pending.ts +25 -0
- package/src/repositories/bundles.ts +191 -0
- package/src/repositories/content-types.ts +831 -0
- package/src/repositories/data-types.ts +222 -0
- package/src/repositories/dictionary.ts +259 -0
- package/src/repositories/documents.ts +2389 -0
- package/src/repositories/domains.ts +108 -0
- package/src/repositories/folders.ts +120 -0
- package/src/repositories/form-entries.ts +314 -0
- package/src/repositories/form-workflows.ts +213 -0
- package/src/repositories/languages.ts +108 -0
- package/src/repositories/log-searches.ts +33 -0
- package/src/repositories/members.ts +427 -0
- package/src/repositories/nodes.ts +361 -0
- package/src/repositories/notifications.ts +54 -0
- package/src/repositories/public-access.ts +187 -0
- package/src/repositories/redirects.ts +274 -0
- package/src/repositories/references.ts +162 -0
- package/src/repositories/tags.ts +93 -0
- package/src/repositories/templates.ts +114 -0
- package/src/repositories/transfer-runs.ts +190 -0
- package/src/repositories/user-data.ts +90 -0
- package/src/repositories/users.ts +898 -0
- package/src/schema/assistant.ts +74 -0
- package/src/schema/built-in-content-types.ts +106 -0
- package/src/schema/built-in-media-types.ts +335 -0
- package/src/schema/built-in-member-types.ts +61 -0
- package/src/schema/bundle-components.ts +49 -0
- package/src/schema/bundles-rename.ts +32 -0
- package/src/schema/change-report.ts +46 -0
- package/src/schema/client-id-prefix.ts +43 -0
- package/src/schema/content-editing.ts +50 -0
- package/src/schema/content-seed.ts +586 -0
- package/src/schema/content.ts +263 -0
- package/src/schema/created-packages.ts +56 -0
- package/src/schema/elements.ts +62 -0
- package/src/schema/form-permission-prefix.ts +50 -0
- package/src/schema/form-permissions.ts +26 -0
- package/src/schema/form-workflows.ts +54 -0
- package/src/schema/forms.ts +63 -0
- package/src/schema/group-permissions.ts +20 -0
- package/src/schema/identity.ts +176 -0
- package/src/schema/log-viewer.ts +68 -0
- package/src/schema/member-property-type.ts +30 -0
- package/src/schema/members.ts +97 -0
- package/src/schema/plan.ts +73 -0
- package/src/schema/public-access.ts +56 -0
- package/src/schema/recycle-bin.ts +21 -0
- package/src/schema/redirects.ts +57 -0
- package/src/schema/rich-text-ui.ts +34 -0
- package/src/schema/seed.ts +298 -0
- package/src/schema/server-role.ts +21 -0
- package/src/schema/since-version.ts +26 -0
- package/src/schema/transfer-runs.ts +71 -0
- package/src/schema/upgrade-report.ts +43 -0
- package/src/schema/users-localisation.ts +85 -0
- package/src/schema/values.ts +108 -0
- package/src/schema-ops.ts +35 -0
- package/src/schema-state.ts +196 -0
- package/README.md +0 -3
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What content imports have been applied here, and what each one touched.
|
|
3
|
+
*
|
|
4
|
+
* The rows exist so that reverting is a lookup rather than a guess: each change
|
|
5
|
+
* remembers the event its node was at beforehand and the published event it was
|
|
6
|
+
* serving, which together *are* the previously-live state.
|
|
7
|
+
*/
|
|
8
|
+
import type { Db } from '../database.ts'
|
|
9
|
+
import { DbDate } from '../dialect.ts'
|
|
10
|
+
|
|
11
|
+
export type TransferDirection = 'import' | 'revert'
|
|
12
|
+
export type TransferRunStatus = 'applied' | 'reverted' | 'failed'
|
|
13
|
+
/**
|
|
14
|
+
* What a run did to one node.
|
|
15
|
+
*
|
|
16
|
+
* `unchanged` is recorded too: a run that decided to do nothing still owns the
|
|
17
|
+
* node. `recycle` is what a *revert* does to a node the import created, and it
|
|
18
|
+
* is recorded so that reverting the revert knows to bring it back out of the
|
|
19
|
+
* bin rather than only restoring its values.
|
|
20
|
+
*/
|
|
21
|
+
export type TransferAction = 'create' | 'update' | 'unchanged' | 'skip' | 'recycle'
|
|
22
|
+
|
|
23
|
+
export interface TransferRun {
|
|
24
|
+
id: string
|
|
25
|
+
bundleId: string
|
|
26
|
+
bundleLabel: string | null
|
|
27
|
+
direction: TransferDirection
|
|
28
|
+
revertsRunId: string | null
|
|
29
|
+
schemaStateId: number
|
|
30
|
+
startedAt: Date
|
|
31
|
+
finishedAt: Date | null
|
|
32
|
+
appliedBy: string | null
|
|
33
|
+
nodeCount: number
|
|
34
|
+
status: TransferRunStatus
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface TransferChange {
|
|
38
|
+
id: number
|
|
39
|
+
runId: string
|
|
40
|
+
nodeKey: string
|
|
41
|
+
kind: string
|
|
42
|
+
action: TransferAction
|
|
43
|
+
/** The node's latest event before this run, or null when the run created it. */
|
|
44
|
+
beforeEventId: number | null
|
|
45
|
+
/** The event it was serving before this run, or null if it was not published. */
|
|
46
|
+
beforePublishedEventId: number | null
|
|
47
|
+
/** The event this run appended, when it appended one. */
|
|
48
|
+
eventId: number | null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class TransferRunRepository {
|
|
52
|
+
#db: Db
|
|
53
|
+
constructor(db: Db) {
|
|
54
|
+
this.#db = db
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async start(run: Omit<TransferRun, 'finishedAt' | 'status'>): Promise<void> {
|
|
58
|
+
await this.#db.exec(
|
|
59
|
+
`INSERT INTO content_transfer_run
|
|
60
|
+
(id, bundle_id, bundle_label, direction, reverts_run_id, schema_state_id, started_at, applied_by, node_count, status)
|
|
61
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'applied')`,
|
|
62
|
+
[
|
|
63
|
+
run.id,
|
|
64
|
+
run.bundleId,
|
|
65
|
+
run.bundleLabel,
|
|
66
|
+
run.direction,
|
|
67
|
+
run.revertsRunId,
|
|
68
|
+
run.schemaStateId,
|
|
69
|
+
DbDate.toDb(run.startedAt),
|
|
70
|
+
run.appliedBy,
|
|
71
|
+
run.nodeCount,
|
|
72
|
+
],
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async finish(id: string, nodeCount: number): Promise<void> {
|
|
77
|
+
await this.#db.exec(
|
|
78
|
+
'UPDATE content_transfer_run SET finished_at = ?, node_count = ? WHERE id = ?',
|
|
79
|
+
[DbDate.toDb(new Date()), nodeCount, id],
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async setStatus(id: string, status: TransferRunStatus): Promise<void> {
|
|
84
|
+
await this.#db.exec('UPDATE content_transfer_run SET status = ? WHERE id = ?', [status, id])
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async record(change: Omit<TransferChange, 'id'>): Promise<void> {
|
|
88
|
+
await this.#db.exec(
|
|
89
|
+
`INSERT INTO content_transfer_change
|
|
90
|
+
(run_id, node_key, kind, action, before_event_id, before_published_event_id, event_id)
|
|
91
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
92
|
+
[
|
|
93
|
+
change.runId,
|
|
94
|
+
change.nodeKey,
|
|
95
|
+
change.kind,
|
|
96
|
+
change.action,
|
|
97
|
+
change.beforeEventId,
|
|
98
|
+
change.beforePublishedEventId,
|
|
99
|
+
change.eventId,
|
|
100
|
+
],
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async byId(id: string): Promise<TransferRun | undefined> {
|
|
105
|
+
const rows = await this.#db.query('SELECT * FROM content_transfer_run WHERE id = ?', [id])
|
|
106
|
+
return rows[0] ? mapRun(rows[0]) : undefined
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The import of this bundle that still stands here, if there is one. A run
|
|
111
|
+
* that was reverted does not count: the content is gone, so importing again
|
|
112
|
+
* is the right answer. This is what makes `start --bundle` import once.
|
|
113
|
+
*/
|
|
114
|
+
async appliedFor(bundleId: string): Promise<TransferRun | undefined> {
|
|
115
|
+
const rows = await this.#db.query(
|
|
116
|
+
`SELECT * FROM content_transfer_run
|
|
117
|
+
WHERE bundle_id = ? AND direction = 'import' AND status = 'applied'
|
|
118
|
+
ORDER BY started_at DESC, id DESC LIMIT 1`,
|
|
119
|
+
[bundleId],
|
|
120
|
+
)
|
|
121
|
+
return rows[0] ? mapRun(rows[0]) : undefined
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Runs, newest first. */
|
|
125
|
+
async recent(limit = 20): Promise<TransferRun[]> {
|
|
126
|
+
const rows = await this.#db.query(
|
|
127
|
+
'SELECT * FROM content_transfer_run ORDER BY started_at DESC, id DESC LIMIT ?',
|
|
128
|
+
[limit],
|
|
129
|
+
)
|
|
130
|
+
return rows.map(mapRun)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async changes(runId: string): Promise<TransferChange[]> {
|
|
134
|
+
const rows = await this.#db.query(
|
|
135
|
+
'SELECT * FROM content_transfer_change WHERE run_id = ? ORDER BY id',
|
|
136
|
+
[runId],
|
|
137
|
+
)
|
|
138
|
+
return rows.map(mapChange)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Runs after this one that touched any of the same nodes. Reverting while one
|
|
143
|
+
* of these stands would quietly undo its work too, so revert refuses.
|
|
144
|
+
*/
|
|
145
|
+
async laterRunsTouching(runId: string): Promise<string[]> {
|
|
146
|
+
const rows = await this.#db.query<{ id: string }>(
|
|
147
|
+
`SELECT DISTINCT later.id
|
|
148
|
+
FROM content_transfer_change mine
|
|
149
|
+
JOIN content_transfer_change theirs ON theirs.node_key = mine.node_key
|
|
150
|
+
JOIN content_transfer_run later ON later.id = theirs.run_id
|
|
151
|
+
JOIN content_transfer_run self ON self.id = mine.run_id
|
|
152
|
+
WHERE mine.run_id = ?
|
|
153
|
+
AND theirs.run_id <> mine.run_id
|
|
154
|
+
AND later.started_at >= self.started_at
|
|
155
|
+
AND later.status = 'applied'`,
|
|
156
|
+
[runId],
|
|
157
|
+
)
|
|
158
|
+
return rows.map((row) => String(row.id))
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function mapRun(row: Record<string, unknown>): TransferRun {
|
|
163
|
+
return {
|
|
164
|
+
id: String(row.id),
|
|
165
|
+
bundleId: String(row.bundle_id),
|
|
166
|
+
bundleLabel: (row.bundle_label as string | null) ?? null,
|
|
167
|
+
direction: String(row.direction) as TransferDirection,
|
|
168
|
+
revertsRunId: (row.reverts_run_id as string | null) ?? null,
|
|
169
|
+
schemaStateId: Number(row.schema_state_id),
|
|
170
|
+
startedAt: DbDate.fromDb(row.started_at) ?? new Date(0),
|
|
171
|
+
finishedAt: DbDate.fromDb(row.finished_at) ?? null,
|
|
172
|
+
appliedBy: (row.applied_by as string | null) ?? null,
|
|
173
|
+
nodeCount: Number(row.node_count),
|
|
174
|
+
status: String(row.status) as TransferRunStatus,
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function mapChange(row: Record<string, unknown>): TransferChange {
|
|
179
|
+
const int = (value: unknown) => (value === null || value === undefined ? null : Number(value))
|
|
180
|
+
return {
|
|
181
|
+
id: Number(row.id),
|
|
182
|
+
runId: String(row.run_id),
|
|
183
|
+
nodeKey: String(row.node_key),
|
|
184
|
+
kind: String(row.kind),
|
|
185
|
+
action: String(row.action) as TransferAction,
|
|
186
|
+
beforeEventId: int(row.before_event_id),
|
|
187
|
+
beforePublishedEventId: int(row.before_published_event_id),
|
|
188
|
+
eventId: int(row.event_id),
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/** Per-user key/value data the backoffice keeps between sessions (Umbraco's `umbracoUserData`). */
|
|
2
|
+
import { normaliseUuid } from '@crombolo/core'
|
|
3
|
+
import type { Db } from '../database.ts'
|
|
4
|
+
|
|
5
|
+
export interface UserDataEntry {
|
|
6
|
+
key: string
|
|
7
|
+
group: string
|
|
8
|
+
identifier: string
|
|
9
|
+
value: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type UserDataResult = 'Success' | 'NotFound' | 'AlreadyExists'
|
|
13
|
+
|
|
14
|
+
const map = (row: Record<string, unknown>): UserDataEntry => ({
|
|
15
|
+
key: normaliseUuid(String(row.key)),
|
|
16
|
+
group: String(row.data_group),
|
|
17
|
+
identifier: String(row.identifier),
|
|
18
|
+
value: String(row.value),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export class UserDataRepository {
|
|
22
|
+
#db: Db
|
|
23
|
+
|
|
24
|
+
constructor(db: Db) {
|
|
25
|
+
this.#db = db
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async list(
|
|
29
|
+
userId: number,
|
|
30
|
+
filter: { groups?: readonly string[]; identifiers?: readonly string[] },
|
|
31
|
+
skip: number,
|
|
32
|
+
take: number,
|
|
33
|
+
): Promise<{ total: number; items: UserDataEntry[] }> {
|
|
34
|
+
const where = ['user_id = ?']
|
|
35
|
+
const params: unknown[] = [userId]
|
|
36
|
+
if (filter.groups?.length) {
|
|
37
|
+
where.push(`data_group IN (${filter.groups.map(() => '?').join(', ')})`)
|
|
38
|
+
params.push(...filter.groups)
|
|
39
|
+
}
|
|
40
|
+
if (filter.identifiers?.length) {
|
|
41
|
+
where.push(`identifier IN (${filter.identifiers.map(() => '?').join(', ')})`)
|
|
42
|
+
params.push(...filter.identifiers)
|
|
43
|
+
}
|
|
44
|
+
const rows = await this.#db.query(
|
|
45
|
+
`SELECT key, data_group, identifier, value FROM user_data WHERE ${where.join(' AND ')}
|
|
46
|
+
ORDER BY data_group, identifier`,
|
|
47
|
+
params,
|
|
48
|
+
)
|
|
49
|
+
return { total: rows.length, items: rows.slice(skip, skip + take).map(map) }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async get(userId: number, key: string): Promise<UserDataEntry | undefined> {
|
|
53
|
+
const rows = await this.#db.query(
|
|
54
|
+
'SELECT key, data_group, identifier, value FROM user_data WHERE user_id = ? AND key = ?',
|
|
55
|
+
[userId, normaliseUuid(key)],
|
|
56
|
+
)
|
|
57
|
+
return rows[0] ? map(rows[0]) : undefined
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async create(userId: number, entry: UserDataEntry): Promise<UserDataResult> {
|
|
61
|
+
const clash = await this.#db.query(
|
|
62
|
+
'SELECT 1 FROM user_data WHERE key = ? OR (user_id = ? AND data_group = ? AND identifier = ?)',
|
|
63
|
+
[normaliseUuid(entry.key), userId, entry.group, entry.identifier],
|
|
64
|
+
)
|
|
65
|
+
if (clash.length > 0) return 'AlreadyExists'
|
|
66
|
+
await this.#db.exec(
|
|
67
|
+
'INSERT INTO user_data (key, user_id, data_group, identifier, value) VALUES (?, ?, ?, ?, ?)',
|
|
68
|
+
[normaliseUuid(entry.key), userId, entry.group, entry.identifier, entry.value],
|
|
69
|
+
)
|
|
70
|
+
return 'Success'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async update(userId: number, entry: UserDataEntry): Promise<UserDataResult> {
|
|
74
|
+
if (!(await this.get(userId, entry.key))) return 'NotFound'
|
|
75
|
+
await this.#db.exec(
|
|
76
|
+
'UPDATE user_data SET data_group = ?, identifier = ?, value = ? WHERE user_id = ? AND key = ?',
|
|
77
|
+
[entry.group, entry.identifier, entry.value, userId, normaliseUuid(entry.key)],
|
|
78
|
+
)
|
|
79
|
+
return 'Success'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async delete(userId: number, key: string): Promise<UserDataResult> {
|
|
83
|
+
if (!(await this.get(userId, key))) return 'NotFound'
|
|
84
|
+
await this.#db.exec('DELETE FROM user_data WHERE user_id = ? AND key = ?', [
|
|
85
|
+
userId,
|
|
86
|
+
normaliseUuid(key),
|
|
87
|
+
])
|
|
88
|
+
return 'Success'
|
|
89
|
+
}
|
|
90
|
+
}
|