@crombolo/data 0.0.0-stage → 0.10.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/LICENSE +21 -0
- package/NOTICE +69 -0
- package/package.json +31 -4
- package/src/bundle-tables/builder.ts +264 -0
- package/src/bundle-tables/capability.ts +377 -0
- package/src/bundle-tables/chain.ts +105 -0
- package/src/bundle-tables/ddl.ts +124 -0
- package/src/bundle-tables/guard.ts +116 -0
- package/src/bundle-tables/index.ts +7 -0
- package/src/bundle-tables/names.ts +58 -0
- package/src/bundle-tables/schema.ts +485 -0
- package/src/change-report.ts +166 -0
- package/src/cluster.ts +108 -0
- package/src/database.ts +121 -0
- package/src/dialect.ts +136 -0
- package/src/index.ts +63 -0
- package/src/locks.ts +103 -0
- package/src/maintenance.ts +51 -0
- package/src/migrations.ts +547 -0
- package/src/node-ref.ts +107 -0
- package/src/pending.ts +25 -0
- package/src/repositories/bundles.ts +223 -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 +208 -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/bundle-package.ts +27 -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 +77 -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-run-package.ts +27 -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,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration 001 — back-office identity.
|
|
3
|
+
*
|
|
4
|
+
* Table and column names follow docs/data-model.md: Umbraco's relational
|
|
5
|
+
* model with snake_case names. `user` is reserved in Postgres, hence
|
|
6
|
+
* `user_account`.
|
|
7
|
+
*/
|
|
8
|
+
import type { Db } from '../database.ts'
|
|
9
|
+
import { INITIAL_STATE, type Migration } from '../migrations.ts'
|
|
10
|
+
|
|
11
|
+
export const STATE_IDENTITY = '{6f1d4a8e-0001-4c21-9d3a-8b1e5c7a2f01}'
|
|
12
|
+
|
|
13
|
+
export const identityMigration: Migration = {
|
|
14
|
+
from: INITIAL_STATE,
|
|
15
|
+
to: STATE_IDENTITY,
|
|
16
|
+
name: 'CreateIdentityTables',
|
|
17
|
+
kind: 'expand',
|
|
18
|
+
release: '0.3.0',
|
|
19
|
+
async up(db: Db) {
|
|
20
|
+
const t = db.dialect.types
|
|
21
|
+
|
|
22
|
+
await db.exec(
|
|
23
|
+
`CREATE TABLE user_account (
|
|
24
|
+
id ${t.identity},
|
|
25
|
+
key ${t.uuid} NOT NULL UNIQUE,
|
|
26
|
+
user_name ${t.textCi} NOT NULL,
|
|
27
|
+
login ${t.textCi} NOT NULL,
|
|
28
|
+
email ${t.textCi} NOT NULL,
|
|
29
|
+
password_hash ${t.text},
|
|
30
|
+
password_config ${t.text},
|
|
31
|
+
security_stamp ${t.text},
|
|
32
|
+
language ${t.varchar(14)},
|
|
33
|
+
avatar ${t.text},
|
|
34
|
+
kind ${t.integer} NOT NULL,
|
|
35
|
+
is_disabled ${t.boolean} NOT NULL,
|
|
36
|
+
is_approved ${t.boolean} NOT NULL,
|
|
37
|
+
failed_login_attempts ${t.integer} NOT NULL,
|
|
38
|
+
last_login_date ${t.timestamp},
|
|
39
|
+
last_lockout_date ${t.timestamp},
|
|
40
|
+
last_password_change_date ${t.timestamp},
|
|
41
|
+
invited_date ${t.timestamp},
|
|
42
|
+
email_confirmed_date ${t.timestamp},
|
|
43
|
+
create_date ${t.timestamp} NOT NULL,
|
|
44
|
+
update_date ${t.timestamp} NOT NULL
|
|
45
|
+
)`,
|
|
46
|
+
)
|
|
47
|
+
await db.exec('CREATE UNIQUE INDEX ux_user_account_login ON user_account (login)')
|
|
48
|
+
|
|
49
|
+
await db.exec(
|
|
50
|
+
`CREATE TABLE user_group (
|
|
51
|
+
id ${t.identity},
|
|
52
|
+
key ${t.uuid} NOT NULL UNIQUE,
|
|
53
|
+
alias ${t.textCi} NOT NULL UNIQUE,
|
|
54
|
+
name ${t.textCi} NOT NULL,
|
|
55
|
+
description ${t.text},
|
|
56
|
+
icon ${t.text},
|
|
57
|
+
has_access_to_all_languages ${t.boolean} NOT NULL,
|
|
58
|
+
start_content_id ${t.integer},
|
|
59
|
+
start_media_id ${t.integer},
|
|
60
|
+
start_element_id ${t.integer},
|
|
61
|
+
create_date ${t.timestamp} NOT NULL,
|
|
62
|
+
update_date ${t.timestamp} NOT NULL
|
|
63
|
+
)`,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
await db.exec(
|
|
67
|
+
`CREATE TABLE user_group_member (
|
|
68
|
+
user_id ${t.integer} NOT NULL REFERENCES user_account (id),
|
|
69
|
+
user_group_id ${t.integer} NOT NULL REFERENCES user_group (id),
|
|
70
|
+
PRIMARY KEY (user_id, user_group_id)
|
|
71
|
+
)`,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
await db.exec(
|
|
75
|
+
`CREATE TABLE user_group_section (
|
|
76
|
+
user_group_id ${t.integer} NOT NULL REFERENCES user_group (id),
|
|
77
|
+
app_alias ${t.varchar(50)} NOT NULL,
|
|
78
|
+
PRIMARY KEY (user_group_id, app_alias)
|
|
79
|
+
)`,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
// Keyed by the group's key, not its id, exactly as Umbraco does.
|
|
83
|
+
await db.exec(
|
|
84
|
+
`CREATE TABLE user_group_permission (
|
|
85
|
+
id ${t.identity},
|
|
86
|
+
user_group_key ${t.uuid} NOT NULL REFERENCES user_group (key),
|
|
87
|
+
permission ${t.varchar(255)} NOT NULL
|
|
88
|
+
)`,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
await db.exec(
|
|
92
|
+
`CREATE TABLE user_group_granular_permission (
|
|
93
|
+
id ${t.identity},
|
|
94
|
+
user_group_key ${t.uuid} NOT NULL REFERENCES user_group (key),
|
|
95
|
+
unique_id ${t.uuid},
|
|
96
|
+
permission ${t.varchar(255)} NOT NULL,
|
|
97
|
+
context ${t.varchar(50)} NOT NULL
|
|
98
|
+
)`,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
await db.exec(
|
|
102
|
+
`CREATE TABLE user_start_node (
|
|
103
|
+
id ${t.identity},
|
|
104
|
+
user_id ${t.integer} NOT NULL REFERENCES user_account (id),
|
|
105
|
+
start_node ${t.integer} NOT NULL,
|
|
106
|
+
start_node_type ${t.integer} NOT NULL
|
|
107
|
+
)`,
|
|
108
|
+
)
|
|
109
|
+
await db.exec(
|
|
110
|
+
'CREATE UNIQUE INDEX ux_user_start_node ON user_start_node (start_node_type, start_node, user_id)',
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A browser session established by the login endpoint. It exists so the
|
|
115
|
+
* authorize endpoint can identify the user without re-prompting, which is
|
|
116
|
+
* how Umbraco's cookie-then-authorization-code flow works.
|
|
117
|
+
*/
|
|
118
|
+
await db.exec(
|
|
119
|
+
`CREATE TABLE user_login_session (
|
|
120
|
+
session_id ${t.uuid} PRIMARY KEY,
|
|
121
|
+
user_id ${t.integer} NOT NULL REFERENCES user_account (id),
|
|
122
|
+
logged_in_utc ${t.timestamp} NOT NULL,
|
|
123
|
+
last_validated_utc ${t.timestamp} NOT NULL,
|
|
124
|
+
logged_out_utc ${t.timestamp},
|
|
125
|
+
ip_address ${t.text}
|
|
126
|
+
)`,
|
|
127
|
+
)
|
|
128
|
+
await db.exec(
|
|
129
|
+
'CREATE INDEX ix_user_login_session_validated ON user_login_session (last_validated_utc)',
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Single-use authorization codes. Only the hash is stored: a code is a
|
|
134
|
+
* bearer credential, so a database read must not yield a usable one.
|
|
135
|
+
*/
|
|
136
|
+
await db.exec(
|
|
137
|
+
`CREATE TABLE auth_code (
|
|
138
|
+
code_hash ${t.text} PRIMARY KEY,
|
|
139
|
+
user_id ${t.integer} NOT NULL REFERENCES user_account (id),
|
|
140
|
+
session_id ${t.uuid} NOT NULL,
|
|
141
|
+
client_id ${t.varchar(255)} NOT NULL,
|
|
142
|
+
redirect_uri ${t.text} NOT NULL,
|
|
143
|
+
code_challenge ${t.text} NOT NULL,
|
|
144
|
+
code_challenge_method ${t.varchar(10)} NOT NULL,
|
|
145
|
+
scope ${t.text} NOT NULL,
|
|
146
|
+
expires_at ${t.timestamp} NOT NULL,
|
|
147
|
+
consumed_at ${t.timestamp},
|
|
148
|
+
create_date ${t.timestamp} NOT NULL
|
|
149
|
+
)`,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Reference tokens. Umbraco issues opaque, server-side tokens rather than
|
|
154
|
+
* JWTs, so validation is a database lookup and revocation is immediate.
|
|
155
|
+
* `parent_id` chains a refreshed token to the one it replaced so a whole
|
|
156
|
+
* chain can be revoked on reuse.
|
|
157
|
+
*/
|
|
158
|
+
await db.exec(
|
|
159
|
+
`CREATE TABLE auth_token (
|
|
160
|
+
id ${t.identity},
|
|
161
|
+
token_hash ${t.text} NOT NULL UNIQUE,
|
|
162
|
+
token_type ${t.varchar(20)} NOT NULL,
|
|
163
|
+
user_id ${t.integer} NOT NULL REFERENCES user_account (id),
|
|
164
|
+
session_id ${t.uuid} NOT NULL,
|
|
165
|
+
client_id ${t.varchar(255)} NOT NULL,
|
|
166
|
+
scope ${t.text} NOT NULL,
|
|
167
|
+
expires_at ${t.timestamp} NOT NULL,
|
|
168
|
+
create_date ${t.timestamp} NOT NULL,
|
|
169
|
+
revoked_at ${t.timestamp},
|
|
170
|
+
parent_id ${t.integer}
|
|
171
|
+
)`,
|
|
172
|
+
)
|
|
173
|
+
await db.exec('CREATE INDEX ix_auth_token_session ON auth_token (session_id)')
|
|
174
|
+
await db.exec('CREATE INDEX ix_auth_token_expires ON auth_token (expires_at)')
|
|
175
|
+
},
|
|
176
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration 012 — the log viewer's saved searches (Umbraco's
|
|
3
|
+
* `umbracoLogViewerQuery`), seeded with Umbraco's installer searches. Two of
|
|
4
|
+
* those ask about Umbraco's own namespaces and message templates; here they ask
|
|
5
|
+
* the same of crombolo's. The two about Umbraco's `SortedComponentTypes`
|
|
6
|
+
* property, which nothing here logs, are left out.
|
|
7
|
+
*/
|
|
8
|
+
import type { Db } from '../database.ts'
|
|
9
|
+
import type { Migration } from '../migrations.ts'
|
|
10
|
+
import { STATE_USERS_LOCALISATION } from './users-localisation.ts'
|
|
11
|
+
|
|
12
|
+
export const STATE_LOG_VIEWER = '{6f1d4a8e-0012-4c21-9d3a-8b1e5c7a2f12}'
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_LOG_SEARCHES: ReadonlyArray<{ name: string; query: string }> = [
|
|
15
|
+
{
|
|
16
|
+
name: 'Find all logs where the Level is NOT Verbose and NOT Debug',
|
|
17
|
+
query: "Not(@Level='Verbose') and Not(@Level='Debug')",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'Find all logs that has an exception property (Warning, Error & Fatal with Exceptions)',
|
|
21
|
+
query: 'Has(@Exception)',
|
|
22
|
+
},
|
|
23
|
+
{ name: "Find all logs that have the property 'Duration'", query: 'Has(Duration)' },
|
|
24
|
+
{
|
|
25
|
+
name: "Find all logs that have the property 'Duration' and the duration is greater than 1000ms",
|
|
26
|
+
query: 'Has(Duration) and Duration > 1000',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "Find all logs that are within the namespace 'crombolo.schema'",
|
|
30
|
+
query: "StartsWith(SourceContext, 'crombolo.schema')",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'Find all logs that use a specific log message template',
|
|
34
|
+
query: "@MessageTemplate = 'Operation {operationId} ({method} {path}) is not implemented'",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'Find all logs that the message has localhost in it with SQL like',
|
|
38
|
+
query: "@Message like '%localhost%'",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "Find all logs that the message that starts with 'end' in it with SQL like",
|
|
42
|
+
query: "@Message like 'end%'",
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
export const logViewerMigration: Migration = {
|
|
47
|
+
from: STATE_USERS_LOCALISATION,
|
|
48
|
+
to: STATE_LOG_VIEWER,
|
|
49
|
+
name: 'AddLogViewerSavedSearches',
|
|
50
|
+
kind: 'expand',
|
|
51
|
+
release: '0.3.0',
|
|
52
|
+
async up(db: Db) {
|
|
53
|
+
const t = db.dialect.types
|
|
54
|
+
// The name is matched exactly, case and all, as Umbraco matches it
|
|
55
|
+
await db.exec(
|
|
56
|
+
`CREATE TABLE log_viewer_query (
|
|
57
|
+
id ${t.identity},
|
|
58
|
+
name ${t.text} NOT NULL UNIQUE,
|
|
59
|
+
query ${t.text} NOT NULL
|
|
60
|
+
)`,
|
|
61
|
+
)
|
|
62
|
+
for (const search of DEFAULT_LOG_SEARCHES)
|
|
63
|
+
await db.exec('INSERT INTO log_viewer_query (name, query) VALUES (?, ?)', [
|
|
64
|
+
search.name,
|
|
65
|
+
search.query,
|
|
66
|
+
])
|
|
67
|
+
},
|
|
68
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration 008 — `member_property_type`: what a member may see and edit of
|
|
3
|
+
* each member-type property, and whether it is sensitive. Umbraco's
|
|
4
|
+
* `cmsMemberType` (`propertytypeId`, `memberCanEdit`, `viewOnProfile`,
|
|
5
|
+
* `isSensitive`), snake-cased.
|
|
6
|
+
*/
|
|
7
|
+
import type { Db } from '../database.ts'
|
|
8
|
+
import type { Migration } from '../migrations.ts'
|
|
9
|
+
import { STATE_GROUP_PERMISSIONS } from './group-permissions.ts'
|
|
10
|
+
|
|
11
|
+
export const STATE_MEMBER_PROPERTY_TYPE = '{6f1d4a8e-0008-4c21-9d3a-8b1e5c7a2f08}'
|
|
12
|
+
|
|
13
|
+
export const memberPropertyTypeMigration: Migration = {
|
|
14
|
+
from: STATE_GROUP_PERMISSIONS,
|
|
15
|
+
to: STATE_MEMBER_PROPERTY_TYPE,
|
|
16
|
+
name: 'AddMemberPropertyType',
|
|
17
|
+
kind: 'expand',
|
|
18
|
+
release: '0.3.0',
|
|
19
|
+
async up(db: Db) {
|
|
20
|
+
const t = db.dialect.types
|
|
21
|
+
await db.exec(
|
|
22
|
+
`CREATE TABLE member_property_type (
|
|
23
|
+
property_type_id ${t.integer} PRIMARY KEY REFERENCES property_type (id),
|
|
24
|
+
member_can_edit ${t.boolean} NOT NULL,
|
|
25
|
+
member_can_view ${t.boolean} NOT NULL,
|
|
26
|
+
is_sensitive ${t.boolean} NOT NULL
|
|
27
|
+
)`,
|
|
28
|
+
)
|
|
29
|
+
},
|
|
30
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration 013 — members and the identities they sign in with.
|
|
3
|
+
*
|
|
4
|
+
* A member is a node facet, exactly like a document: `node` + `content` +
|
|
5
|
+
* `content_version` + `member`, so member types, properties and versioning are
|
|
6
|
+
* the machinery that already exists. Member groups are plain `node` rows with
|
|
7
|
+
* the MemberGroup object type — no table of their own — and `member_group_member`
|
|
8
|
+
* is the membership between them (Umbraco's `cmsMember2MemberGroup`).
|
|
9
|
+
*
|
|
10
|
+
* `external_login` is deliberately polymorphic: `user_or_member_key` serves
|
|
11
|
+
* back-office users and members alike, as Umbraco's does, so federation added
|
|
12
|
+
* for one works for the other.
|
|
13
|
+
*/
|
|
14
|
+
import type { Db } from '../database.ts'
|
|
15
|
+
import type { Migration } from '../migrations.ts'
|
|
16
|
+
import { STATE_LOG_VIEWER } from './log-viewer.ts'
|
|
17
|
+
|
|
18
|
+
export const STATE_MEMBERS = '{6f1d4a8e-0013-4c21-9d3a-8b1e5c7a2f13}'
|
|
19
|
+
|
|
20
|
+
export const membersMigration: Migration = {
|
|
21
|
+
from: STATE_LOG_VIEWER,
|
|
22
|
+
to: STATE_MEMBERS,
|
|
23
|
+
name: 'AddMembers',
|
|
24
|
+
kind: 'expand',
|
|
25
|
+
release: '0.3.0',
|
|
26
|
+
async up(db: Db) {
|
|
27
|
+
const t = db.dialect.types
|
|
28
|
+
|
|
29
|
+
// Umbraco's `cmsMember`, snake-cased. The password columns stay nullable:
|
|
30
|
+
// a member who only ever signs in through a provider has no password.
|
|
31
|
+
await db.exec(
|
|
32
|
+
`CREATE TABLE member (
|
|
33
|
+
node_id ${t.integer} PRIMARY KEY REFERENCES content (node_id),
|
|
34
|
+
email ${t.textCi} NOT NULL,
|
|
35
|
+
login_name ${t.textCi} NOT NULL,
|
|
36
|
+
password ${t.text},
|
|
37
|
+
password_config ${t.text},
|
|
38
|
+
security_stamp_token ${t.text},
|
|
39
|
+
email_confirmed_date ${t.timestamp},
|
|
40
|
+
failed_password_attempts ${t.integer} NOT NULL,
|
|
41
|
+
is_locked_out ${t.boolean} NOT NULL,
|
|
42
|
+
is_approved ${t.boolean} NOT NULL,
|
|
43
|
+
last_login_date ${t.timestamp},
|
|
44
|
+
last_lockout_date ${t.timestamp},
|
|
45
|
+
last_password_change_date ${t.timestamp}
|
|
46
|
+
)`,
|
|
47
|
+
)
|
|
48
|
+
await db.exec('CREATE UNIQUE INDEX ux_member_login ON member (login_name)')
|
|
49
|
+
await db.exec('CREATE INDEX ix_member_email ON member (email)')
|
|
50
|
+
|
|
51
|
+
await db.exec(
|
|
52
|
+
`CREATE TABLE member_group_member (
|
|
53
|
+
member_id ${t.integer} NOT NULL REFERENCES member (node_id),
|
|
54
|
+
member_group_id ${t.integer} NOT NULL REFERENCES node (id),
|
|
55
|
+
PRIMARY KEY (member_id, member_group_id)
|
|
56
|
+
)`,
|
|
57
|
+
)
|
|
58
|
+
await db.exec(
|
|
59
|
+
'CREATE INDEX ix_member_group_member_group ON member_group_member (member_group_id)',
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
await db.exec(
|
|
63
|
+
`CREATE TABLE external_login (
|
|
64
|
+
id ${t.identity},
|
|
65
|
+
user_or_member_key ${t.text} NOT NULL,
|
|
66
|
+
login_provider ${t.textCi} NOT NULL,
|
|
67
|
+
provider_key ${t.text} NOT NULL,
|
|
68
|
+
create_date ${t.timestamp} NOT NULL,
|
|
69
|
+
user_data ${t.text}
|
|
70
|
+
)`,
|
|
71
|
+
)
|
|
72
|
+
// One identity per provider per principal, and the sign-in lookup is by
|
|
73
|
+
// the provider's own key.
|
|
74
|
+
await db.exec(
|
|
75
|
+
`CREATE UNIQUE INDEX ux_external_login_provider_principal
|
|
76
|
+
ON external_login (login_provider, user_or_member_key)`,
|
|
77
|
+
)
|
|
78
|
+
await db.exec(
|
|
79
|
+
`CREATE INDEX ix_external_login_provider_key
|
|
80
|
+
ON external_login (login_provider, provider_key)`,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
await db.exec(
|
|
84
|
+
`CREATE TABLE external_login_token (
|
|
85
|
+
id ${t.identity},
|
|
86
|
+
external_login_id ${t.integer} NOT NULL REFERENCES external_login (id),
|
|
87
|
+
name ${t.textCi} NOT NULL,
|
|
88
|
+
value ${t.text} NOT NULL,
|
|
89
|
+
create_date ${t.timestamp} NOT NULL
|
|
90
|
+
)`,
|
|
91
|
+
)
|
|
92
|
+
await db.exec(
|
|
93
|
+
`CREATE UNIQUE INDEX ux_external_login_token_name
|
|
94
|
+
ON external_login_token (external_login_id, name)`,
|
|
95
|
+
)
|
|
96
|
+
},
|
|
97
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The migration chain. Append new migrations; never edit a released one.
|
|
3
|
+
*
|
|
4
|
+
* Every step here declares `release: '0.3.0'`, because 0.3.0 is the first release
|
|
5
|
+
* there has ever been: nothing was published before the repository was split, so
|
|
6
|
+
* none of these shipped in any earlier version, whatever the numbers said while
|
|
7
|
+
* they were being written.
|
|
8
|
+
*
|
|
9
|
+
* `release` is compared between a contract and the expand it removes — a
|
|
10
|
+
* contract may only take away what an *earlier* release added — and a contract
|
|
11
|
+
* may only ship in a major (`x.0.0`), because every minor and patch is rolled
|
|
12
|
+
* under live nodes. The first contract will therefore name 1.0.0 at the earliest.
|
|
13
|
+
*/
|
|
14
|
+
import { MigrationPlan } from '../migrations.ts'
|
|
15
|
+
import { assistantMigration } from './assistant.ts'
|
|
16
|
+
import { bundleComponentsMigration } from './bundle-components.ts'
|
|
17
|
+
import { bundlePackageMigration } from './bundle-package.ts'
|
|
18
|
+
import { bundlesRenameMigration } from './bundles-rename.ts'
|
|
19
|
+
import { changeReportMigration } from './change-report.ts'
|
|
20
|
+
import { clientIdPrefixMigration } from './client-id-prefix.ts'
|
|
21
|
+
import { contentMigration } from './content.ts'
|
|
22
|
+
import { contentEditingMigration } from './content-editing.ts'
|
|
23
|
+
import { createdPackagesMigration } from './created-packages.ts'
|
|
24
|
+
import { elementsMigration } from './elements.ts'
|
|
25
|
+
import { formPermissionPrefixMigration } from './form-permission-prefix.ts'
|
|
26
|
+
import { formPermissionsMigration } from './form-permissions.ts'
|
|
27
|
+
import { formWorkflowsMigration } from './form-workflows.ts'
|
|
28
|
+
import { formsMigration } from './forms.ts'
|
|
29
|
+
import { groupPermissionsMigration } from './group-permissions.ts'
|
|
30
|
+
import { identityMigration } from './identity.ts'
|
|
31
|
+
import { logViewerMigration } from './log-viewer.ts'
|
|
32
|
+
import { memberPropertyTypeMigration } from './member-property-type.ts'
|
|
33
|
+
import { membersMigration } from './members.ts'
|
|
34
|
+
import { publicAccessMigration } from './public-access.ts'
|
|
35
|
+
import { recycleBinMigration } from './recycle-bin.ts'
|
|
36
|
+
import { redirectsMigration } from './redirects.ts'
|
|
37
|
+
import { richTextUiMigration } from './rich-text-ui.ts'
|
|
38
|
+
import { serverRoleMigration } from './server-role.ts'
|
|
39
|
+
import { sinceVersionMigration } from './since-version.ts'
|
|
40
|
+
import { transferRunPackageMigration } from './transfer-run-package.ts'
|
|
41
|
+
import { transferRunsMigration } from './transfer-runs.ts'
|
|
42
|
+
import { upgradeReportMigration } from './upgrade-report.ts'
|
|
43
|
+
import { usersLocalisationMigration } from './users-localisation.ts'
|
|
44
|
+
import { valuesMigration } from './values.ts'
|
|
45
|
+
|
|
46
|
+
export const cromboloPlan = new MigrationPlan([
|
|
47
|
+
identityMigration,
|
|
48
|
+
contentMigration,
|
|
49
|
+
valuesMigration,
|
|
50
|
+
sinceVersionMigration,
|
|
51
|
+
upgradeReportMigration,
|
|
52
|
+
recycleBinMigration,
|
|
53
|
+
groupPermissionsMigration,
|
|
54
|
+
memberPropertyTypeMigration,
|
|
55
|
+
richTextUiMigration,
|
|
56
|
+
contentEditingMigration,
|
|
57
|
+
usersLocalisationMigration,
|
|
58
|
+
logViewerMigration,
|
|
59
|
+
membersMigration,
|
|
60
|
+
publicAccessMigration,
|
|
61
|
+
redirectsMigration,
|
|
62
|
+
elementsMigration,
|
|
63
|
+
assistantMigration,
|
|
64
|
+
changeReportMigration,
|
|
65
|
+
transferRunsMigration,
|
|
66
|
+
clientIdPrefixMigration,
|
|
67
|
+
serverRoleMigration,
|
|
68
|
+
createdPackagesMigration,
|
|
69
|
+
formsMigration,
|
|
70
|
+
formWorkflowsMigration,
|
|
71
|
+
formPermissionsMigration,
|
|
72
|
+
formPermissionPrefixMigration,
|
|
73
|
+
bundlesRenameMigration,
|
|
74
|
+
bundleComponentsMigration,
|
|
75
|
+
bundlePackageMigration,
|
|
76
|
+
transferRunPackageMigration,
|
|
77
|
+
])
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration 014 — public access: which pages are protected, and by what.
|
|
3
|
+
*
|
|
4
|
+
* Umbraco's `umbracoAccess` + `umbracoAccessRule`, snake-cased. One entry per
|
|
5
|
+
* protected branch root, naming the page to show a caller who is not signed in
|
|
6
|
+
* (`login_node_id`) and the page to show one who is signed in but not allowed
|
|
7
|
+
* (`error_node_id`). The rules are values, not foreign keys — a member group's
|
|
8
|
+
* **name** or a member's **username**, exactly as Umbraco stores them — because
|
|
9
|
+
* a rule is evaluated against the claims a signed-in member carries, and renaming
|
|
10
|
+
* the group is what breaks the rule, not deleting a row.
|
|
11
|
+
*/
|
|
12
|
+
import type { Db } from '../database.ts'
|
|
13
|
+
import type { Migration } from '../migrations.ts'
|
|
14
|
+
import { STATE_MEMBERS } from './members.ts'
|
|
15
|
+
|
|
16
|
+
export const STATE_PUBLIC_ACCESS = '{6f1d4a8e-0014-4c21-9d3a-8b1e5c7a2f14}'
|
|
17
|
+
|
|
18
|
+
export const publicAccessMigration: Migration = {
|
|
19
|
+
from: STATE_MEMBERS,
|
|
20
|
+
to: STATE_PUBLIC_ACCESS,
|
|
21
|
+
name: 'AddPublicAccess',
|
|
22
|
+
kind: 'expand',
|
|
23
|
+
release: '0.3.0',
|
|
24
|
+
async up(db: Db) {
|
|
25
|
+
const t = db.dialect.types
|
|
26
|
+
|
|
27
|
+
await db.exec(
|
|
28
|
+
`CREATE TABLE public_access (
|
|
29
|
+
id ${t.identity},
|
|
30
|
+
key ${t.text} NOT NULL,
|
|
31
|
+
node_id ${t.integer} NOT NULL REFERENCES node (id),
|
|
32
|
+
login_node_id ${t.integer} NOT NULL REFERENCES node (id),
|
|
33
|
+
error_node_id ${t.integer} NOT NULL REFERENCES node (id),
|
|
34
|
+
create_date ${t.timestamp} NOT NULL,
|
|
35
|
+
update_date ${t.timestamp} NOT NULL
|
|
36
|
+
)`,
|
|
37
|
+
)
|
|
38
|
+
// One entry per protected page, and the render path looks an entry up by node.
|
|
39
|
+
await db.exec('CREATE UNIQUE INDEX ux_public_access_node ON public_access (node_id)')
|
|
40
|
+
await db.exec('CREATE UNIQUE INDEX ux_public_access_key ON public_access (key)')
|
|
41
|
+
|
|
42
|
+
await db.exec(
|
|
43
|
+
`CREATE TABLE public_access_rule (
|
|
44
|
+
id ${t.identity},
|
|
45
|
+
public_access_id ${t.integer} NOT NULL REFERENCES public_access (id),
|
|
46
|
+
rule_type ${t.varchar(255)} NOT NULL,
|
|
47
|
+
rule_value ${t.textCi} NOT NULL,
|
|
48
|
+
create_date ${t.timestamp} NOT NULL
|
|
49
|
+
)`,
|
|
50
|
+
)
|
|
51
|
+
await db.exec(
|
|
52
|
+
`CREATE UNIQUE INDEX ux_public_access_rule
|
|
53
|
+
ON public_access_rule (public_access_id, rule_type, rule_value)`,
|
|
54
|
+
)
|
|
55
|
+
},
|
|
56
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration 006 — `document.original_parent_id`: where a trashed document came
|
|
3
|
+
* from, so the recycle bin can say so and a restore can put it back.
|
|
4
|
+
*/
|
|
5
|
+
import type { Db } from '../database.ts'
|
|
6
|
+
import type { Migration } from '../migrations.ts'
|
|
7
|
+
import { schemaOps } from '../schema-ops.ts'
|
|
8
|
+
import { STATE_UPGRADE_REPORT } from './upgrade-report.ts'
|
|
9
|
+
|
|
10
|
+
export const STATE_RECYCLE_BIN = '{6f1d4a8e-0006-4c21-9d3a-8b1e5c7a2f06}'
|
|
11
|
+
|
|
12
|
+
export const recycleBinMigration: Migration = {
|
|
13
|
+
from: STATE_UPGRADE_REPORT,
|
|
14
|
+
to: STATE_RECYCLE_BIN,
|
|
15
|
+
name: 'AddRecycleBinOriginalParent',
|
|
16
|
+
kind: 'expand',
|
|
17
|
+
release: '0.3.0',
|
|
18
|
+
async up(db: Db) {
|
|
19
|
+
await schemaOps(db).addColumn('document', 'original_parent_id', db.dialect.types.integer)
|
|
20
|
+
},
|
|
21
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration 015 — redirects: the URLs a page used to answer on.
|
|
3
|
+
*
|
|
4
|
+
* Umbraco's `umbracoRedirectUrl` holds one row per retired route, always pointing
|
|
5
|
+
* at a document, and resolves a duplicate by taking the most recent. This table
|
|
6
|
+
* widens it in two ways, both needed by redirects a site declares in code rather
|
|
7
|
+
* than earns by being renamed: a rule may match a subtree or a regular expression
|
|
8
|
+
* as well as one exact route, and it may point at a path or an external URL as
|
|
9
|
+
* well as at a document.
|
|
10
|
+
*
|
|
11
|
+
* `root_key` scopes a rule to the document a hostname roots, as Umbraco scopes a
|
|
12
|
+
* route by the node id its domain sits on — so renaming the hostname leaves every
|
|
13
|
+
* redirect below it working. `match_hash` is a digest of everything a rule matches
|
|
14
|
+
* on: a unique index cannot dedupe over nullable columns, since NULL never equals
|
|
15
|
+
* NULL in either dialect, and a pattern is too long to index directly. Umbraco
|
|
16
|
+
* indexes a hash of the URL for the same two reasons.
|
|
17
|
+
*/
|
|
18
|
+
import type { Db } from '../database.ts'
|
|
19
|
+
import type { Migration } from '../migrations.ts'
|
|
20
|
+
import { STATE_PUBLIC_ACCESS } from './public-access.ts'
|
|
21
|
+
|
|
22
|
+
export const STATE_REDIRECTS = '{6f1d4a8e-0015-4b73-a2c5-91e4d7b3086a}'
|
|
23
|
+
|
|
24
|
+
export const redirectsMigration: Migration = {
|
|
25
|
+
from: STATE_PUBLIC_ACCESS,
|
|
26
|
+
to: STATE_REDIRECTS,
|
|
27
|
+
name: 'AddRedirectUrl',
|
|
28
|
+
kind: 'expand',
|
|
29
|
+
release: '0.3.0',
|
|
30
|
+
async up(db: Db) {
|
|
31
|
+
const t = db.dialect.types
|
|
32
|
+
|
|
33
|
+
await db.exec(
|
|
34
|
+
`CREATE TABLE redirect_url (
|
|
35
|
+
id ${t.identity},
|
|
36
|
+
key ${t.text} NOT NULL,
|
|
37
|
+
source ${t.varchar(16)} NOT NULL,
|
|
38
|
+
match_kind ${t.varchar(16)} NOT NULL,
|
|
39
|
+
pattern ${t.text} NOT NULL,
|
|
40
|
+
root_key ${t.text} NULL,
|
|
41
|
+
culture ${t.varchar(32)} NULL,
|
|
42
|
+
target_kind ${t.varchar(16)} NOT NULL,
|
|
43
|
+
target ${t.text} NOT NULL,
|
|
44
|
+
status_code ${t.integer} NOT NULL,
|
|
45
|
+
sort_order ${t.integer} NOT NULL,
|
|
46
|
+
match_hash ${t.varchar(64)} NOT NULL,
|
|
47
|
+
create_date ${t.timestamp} NOT NULL
|
|
48
|
+
)`,
|
|
49
|
+
)
|
|
50
|
+
await db.exec('CREATE UNIQUE INDEX ux_redirect_url_key ON redirect_url (key)')
|
|
51
|
+
// One rule per thing matched: registering a route a page already gave up
|
|
52
|
+
// replaces where it points rather than stacking another row behind it.
|
|
53
|
+
await db.exec('CREATE UNIQUE INDEX ux_redirect_url_match ON redirect_url (match_hash)')
|
|
54
|
+
// The dashboard lists by recency, and the Info tab lists by document.
|
|
55
|
+
await db.exec('CREATE INDEX ix_redirect_url_target ON redirect_url (target_kind, target)')
|
|
56
|
+
},
|
|
57
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration 009 — the rich text data type's editor UI. Databases seeded
|
|
3
|
+
* before this carry `Umb.PropertyEditorUi.RichText`, which the backoffice does
|
|
4
|
+
* not register ("The configured property editor UI could not be found");
|
|
5
|
+
* Umbraco seeds `Umb.PropertyEditorUi.Tiptap` with its configuration. Only a
|
|
6
|
+
* row still carrying the wrong alias is changed; a customised one is left.
|
|
7
|
+
*/
|
|
8
|
+
import type { Db } from '../database.ts'
|
|
9
|
+
import type { Migration } from '../migrations.ts'
|
|
10
|
+
import { RICH_TEXT_CONFIG } from './content-seed.ts'
|
|
11
|
+
import { STATE_MEMBER_PROPERTY_TYPE } from './member-property-type.ts'
|
|
12
|
+
|
|
13
|
+
export const STATE_RICH_TEXT_UI = '{6f1d4a8e-0009-4c21-9d3a-8b1e5c7a2f09}'
|
|
14
|
+
|
|
15
|
+
export const richTextUiMigration: Migration = {
|
|
16
|
+
from: STATE_MEMBER_PROPERTY_TYPE,
|
|
17
|
+
to: STATE_RICH_TEXT_UI,
|
|
18
|
+
name: 'FixRichTextEditorUi',
|
|
19
|
+
kind: 'expand',
|
|
20
|
+
release: '0.3.0',
|
|
21
|
+
async up(db: Db) {
|
|
22
|
+
await db.exec(
|
|
23
|
+
`UPDATE data_type SET editor_ui_alias = ?, config = ?
|
|
24
|
+
WHERE editor_ui_alias = ?
|
|
25
|
+
AND node_id = (SELECT id FROM node WHERE unique_id = ?)`,
|
|
26
|
+
[
|
|
27
|
+
'Umb.PropertyEditorUi.Tiptap',
|
|
28
|
+
JSON.stringify(RICH_TEXT_CONFIG),
|
|
29
|
+
'Umb.PropertyEditorUi.RichText',
|
|
30
|
+
'ca90c950-0aff-4e72-b976-a30b1ac57dad',
|
|
31
|
+
],
|
|
32
|
+
)
|
|
33
|
+
},
|
|
34
|
+
}
|