diff --git a/README.md b/README.md
index fbbeaf8bc..cdaf84002 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ The repo is setup to create a local deployment of the Portal along with required
docker build -t gwa-api:e2e .
```
-1. Build: Back in `api-services-portal`, run `docker compose --profile testsuite build`.
+1. Build: Back in `api-services-portal`, run `docker compose build`.
1. Run: `docker compose up`. Wait for startup to complete - look for `Swagger UI registered`.
1. The Portal is now live at http://oauth2proxy.localtest.me:4180
1. To login, use username `janis@idir` and password `awsummer` (or username `local` and password `local`).
@@ -35,7 +35,12 @@ The repo is setup to create a local deployment of the Portal along with required
### Cypress testing
-To run the Cypress test automation suite, run `docker compose --profile testsuite up`.
+To run the Cypress test automation suite, run
+
+```sh
+docker compose --profile testsuite build
+docker compose --profile testsuite up
+```
### gwa CLI configuration
diff --git a/docker-compose.yml b/docker-compose.yml
index f8a230546..d75a52715 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -9,7 +9,7 @@ x-common-variables: &common-variables
services:
keycloak:
- image: jboss/keycloak:15.1.1
+ image: quay.io/keycloak/keycloak:15.1.1
container_name: keycloak
hostname: keycloak
depends_on:
diff --git a/src/mocks/resolvers/api-directory.js b/src/mocks/resolvers/api-directory.js
index 610852f4e..6d1b06346 100644
--- a/src/mocks/resolvers/api-directory.js
+++ b/src/mocks/resolvers/api-directory.js
@@ -1,4 +1,77 @@
+import YAML from 'js-yaml';
+
+const markdown = YAML.load(`
+notes: |
+ Here is some markdown.
+ # Heading 1
+ ## Heading 2
+ ### Heading 3
+ #### Heading 4
+ Then I will do **bold**, _italics_ and ~~strikethrough~~.
+
+ #### Heading 4
+
+ And some text.
+
+ How about a table?
+ | Col1 | Col2 |
+ | ---- | ---- |
+ | Val1 | Val2 |
+
+ How about a new line.
+
+ And another line.
+
+ Try a list:
+ - one
+ - two
+ - three
+
+ Or an ordered list:
+ 1. one
+ 1. two
+ 1. three
+
+ Then there are images
+
+ ![image](http://localhost:3000/images/bc_logo_header.svg)
+
+ And links [my docs](https://github.com).
+
+ Here are some block quotes
+
+ > A block quote about something.
+
+ What about a bit of code - \`alert("hi")\`.
+
+ Code block?
+ \`\`\`
+ function (a) {
+ // comment
+ }
+ \`\`\`
+
+`);
+
const directories = [
+ {
+ id: 'api1',
+ name: 'markdown-test',
+ title: 'Testing Markdown on Dataset',
+ notes: markdown.notes,
+ sector: 'Natural Resources',
+ license_title: 'Access Only',
+ view_audience: 'Named users',
+ security_class: 'LOW-PUBLIC',
+ record_publish_date: '2020-04-28',
+ tags: '["API","CDOGS","Document","Document Generation"]',
+ organization: {
+ title: 'Ministry of Environment and Climate Change Strategy',
+ },
+ organizationUnit: {
+ title: 'Information Innovation and Technology',
+ },
+ },
{
id: 'api1',
name: 'common-service-api',
diff --git a/src/nextapp/pages/devportal/api-directory/[id].tsx b/src/nextapp/pages/devportal/api-directory/[id].tsx
index 9a8475efb..a1a752859 100644
--- a/src/nextapp/pages/devportal/api-directory/[id].tsx
+++ b/src/nextapp/pages/devportal/api-directory/[id].tsx
@@ -26,6 +26,7 @@ import ReactMarkdownWithHtml from 'react-markdown/with-html';
import gfm from 'remark-gfm';
import { uid } from 'react-uid';
import { useAuth } from '@/shared/services/auth';
+import style from '@/shared/styles/markdown.module.css';
const renderers = {
link: InternalLink,
@@ -144,20 +145,28 @@ const ApiPage: React.FC<
About This Dataset
-
+
{data?.notes}
- {data?.products?.sort((a,b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)).map((p) => (
-
- ))}
+ {data?.products
+ ?.sort((a, b) =>
+ a.name > b.name ? 1 : b.name > a.name ? -1 : 0
+ )
+ .map((p) => (
+
+ ))}
diff --git a/src/nextapp/shared/styles/markdown.module.css b/src/nextapp/shared/styles/markdown.module.css
new file mode 100644
index 000000000..a6e77eb52
--- /dev/null
+++ b/src/nextapp/shared/styles/markdown.module.css
@@ -0,0 +1,72 @@
+.markdown {
+}
+
+.markdown h1 {
+ margin-bottom: 1em;
+}
+
+.markdown h2 {
+ margin-bottom: 1em;
+}
+
+.markdown h3 {
+ margin-bottom: 1em;
+}
+
+.markdown h4 {
+ margin-bottom: 1em;
+}
+
+.markdown ul {
+ margin-top: 1em;
+ margin-bottom: 1em;
+ list-style: disc outside none;
+}
+
+.markdown ul li {
+ margin-left: 2em;
+ display: list-item;
+ text-align: -webkit-match-parent;
+}
+
+.markdown ol {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+.markdown ol li {
+ margin-left: 2em;
+ display: list-item;
+ text-align: -webkit-match-parent;
+}
+
+.markdown img {
+ display: none;
+}
+
+.markdown table {
+ margin-top: 1em;
+ margin-bottom: 1em;
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.markdown td {
+ padding: 8px;
+ border: 1px solid #cccccc;
+}
+
+.markdown th {
+ padding: 8px;
+ text-align: left;
+ border: 1px solid #cccccc;
+}
+
+.markdown blockquote {
+ margin-top: 1em;
+ margin-bottom: 1em;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ padding-left: 5px;
+ border-left: 10px solid #cccccc;
+}
diff --git a/src/package.json b/src/package.json
index 5ab4e899e..d3bf15ed6 100644
--- a/src/package.json
+++ b/src/package.json
@@ -33,6 +33,7 @@
"copy-keystone-admin-assets": "ts-node tools/copyKeystoneAdminAssets",
"x-prestart": "npm run build",
"x-dev": "nodemon",
+ "nextapp-dev": "cross-env NEXT_PUBLIC_MOCKS=on NODE_ENV=development NODE_OPTIONS='--openssl-legacy-provider --no-experimental-fetch --dns-result-order=ipv4first' next dev ./nextapp",
"batch": "cross-env NODE_ENV=development node dist/server-batch.js",
"intg-build": "cross-env NODE_ENV=development npm-run-all delete-assets copy-assets ts-build",
"dev": "cross-env NODE_ENV=development NODE_OPTIONS='--openssl-legacy-provider --no-experimental-fetch --dns-result-order=ipv4first' npm-run-all delete-assets copy-assets tsoa-gen-types tsoa-build-v1 tsoa-build-v2 ts-build ks-dev",
diff --git a/src/services/keystone/application.ts b/src/services/keystone/application.ts
index d09b507d7..ac9f7142c 100644
--- a/src/services/keystone/application.ts
+++ b/src/services/keystone/application.ts
@@ -12,6 +12,10 @@ export async function lookupApplication(
allApplications(where: {id: $id}) {
id
appId
+ name
+ owner {
+ name
+ }
}
}`,
variables: { id },
diff --git a/src/services/kong/consumer-service.ts b/src/services/kong/consumer-service.ts
index 194f786ab..aa8a707ee 100644
--- a/src/services/kong/consumer-service.ts
+++ b/src/services/kong/consumer-service.ts
@@ -10,6 +10,8 @@ import {
KeyAuthResponse,
KongConsumer,
} from './types';
+import { Application } from '../keystone/types';
+import { alphanumericNoSpaces } from '../utils';
const logger = Logger('kong.consumer');
@@ -41,7 +43,8 @@ export class KongConsumerService {
public async createOrGetConsumer(
username: string,
- customId: string
+ customId: string,
+ app: Application
): Promise {
logger.debug('createOrGetConsumer');
try {
@@ -51,20 +54,28 @@ export class KongConsumerService {
return { created: false, consumer: result };
} catch (err) {
logger.debug('createOrGetConsumer - CATCH ERROR %s', err);
- const result = await this.createKongConsumer(username, customId);
+ const result = await this.createKongConsumer(username, customId, app);
logger.debug('createOrGetConsumer - CATCH RESULT %j', result);
- return { created: false, consumer: result };
+ return { created: true, consumer: result };
}
}
- public async createKongConsumer(username: string, customId: string) {
+ public async createKongConsumer(
+ username: string,
+ customId: string,
+ app: Application
+ ) {
let body: KongConsumer = {
username: username,
- tags: ['aps-portal'],
+ tags: [],
};
if (customId) {
body['custom_id'] = customId;
}
+ if (app) {
+ body.tags.push(`app:${alphanumericNoSpaces(app.name)}`);
+ body.tags.push(`owner:${alphanumericNoSpaces(app.owner.name)}`);
+ }
logger.debug('[createKongConsumer] %s', `${this.kongUrl}/consumers`);
try {
let response = await fetch(`${this.kongUrl}/consumers`, {
diff --git a/src/services/utils.ts b/src/services/utils.ts
index ee8b8e195..f6411473a 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -75,3 +75,7 @@ export async function fetchWithTimeout(resource: string, options: any = {}) {
return response;
}
+
+export function alphanumericNoSpaces(str: string) {
+ return str.replace(/[^A-Za-z0-9:-]/gim, '').replace(/[:]/gim, '-');
+}
diff --git a/src/services/workflow/create-service-account.ts b/src/services/workflow/create-service-account.ts
index 474396e75..dc0b21846 100644
--- a/src/services/workflow/create-service-account.ts
+++ b/src/services/workflow/create-service-account.ts
@@ -79,7 +79,11 @@ export const CreateServiceAccount = async (
const nickname = client.client.clientId;
const kongApi = new KongConsumerService(process.env.KONG_URL);
- const consumer = await kongApi.createKongConsumer(nickname, clientId);
+ const consumer = await kongApi.createKongConsumer(
+ nickname,
+ clientId,
+ application
+ );
const consumerPK = await AddClientConsumer(
context,
nickname,
diff --git a/src/services/workflow/generate-credential.ts b/src/services/workflow/generate-credential.ts
index bbe605709..3fb2466bd 100644
--- a/src/services/workflow/generate-credential.ts
+++ b/src/services/workflow/generate-credential.ts
@@ -53,7 +53,12 @@ export const generateCredential = async (
const nickname = clientId;
- const newApiKey = await registerApiKey(context, clientId, nickname);
+ const newApiKey = await registerApiKey(
+ context,
+ clientId,
+ nickname,
+ application
+ );
logger.debug('new-api-key CREATED FOR %s', clientId);
@@ -138,7 +143,11 @@ export const generateCredential = async (
logger.debug('new-client %j', newClient);
const kongApi = new KongConsumerService(process.env.KONG_URL);
- const consumer = await kongApi.createKongConsumer(nickname, clientId);
+ const consumer = await kongApi.createKongConsumer(
+ nickname,
+ clientId,
+ application
+ );
const consumerPK = await AddClientConsumer(
context,
nickname,
diff --git a/src/services/workflow/kong-api-key.ts b/src/services/workflow/kong-api-key.ts
index 0c9285bfc..92d4d65b9 100644
--- a/src/services/workflow/kong-api-key.ts
+++ b/src/services/workflow/kong-api-key.ts
@@ -1,5 +1,6 @@
const { addKongConsumer } = require('../../services/keystone');
+import { Application } from '../keystone/types';
import { KongConsumerService } from '../kong';
/**
@@ -14,11 +15,12 @@ import { KongConsumerService } from '../kong';
export async function registerApiKey(
context: any,
newClientId: string,
- nickname: string
+ nickname: string,
+ app: Application
) {
const kongApi = new KongConsumerService(process.env.KONG_URL);
- const consumer = await kongApi.createKongConsumer(nickname, newClientId);
+ const consumer = await kongApi.createKongConsumer(nickname, newClientId, app);
const apiKey = await kongApi.addKeyAuthToConsumer(consumer.id);
diff --git a/src/services/workflow/link-consumer-to-namespace.ts b/src/services/workflow/link-consumer-to-namespace.ts
index 95d604d0e..8e18e98e1 100644
--- a/src/services/workflow/link-consumer-to-namespace.ts
+++ b/src/services/workflow/link-consumer-to-namespace.ts
@@ -30,6 +30,7 @@ export const LinkConsumerToNamespace = async (
const kongApi = new KongConsumerService(process.env.KONG_URL);
const consumerResult = await kongApi.createOrGetConsumer(
consumerUsername,
+ null,
null
);
const consumerPK: any = { id: null };
diff --git a/src/test/services/utils.test.js b/src/test/services/utils.test.js
new file mode 100644
index 000000000..9aa7fe164
--- /dev/null
+++ b/src/test/services/utils.test.js
@@ -0,0 +1,51 @@
+import { alphanumericNoSpaces } from '../../services/utils';
+
+describe('alphanumericNoSpaces tests', () => {
+ it('should remove spaces from the string', () => {
+ const input = 'hello world';
+ const expectedOutput = 'helloworld';
+ expect(alphanumericNoSpaces(input)).toEqual(expectedOutput);
+ });
+
+ it('should remove special characters', () => {
+ const input = 'hello@world!how%^&*are you?';
+ const expectedOutput = 'helloworldhowareyou';
+ expect(alphanumericNoSpaces(input)).toEqual(expectedOutput);
+ });
+
+ it('should replace colons with hyphens', () => {
+ const input = 'this:is:a:test';
+ const expectedOutput = 'this-is-a-test';
+ expect(alphanumericNoSpaces(input)).toEqual(expectedOutput);
+ });
+
+ it('should handle empty string', () => {
+ const input = '';
+ const expectedOutput = '';
+ expect(alphanumericNoSpaces(input)).toEqual(expectedOutput);
+ });
+
+ it('should handle string with only special characters', () => {
+ const input = '@#$%^&*()';
+ const expectedOutput = '';
+ expect(alphanumericNoSpaces(input)).toEqual(expectedOutput);
+ });
+
+ it('should handle string with only colons', () => {
+ const input = ':::';
+ const expectedOutput = '---';
+ expect(alphanumericNoSpaces(input)).toEqual(expectedOutput);
+ });
+
+ it('should handle string with only alphanumeric characters', () => {
+ const input = 'abcdef12345';
+ const expectedOutput = 'abcdef12345';
+ expect(alphanumericNoSpaces(input)).toEqual(expectedOutput);
+ });
+
+ it('should handle string with mixed characters', () => {
+ const input = 'hello!-world, how:are?you';
+ const expectedOutput = 'hello-worldhow-areyou';
+ expect(alphanumericNoSpaces(input)).toEqual(expectedOutput);
+ });
+});