|
1 | 1 | /*
|
2 |
| - * Copyright (C) 2018 Shivam Tripathi |
| 2 | + * Copyright (C) 2024 David Kellner |
3 | 3 | *
|
4 | 4 | * This program is free software; you can redistribute it and/or modify
|
5 | 5 | * it under the terms of the GNU General Public License as published by
|
|
16 | 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
17 | 17 | */
|
18 | 18 |
|
19 |
| -import * as _ from 'lodash'; |
20 |
| -import { |
21 |
| - getAdditionalEntityProps, getEntityModelByType, getEntitySetMetadataByType |
22 |
| -} from '../entity'; |
| 19 | +import type {EntityType} from '../../types/schema'; |
23 | 20 | import type {ImportMetadataWithSourceT} from '../../types/imports';
|
24 | 21 | import type {ORM} from '../..';
|
25 |
| -import type {Transaction} from '../types'; |
26 |
| -import {createNote} from '../note'; |
27 |
| -import {deleteImport} from './delete-import'; |
28 |
| -import {incrementEditorEditCountById} from '../editor'; |
| 22 | +import {uncapitalize} from '../../util'; |
29 | 23 |
|
30 | 24 |
|
31 |
| -interface approveEntityPropsType { |
32 |
| - orm: ORM, |
33 |
| - transacting: Transaction, |
| 25 | +export async function approveImport({editorId, importEntity, orm}: { |
| 26 | + editorId: number, |
34 | 27 | importEntity: any,
|
35 |
| - editorId: string |
36 |
| -} |
37 |
| - |
38 |
| -export async function approveImport( |
39 |
| - {orm, transacting, importEntity, editorId}: approveEntityPropsType |
40 |
| -): Promise<Record<string, unknown>> { |
41 |
| - const {bbid: pendingEntityBbid, type: entityType, disambiguationId, aliasSet, |
42 |
| - identifierSetId, annotationId} = importEntity; |
| 28 | + orm: ORM, |
| 29 | +}) { |
| 30 | + const {bbid, type, annotationId} = importEntity; |
43 | 31 | const metadata: ImportMetadataWithSourceT = importEntity.importMetadata;
|
44 |
| - const {id: aliasSetId} = aliasSet; |
45 |
| - |
46 |
| - const {Annotation, Entity, Revision} = orm; |
47 |
| - |
48 |
| - // Increase user edit count |
49 |
| - const editorUpdatePromise = |
50 |
| - incrementEditorEditCountById(orm, editorId, transacting); |
51 |
| - |
52 |
| - // Create a new revision record |
53 |
| - const revision = await new Revision({ |
54 |
| - authorId: editorId |
55 |
| - }).save(null, {transacting}); |
56 |
| - const revisionId = revision.get('id'); |
57 |
| - |
58 |
| - if (annotationId) { |
59 |
| - // Set revision of our annotation which is NULL for imports |
60 |
| - await new Annotation({id: annotationId}) |
61 |
| - .save({lastRevisionId: revisionId}, {transacting}); |
62 |
| - } |
63 |
| - |
64 |
| - const note = `Approved from automatically imported record of ${metadata.source}`; |
65 |
| - // Create a new note promise |
66 |
| - const notePromise = createNote(orm, note, editorId, revision, transacting); |
67 |
| - |
68 |
| - // Get additional props |
69 |
| - const additionalProps = getAdditionalEntityProps(importEntity, entityType); |
70 |
| - |
71 |
| - // Collect the entity sets from the importEntity |
72 |
| - const entitySetMetadata = getEntitySetMetadataByType(entityType); |
73 |
| - const entitySets = entitySetMetadata.reduce( |
74 |
| - (set, {entityIdField}) => |
75 |
| - _.assign(set, {[entityIdField]: importEntity[entityIdField]}) |
76 |
| - , {} |
77 |
| - ); |
78 |
| - |
79 |
| - await Promise.all([notePromise, editorUpdatePromise]); |
80 |
| - |
81 |
| - const newEntity = await new Entity({type: entityType}) |
82 |
| - .save(null, {transacting}); |
83 |
| - const acceptedEntityBbid = newEntity.get('bbid'); |
84 |
| - const propsToSet = _.extend({ |
85 |
| - aliasSetId, |
86 |
| - annotationId, |
87 |
| - bbid: acceptedEntityBbid, |
88 |
| - disambiguationId, |
89 |
| - identifierSetId, |
90 |
| - revisionId |
91 |
| - }, entitySets, additionalProps); |
92 |
| - |
93 |
| - const Model = getEntityModelByType(orm, entityType); |
94 |
| - |
95 |
| - const entityModel = await new Model(propsToSet) |
96 |
| - .save(null, { |
97 |
| - method: 'insert', |
98 |
| - transacting |
99 |
| - }); |
100 |
| - |
101 |
| - const entity = await entityModel.refresh({ |
102 |
| - transacting, |
103 |
| - withRelated: ['defaultAlias'] |
| 32 | + const entityType = uncapitalize(type as EntityType); |
| 33 | + |
| 34 | + await orm.kysely.transaction().execute(async (trx) => { |
| 35 | + const pendingUpdates = [ |
| 36 | + // Mark the pending entity as accepted |
| 37 | + trx.updateTable('entity') |
| 38 | + .set('isImport', false) |
| 39 | + .where((eb) => eb.and({bbid, isImport: true})) |
| 40 | + .executeTakeFirst(), |
| 41 | + // Indicate approval of the entity by setting the accepted BBID |
| 42 | + trx.updateTable('importMetadata') |
| 43 | + .set('acceptedEntityBbid', bbid) |
| 44 | + .where('pendingEntityBbid', '=', bbid) |
| 45 | + .executeTakeFirst(), |
| 46 | + // Increment revision count of the active editor |
| 47 | + trx.updateTable('editor') |
| 48 | + .set((eb) => ({ |
| 49 | + revisionsApplied: eb('revisionsApplied', '+', 1), |
| 50 | + totalRevisions: eb('totalRevisions', '+', 1), |
| 51 | + })) |
| 52 | + .where('id', '=', editorId) |
| 53 | + .executeTakeFirst(), |
| 54 | + ]; |
| 55 | + |
| 56 | + // Create a new revision and an entity header |
| 57 | + const revision = await trx.insertInto('revision') |
| 58 | + .values({authorId: editorId}) |
| 59 | + .returning('id') |
| 60 | + .executeTakeFirstOrThrow(); |
| 61 | + await trx.insertInto(`${entityType}Header`) |
| 62 | + .values({bbid}) |
| 63 | + .executeTakeFirstOrThrow(); |
| 64 | + |
| 65 | + // Create initial entity revision using the entity data from the import |
| 66 | + await trx.insertInto(`${entityType}Revision`) |
| 67 | + .values((eb) => ({ |
| 68 | + bbid, |
| 69 | + dataId: eb.selectFrom(`${entityType}ImportHeader`) |
| 70 | + .select('dataId') |
| 71 | + .where('bbid', '=', bbid), |
| 72 | + id: revision.id |
| 73 | + })) |
| 74 | + .executeTakeFirstOrThrow(); |
| 75 | + |
| 76 | + // Update the entity header with the revision, doing this earlier causes a FK constraint violation |
| 77 | + pendingUpdates.push(trx.updateTable(`${entityType}Header`) |
| 78 | + .set('masterRevisionId', revision.id) |
| 79 | + .where('bbid', '=', bbid) |
| 80 | + .executeTakeFirst()); |
| 81 | + |
| 82 | + if (annotationId) { |
| 83 | + // Set revision of our annotation which is NULL for pending imports |
| 84 | + pendingUpdates.push(trx.updateTable('annotation') |
| 85 | + .set('lastRevisionId', revision.id) |
| 86 | + .where('id', '=', annotationId) |
| 87 | + .executeTakeFirst()); |
| 88 | + } |
| 89 | + |
| 90 | + // Create edit note |
| 91 | + await trx.insertInto('note') |
| 92 | + .values({ |
| 93 | + authorId: editorId, |
| 94 | + content: `Approved automatically imported record ${metadata.externalIdentifier} from ${metadata.source}`, |
| 95 | + revisionId: revision.id, |
| 96 | + }) |
| 97 | + .executeTakeFirstOrThrow(); |
| 98 | + |
| 99 | + return Promise.all(pendingUpdates.map(async (update) => { |
| 100 | + const {numUpdatedRows} = await update; |
| 101 | + if (Number(numUpdatedRows) !== 1) { |
| 102 | + throw new Error(`Failed to approve import of ${bbid}`); |
| 103 | + } |
| 104 | + })); |
104 | 105 | });
|
105 |
| - |
106 |
| - await deleteImport(transacting, pendingEntityBbid, acceptedEntityBbid); |
107 |
| - |
108 |
| - return entity; |
109 | 106 | }
|
0 commit comments