diff --git a/.env.example b/.env.example index f547258..7c3f101 100644 --- a/.env.example +++ b/.env.example @@ -5,3 +5,10 @@ VITE_OBP_EXPLORER_HOST=http://localhost:5173 VITE_OBP_CONSUMER_KEY=your_consumer_key VITE_OBP_CONSUMER_SECRET=your_consumer_secret VITE_OBP_REDIRECT_URL=http://localhost:5173/api/callback +VITE_OPB_SERVER_SESSION_PASSWORD=very secret +# Product styling setting +#VITE_OBP_LINKS_COLOR="#52b165" +#VITE_OBP_HEADER_LINKS_COLOR="#39455f" +#VITE_OBP_HEADER_LINKS_HOVER_COLOR="#39455f" +#VITE_OBP_HEADER_LINKS_BACKGROUND_COLOR="#eef0f4" +#VITE_OBP_LOGO_URL=https://static.openbankproject.com/images/obp_logo.png diff --git a/.gitignore b/.gitignore index ce99465..bfafd79 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* -*.lock node_modules .DS_Store dist @@ -29,4 +28,14 @@ coverage *.njsproj *.sln *.sw? -.fleet + +#files from npm-run-build +package-lock.json +tsconfig.app.tsbuildinfo +tsconfig.vitest.tsbuildinfo +tsconfig.node.tsbuildinfo +vite.config.d.ts +vite.config.js +vitest.config.d.ts +vitest.config.js +components.d.ts \ No newline at end of file diff --git a/README.md b/README.md index 66ce466..a290669 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Welcome to the OBP API Explorer II This application is used to explore OBP APIs and interact with the data and services in the context of the logged in user. +This application will gradually replace the original API Explorer. Long live the API Explorer! + ## Install the Prerequisite Software @@ -13,7 +15,7 @@ This application is used to explore OBP APIs and interact with the data and serv ### Development Project Setup - * Setup your .env (see .env.example) + * Setup your .env file (see .env.example) ##### Install dependencies @@ -52,52 +54,17 @@ npm test:unit ``` - -##### Lint with [ESLint](https://eslint.org/) - -```sh -yarn lint -``` -or -```sh -npm lint -``` - -##### Format with [Prettier](https://prettier.io/) - -```sh -yarn format -``` -or -```sh -npm format -``` - ## Compile and Minify for Production -##### Build the frontend +##### Build ```sh -yarn build -``` -or -```sh -npm build -``` - -##### Build the backend - -```sh -yarn build-server -``` -or -```sh -npm build-server +npm run build ``` ##### Start the backend server ```sh -your-absolute-path-to-server-dist> node app.js +npx ts-node /server/app.ts ``` ##### Nginx deployment @@ -106,7 +73,7 @@ your-absolute-path-to-server-dist> node app.js server { # Frontend location / { - root /your_absolute_path_to_dist/dist; + root /path_to_dist/dist; try_files $uri $uri/ /index.html; } @@ -117,11 +84,6 @@ server { } ``` -```sh -nginx -s reload //Restart your nginx -``` - - # LICENSE This project is licensed under the AGPL V3 (see NOTICE) and a commercial license from TESOBE. diff --git a/components.d.ts b/components.d.ts index fa3f81c..f58c18f 100644 --- a/components.d.ts +++ b/components.d.ts @@ -14,10 +14,13 @@ declare module '@vue/runtime-core' { ElAside: typeof import('element-plus/es')['ElAside'] ElBacktop: typeof import('element-plus/es')['ElBacktop'] ElButton: typeof import('element-plus/es')['ElButton'] + ElCard: typeof import('element-plus/es')['ElCard'] ElCol: typeof import('element-plus/es')['ElCol'] ElCollapse: typeof import('element-plus/es')['ElCollapse'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] ElContainer: typeof import('element-plus/es')['ElContainer'] + ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] + ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] ElDivider: typeof import('element-plus/es')['ElDivider'] ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] @@ -30,9 +33,13 @@ declare module '@vue/runtime-core' { ElInput: typeof import('element-plus/es')['ElInput'] ElMain: typeof import('element-plus/es')['ElMain'] ElRow: typeof import('element-plus/es')['ElRow'] + ElTable: typeof import('element-plus/es')['ElTable'] + ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] + ElTag: typeof import('element-plus/es')['ElTag'] GlossarySearchNav: typeof import('./src/components/GlossarySearchNav.vue')['default'] HeaderNav: typeof import('./src/components/HeaderNav.vue')['default'] Menu: typeof import('./src/components/Menu.vue')['default'] + MessageDocsSearchNav: typeof import('./src/components/MessageDocsSearchNav.vue')['default'] Preview: typeof import('./src/components/Preview.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] diff --git a/index.html b/index.html index 882bc82..d3d630b 100644 --- a/index.html +++ b/index.html @@ -1,17 +1,76 @@ - - - + + + API Explorer - - + + + + -
+
+
+ +
+
+
+
+ diff --git a/package.json b/package.json index 350aaf0..63f6122 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "api-explorer", - "version": "0.0.0", + "version": "1.0.14", "private": true, "scripts": { "dev": "vite & ts-node server/app.ts", @@ -25,7 +25,7 @@ "express-session": "^1.17.3", "highlight.js": "^11.7.0", "oauth": "^0.10.0", - "obp-typescript": "^1.0.34", + "obp-typescript": "^1.0.36", "pinia": "^2.0.32", "reflect-metadata": "^0.1.13", "routing-controllers": "^0.10.3", @@ -49,6 +49,7 @@ "jsdom": "^21.1.0", "npm-run-all": "^4.1.5", "prettier": "^2.8.4", + "superagent": "^8.1.2", "ts-node": "^10.9.1", "typescript": "~4.8.4", "unplugin-auto-import": "^0.15.1", diff --git a/public/js/worker/web-worker.js b/public/js/worker/web-worker.js new file mode 100644 index 0000000..328a742 --- /dev/null +++ b/public/js/worker/web-worker.js @@ -0,0 +1,3 @@ +onmessage = (event) => { + postMessage(event.data); +}; diff --git a/server/app.ts b/server/app.ts index 8cc36be..5326881 100644 --- a/server/app.ts +++ b/server/app.ts @@ -8,25 +8,40 @@ import path from 'path' const port = 8085 const app: Application = express() +const host = process.env.VITE_OBP_EXPLORER_HOST +const httpsOrNot = host ? host.indexOf("https://") == 0 ? true : false : true + app.use(express.json()) +let sessionObject = { + secret: process.env.VITE_OPB_SERVER_SESSION_PASSWORD, + resave: false, + saveUninitialized: true, + cookie: { + httpOnly: true, + secure: false, + maxAge: 300*1000, // 5 minutes in milliseconds + } +} +if (app.get('env') === 'production') { + app.set('trust proxy', 1) // trust first proxy + sessionObject.cookie.secure = true // serve secure cookies +} app.use( - session({ - secret: 'very secret', - resave: false, - saveUninitialized: true - }) + session(sessionObject) ) useContainer(Container) +const routePrefix = '/api' + const server = useExpressServer(app, { //routePrefix: '/api/v1', - routePrefix: '/api', + routePrefix: routePrefix, controllers: [path.join(__dirname + '/controllers/*.ts')], middlewares: [path.join(__dirname + '/middlewares/*.ts')] }) export const instance = server.listen(port) -console.log('Server running at http://localhost:' + port) +console.log(`Backend is running. You can check a status at http://localhost:${port}${routePrefix}/status`) export default app diff --git a/server/controllers/StatusController.ts b/server/controllers/StatusController.ts new file mode 100644 index 0000000..7dd7f49 --- /dev/null +++ b/server/controllers/StatusController.ts @@ -0,0 +1,95 @@ +import { Controller, Session, Req, Res, Get } from 'routing-controllers' +import { Request, Response } from 'express' +import OBPClientService from '../services/OBPClientService' +import OauthInjectedService from '../services/OauthInjectedService' +import { Service } from 'typedi' +import { OAuthConfig } from 'obp-typescript' + +@Service() +@Controller('/status') +export class StatusController { + private obpExplorerHome = process.env.VITE_OBP_EXPLORER_HOST + private connectors = [ + 'kafka_vSept2018', + 'akka_vDec2018', + 'rest_vMar2019', + 'stored_procedure_vDec2019' + ] + constructor( + private obpClientService: OBPClientService, + private oauthInjectedService: OauthInjectedService + ) {} + @Get('/') + async index( + @Session() session: any, + @Req() request: Request, + @Res() response: Response + ): Response { + const oauthConfig = session['clientConfig'] + const version = this.obpClientService.getOBPVersion() + const apiVersions = await this.checkApiVersions(oauthConfig, version) + const messageDocs = await this.checkMessagDocs(oauthConfig, version) + const resourceDocs = await this.checkResourceDocs(oauthConfig, version) + return response.json({ + status: apiVersions && messageDocs && resourceDocs, + apiVersions, + messageDocs, + resourceDocs + }) + } + + isCodeError(response: any, path: string): boolean { + console.log(`Validating ${path} response...`) + if (!response || Object.keys(response).length == 0) return true + if (Object.keys(response).includes('code')) { + const code = response['code'] + if (code >= 400) { + console.log(response) // Log error responce + return true + } + } + return false + } + + async checkResourceDocs(oauthConfig: OAuthConfig, version: string): Promise { + try { + const path = `/obp/${version}/resource-docs/${version}/obp` + const resourceDocs = await this.obpClientService.get( + path, + oauthConfig + ) + return !this.isCodeError(resourceDocs, path) + } catch (error) { + return false + } + } + async checkMessagDocs(oauthConfig: OAuthConfig, version: string): Promise { + try { + const messageDocsCodeResult = await Promise.all( + this.connectors.map(async (connector) => { + const path = `/obp/${version}/message-docs/${connector}` + return !this.isCodeError( + await this.obpClientService.get( + path, + oauthConfig + ), + path + ) + }) + ) + return messageDocsCodeResult.every((isCodeError: boolean) => isCodeError) + } catch (error) { + return false + } + } + + async checkApiVersions(oauthConfig: OAuthConfig, version: string): Promise { + try { + const path = `/obp/${version}/api/versions` + const versions = await this.obpClientService.get(path, oauthConfig) + return !this.isCodeError(versions, path) + } catch (error) { + return false + } + } +} diff --git a/server/middlewares/OauthAccessTokenMiddleware.ts b/server/middlewares/OauthAccessTokenMiddleware.ts index 048680e..9c561ee 100644 --- a/server/middlewares/OauthAccessTokenMiddleware.ts +++ b/server/middlewares/OauthAccessTokenMiddleware.ts @@ -16,6 +16,7 @@ export default class OauthAccessTokenMiddleware implements ExpressMiddlewareInte const consumer = oauthService.getConsumer() const oauthVerifier = request.query.oauth_verifier const session = request.session + console.log('OauthAccessTokenMiddleware.ts use says: Before consumer.getOAuthAccessToken') consumer.getOAuthAccessToken( oauthService.requestTokenKey, oauthService.requestTokenSecret, @@ -34,6 +35,7 @@ export default class OauthAccessTokenMiddleware implements ExpressMiddlewareInte secret: oauthTokenSecret } session['clientConfig'] = clientConfig + console.log('OauthAccessTokenMiddleware.ts use says: Seems OK, redirecting..') response.redirect(`${process.env.VITE_OBP_EXPLORER_HOST}`) } } diff --git a/server/middlewares/OauthRequestTokenMiddleware.ts b/server/middlewares/OauthRequestTokenMiddleware.ts index 1da5cfe..8f7be1b 100644 --- a/server/middlewares/OauthRequestTokenMiddleware.ts +++ b/server/middlewares/OauthRequestTokenMiddleware.ts @@ -19,6 +19,7 @@ export default class OauthRequestTokenMiddleware implements ExpressMiddlewareInt } else { oauthService.requestTokenKey = oauthTokenKey oauthService.requestTokenSecret = oauthTokenSecret + console.log('OauthRequestTokenMiddleware.ts consumer.getOAuthRequestToken says: Redirecting to /oauth/authorize?oauth_token=XXX') response.redirect(apiHost + '/oauth/authorize?oauth_token=' + oauthTokenKey) } }) diff --git a/src/assets/element-plus-override.css b/src/assets/element-plus-override.css index 3d5a8e0..c1b0af3 100644 --- a/src/assets/element-plus-override.css +++ b/src/assets/element-plus-override.css @@ -50,10 +50,6 @@ font-family: Roboto; } -.el-dropdown-menu__item:hover { - color: #52b165 !important; -} - .el-alert--info.is-light { background-color: #253047; } diff --git a/src/assets/main.css b/src/assets/main.css index 1b58f18..e2e4c62 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -34,3 +34,9 @@ main { .favoriteButton:hover { color: #fcc406; } + +.search-nav { + background-color: #f8f9fb; + padding: 8px; + border-right: solid 1px var(--el-menu-border-color); +} diff --git a/src/components/Collections.vue b/src/components/Collections.vue new file mode 100644 index 0000000..d482ad8 --- /dev/null +++ b/src/components/Collections.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/components/Content.vue b/src/components/Content.vue index a1846b2..8cf03ab 100644 --- a/src/components/Content.vue +++ b/src/components/Content.vue @@ -1,58 +1,69 @@ - diff --git a/src/components/Menu.vue b/src/components/Menu.vue index 29b1f5c..140939b 100644 --- a/src/components/Menu.vue +++ b/src/components/Menu.vue @@ -1,25 +1,38 @@ @@ -330,6 +357,15 @@ li { display: flex; flex-direction: row; } +.footnote { + color: var(--el-color-info); + font-size: 12px; +} +.divider { + border-top: 1px #253047 solid; + margin-left: -25px; + padding-right: 50px; +} #search-input { -webkit-border-top-right-radius: 0; -moz-border-top-right-radius: 0; diff --git a/src/components/SearchNav.vue b/src/components/SearchNav.vue index 39ac6bc..9df19b9 100644 --- a/src/components/SearchNav.vue +++ b/src/components/SearchNav.vue @@ -1,10 +1,13 @@ + + + + diff --git a/src/views/APIServerStatusView.vue b/src/views/APIServerStatusView.vue new file mode 100644 index 0000000..09f0445 --- /dev/null +++ b/src/views/APIServerStatusView.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/src/views/BodyView.vue b/src/views/BodyView.vue index 6a6fa5e..705c4f3 100644 --- a/src/views/BodyView.vue +++ b/src/views/BodyView.vue @@ -1,6 +1,9 @@