chore: refactor service and backend to use REST
Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi>
This commit is contained in:
@@ -19,7 +19,9 @@ export default async function createPlugin(
|
||||
return await createRouter({
|
||||
logger: env.logger,
|
||||
identity: env.identity,
|
||||
notificationService: env.notificationService,
|
||||
tokenManager: env.tokenManager,
|
||||
database: env.database,
|
||||
discovery: env.discovery,
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -37,3 +39,8 @@ async function main() {
|
||||
apiRouter.use('/notifications', await notifications(notificationsEnv));
|
||||
}
|
||||
```
|
||||
|
||||
## Extending Notifications
|
||||
|
||||
The notifications can be extended with `NotificationProcessor`. These processors allow to decorate notifications
|
||||
before they are sent or/and send the notifications to external services.
|
||||
|
||||
@@ -4,25 +4,43 @@
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import express from 'express';
|
||||
import { IdentityApi } from '@backstage/plugin-auth-node';
|
||||
import { Logger } from 'winston';
|
||||
import { NotificationService } from '@backstage/plugin-notifications-node';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { Notification as Notification_2 } from '@backstage/plugin-notifications-common';
|
||||
import { PluginDatabaseManager } from '@backstage/backend-common';
|
||||
import { TokenManager } from '@backstage/backend-common';
|
||||
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type NotificationProcessor = {
|
||||
decorate?(notification: Notification_2): Promise<Notification_2>;
|
||||
send?(notification: Notification_2): Promise<void>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const notificationsPlugin: () => BackendFeature;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RouterOptions {
|
||||
// (undocumented)
|
||||
catalog?: CatalogApi;
|
||||
// (undocumented)
|
||||
database: PluginDatabaseManager;
|
||||
// (undocumented)
|
||||
discovery: DiscoveryApi;
|
||||
// (undocumented)
|
||||
identity: IdentityApi;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
logger: LoggerService;
|
||||
// (undocumented)
|
||||
notificationService: NotificationService;
|
||||
processors?: NotificationProcessor[];
|
||||
// (undocumented)
|
||||
tokenManager: TokenManager;
|
||||
}
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2023 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 up(knex) {
|
||||
await knex.schema.createTable('notifications', table => {
|
||||
table.uuid('id').primary();
|
||||
table.string('userRef').notNullable();
|
||||
table.string('title').notNullable();
|
||||
table.text('description').notNullable();
|
||||
table.text('link').notNullable();
|
||||
table.text('icon').nullable();
|
||||
table.text('image').nullable();
|
||||
table.datetime('created').notNullable();
|
||||
table.datetime('read').nullable();
|
||||
table.boolean('saved').defaultTo(false).notNullable();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('knex').Knex} knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
await knex.schema.dropTable('notifications');
|
||||
};
|
||||
@@ -24,14 +24,20 @@
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "workspace:^",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/catalog-client": "workspace:^",
|
||||
"@backstage/catalog-model": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/plugin-auth-node": "workspace:^",
|
||||
"@backstage/plugin-notifications-common": "workspace:^",
|
||||
"@backstage/plugin-notifications-node": "workspace:^",
|
||||
"@types/express": "*",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"knex": "^3.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"uuid": "^8.0.0",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2023 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 {
|
||||
PluginDatabaseManager,
|
||||
resolvePackagePath,
|
||||
} from '@backstage/backend-common';
|
||||
import {
|
||||
NotificationGetOptions,
|
||||
NotificationModifyOptions,
|
||||
NotificationsStore,
|
||||
} from './NotificationsStore';
|
||||
import { Notification } from '@backstage/plugin-notifications-common';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
const migrationsDir = resolvePackagePath(
|
||||
'@backstage/plugin-notifications-backend',
|
||||
'migrations',
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export class DatabaseNotificationsStore implements NotificationsStore {
|
||||
private constructor(private readonly db: Knex) {}
|
||||
|
||||
static async create({
|
||||
database,
|
||||
skipMigrations,
|
||||
}: {
|
||||
database: PluginDatabaseManager;
|
||||
skipMigrations?: boolean;
|
||||
}): Promise<DatabaseNotificationsStore> {
|
||||
const client = await database.getClient();
|
||||
|
||||
if (!database.migrations?.skip && !skipMigrations) {
|
||||
await client.migrate.latest({
|
||||
directory: migrationsDir,
|
||||
});
|
||||
}
|
||||
|
||||
return new DatabaseNotificationsStore(client);
|
||||
}
|
||||
|
||||
private mapToInteger = (val: string | number | undefined): number => {
|
||||
return typeof val === 'string' ? Number.parseInt(val, 10) : val ?? 0;
|
||||
};
|
||||
|
||||
private getNotificationsBaseQuery = (
|
||||
options: NotificationGetOptions | NotificationModifyOptions,
|
||||
) => {
|
||||
const { user_ref, type } = options;
|
||||
const query = this.db('notifications').where('userRef', user_ref);
|
||||
|
||||
if (type === 'unread') {
|
||||
query.whereNull('read');
|
||||
} else if (type === 'read') {
|
||||
query.whereNotNull('read');
|
||||
} else if (type === 'saved') {
|
||||
query.where('saved', true);
|
||||
}
|
||||
|
||||
if ('ids' in options && options.ids) {
|
||||
query.whereIn('id', options.ids);
|
||||
}
|
||||
|
||||
return query;
|
||||
};
|
||||
|
||||
async getNotifications(options: NotificationGetOptions) {
|
||||
const notificationQuery = this.getNotificationsBaseQuery(options);
|
||||
const notifications = await notificationQuery.select('*');
|
||||
return notifications;
|
||||
}
|
||||
|
||||
async saveNotification(notification: Notification) {
|
||||
await this.db.insert(notification).into('notifications');
|
||||
}
|
||||
|
||||
async getStatus(options: NotificationGetOptions) {
|
||||
const notificationQuery = this.getNotificationsBaseQuery(options);
|
||||
const unreadQuery = await notificationQuery
|
||||
.clone()
|
||||
.whereNull('read')
|
||||
.count('id as UNREAD')
|
||||
.first();
|
||||
const readQuery = await notificationQuery
|
||||
.clone()
|
||||
.whereNotNull('read')
|
||||
.count('id as READ')
|
||||
.first();
|
||||
|
||||
return {
|
||||
unread: this.mapToInteger((unreadQuery as any)?.UNREAD),
|
||||
read: this.mapToInteger((readQuery as any)?.READ),
|
||||
};
|
||||
}
|
||||
|
||||
async markRead(options: NotificationModifyOptions): Promise<void> {
|
||||
const notificationQuery = this.getNotificationsBaseQuery(options);
|
||||
await notificationQuery.update({ read: new Date() });
|
||||
}
|
||||
|
||||
async markUnread(options: NotificationModifyOptions): Promise<void> {
|
||||
const notificationQuery = this.getNotificationsBaseQuery(options);
|
||||
await notificationQuery.update({ read: null });
|
||||
}
|
||||
|
||||
async markSaved(options: NotificationModifyOptions): Promise<void> {
|
||||
const notificationQuery = this.getNotificationsBaseQuery(options);
|
||||
await notificationQuery.update({ saved: true });
|
||||
}
|
||||
|
||||
async markUnsaved(options: NotificationModifyOptions): Promise<void> {
|
||||
const notificationQuery = this.getNotificationsBaseQuery(options);
|
||||
await notificationQuery.update({ saved: false });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2023 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 {
|
||||
Notification,
|
||||
NotificationStatus,
|
||||
NotificationType,
|
||||
} from '@backstage/plugin-notifications-common';
|
||||
|
||||
/** @public */
|
||||
export type NotificationGetOptions = {
|
||||
user_ref: string;
|
||||
type?: NotificationType;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type NotificationModifyOptions = {
|
||||
ids: string[];
|
||||
} & NotificationGetOptions;
|
||||
|
||||
/** @public */
|
||||
export interface NotificationsStore {
|
||||
getNotifications(options: NotificationGetOptions): Promise<Notification[]>;
|
||||
|
||||
saveNotification(notification: Notification): Promise<void>;
|
||||
|
||||
getStatus(options: NotificationGetOptions): Promise<NotificationStatus>;
|
||||
|
||||
markRead(options: NotificationModifyOptions): Promise<void>;
|
||||
|
||||
markUnread(options: NotificationModifyOptions): Promise<void>;
|
||||
|
||||
markSaved(options: NotificationModifyOptions): Promise<void>;
|
||||
|
||||
markUnsaved(options: NotificationModifyOptions): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2023 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 './DatabaseNotificationsStore';
|
||||
export * from './NotificationsStore';
|
||||
@@ -15,3 +15,4 @@
|
||||
*/
|
||||
export * from './service/router';
|
||||
export * from './plugin';
|
||||
export type { NotificationProcessor } from './types';
|
||||
|
||||
@@ -13,13 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { loggerToWinstonLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { createRouter } from './service/router';
|
||||
import { notificationService } from '@backstage/plugin-notifications-node';
|
||||
|
||||
/**
|
||||
* Notifications backend plugin
|
||||
@@ -34,14 +32,25 @@ export const notificationsPlugin = createBackendPlugin({
|
||||
httpRouter: coreServices.httpRouter,
|
||||
logger: coreServices.logger,
|
||||
identity: coreServices.identity,
|
||||
service: notificationService,
|
||||
database: coreServices.database,
|
||||
tokenManager: coreServices.tokenManager,
|
||||
discovery: coreServices.discovery,
|
||||
},
|
||||
async init({ httpRouter, logger, identity, service }) {
|
||||
async init({
|
||||
httpRouter,
|
||||
logger,
|
||||
identity,
|
||||
database,
|
||||
tokenManager,
|
||||
discovery,
|
||||
}) {
|
||||
httpRouter.use(
|
||||
await createRouter({
|
||||
logger: loggerToWinstonLogger(logger),
|
||||
logger,
|
||||
identity,
|
||||
notificationService: service,
|
||||
database,
|
||||
tokenManager,
|
||||
discovery,
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -13,13 +13,32 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
DatabaseManager,
|
||||
getVoidLogger,
|
||||
PluginDatabaseManager,
|
||||
PluginEndpointDiscovery,
|
||||
TokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
|
||||
import { createRouter } from './router';
|
||||
import { IdentityApi } from '@backstage/plugin-auth-node';
|
||||
import { NotificationService } from '@backstage/plugin-notifications-node';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
function createDatabase(): PluginDatabaseManager {
|
||||
return DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
client: 'better-sqlite3',
|
||||
connection: ':memory:',
|
||||
},
|
||||
},
|
||||
}),
|
||||
).forPlugin('notifications');
|
||||
}
|
||||
|
||||
describe('createRouter', () => {
|
||||
let app: express.Express;
|
||||
@@ -36,17 +55,23 @@ describe('createRouter', () => {
|
||||
};
|
||||
},
|
||||
};
|
||||
const mockedTokenManager: jest.Mocked<TokenManager> = {
|
||||
getToken: jest.fn(),
|
||||
authenticate: jest.fn(),
|
||||
};
|
||||
|
||||
const notificationServiceMock: jest.Mocked<Partial<NotificationService>> = {
|
||||
getStore: jest.fn(),
|
||||
send: jest.fn(),
|
||||
const discovery: jest.Mocked<PluginEndpointDiscovery> = {
|
||||
getBaseUrl: jest.fn(),
|
||||
getExternalBaseUrl: jest.fn(),
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const router = await createRouter({
|
||||
logger: getVoidLogger(),
|
||||
identity: identityMock,
|
||||
notificationService: notificationServiceMock as NotificationService,
|
||||
database: createDatabase(),
|
||||
tokenManager: mockedTokenManager,
|
||||
discovery,
|
||||
});
|
||||
app = express().use(router);
|
||||
});
|
||||
|
||||
@@ -13,36 +13,125 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { errorHandler } from '@backstage/backend-common';
|
||||
import {
|
||||
errorHandler,
|
||||
PluginDatabaseManager,
|
||||
TokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import express, { Request } from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
getBearerTokenFromAuthorizationHeader,
|
||||
IdentityApi,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
DatabaseNotificationsStore,
|
||||
NotificationGetOptions,
|
||||
NotificationService,
|
||||
} from '@backstage/plugin-notifications-node';
|
||||
import { IdentityApi } from '@backstage/plugin-auth-node';
|
||||
} from '../database';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
|
||||
import {
|
||||
Entity,
|
||||
isGroupEntity,
|
||||
isUserEntity,
|
||||
RELATION_HAS_MEMBER,
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { NotificationProcessor } from '../types';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
|
||||
/** @public */
|
||||
export interface RouterOptions {
|
||||
logger: Logger;
|
||||
logger: LoggerService;
|
||||
identity: IdentityApi;
|
||||
notificationService: NotificationService;
|
||||
database: PluginDatabaseManager;
|
||||
tokenManager: TokenManager;
|
||||
discovery: DiscoveryApi;
|
||||
catalog?: CatalogApi;
|
||||
processors?: NotificationProcessor[];
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const { logger, notificationService, identity } = options;
|
||||
const {
|
||||
logger,
|
||||
database,
|
||||
identity,
|
||||
discovery,
|
||||
catalog,
|
||||
tokenManager,
|
||||
processors,
|
||||
} = options;
|
||||
|
||||
const store = await notificationService.getStore();
|
||||
const catalogClient =
|
||||
catalog ?? new CatalogClient({ discoveryApi: discovery });
|
||||
const store = await DatabaseNotificationsStore.create({ database });
|
||||
|
||||
const getUser = async (req: Request<unknown>) => {
|
||||
const user = await identity.getIdentity({ request: req });
|
||||
return user ? user.identity.userEntityRef : 'user:default/guest';
|
||||
};
|
||||
|
||||
const authenticateService = async (req: Request<unknown>) => {
|
||||
const token = getBearerTokenFromAuthorizationHeader(
|
||||
req.header('authorization'),
|
||||
);
|
||||
if (!token) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
await tokenManager.authenticate(token);
|
||||
};
|
||||
|
||||
const getUsersForEntityRef = async (
|
||||
entityRef: string | string[],
|
||||
): Promise<string[]> => {
|
||||
const refs = Array.isArray(entityRef) ? entityRef : [entityRef];
|
||||
const { token } = await tokenManager.getToken();
|
||||
const entities = await catalogClient.getEntitiesByRefs(
|
||||
{
|
||||
entityRefs: refs,
|
||||
},
|
||||
{ token },
|
||||
);
|
||||
const mapEntity = async (entity: Entity | undefined): Promise<string[]> => {
|
||||
if (!entity) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (isUserEntity(entity)) {
|
||||
return [stringifyEntityRef(entity)];
|
||||
} else if (isGroupEntity(entity) && entity.relations) {
|
||||
return entity.relations
|
||||
.filter(
|
||||
relation =>
|
||||
relation.type === RELATION_HAS_MEMBER && relation.targetRef,
|
||||
)
|
||||
.map(r => r.targetRef);
|
||||
} else if (!isGroupEntity(entity) && entity.spec?.owner) {
|
||||
const owner = await catalogClient.getEntityByRef(
|
||||
entity.spec.owner as string,
|
||||
{ token },
|
||||
);
|
||||
if (owner) {
|
||||
return mapEntity(owner);
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const users: string[] = [];
|
||||
for (const entity of entities.items) {
|
||||
const u = await mapEntity(entity);
|
||||
users.push(...u);
|
||||
}
|
||||
return users;
|
||||
};
|
||||
|
||||
const router = Router();
|
||||
router.use(express.json());
|
||||
|
||||
@@ -114,6 +203,57 @@ export async function createRouter(
|
||||
res.status(200).send({ ids });
|
||||
});
|
||||
|
||||
router.post('/notifications', async (req, res) => {
|
||||
const { entityRef, title, description, link } = req.body;
|
||||
const notifications = [];
|
||||
let users = [];
|
||||
|
||||
try {
|
||||
await authenticateService(req);
|
||||
} catch (e) {
|
||||
logger.error(`Failed to authenticate notification request ${e}`);
|
||||
res.status(401).send();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
users = await getUsersForEntityRef(entityRef);
|
||||
} catch (e) {
|
||||
logger.error(`Failed to resolve notification receiver ${e}`);
|
||||
res.status(400).send();
|
||||
return;
|
||||
}
|
||||
|
||||
const baseNotification = {
|
||||
id: uuid(),
|
||||
title,
|
||||
description,
|
||||
link,
|
||||
created: new Date(),
|
||||
saved: false,
|
||||
};
|
||||
|
||||
for (const user of users) {
|
||||
let notification = { ...baseNotification, userRef: user };
|
||||
for (const processor of processors ?? []) {
|
||||
notification = processor.decorate
|
||||
? await processor.decorate(notification)
|
||||
: notification;
|
||||
}
|
||||
|
||||
await store.saveNotification(notification);
|
||||
for (const processor of processors ?? []) {
|
||||
if (processor.send) {
|
||||
processor.send(notification);
|
||||
}
|
||||
}
|
||||
notifications.push(notification);
|
||||
// TODO: Signal service
|
||||
}
|
||||
|
||||
res.send(notifications);
|
||||
});
|
||||
|
||||
router.use(errorHandler());
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
*/
|
||||
import {
|
||||
createServiceBuilder,
|
||||
HostDiscovery,
|
||||
loadBackendConfig,
|
||||
PluginDatabaseManager,
|
||||
PluginEndpointDiscovery,
|
||||
ServerTokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
@@ -25,7 +26,11 @@ import { createRouter } from './router';
|
||||
import Knex from 'knex';
|
||||
import { IdentityApi } from '@backstage/plugin-auth-node';
|
||||
import { Request } from 'express';
|
||||
import { NotificationService } from '@backstage/plugin-notifications-node';
|
||||
import {
|
||||
CatalogApi,
|
||||
CatalogRequestOptions,
|
||||
GetEntitiesByRefsRequest,
|
||||
} from '@backstage/catalog-client';
|
||||
|
||||
export interface ServerOptions {
|
||||
port: number;
|
||||
@@ -42,26 +47,34 @@ export async function startStandaloneServer(
|
||||
const config = await loadBackendConfig({ logger, argv: process.argv });
|
||||
const db = Knex(config.get('backend.database'));
|
||||
|
||||
const tokenManager = ServerTokenManager.fromConfig(config, {
|
||||
logger,
|
||||
});
|
||||
const discovery = HostDiscovery.fromConfig(config);
|
||||
|
||||
const dbMock: PluginDatabaseManager = {
|
||||
async getClient() {
|
||||
return db;
|
||||
},
|
||||
};
|
||||
|
||||
const discoveryMock: PluginEndpointDiscovery = {
|
||||
async getBaseUrl(pluginId: string) {
|
||||
return `http://localhost:7007/api/${pluginId}`;
|
||||
const catalogApi = {
|
||||
async getEntitiesByRefs(
|
||||
_request: GetEntitiesByRefsRequest,
|
||||
__options?: CatalogRequestOptions,
|
||||
) {
|
||||
return {
|
||||
items: [
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: { name: 'user', namespace: 'default' },
|
||||
spec: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
async getExternalBaseUrl(pluginId: string) {
|
||||
return `http://localhost:7007/api/${pluginId}`;
|
||||
},
|
||||
};
|
||||
|
||||
const notificationService = NotificationService.create({
|
||||
database: dbMock,
|
||||
discovery: discoveryMock,
|
||||
});
|
||||
} as Partial<CatalogApi> as CatalogApi;
|
||||
|
||||
const identityMock: IdentityApi = {
|
||||
async getIdentity({ request }: { request: Request<unknown> }) {
|
||||
@@ -80,7 +93,10 @@ export async function startStandaloneServer(
|
||||
const router = await createRouter({
|
||||
logger,
|
||||
identity: identityMock,
|
||||
notificationService,
|
||||
database: dbMock,
|
||||
catalog: catalogApi,
|
||||
discovery,
|
||||
tokenManager,
|
||||
});
|
||||
|
||||
let service = createServiceBuilder(module)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Notification } from '@backstage/plugin-notifications-common';
|
||||
|
||||
/** @public */
|
||||
export type NotificationProcessor = {
|
||||
/**
|
||||
* Decorate notification before sending it
|
||||
*
|
||||
* @param notification - The notification to decorate
|
||||
* @returns The same notification or a modified version of it
|
||||
*/
|
||||
decorate?(notification: Notification): Promise<Notification>;
|
||||
|
||||
/**
|
||||
* Send notification using this processor.
|
||||
*
|
||||
* @param notification - The notification to send
|
||||
*/
|
||||
send?(notification: Notification): Promise<void>;
|
||||
};
|
||||
Reference in New Issue
Block a user