From c5fb430322b55c724765bca153e5bc341f75e0ea Mon Sep 17 00:00:00 2001 From: tri10 Date: Sat, 12 Mar 2022 14:51:52 +0530 Subject: [PATCH 1/4] created post route for adding relationships --- src/server/helpers/entityRouteUtils.tsx | 18 +++++++ src/server/routes/entity/author.js | 5 ++ src/server/routes/entity/edition-group.js | 5 ++ src/server/routes/entity/edition.js | 5 ++ src/server/routes/entity/entity.tsx | 61 +++++++++++++++++++++++ src/server/routes/entity/publisher.js | 5 ++ src/server/routes/entity/series.js | 5 ++ src/server/routes/entity/work.js | 5 ++ 8 files changed, 109 insertions(+) diff --git a/src/server/helpers/entityRouteUtils.tsx b/src/server/helpers/entityRouteUtils.tsx index 2408cc7408..59ab0c13d3 100644 --- a/src/server/helpers/entityRouteUtils.tsx +++ b/src/server/helpers/entityRouteUtils.tsx @@ -28,6 +28,7 @@ import * as utils from './utils'; import type {Request as $Request, Response as $Response} from 'express'; import EntityEditor from '../../client/entity-editor/entity-editor'; import EntityMerge from '../../client/entity-editor/entity-merge'; +import {EntityTypeString} from 'bookbrainz-data/lib/func/types'; import Layout from '../../client/containers/layout'; import {Provider} from 'react-redux'; import ReactDOMServer from 'react-dom/server'; @@ -312,3 +313,20 @@ export function addInitialRelationship(props, relationshipTypeId, relationshipIn return props; } + +/** + * Makes a middleware handler for adding relationships. + * @param {string} entityType - entity type + * @returns {addRelationshipHandler} addRelationshipHandler - middleware handler + */ + +export function makeAddRelationshipHandler(entityType:string) { + const entityName = _.upperFirst(entityType); + return function addRelationsipHandler(req:PassportRequest, res:$Response) { + if (!req.body.relationships) { + throw new error.ConflictError('Relationships field not found!'); + } + req.body.relationships = entityRoutes.constructRelationships(req.body); + return entityRoutes.handleAddRelationship(req, res, entityName as EntityTypeString); + }; +} diff --git a/src/server/routes/entity/author.js b/src/server/routes/entity/author.js index 37c6227606..2f397b9a71 100644 --- a/src/server/routes/entity/author.js +++ b/src/server/routes/entity/author.js @@ -25,6 +25,7 @@ import * as utils from '../../helpers/utils'; import { entityEditorMarkup, generateEntityProps, + makeAddRelationshipHandler, makeEntityCreateOrEditHandler } from '../../helpers/entityRouteUtils'; @@ -77,6 +78,8 @@ function transformNewForm(data) { }; } +const addRelationshipHandler = makeAddRelationshipHandler('author'); + const createOrEditHandler = makeEntityCreateOrEditHandler( 'author', transformNewForm, additionalAuthorProps ); @@ -159,6 +162,8 @@ router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { return entityRoutes.displayDeleteEntity(req, res); }); +router.post('/:bbid/relationships', auth.isAuthenticatedForHandler, addRelationshipHandler); + router.post( '/:bbid/delete/handler', auth.isAuthenticatedForHandler, (req, res) => { diff --git a/src/server/routes/entity/edition-group.js b/src/server/routes/entity/edition-group.js index 7e1e62435a..45d52bc60c 100644 --- a/src/server/routes/entity/edition-group.js +++ b/src/server/routes/entity/edition-group.js @@ -25,6 +25,7 @@ import * as utils from '../../helpers/utils'; import { entityEditorMarkup, generateEntityProps, + makeAddRelationshipHandler, makeEntityCreateOrEditHandler } from '../../helpers/entityRouteUtils'; @@ -62,6 +63,8 @@ function transformNewForm(data) { }; } +const addRelationshipHandler = makeAddRelationshipHandler('editionGroup'); + const createOrEditHandler = makeEntityCreateOrEditHandler( 'editionGroup', transformNewForm, 'typeId' ); @@ -143,6 +146,8 @@ router.get('/:bbid', middleware.loadEntityRelationships, (req, res) => { entityRoutes.displayEntity(req, res); }); +router.post('/:bbid/relationships', auth.isAuthenticatedForHandler, addRelationshipHandler); + router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { if (!res.locals.entity.dataId) { return next(new ConflictError('This entity has already been deleted')); diff --git a/src/server/routes/entity/edition.js b/src/server/routes/entity/edition.js index ef046c1c73..2895a268cd 100644 --- a/src/server/routes/entity/edition.js +++ b/src/server/routes/entity/edition.js @@ -26,6 +26,7 @@ import { addInitialRelationship, entityEditorMarkup, generateEntityProps, + makeAddRelationshipHandler, makeEntityCreateOrEditHandler } from '../../helpers/entityRouteUtils'; @@ -110,6 +111,8 @@ function getInitialNameSection(entity) { }; } +const addRelationshipHandler = makeAddRelationshipHandler('edition'); + const createOrEditHandler = makeEntityCreateOrEditHandler( 'edition', transformNewForm, additionalEditionProps ); @@ -269,6 +272,8 @@ router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { return entityRoutes.displayDeleteEntity(req, res); }); +router.post('/:bbid/relationships', auth.isAuthenticatedForHandler, addRelationshipHandler); + router.post( '/:bbid/delete/handler', auth.isAuthenticatedForHandler, (req, res) => { diff --git a/src/server/routes/entity/entity.tsx b/src/server/routes/entity/entity.tsx index eb5727994d..f6f0860a19 100644 --- a/src/server/routes/entity/entity.tsx +++ b/src/server/routes/entity/entity.tsx @@ -992,6 +992,67 @@ async function indexAutoCreatedEditionGroup(orm, newEdition, transacting) { } } +export function handleAddRelationship( + req:PassportRequest, + res:$Response, + entityType:EntityTypeString +) { + const {body} = req; + const {orm} = req.app.locals; + const editorJSON = req.user; + const {Revision, bookshelf} = orm; + const currentEntity: { + aliasSet: {id: number} | null | undefined, + annotation: {id: number} | null | undefined, + bbid: string, + disambiguation: {id: number} | null | undefined, + identifierSet: {id: number} | null | undefined, + type: EntityTypeString + } | null | undefined = res.locals.entity; + const entityEditPromise = bookshelf.transaction(async (transacting) => { + const newRevision = await new Revision({ + authorId: editorJSON.id, + isMerge: false + }).save(null, {transacting}); + const relationshipSets = await getNextRelationshipSets( + orm, transacting, currentEntity, body + ); + if (_.isEmpty(relationshipSets)) { + throw new error.FormSubmissionError('No Updated Relationship Field'); + } + // Fetch main entity + const mainEntity = await fetchOrCreateMainEntity( + orm, transacting, false, currentEntity.bbid, entityType + ); + // Fetch all entities that definitely exist + const otherEntities = await fetchEntitiesForRelationships( + orm, transacting, currentEntity, relationshipSets + ); + otherEntities.forEach(entity => { entity.shouldInsert = false; }); + mainEntity.shouldInsert = false; + const allEntities = [...otherEntities, mainEntity] + .filter(entity => entity.get('dataId') !== null); + _.forEach(allEntities, (entityModel) => { + const bbid: string = entityModel.get('bbid'); + if (_.has(relationshipSets, bbid)) { + entityModel.set( + 'relationshipSetId', + // Set to relationshipSet id or null if empty set + relationshipSets[bbid] && relationshipSets[bbid].get('id') + ); + } + }); + const savedMainEntity = await saveEntitiesAndFinishRevision( + orm, transacting, false, newRevision, mainEntity, allEntities, + editorJSON.id, body.note + ); + return savedMainEntity.toJSON(); + }); + return handler.sendPromiseResult( + res, + entityEditPromise + ); +} export function handleCreateOrEditEntity( req: PassportRequest, res: $Response, diff --git a/src/server/routes/entity/publisher.js b/src/server/routes/entity/publisher.js index 2c4730b4cc..8b991915a1 100644 --- a/src/server/routes/entity/publisher.js +++ b/src/server/routes/entity/publisher.js @@ -25,6 +25,7 @@ import * as utils from '../../helpers/utils'; import { entityEditorMarkup, generateEntityProps, + makeAddRelationshipHandler, makeEntityCreateOrEditHandler } from '../../helpers/entityRouteUtils'; @@ -72,6 +73,8 @@ function transformNewForm(data) { }; } +const addRelationshipHandler = makeAddRelationshipHandler('publisher'); + const createOrEditHandler = makeEntityCreateOrEditHandler( 'publisher', transformNewForm, additionalPublisherProps ); @@ -166,6 +169,8 @@ router.get('/:bbid', middleware.loadEntityRelationships, (req, res, next) => { .catch(next); }); +router.post('/:bbid/relationships', auth.isAuthenticatedForHandler, addRelationshipHandler); + router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { if (!res.locals.entity.dataId) { return next(new ConflictError('This entity has already been deleted')); diff --git a/src/server/routes/entity/series.js b/src/server/routes/entity/series.js index 5dcb43f3ad..24c445411f 100644 --- a/src/server/routes/entity/series.js +++ b/src/server/routes/entity/series.js @@ -25,6 +25,7 @@ import * as utils from '../../helpers/utils'; import { entityEditorMarkup, generateEntityProps, + makeAddRelationshipHandler, makeEntityCreateOrEditHandler } from '../../helpers/entityRouteUtils'; @@ -69,6 +70,8 @@ function transformNewForm(data) { }; } +const addRelationshipHandler = makeAddRelationshipHandler('series'); + const createOrEditHandler = makeEntityCreateOrEditHandler( 'series', transformNewForm, additionalSeriesProps ); @@ -157,6 +160,8 @@ router.get('/:bbid/delete', auth.isAuthenticated, (req, res, next) => { return entityRoutes.displayDeleteEntity(req, res); }); +router.post('/:bbid/relationships', auth.isAuthenticatedForHandler, addRelationshipHandler); + router.post( '/:bbid/delete/handler', auth.isAuthenticatedForHandler, (req, res) => { diff --git a/src/server/routes/entity/work.js b/src/server/routes/entity/work.js index d75ea3467a..9746a2b0c9 100644 --- a/src/server/routes/entity/work.js +++ b/src/server/routes/entity/work.js @@ -26,6 +26,7 @@ import { addInitialRelationship, entityEditorMarkup, generateEntityProps, + makeAddRelationshipHandler, makeEntityCreateOrEditHandler } from '../../helpers/entityRouteUtils'; @@ -70,6 +71,8 @@ function transformNewForm(data) { }; } +const addRelationshipHandler = makeAddRelationshipHandler('work'); + const createOrEditHandler = makeEntityCreateOrEditHandler( 'work', transformNewForm, 'typeId' ); @@ -199,6 +202,8 @@ router.post( } ); +router.post('/:bbid/relationships', auth.isAuthenticatedForHandler, addRelationshipHandler); + router.get('/:bbid/revisions', (req, res, next) => { const {WorkRevision} = req.app.locals.orm; _setWorkTitle(res); From f11eceb0582885fbf869562c952c5febf09f023c Mon Sep 17 00:00:00 2001 From: tri10 Date: Sun, 13 Mar 2022 12:22:25 +0530 Subject: [PATCH 2/4] added add edition entity modal for work entity --- .../pages/entities/edition-table.js | 51 +++- src/client/components/pages/entities/work.js | 1 + .../pages/parts/add-entity-relation-modal.js | 248 ++++++++++++++++++ src/server/helpers/entityRouteUtils.tsx | 2 +- 4 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 src/client/components/pages/parts/add-entity-relation-modal.js diff --git a/src/client/components/pages/entities/edition-table.js b/src/client/components/pages/entities/edition-table.js index 59d6227d36..ffa364c5d1 100644 --- a/src/client/components/pages/entities/edition-table.js +++ b/src/client/components/pages/entities/edition-table.js @@ -20,11 +20,12 @@ import * as bootstrap from 'react-bootstrap'; import * as entityHelper from '../../../helpers/entity'; import * as utilHelper from '../../../helpers/utils'; +import React, {useState} from 'react'; import {faBook, faPlus} from '@fortawesome/free-solid-svg-icons'; +import AddEntityRelationModal from '../parts/add-entity-relation-modal'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import PropTypes from 'prop-types'; -import React from 'react'; import {kebabCase as _kebabCase} from 'lodash'; @@ -32,7 +33,7 @@ const { getEditionReleaseDate, getEntityLabel, getEntityDisambiguation, getISBNOfEdition, getEditionFormat } = entityHelper; -const {Button, Table} = bootstrap; +const {Alert, Button, Table} = bootstrap; function EditionTableRow({edition, showAddedAtColumn, showCheckboxes, selectedEntities, onToggleRow}) { const name = getEntityLabel(edition); @@ -95,8 +96,28 @@ EditionTableRow.defaultProps = { showCheckboxes: false }; -function EditionTable({editions, entity, showAddedAtColumn, showAdd, showCheckboxes, selectedEntities, onToggleRow}) { +function EditionTable({editions, entity, showAddedAtColumn, showAdd, showCheckboxes, selectedEntities, onToggleRow, relationshipTypeId}) { let tableContent; + const [show, setShow] = useState(false); + const [message, setMessage] = useState({}); + const haveRelationshipId = Boolean(relationshipTypeId); + function onCloseModalHandler() { + setShow(false); + } + function handleOpenModal() { + setShow(true); + } + function closeModalAndShowMessage(msg) { + setMessage(msg); + onCloseModalHandler(); + } + function handleAlertDismiss() { + setMessage({}); + } + const submitUrl = `/${entity.type.toLowerCase()}/${ + entity.bbid + }/relationships`; + const buttonProps = !haveRelationshipId ? {href: `/edition/create?${_kebabCase(entity.type)}=${entity.bbid}`} : {onClick: handleOpenModal}; if (editions.length) { tableContent = ( @@ -128,15 +149,17 @@ function EditionTable({editions, entity, showAddedAtColumn, showAdd, showCheckbo } - {showAdd && + {showAdd && ( + ) } ); @@ -146,8 +169,8 @@ function EditionTable({editions, entity, showAddedAtColumn, showAdd, showCheckbo + ); + return ( +
+ this.handleChangeEntity(newEntity, index)} + /> +
+ ); + }) + } + + + + ); + + return ( + + + + Add {upperFirst(this.props.targetEntityType)}s + + + + {addEntityToCollectionForm} + {errorComponent} + + + + + + + + + + ); + } +} + +AddEntityRelationModal.displayName = 'AddEntityRelationModal'; +AddEntityRelationModal.propTypes = { + closeModalAndShowMessage: PropTypes.func.isRequired, + entity: PropTypes.object.isRequired, + isSource: PropTypes.bool.isRequired, + onCloseModal: PropTypes.func.isRequired, + relationshipTypeId: PropTypes.number.isRequired, + show: PropTypes.bool.isRequired, + submitUrl: PropTypes.string.isRequired, + targetEntityType: PropTypes.string.isRequired +}; +export default AddEntityRelationModal; diff --git a/src/server/helpers/entityRouteUtils.tsx b/src/server/helpers/entityRouteUtils.tsx index 59ab0c13d3..e9bd8b97bb 100644 --- a/src/server/helpers/entityRouteUtils.tsx +++ b/src/server/helpers/entityRouteUtils.tsx @@ -324,7 +324,7 @@ export function makeAddRelationshipHandler(entityType:string) { const entityName = _.upperFirst(entityType); return function addRelationsipHandler(req:PassportRequest, res:$Response) { if (!req.body.relationships) { - throw new error.ConflictError('Relationships field not found!'); + throw new error.BadRequestError('Relationships field does not exist on request body!'); } req.body.relationships = entityRoutes.constructRelationships(req.body); return entityRoutes.handleAddRelationship(req, res, entityName as EntityTypeString); From 4a249136ec12807d738f48db262493ab5cb7d48c Mon Sep 17 00:00:00 2001 From: tri10 Date: Mon, 14 Mar 2022 17:46:16 +0530 Subject: [PATCH 3/4] added modal for selecting works in edition --- .../components/pages/entities/edition.js | 1 + .../components/pages/entities/work-table.js | 48 +++++++++++++++++-- .../pages/parts/add-entity-relation-modal.js | 2 +- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/client/components/pages/entities/edition.js b/src/client/components/pages/entities/edition.js index 2ebf26a6f5..5e4871f035 100644 --- a/src/client/components/pages/entities/edition.js +++ b/src/client/components/pages/entities/edition.js @@ -150,6 +150,7 @@ function EditionDisplayPage({entity, identifierTypes, user}) { Add Work @@ -136,7 +158,7 @@ function WorkTable({entity, showAddedAtColumn, works, showAdd, showCheckboxes, s From 56ec7107677e7558e0a6a7722fe429cd4b73a188 Mon Sep 17 00:00:00 2001 From: tri10 Date: Mon, 14 Mar 2022 20:36:07 +0530 Subject: [PATCH 4/4] fixing minor bugs --- src/client/components/pages/entities/work-table.js | 13 ++++++++----- .../pages/parts/add-entity-relation-modal.js | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/client/components/pages/entities/work-table.js b/src/client/components/pages/entities/work-table.js index 23891eb445..9df1da87f7 100644 --- a/src/client/components/pages/entities/work-table.js +++ b/src/client/components/pages/entities/work-table.js @@ -104,11 +104,14 @@ function WorkTable({entity, showAddedAtColumn, works, showAdd, showCheckboxes, s function handleAlertDismiss() { setMessage({}); } - const submitUrl = `/${entity.type.toLowerCase()}/${ - entity.bbid - }/relationships`; - const buttonProps = !haveRelationshipId ? {href: `/edition/create?${_kebabCase(entity.type)}=${entity.bbid}`} : {onClick: handleOpenModal}; - + let submitUrl; + let buttonProps; + if (showAdd) { + submitUrl = `/${entity.type.toLowerCase()}/${ + entity.bbid + }/relationships`; + buttonProps = !haveRelationshipId ? {href: `/work/create?${_kebabCase(entity.type)}=${entity.bbid}`} : {onClick: handleOpenModal}; + } if (works.length) { const showAuthors = works[0].authorsData; tableContent = ( diff --git a/src/client/components/pages/parts/add-entity-relation-modal.js b/src/client/components/pages/parts/add-entity-relation-modal.js index 022e70a37c..cb02d4c208 100644 --- a/src/client/components/pages/parts/add-entity-relation-modal.js +++ b/src/client/components/pages/parts/add-entity-relation-modal.js @@ -202,7 +202,7 @@ class AddEntityRelationModal extends React.Component {