diff --git a/pyproject.toml b/pyproject.toml index 1c90d8c..ee2cf32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ classifiers = [ requires-python = '>=3.10, <4' dependencies = [ 'fastapi', + 'httpx', 'Jinja2', 'rdflib', ] diff --git a/requirements.txt b/requirements.txt index 5ecbe78..302ddb4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,10 @@ annotated-types==0.6.0 anyio==3.7.1 +certifi==2023.7.22 fastapi==0.104.1 +h11==0.14.0 +httpcore==0.18.0 +httpx==0.25.0 idna==3.4 isodate==0.6.1 Jinja2==3.1.2 diff --git a/src/smp_importer/app.py b/src/smp_importer/app.py index 1b4c271..1253134 100644 --- a/src/smp_importer/app.py +++ b/src/smp_importer/app.py @@ -6,6 +6,10 @@ import fastapi.responses import fastapi.staticfiles import fastapi.templating +import httpx +import pydantic + +from .logic import prepare_import_mapping ROOT_DIR = pathlib.Path(__file__).parent @@ -23,6 +27,17 @@ templates = fastapi.templating.Jinja2Templates(directory=TEMPLATES_DIR) +class BodyURL(pydantic.BaseModel): + url: str + + +class BodyFile(pydantic.BaseModel): + name: str + contents: str + type: str + bytesize: int + + @app.get('/', response_class=fastapi.responses.HTMLResponse) async def get_index(request: fastapi.Request): return templates.TemplateResponse( @@ -35,10 +50,44 @@ async def get_index(request: fastapi.Request): ) -@app.get('/api/import', response_class=fastapi.responses.JSONResponse) -async def list_fips(request: fastapi.Request): +@app.post('/api/import-file', response_class=fastapi.responses.JSONResponse) +async def api_import_from_file(body_file: BodyFile): + try: + result = prepare_import_mapping( + contents=body_file.contents, + content_type=body_file.type, + ) + return fastapi.responses.JSONResponse(content={ + 'sourcce': 'file', + 'name': body_file.name, + 'actions': result['actions'], + }) + except Exception as e: + LOG.error(f'Error appeared: {str(e)}', exc_info=e) + raise fastapi.HTTPException(status_code=500) + + +@app.post('/api/import-url', response_class=fastapi.responses.JSONResponse) +async def api_import_from_url(body_url: BodyURL): try: - return fastapi.responses.JSONResponse(content={'text': 'Hello'}) + result = await fetch_from_url(body_url.url) + return fastapi.responses.JSONResponse(content={ + 'source': 'url', + 'url': body_url.url, + 'actions': result['actions'], + }) except Exception as e: LOG.error(f'Error appeared: {str(e)}', exc_info=e) raise fastapi.HTTPException(status_code=500) + + +# ----- logic + +async def fetch_from_url(url: str) -> dict: + async with httpx.AsyncClient() as client: + r = await client.get(url) + r.raise_for_status() + return prepare_import_mapping( + contents=r.content.decode(encoding=r.charset_encoding or 'utf-8'), + content_type=r.headers.get('content-type'), + ) diff --git a/src/smp_importer/static/script.js b/src/smp_importer/static/script.js index e69de29..e41487d 100644 --- a/src/smp_importer/static/script.js +++ b/src/smp_importer/static/script.js @@ -0,0 +1,131 @@ +const importer = new DSWImporter() + +importer + .init({ + useWizardStyles: true, + windowSize: { + width: 300, + height: 500, + }, + }) + .then(() => { + jQuery('#file-input').on('input', function (e) { + const files = e.target.files + console.log(files) + const file = files[0] + + const reader = new FileReader() + reader.addEventListener('load', (event) => { + let data = '' + try { + data = event.target.result + } catch (error) { + alert('Failed to load file') + } + + jQuery.ajax({ + type: 'POST', + url: `/api/import-file`, + data: JSON.stringify({ + 'contents': data, + 'type': file.type, + 'name': file.name, + 'bytesize': file.size, + }), + contentType: "application/json; charset=utf-8", + traditional: true, + success: function (result) { + doImport(result.actions) + }, + error: function (result) { + console.log(result) + alert('failed') + } + }) + }) + reader.readAsText(file) + }) + + jQuery('#btn-load').on('click', function () { + const url = jQuery('#url-input').val() + console.log(url) + + if (!isValidUrl(url)) { + alert('Invalid URL!') + jQuery('#url-input').val('') + } else { + jQuery.ajax({ + type: 'POST', + url: `/api/import-url`, + data: JSON.stringify({'url': url}), + contentType: "application/json; charset=utf-8", + traditional: true, + success: function (result) { + doImport(result.actions) + }, + error: function (result) { + console.log(result) + alert('failed') + } + }) + } + }) + }) + .catch(error => { + console.error(error) + }) + + +function isValidUrl(urlString) { + const urlPattern = new RegExp('^(https?:\\/\\/)?'+ // validate protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // validate domain name + '((\\d{1,3}\\.){3}\\d{1,3}))'+ // validate OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // validate port and path + '(\\?[;&a-z\\d%_.~+=-]*)?'+ // validate query string + '(\\#[-a-z\\d_]*)?$','i') // validate fragment locator + return !!urlPattern.test(urlString) +} + +function doImport(actions) { + const replacements = new Map() + const debug = jQuery('#debug-checkbox').is(':checked') + console.log(actions) + + const replacePath = (path) => { + let newPath = []; + path.forEach((pathItem) => { + if (replacements.has(pathItem)) { + newPath.push(replacements.get(pathItem)) + } else { + newPath.push(pathItem) + } + }) + return newPath + } + + actions.forEach((item) => { + if (item.type === 'debug') { + console.log('DEBUG:', item.message) + } else if (item.type === 'setReply') { + const path = replacePath(item.path) + console.log(`SET REPLY: ${path} = "${item.value}"`) + importer.setReply(path, item.value) + } else if (item.type === 'setIntegrationReply') { + const path = replacePath(item.path) + console.log(`SET INTEGRATION REPLY: ${path} = "${item.value}" [${item.itemId}]`) + importer.setIntegrationReply(path, item.value, item.itemId) + } else if (item.type === 'addItem') { + const path = replacePath(item.path) + const itemUuid = importer.addItem(path) + console.log(`ADD ITEM: ${path} = ${itemUuid} [${item.var}]`) + replacements.set(item.var, itemUuid) + } + }) + + if (debug) { + alert('Stop (debug enabled)') + } + + importer.send() +} + diff --git a/src/smp_importer/templates/index.html.j2 b/src/smp_importer/templates/index.html.j2 index f7e4892..704c654 100644 --- a/src/smp_importer/templates/index.html.j2 +++ b/src/smp_importer/templates/index.html.j2 @@ -25,7 +25,7 @@ window.postMessage({ type: 'ready', origin: '', - styleUrl: 'https://s3.ds-wizard.org/dsw-server-app/17032b53-f4da-47bb-972c-ea82176422eb/public/customization.6595603168296135982.css', + styleUrl: 'https://s3.ds-wizard.org/dsw-server-app/8bfcffb9-d276-4777-92a5-3ca25f7cd2bc/public/customization.2794146750364175066.css', knowledgeModel: '{}', }) } @@ -37,6 +37,32 @@