diff --git a/client-sdk/src/services/BillService.ts b/client-sdk/src/services/BillService.ts index 1fdc5c11..72464fb4 100644 --- a/client-sdk/src/services/BillService.ts +++ b/client-sdk/src/services/BillService.ts @@ -89,4 +89,22 @@ export class BillService { }, }); } + /** + * Publish xml + * Publish bill XML + * @param id + * @returns any Successful response + * @throws ApiError + */ + public static billControllerPublishXml( + id: number, + ): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/bills/{id}/xml', + path: { + 'id': id, + }, + }); + } } diff --git a/client/src/features/Bills/CreateBillButton.tsx b/client/src/features/Bills/CreateBillButton.tsx index 46569163..fda794fa 100644 --- a/client/src/features/Bills/CreateBillButton.tsx +++ b/client/src/features/Bills/CreateBillButton.tsx @@ -25,7 +25,7 @@ const CreateBillButton: FC = ({ onSubmit }) => { > {t('Create New Bill')} - +
@@ -34,4 +34,4 @@ const CreateBillButton: FC = ({ onSubmit }) => { ); }; -export default CreateBillButton; \ No newline at end of file +export default CreateBillButton; diff --git a/client/src/features/Bills/useBillPage.ts b/client/src/features/Bills/useBillPage.ts index 50314bec..decdec91 100644 --- a/client/src/features/Bills/useBillPage.ts +++ b/client/src/features/Bills/useBillPage.ts @@ -1,5 +1,5 @@ import { notification } from 'antd'; -import { BillDocumentService, DocumentService } from 'client-sdk'; +import { BillDocumentService, DocumentService, BillService } from 'client-sdk'; import { LawEditor, getTitle } from 'law-document'; import { useCallback, useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router'; @@ -146,6 +146,26 @@ const useBillPage = (disableActions = false, billPage = '/bill') => { useEffect(loadDocument, [loadDocument]); + const publishBill = useCallback(() => { + if (!bill || !bill.id || !selected) { + return; + } + + log('publish bill document', { bill, selected }); + + BillService.billControllerPublishXml(bill.id) + .then((billItem) => { + notification.success({ message: t('Bill published'), description: `${selected} ${bill.title}` }); + log('published bill', { billItem }); + setError(false); + }) + .catch((error) => { + log('Error publishing bill'); + setError(true); + console.error(error); + }); + }, [bill, selected, t]); + return { bill, openDocument, @@ -158,10 +178,11 @@ const useBillPage = (disableActions = false, billPage = '/bill') => { slate, originalDocument, loadDocument, + publishBill, hasBillLoadingError: hasBillError, hasDocumentLoadingError: hasError, importError, }; }; -export default useBillPage; \ No newline at end of file +export default useBillPage; diff --git a/client/src/pages/BillPage.tsx b/client/src/pages/BillPage.tsx index f20d7c45..1e0385d6 100644 --- a/client/src/pages/BillPage.tsx +++ b/client/src/pages/BillPage.tsx @@ -31,6 +31,7 @@ const BillPage: FC = () => { xml, originalDocument, saveDocument, + publishBill, hasBillLoadingError, hasDocumentLoadingError, isBillDocument, @@ -80,6 +81,7 @@ const BillPage: FC = () => { originalDocument={originalDocument!} xml={xml!} saveDocument={saveDocument} + publishBill={publishBill} readOnly={!isBillDocument} bill={bill} t={t} @@ -98,4 +100,4 @@ const BillPage: FC = () => { ); }; -export default BillPage; \ No newline at end of file +export default BillPage; diff --git a/law-document-editor/src/Editor/Editor.tsx b/law-document-editor/src/Editor/Editor.tsx index 9f11d726..7260595f 100644 --- a/law-document-editor/src/Editor/Editor.tsx +++ b/law-document-editor/src/Editor/Editor.tsx @@ -28,13 +28,14 @@ interface Props { xml: string; readOnly?: boolean; saveDocument?: (editor: LawEditor) => void; + publishBill?: () => void; bill?: Bill; navigationBlocker: NavigationBlocker; t: Translator; } export const Editor: FC = (props) => { - const { slate, originalDocument, xml, readOnly, saveDocument, bill, navigationBlocker, t } = props; + const { slate, originalDocument, xml, readOnly, saveDocument, publishBill, bill, navigationBlocker, t } = props; const hasHighlight = useEditorConfig(state => state.highlightStructure); const editor = useMemo(() => createEditor(), []); const { handleChange, handleSave } = useEditorNavigationBlock(editor, navigationBlocker, saveDocument); @@ -71,7 +72,7 @@ export const Editor: FC = (props) => { - { readOnly ? null : } + { readOnly ? null : } = (props) => { ); -}; \ No newline at end of file +}; diff --git a/law-document-editor/src/Editor/Toolbar/AddEntryModal.tsx b/law-document-editor/src/Editor/Toolbar/AddEntryModal.tsx index ae0585b0..fa934c09 100644 --- a/law-document-editor/src/Editor/Toolbar/AddEntryModal.tsx +++ b/law-document-editor/src/Editor/Toolbar/AddEntryModal.tsx @@ -17,4 +17,4 @@ export const AddEntryModal: FC = ({ isOpen, onClose, t }) => {
); -}; \ No newline at end of file +}; diff --git a/law-document-editor/src/Editor/Toolbar/Toolbar.tsx b/law-document-editor/src/Editor/Toolbar/Toolbar.tsx index fa1e21b9..a3312d9c 100644 --- a/law-document-editor/src/Editor/Toolbar/Toolbar.tsx +++ b/law-document-editor/src/Editor/Toolbar/Toolbar.tsx @@ -9,12 +9,13 @@ import { Translator } from '../../translations'; interface Props { saveDocument?: (editor: LawEditor) => void; + publishBill?: (editor: LawEditor) => void; bill?: Bill; t: Translator; navigationBlocker: NavigationBlocker; } -export const Toolbar: FC = ({ saveDocument, bill, t, navigationBlocker }) => { +export const Toolbar: FC = ({ saveDocument, publishBill, bill, t, navigationBlocker }) => { const { setAutoNumberIncrements, setHighlightStructure, @@ -34,6 +35,7 @@ export const Toolbar: FC = ({ saveDocument, bill, t, navigationBlocker }) {saveDocument && } {bill && } + {publishBill && } ); -}; \ No newline at end of file +}; diff --git a/law-document-editor/src/Editor/useEditorNaviationBlock.ts b/law-document-editor/src/Editor/useEditorNaviationBlock.ts index c32a0aad..07bf557e 100644 --- a/law-document-editor/src/Editor/useEditorNaviationBlock.ts +++ b/law-document-editor/src/Editor/useEditorNaviationBlock.ts @@ -43,4 +43,4 @@ export const useEditorNavigationBlock = (editor: LawEditor, navigationBlocker: N handleChange, handleSave, }; -}; \ No newline at end of file +}; diff --git a/law-document/src/slate/config/translations.ts b/law-document/src/slate/config/translations.ts index 377244a6..6b9f4f33 100644 --- a/law-document/src/slate/config/translations.ts +++ b/law-document/src/slate/config/translations.ts @@ -77,7 +77,7 @@ export const translations: { [key: string]: { [key: string]: string } } = { 'Documents in the Bill': 'Skjöl í frumvarpi', 'Add document to bill and start editing': 'Bæta við skjali í frumvarp og byrja að breyta', 'Remove document from bill and delete changes': 'Fjarlægja skjal úr frumvarpi og eyða breytingum', - 'Create New Bill': 'Búa til nýtt frumvarp', + 'Create New Bill': 'Stofna nýtt frumvarp', 'Delete Bill Document': 'Eyða skjali', 'Are you sure you want to permanently and irreversibly delete this document?': 'Ertu viss um að þú viljir eyða þessu skjali varanlega og óafturkræft?', 'Yes, delete!': 'Já, eyða!', @@ -92,5 +92,7 @@ export const translations: { [key: string]: { [key: string]: string } } = { 'Open Bill Preview': 'Opna frumvarpsyfirlit', 'This bill is still empty!': 'Þetta frumvarp er enn tómt!', 'Tip: Start editing this bill by adding a document to it.': 'Ábending: Byrjaðu á að breyta þessu frumvarpi með því að bæta við skjali í það.', + 'Publish Bill': 'Birta frumvarp', + 'Bill published': 'Frumvarp hefur verið birt', }, -}; \ No newline at end of file +}; diff --git a/server/src/controllers/BillController.ts b/server/src/controllers/BillController.ts index e3192eda..ace19f02 100644 --- a/server/src/controllers/BillController.ts +++ b/server/src/controllers/BillController.ts @@ -1,9 +1,10 @@ import passport from 'koa-passport'; -import { Body, Get, JsonController, Param, Post, Put, UseBefore } from 'routing-controllers'; +import { Body, Get, JsonController, Param, Post, Put, UseBefore, ContentType, HttpError } from 'routing-controllers'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { exportBillXml } from 'law-document'; import Bill from '../entities/Bill'; import BillDocument from '../entities/BillDocument'; +import { postBillForValidation, postBillForPublishing } from '../integration/lagasafnApi'; @JsonController() @OpenAPI({ @@ -39,6 +40,45 @@ class BillController { return Bill.save(bill); } + @Post('/bills/:id/xml') + @ContentType('text/xml') + @OpenAPI({ + description: 'Publish bill XML', + }) + async publishXml( + @Param('id') id: number, + ) { + const bills = await Bill.find({ where: { id } }); + + if ( bills.length < 1 || ! bills[0]?.documents ) { + throw new HttpError( 404, 'Unable to find bill documents'); + } + + const billsXml: string[] = []; + + bills[0].documents.forEach( async bill => { + const documents = await BillDocument.find({ + where: { id: bill.id }, + select: ['content'] + }); + + const billXml = exportBillXml(bill.title, documents); + billsXml.push( billXml ); + + try { + // First, validate XML. + await postBillForValidation( billXml ); + + // Second, publish the XML. + await postBillForPublishing( billXml ); + } catch( error: any ) { + throw new HttpError( 400, error?.message ); + } + } ); + + return billsXml; + } + @Put('/bills/:id') @ResponseSchema(Bill) update( @@ -49,4 +89,4 @@ class BillController { } } -export default BillController; \ No newline at end of file +export default BillController; diff --git a/server/src/index.ts b/server/src/index.ts index 6f5d2978..a2f18ce9 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -20,4 +20,4 @@ void (async () => { console.log('Generating Client SDK...'); exec('npm run build:sdk'); } -})(); \ No newline at end of file +})(); diff --git a/server/src/integration/lagasafnApi/index.ts b/server/src/integration/lagasafnApi/index.ts index b16aa498..27feca16 100644 --- a/server/src/integration/lagasafnApi/index.ts +++ b/server/src/integration/lagasafnApi/index.ts @@ -1,10 +1,34 @@ -import axios from 'axios'; +import axios, { AxiosRequestConfig } from 'axios'; import { lagasafnApi } from '../../config/lagasafnApi'; +const config: AxiosRequestConfig = { + headers: { + 'Content-Type': 'application/xml', + }, +}; + export const postBillForValidation = async (billXml: string) => { try { - await axios.post(lagasafnApi.url, billXml); - } catch (error) { + await axios.post( + lagasafnApi.url + '/api/bill/document/validate', + billXml, + config + ); + } catch (error: any) { + // Do not interrupt processing in case of external service failure + console.log('Request to lagasafn failed', error); + } +}; + +export const postBillForPublishing = async (billXml: string) => { + try { + await axios.post( + lagasafnApi.url + '/api/bill/document/publish', + billXml, + config + ); + } catch (error: any) { + // Do not interrupt processing in case of external service failure console.log('Request to lagasafn failed', error); } -}; \ No newline at end of file +}; diff --git a/server/src/services/DocumentService.test.ts b/server/src/services/DocumentService.test.ts index f235e96a..2c438551 100644 --- a/server/src/services/DocumentService.test.ts +++ b/server/src/services/DocumentService.test.ts @@ -13,10 +13,8 @@ describe('DocumentService', () => { const identifier = '2023.65'; xmlContent = ''; - const [doc1, doc2] = await Promise.all([ - findOrImportDocument(identifier), - findOrImportDocument(identifier), - ]); + const doc1 = await findOrImportDocument(identifier); + const doc2 = await findOrImportDocument(identifier); expect(doc1).toEqual(doc2); });