@hraness/peopleblade 0.1.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/README.md +79 -0
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/dist/cli.ts +61 -0
- package/dist/migrations/001_core.sql +223 -0
- package/dist/migrations/002_incremental_sources.sql +93 -0
- package/dist/migrations/003_identity_review.sql +67 -0
- package/dist/migrations/004_source_realms_and_observations.sql +91 -0
- package/dist/migrations/005_identity_representatives.sql +87 -0
- package/dist/migrations/006_profile_url_trust.sql +15 -0
- package/dist/migrations/007_person_notes.sql +39 -0
- package/dist/migrations/008_name_identity_trust.sql +33 -0
- package/dist/migrations/009_provider_collection_states.sql +23 -0
- package/dist/migrations/010_source_execution_receipts.sql +51 -0
- package/dist/migrations/011_source_account_convergence.sql +125 -0
- package/dist/migrations/012_granola_note_attribution.sql +389 -0
- package/dist/migrations/013_source_binding_incarnations.sql +339 -0
- package/dist/migrations/014_source_binding_transition_guard_recovery.sql +121 -0
- package/dist/peopleblade.js +20203 -0
- package/package.json +36 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
PRAGMA foreign_keys = ON;
|
|
2
|
+
|
|
3
|
+
DROP INDEX person_notes_source_identity;
|
|
4
|
+
|
|
5
|
+
CREATE UNIQUE INDEX person_notes_non_granola_source_identity
|
|
6
|
+
ON person_notes(source, source_id, person_id)
|
|
7
|
+
WHERE source_id IS NOT NULL AND source <> 'granola';
|
|
8
|
+
|
|
9
|
+
CREATE UNIQUE INDEX person_notes_granola_legacy_source_identity
|
|
10
|
+
ON person_notes(source, source_id, person_id)
|
|
11
|
+
WHERE source = 'granola'
|
|
12
|
+
AND source_id IS NOT NULL
|
|
13
|
+
AND json_type(metadata_json, '$.granolaAttendeeIdentity') IS NOT 'object';
|
|
14
|
+
|
|
15
|
+
CREATE INDEX person_notes_granola_source_id
|
|
16
|
+
ON person_notes(source_id, id)
|
|
17
|
+
WHERE source = 'granola' AND source_id IS NOT NULL;
|
|
18
|
+
|
|
19
|
+
CREATE TRIGGER person_notes_positive_id
|
|
20
|
+
AFTER INSERT ON person_notes FOR EACH ROW
|
|
21
|
+
WHEN NEW.id < 1
|
|
22
|
+
BEGIN SELECT RAISE(ABORT, 'person note ids must be positive'); END;
|
|
23
|
+
|
|
24
|
+
CREATE TRIGGER person_notes_no_replace
|
|
25
|
+
BEFORE INSERT ON person_notes FOR EACH ROW
|
|
26
|
+
WHEN EXISTS (
|
|
27
|
+
SELECT 1 FROM person_notes existing
|
|
28
|
+
WHERE (NEW.id IS NOT -1 AND existing.id = NEW.id)
|
|
29
|
+
OR (
|
|
30
|
+
NEW.source_id IS NOT NULL
|
|
31
|
+
AND existing.source = NEW.source
|
|
32
|
+
AND existing.source_id = NEW.source_id
|
|
33
|
+
AND (
|
|
34
|
+
(NEW.source <> 'granola' AND existing.person_id = NEW.person_id)
|
|
35
|
+
OR (
|
|
36
|
+
NEW.source = 'granola'
|
|
37
|
+
AND (
|
|
38
|
+
(
|
|
39
|
+
json_type(NEW.metadata_json, '$.granolaAttendeeIdentity') IS NOT 'object'
|
|
40
|
+
AND existing.person_id = NEW.person_id
|
|
41
|
+
)
|
|
42
|
+
OR (
|
|
43
|
+
json_type(NEW.metadata_json, '$.granolaAttendeeIdentity') = 'object'
|
|
44
|
+
AND json_type(existing.metadata_json, '$.granolaAttendeeIdentity') = 'object'
|
|
45
|
+
AND json_extract(
|
|
46
|
+
NEW.metadata_json, '$.granolaAttendeeIdentity.kind'
|
|
47
|
+
) IS json_extract(
|
|
48
|
+
existing.metadata_json, '$.granolaAttendeeIdentity.kind'
|
|
49
|
+
)
|
|
50
|
+
AND json_extract(
|
|
51
|
+
NEW.metadata_json, '$.granolaAttendeeIdentity.valueSha256'
|
|
52
|
+
) IS json_extract(
|
|
53
|
+
existing.metadata_json, '$.granolaAttendeeIdentity.valueSha256'
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
OR (
|
|
57
|
+
existing.person_id = NEW.person_id
|
|
58
|
+
AND json_type(
|
|
59
|
+
existing.metadata_json, '$.granolaAttendeeIdentity'
|
|
60
|
+
) IS NOT 'object'
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
BEGIN SELECT RAISE(ABORT, 'person notes cannot be replaced'); END;
|
|
68
|
+
|
|
69
|
+
CREATE TABLE person_note_granola_identity_sources (
|
|
70
|
+
note_id INTEGER PRIMARY KEY CHECK(note_id >= 1)
|
|
71
|
+
REFERENCES person_notes(id) ON DELETE RESTRICT,
|
|
72
|
+
source_id TEXT NOT NULL CHECK(length(source_id) BETWEEN 1 AND 256),
|
|
73
|
+
source_contact_method_id INTEGER NOT NULL CHECK(source_contact_method_id >= 1)
|
|
74
|
+
REFERENCES contact_methods(id) ON DELETE RESTRICT,
|
|
75
|
+
identity_kind TEXT NOT NULL CHECK(identity_kind IN ('email','phone')),
|
|
76
|
+
identity_value_sha256 TEXT NOT NULL CHECK(
|
|
77
|
+
length(identity_value_sha256) = 64
|
|
78
|
+
AND identity_value_sha256 NOT GLOB '*[^0-9a-f]*'
|
|
79
|
+
),
|
|
80
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
81
|
+
UNIQUE(source_id, identity_kind, identity_value_sha256)
|
|
82
|
+
) STRICT;
|
|
83
|
+
|
|
84
|
+
CREATE INDEX person_note_granola_identity_sources_method
|
|
85
|
+
ON person_note_granola_identity_sources(source_contact_method_id);
|
|
86
|
+
|
|
87
|
+
CREATE TRIGGER person_note_granola_identity_source_valid
|
|
88
|
+
BEFORE INSERT ON person_note_granola_identity_sources FOR EACH ROW
|
|
89
|
+
BEGIN
|
|
90
|
+
SELECT CASE WHEN
|
|
91
|
+
EXISTS (
|
|
92
|
+
SELECT 1 FROM person_note_granola_identity_sources existing
|
|
93
|
+
WHERE existing.note_id = NEW.note_id
|
|
94
|
+
OR (
|
|
95
|
+
existing.source_id = NEW.source_id
|
|
96
|
+
AND existing.identity_kind = NEW.identity_kind
|
|
97
|
+
AND existing.identity_value_sha256 = NEW.identity_value_sha256
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
OR NOT EXISTS (
|
|
101
|
+
SELECT 1
|
|
102
|
+
FROM person_notes note
|
|
103
|
+
JOIN contact_methods method ON method.id = NEW.source_contact_method_id
|
|
104
|
+
LEFT JOIN provider_resources resource ON resource.id = method.provider_resource_id
|
|
105
|
+
LEFT JOIN source_realms realm ON realm.id = resource.source_realm_id
|
|
106
|
+
JOIN person_identity_components method_component
|
|
107
|
+
ON method_component.person_id = method.person_id
|
|
108
|
+
JOIN person_identity_components note_component
|
|
109
|
+
ON note_component.person_id = note.person_id
|
|
110
|
+
WHERE note.id = NEW.note_id
|
|
111
|
+
AND note.source = 'granola'
|
|
112
|
+
AND note.source_id = NEW.source_id
|
|
113
|
+
AND json_type(note.metadata_json, '$.granolaAttendeeIdentity') = 'object'
|
|
114
|
+
AND json_extract(note.metadata_json, '$.granolaAttendeeIdentity.schemaVersion') = 1
|
|
115
|
+
AND json_extract(note.metadata_json, '$.granolaAttendeeIdentity.kind') = NEW.identity_kind
|
|
116
|
+
AND json_extract(
|
|
117
|
+
note.metadata_json, '$.granolaAttendeeIdentity.valueSha256'
|
|
118
|
+
) = NEW.identity_value_sha256
|
|
119
|
+
AND json_type(
|
|
120
|
+
note.metadata_json, '$.granolaAttendeeIdentity.contactMethodId'
|
|
121
|
+
) = 'integer'
|
|
122
|
+
AND json_extract(
|
|
123
|
+
note.metadata_json, '$.granolaAttendeeIdentity.contactMethodId'
|
|
124
|
+
) = NEW.source_contact_method_id
|
|
125
|
+
AND method.kind = NEW.identity_kind
|
|
126
|
+
AND method.confidence = 'exact'
|
|
127
|
+
AND method.active = 1
|
|
128
|
+
AND method.identity_eligible = 1
|
|
129
|
+
AND method_component.canonical_person_id = note_component.canonical_person_id
|
|
130
|
+
AND (
|
|
131
|
+
method.provider_resource_id IS NULL
|
|
132
|
+
OR (resource.active = 1 AND (realm.id IS NULL OR realm.active = 1))
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
THEN RAISE(ABORT, 'Granola note identity source is not exact') END;
|
|
136
|
+
END;
|
|
137
|
+
|
|
138
|
+
CREATE TRIGGER person_note_granola_identity_sources_immutable_update
|
|
139
|
+
BEFORE UPDATE ON person_note_granola_identity_sources FOR EACH ROW
|
|
140
|
+
BEGIN SELECT RAISE(ABORT, 'Granola note identity sources are immutable'); END;
|
|
141
|
+
|
|
142
|
+
CREATE TRIGGER person_note_granola_identity_sources_immutable_delete
|
|
143
|
+
BEFORE DELETE ON person_note_granola_identity_sources FOR EACH ROW
|
|
144
|
+
BEGIN SELECT RAISE(ABORT, 'Granola note identity sources are immutable'); END;
|
|
145
|
+
|
|
146
|
+
CREATE TABLE person_note_attribution_revisions (
|
|
147
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(id >= 1),
|
|
148
|
+
note_id INTEGER NOT NULL
|
|
149
|
+
REFERENCES person_note_granola_identity_sources(note_id) ON DELETE RESTRICT,
|
|
150
|
+
source_id TEXT NOT NULL CHECK(length(source_id) BETWEEN 1 AND 256),
|
|
151
|
+
from_person_id INTEGER NOT NULL REFERENCES people(id) ON DELETE RESTRICT,
|
|
152
|
+
to_person_id INTEGER NOT NULL REFERENCES people(id) ON DELETE RESTRICT,
|
|
153
|
+
target_contact_method_id INTEGER NOT NULL CHECK(target_contact_method_id >= 1)
|
|
154
|
+
REFERENCES contact_methods(id) ON DELETE RESTRICT,
|
|
155
|
+
identity_kind TEXT NOT NULL CHECK(identity_kind IN ('email','phone')),
|
|
156
|
+
identity_value_sha256 TEXT NOT NULL CHECK(
|
|
157
|
+
length(identity_value_sha256) = 64
|
|
158
|
+
AND identity_value_sha256 NOT GLOB '*[^0-9a-f]*'
|
|
159
|
+
),
|
|
160
|
+
identity_revision INTEGER NOT NULL CHECK(identity_revision >= 0),
|
|
161
|
+
reason TEXT NOT NULL CHECK(reason = 'exact-granola-source-replay'),
|
|
162
|
+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
163
|
+
CHECK(from_person_id <> to_person_id)
|
|
164
|
+
) STRICT;
|
|
165
|
+
|
|
166
|
+
CREATE INDEX person_note_attribution_revisions_note
|
|
167
|
+
ON person_note_attribution_revisions(note_id, id);
|
|
168
|
+
|
|
169
|
+
CREATE INDEX person_note_attribution_revisions_target_method
|
|
170
|
+
ON person_note_attribution_revisions(target_contact_method_id);
|
|
171
|
+
|
|
172
|
+
CREATE TRIGGER person_note_attribution_revision_valid
|
|
173
|
+
BEFORE INSERT ON person_note_attribution_revisions FOR EACH ROW
|
|
174
|
+
BEGIN
|
|
175
|
+
SELECT CASE WHEN
|
|
176
|
+
NEW.id IS NOT -1
|
|
177
|
+
OR NEW.from_person_id IS NOT coalesce(
|
|
178
|
+
(SELECT prior.to_person_id
|
|
179
|
+
FROM person_note_attribution_revisions prior
|
|
180
|
+
WHERE prior.note_id = NEW.note_id
|
|
181
|
+
ORDER BY prior.id DESC LIMIT 1),
|
|
182
|
+
(SELECT note.person_id FROM person_notes note WHERE note.id = NEW.note_id)
|
|
183
|
+
)
|
|
184
|
+
OR NOT EXISTS (
|
|
185
|
+
SELECT 1
|
|
186
|
+
FROM person_note_granola_identity_sources source
|
|
187
|
+
JOIN person_notes note ON note.id = source.note_id
|
|
188
|
+
JOIN contact_methods original_method
|
|
189
|
+
ON original_method.id = source.source_contact_method_id
|
|
190
|
+
JOIN contact_methods method ON method.id = NEW.target_contact_method_id
|
|
191
|
+
LEFT JOIN provider_resources resource ON resource.id = method.provider_resource_id
|
|
192
|
+
LEFT JOIN source_realms realm ON realm.id = resource.source_realm_id
|
|
193
|
+
JOIN person_identity_components method_component
|
|
194
|
+
ON method_component.person_id = method.person_id
|
|
195
|
+
JOIN person_identity_components target_component
|
|
196
|
+
ON target_component.person_id = NEW.to_person_id
|
|
197
|
+
WHERE source.note_id = NEW.note_id
|
|
198
|
+
AND source.source_id = NEW.source_id
|
|
199
|
+
AND source.identity_kind = NEW.identity_kind
|
|
200
|
+
AND source.identity_value_sha256 = NEW.identity_value_sha256
|
|
201
|
+
AND note.source = 'granola'
|
|
202
|
+
AND note.source_id = source.source_id
|
|
203
|
+
AND json_type(note.metadata_json, '$.granolaAttendeeIdentity') = 'object'
|
|
204
|
+
AND json_extract(note.metadata_json, '$.granolaAttendeeIdentity.schemaVersion') = 1
|
|
205
|
+
AND json_extract(note.metadata_json, '$.granolaAttendeeIdentity.kind')
|
|
206
|
+
= source.identity_kind
|
|
207
|
+
AND json_extract(
|
|
208
|
+
note.metadata_json, '$.granolaAttendeeIdentity.valueSha256'
|
|
209
|
+
) = source.identity_value_sha256
|
|
210
|
+
AND json_extract(
|
|
211
|
+
note.metadata_json, '$.granolaAttendeeIdentity.contactMethodId'
|
|
212
|
+
) = source.source_contact_method_id
|
|
213
|
+
AND original_method.kind = source.identity_kind
|
|
214
|
+
AND method.kind = NEW.identity_kind
|
|
215
|
+
AND method.confidence = 'exact'
|
|
216
|
+
AND method.active = 1
|
|
217
|
+
AND method.identity_eligible = 1
|
|
218
|
+
AND original_method.normalized_value = method.normalized_value
|
|
219
|
+
AND method_component.canonical_person_id = target_component.canonical_person_id
|
|
220
|
+
AND (
|
|
221
|
+
method.provider_resource_id IS NULL
|
|
222
|
+
OR (resource.active = 1 AND (realm.id IS NULL OR realm.active = 1))
|
|
223
|
+
)
|
|
224
|
+
AND (
|
|
225
|
+
NOT EXISTS (
|
|
226
|
+
SELECT 1
|
|
227
|
+
FROM contact_methods rival
|
|
228
|
+
LEFT JOIN provider_resources rival_resource
|
|
229
|
+
ON rival_resource.id = rival.provider_resource_id
|
|
230
|
+
LEFT JOIN source_realms rival_realm
|
|
231
|
+
ON rival_realm.id = rival_resource.source_realm_id
|
|
232
|
+
JOIN person_identity_components rival_component
|
|
233
|
+
ON rival_component.person_id = rival.person_id
|
|
234
|
+
WHERE rival.kind = method.kind
|
|
235
|
+
AND rival.normalized_value = method.normalized_value
|
|
236
|
+
AND rival.confidence = 'exact'
|
|
237
|
+
AND rival.active = 1
|
|
238
|
+
AND rival.identity_eligible = 1
|
|
239
|
+
AND (
|
|
240
|
+
rival.provider_resource_id IS NULL
|
|
241
|
+
OR (
|
|
242
|
+
rival_resource.active = 1
|
|
243
|
+
AND (rival_realm.id IS NULL OR rival_realm.active = 1)
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
AND rival_component.canonical_person_id
|
|
247
|
+
<> target_component.canonical_person_id
|
|
248
|
+
)
|
|
249
|
+
OR (
|
|
250
|
+
method.kind = 'email'
|
|
251
|
+
AND method.provider_resource_id IS NULL
|
|
252
|
+
AND json_extract(method.metadata_json, '$.source')
|
|
253
|
+
= 'explicit-user-attestation'
|
|
254
|
+
AND json_extract(method.metadata_json, '$.conflictOverride') = 1
|
|
255
|
+
AND json_type(method.metadata_json, '$.reviewedConflictingMethods') = 'array'
|
|
256
|
+
AND (
|
|
257
|
+
SELECT count(*)
|
|
258
|
+
FROM json_each(
|
|
259
|
+
method.metadata_json, '$.reviewedConflictingMethods'
|
|
260
|
+
) reviewed
|
|
261
|
+
) = (
|
|
262
|
+
SELECT count(*)
|
|
263
|
+
FROM contact_methods rival
|
|
264
|
+
LEFT JOIN provider_resources rival_resource
|
|
265
|
+
ON rival_resource.id = rival.provider_resource_id
|
|
266
|
+
LEFT JOIN source_realms rival_realm
|
|
267
|
+
ON rival_realm.id = rival_resource.source_realm_id
|
|
268
|
+
JOIN person_identity_components rival_component
|
|
269
|
+
ON rival_component.person_id = rival.person_id
|
|
270
|
+
WHERE rival.kind = method.kind
|
|
271
|
+
AND rival.normalized_value = method.normalized_value
|
|
272
|
+
AND rival.confidence = 'exact'
|
|
273
|
+
AND rival.active = 1
|
|
274
|
+
AND rival.identity_eligible = 1
|
|
275
|
+
AND (
|
|
276
|
+
rival.provider_resource_id IS NULL
|
|
277
|
+
OR (
|
|
278
|
+
rival_resource.active = 1
|
|
279
|
+
AND (rival_realm.id IS NULL OR rival_realm.active = 1)
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
AND rival_component.canonical_person_id
|
|
283
|
+
<> target_component.canonical_person_id
|
|
284
|
+
)
|
|
285
|
+
AND NOT EXISTS (
|
|
286
|
+
SELECT 1
|
|
287
|
+
FROM contact_methods rival
|
|
288
|
+
LEFT JOIN provider_resources rival_resource
|
|
289
|
+
ON rival_resource.id = rival.provider_resource_id
|
|
290
|
+
LEFT JOIN source_realms rival_realm
|
|
291
|
+
ON rival_realm.id = rival_resource.source_realm_id
|
|
292
|
+
JOIN person_identity_components rival_component
|
|
293
|
+
ON rival_component.person_id = rival.person_id
|
|
294
|
+
WHERE rival.kind = method.kind
|
|
295
|
+
AND rival.normalized_value = method.normalized_value
|
|
296
|
+
AND rival.confidence = 'exact'
|
|
297
|
+
AND rival.active = 1
|
|
298
|
+
AND rival.identity_eligible = 1
|
|
299
|
+
AND (
|
|
300
|
+
rival.provider_resource_id IS NULL
|
|
301
|
+
OR (
|
|
302
|
+
rival_resource.active = 1
|
|
303
|
+
AND (rival_realm.id IS NULL OR rival_realm.active = 1)
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
AND rival_component.canonical_person_id
|
|
307
|
+
<> target_component.canonical_person_id
|
|
308
|
+
AND NOT EXISTS (
|
|
309
|
+
SELECT 1
|
|
310
|
+
FROM json_each(
|
|
311
|
+
method.metadata_json, '$.reviewedConflictingMethods'
|
|
312
|
+
) reviewed
|
|
313
|
+
WHERE json_type(reviewed.value) = 'object'
|
|
314
|
+
AND json_extract(reviewed.value, '$.methodId') = rival.id
|
|
315
|
+
AND json_extract(reviewed.value, '$.personId') = rival.person_id
|
|
316
|
+
AND json_extract(reviewed.value, '$.canonicalPersonId')
|
|
317
|
+
= rival_component.canonical_person_id
|
|
318
|
+
AND json_extract(reviewed.value, '$.provider')
|
|
319
|
+
IS rival_resource.provider
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
OR NEW.identity_revision IS NOT (
|
|
326
|
+
SELECT identity_revision FROM local_revisions WHERE singleton = 1
|
|
327
|
+
)
|
|
328
|
+
THEN RAISE(ABORT, 'Granola attribution revision is not exact and current') END;
|
|
329
|
+
END;
|
|
330
|
+
|
|
331
|
+
CREATE TRIGGER person_note_attribution_revisions_immutable_update
|
|
332
|
+
BEFORE UPDATE ON person_note_attribution_revisions FOR EACH ROW
|
|
333
|
+
BEGIN SELECT RAISE(ABORT, 'person note attribution revisions are immutable'); END;
|
|
334
|
+
|
|
335
|
+
CREATE TRIGGER person_note_attribution_revisions_immutable_delete
|
|
336
|
+
BEFORE DELETE ON person_note_attribution_revisions FOR EACH ROW
|
|
337
|
+
BEGIN SELECT RAISE(ABORT, 'person note attribution revisions are immutable'); END;
|
|
338
|
+
|
|
339
|
+
CREATE TRIGGER person_note_identity_method_coordinate_immutable_update
|
|
340
|
+
BEFORE UPDATE OF id, person_id, kind, normalized_value
|
|
341
|
+
ON contact_methods FOR EACH ROW
|
|
342
|
+
WHEN (
|
|
343
|
+
NEW.id IS NOT OLD.id
|
|
344
|
+
OR NEW.person_id IS NOT OLD.person_id
|
|
345
|
+
OR NEW.kind IS NOT OLD.kind
|
|
346
|
+
OR NEW.normalized_value IS NOT OLD.normalized_value
|
|
347
|
+
)
|
|
348
|
+
AND (
|
|
349
|
+
EXISTS (
|
|
350
|
+
SELECT 1 FROM person_note_granola_identity_sources source
|
|
351
|
+
WHERE source.source_contact_method_id = OLD.id
|
|
352
|
+
)
|
|
353
|
+
OR EXISTS (
|
|
354
|
+
SELECT 1 FROM person_note_attribution_revisions revision
|
|
355
|
+
WHERE revision.target_contact_method_id = OLD.id
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
BEGIN SELECT RAISE(ABORT, 'person note identity method coordinate is immutable'); END;
|
|
359
|
+
|
|
360
|
+
CREATE TRIGGER person_note_identity_method_immutable_delete
|
|
361
|
+
BEFORE DELETE ON contact_methods FOR EACH ROW
|
|
362
|
+
WHEN EXISTS (
|
|
363
|
+
SELECT 1 FROM person_note_granola_identity_sources source
|
|
364
|
+
WHERE source.source_contact_method_id = OLD.id
|
|
365
|
+
)
|
|
366
|
+
OR EXISTS (
|
|
367
|
+
SELECT 1 FROM person_note_attribution_revisions revision
|
|
368
|
+
WHERE revision.target_contact_method_id = OLD.id
|
|
369
|
+
)
|
|
370
|
+
BEGIN SELECT RAISE(ABORT, 'person note identity method is immutable'); END;
|
|
371
|
+
|
|
372
|
+
CREATE VIEW person_notes_current AS
|
|
373
|
+
SELECT
|
|
374
|
+
note.id,
|
|
375
|
+
coalesce(
|
|
376
|
+
(SELECT revision.to_person_id
|
|
377
|
+
FROM person_note_attribution_revisions revision
|
|
378
|
+
WHERE revision.note_id = note.id
|
|
379
|
+
ORDER BY revision.id DESC LIMIT 1),
|
|
380
|
+
note.person_id
|
|
381
|
+
) AS person_id,
|
|
382
|
+
note.occurred_at,
|
|
383
|
+
note.source,
|
|
384
|
+
note.source_id,
|
|
385
|
+
note.title,
|
|
386
|
+
note.body,
|
|
387
|
+
note.metadata_json,
|
|
388
|
+
note.created_at
|
|
389
|
+
FROM person_notes note;
|