second draft of the Bazaar plugin

signed-off-by: lykkeaxlin <lykkeaxlin@hotmail.com>

Co-authored-by: klaraab <klarabroman@live.se>
Signed-off-by: Lykke Axlin <lykkeaxlin@hotmail.com>
This commit is contained in:
Lykke Axlin
2021-09-14 13:11:18 +02:00
parent 34fa0b3450
commit eb75e42ce7
66 changed files with 7165 additions and 5574 deletions
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
};
+49
View File
@@ -0,0 +1,49 @@
# Bazaar Backend
Welcome to the Bazaar backend plugin!
# Installation
## Install the package
```bash
# From your Backstage root directory
cd packages/backend
yarn add @backstage/plugin-bazaar-backend
```
## Adding the plugin to your `packages/backend`
You'll need to add the plugin to the router in your `backend` package. You can do this by creating a file called `packages/backend/src/plugins/bazaar.ts`
```tsx
import { PluginEnvironment } from '../types';
import { createRouter } from '@internal/plugin-bazaar-backend';
export default async function createPlugin({
logger,
database,
config,
}: PluginEnvironment) {
return await createRouter({ logger, config, database });
}
```
With the `bazaar.ts` router setup in place, add the router to `packages/backend/src/index.ts`:
```diff
+ import bazaar from './plugins/bazaar';
async function main() {
...
const createEnv = makeCreateEnv(config);
const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
+ const bazaarEnv = useHotMemoize(module, () => createEnv('bazaar'));
const apiRouter = Router();
+ apiRouter.use('/bazaar', await bazaar(bazaarEnv));
...
apiRouter.use(notFoundHandler());
```
+27
View File
@@ -0,0 +1,27 @@
## API Report File for "@internal/plugin-bazaar-backend"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { Config } from '@backstage/config';
import express from 'express';
import { Logger } from 'winston';
import { PluginDatabaseManager } from '@backstage/backend-common';
// @public (undocumented)
export function createRouter(options: RouterOptions): Promise<express.Router>;
// @public (undocumented)
export interface RouterOptions {
// (undocumented)
config: Config;
// (undocumented)
database?: PluginDatabaseManager;
// (undocumented)
logger: Logger;
}
// (No @packageDocumentation comment for this package)
```
Binary file not shown.
+67
View File
@@ -0,0 +1,67 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module.exports = {
client: 'postgresql',
connection: {
database: 'backstage_plugin_bazaar',
user: 'postgres',
password: 'herpderp',
},
useNullAsDefault: true,
migrations: {
directory: './migrations',
},
// development: {
// client: 'sqlite3',
// connection: {
// filename: './dev.sqlite3'
// }
// },
// staging: {
// client: 'postgresql',
// connection: {
// database: 'my_db',
// user: 'username',
// password: 'password'
// },
// pool: {
// min: 2,
// max: 10
// },
// migrations: {
// tableName: 'knex_migrations'
// }
// },
// production: {
// client: 'postgresql',
// connection: {
// database: 'my_db',
// user: 'username',
// password: 'password'
// },
// pool: {
// min: 2,
// max: 10
// },
// migrations: {
// tableName: 'knex_migrations'
// }
// }
};
@@ -0,0 +1,53 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
exports.up = async function setUpTables(knex) {
await knex.schema.createTable('metadata', table => {
table.comment('The table of Bazaar metadata');
table.text('name').notNullable().comment('The name of the entity');
table.text('entity_ref').notNullable().comment('The ref of the entity');
table
.text('announcement')
.notNullable()
.comment('The announcement of the bazaar project');
table
.text('status')
.defaultTo('proposed')
.notNullable()
.comment('The status of the bazaar project');
table
.dateTime('updated_at')
.defaultTo(knex.fn.now())
.notNullable()
.comment('The timestamp when this entry was updated');
});
await knex.schema.createTable('members', table => {
table.comment('The table of Bazaar members');
table.text('entity_ref').notNullable().comment('The ref of the entity');
table.text('user_id').notNullable().comment('The user id of the member');
table
.dateTime('join_date')
.defaultTo(knex.fn.now())
.notNullable()
.comment('The timestamp when this member joined');
});
};
exports.down = async function tearDownTables(knex) {
await knex.schema.dropTable('metadata');
await knex.schema.dropTable('members');
};
+42
View File
@@ -0,0 +1,42 @@
{
"name": "@internal/plugin-bazaar-backend",
"version": "0.1.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"scripts": {
"start": "backstage-cli backend:dev",
"build": "backstage-cli backend:build",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.9.2",
"@backstage/config": "^0.1.5",
"@types/express": "^4.17.6",
"cross-fetch": "^3.0.6",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"knex": "^0.95.10",
"winston": "^3.2.1",
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.7.6",
"@types/supertest": "^2.0.8",
"msw": "^0.29.0",
"supertest": "^4.0.2"
},
"files": [
"dist",
"migrations/**/*.{js,d.ts}"
]
}
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './service/router';
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
startStandaloneServer({ port, enableCors, logger }).catch(err => {
logger.error(err);
process.exit(1);
});
process.on('SIGINT', () => {
logger.info('CTRL+C pressed; exiting.');
process.exit(0);
});
@@ -0,0 +1,189 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
errorHandler,
PluginDatabaseManager,
resolvePackagePath,
useHotMemoize,
} from '@backstage/backend-common';
import express from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
import Knex from 'knex';
import { Config } from '@backstage/config';
export interface RouterOptions {
logger: Logger;
database?: PluginDatabaseManager;
config: Config;
}
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const { logger, config } = options;
const db = await options.database?.getClient();
const connection = config
.getConfig('backend')
.getConfig('database')
.getConfig('connection');
logger.info('Initializing Bazaar backend');
const database = useHotMemoize(module, () => {
const knex = Knex({
client: 'postgresql',
connection: {
database: 'backstage_plugin_bazaar',
user: connection.getString('user'),
password: connection.getString('password'),
},
useNullAsDefault: true,
});
knex.client.pool.on('createSuccess', (_eventId: any, resource: any) => {
resource.run('PRAGMA foreign_keys = ON', () => {});
});
return knex;
});
const migrationsDir = resolvePackagePath(
'@internal/plugin-bazaar-backend',
'migrations',
);
await database?.migrate.latest({
directory: migrationsDir,
});
const router = Router();
router.use(express.json());
router.get('/health', (_, response) => {
logger.info('PONG!');
response.send({ status: 'ok' });
});
router.get('/members', async (request, response) => {
const entityRef = request.headers.entity_ref;
const data = await db
?.select('*')
.from('public.members')
.where({ entity_ref: entityRef });
if (data?.length) {
response.send({ status: 'ok', data: data });
} else {
response.send({ status: 'ok', data: [] });
}
});
router.put('/members/add', async (request, response) => {
const userId = request.headers.user_id;
const entityRef = request.headers.entity_ref;
await db
?.insert({
entity_ref: entityRef,
user_id: userId,
})
.into('public.members');
response.send({ status: 'ok' });
});
router.delete('/members/remove', async (request, response) => {
const userId = request.headers.user_id;
const entityRef = request.headers.entity_ref;
const count = await db?.('public.members')
.where({ entity_ref: entityRef })
.andWhere('user_id', userId)
.del();
if (count) {
response.send({ status: 'ok' });
} else {
response.status(404).json({ message: 'Record not found' });
}
});
router.get('/metadata', async (request, response) => {
const entityRef = request.headers.entity_ref;
const data = await db
?.select('*')
.from('public.metadata')
.where({ entity_ref: entityRef });
if (data?.length) {
response.send({ status: 'ok', data: data });
} else {
response.status(404).json({ message: 'Record not found' });
}
});
router.get('/entities', async (_, response) => {
const data = await db?.select('*').from('public.metadata');
response.send({ status: 'ok', data: data });
});
router.put('/metadata', async (request, response) => {
const entityRef = request.headers.entity_ref;
const { name, announcement, status } = request.body;
const count = await db?.('public.metadata')
.where({ entity_ref: entityRef })
.update({
announcement: announcement,
status: status,
});
if (count) {
response.send({ status: 'ok' });
} else {
await db
?.insert({
name: name,
entity_ref: entityRef,
announcement: announcement,
status: status,
})
.into('public.metadata');
response.send({ status: 'ok' });
}
});
router.delete('/metadata', async (request, response) => {
const entityRef = request.headers.entity_ref;
const count = await db?.('public.metadata')
.where({ entity_ref: entityRef })
.del();
if (count) {
response.send({ status: 'ok' });
} else {
response.status(404).json({ message: 'Record not found' });
}
});
router.use(errorHandler());
return router;
}
@@ -0,0 +1,58 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
createServiceBuilder,
PluginDatabaseManager,
loadBackendConfig,
} from '@backstage/backend-common';
import { Server } from 'http';
import { Logger } from 'winston';
import { createRouter } from './router';
export interface ServerOptions {
port: number;
enableCors: boolean;
logger: Logger;
database?: PluginDatabaseManager;
}
export async function startStandaloneServer(
options: ServerOptions,
): Promise<Server> {
const logger = options.logger.child({ service: 'bazaar-backend' });
const config = await loadBackendConfig({ logger, argv: process.argv });
const router = await createRouter({
logger,
database: options.database,
config: config,
});
let service = createServiceBuilder(module)
.setPort(options.port)
.addRouter('/bazaar', router);
if (options.enableCors) {
service = service.enableCors({ origin: 'http://localhost:3000' });
}
return await service.start().catch(err => {
logger.error(err);
process.exit(1);
});
}
module.hot?.accept();
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {};