Skip to content

Commit

Permalink
create router in BE based on openapi spec
Browse files Browse the repository at this point in the history
FLPATH756
https://issues.redhat.com/browse/FLPATH-756

Signed-off-by: Yaron Dayagi <[email protected]>
  • Loading branch information
ydayagi committed Dec 12, 2023
1 parent 2e0332c commit f954d00
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 145 deletions.
7 changes: 6 additions & 1 deletion plugins/notifications-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,23 @@
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"tsc": "tsc"
"tsc": "tsc",
"openapi": "npx openapicmd typegen src/openapi.yaml > src/openapi.d.ts"
},
"dependencies": {
"@backstage/backend-common": "0.19.8",
"@backstage/backend-openapi-utils": "^0.1.0",
"@backstage/catalog-client": "^1.4.5",
"@backstage/config": "^1.1.1",
"@types/express": "*",
"ajv-formats": "^2.1.1",
"express": "^4.18.2",
"express-promise-router": "^4.1.1",
"knex": "2.5.1",
"lodash": "^4.17.21",
"node-fetch": "^3.3.2",
"openapi": "^1.0.1",
"openapi-backend": "^5.10.5",
"winston": "^3.11.0",
"yn": "^4.0.0"
},
Expand Down
217 changes: 217 additions & 0 deletions plugins/notifications-backend/src/openapi.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import type {
AxiosRequestConfig,
OpenAPIClient,
OperationResponse,
Parameters,
UnknownParamsObject,
} from 'openapi-client-axios';

declare namespace Components {
namespace Schemas {
export interface Action {
id: string;
title: string;
url: string;
}
export interface CreateBody {
origin: string;
title: string;
message?: string;
actions?: {
title: string;
url: string;
}[];
topic?: string;
targetUsers?: string[];
targetGroups?: string[];
}
export interface Notification {
id: string;
created: string; // date-time
readByUser: boolean;
isSystem: boolean;
origin: string;
title: string;
message?: string;
topic?: string;
actions: Action[];
}
export type Notifications = Notification[];
}
}
declare namespace Paths {
namespace CreateNotification {
export type RequestBody = Components.Schemas.CreateBody;
namespace Responses {
export interface $200 {
/**
* example:
* bc9f19de-8b7b-49a8-9262-c5036a1ed35e
*/
messageId: string;
}
}
}
namespace GetNotifications {
namespace Parameters {
export type ContainsText = string;
export type CreatedAfter = string; // date-time
export type MessageScope = 'all' | 'user' | 'system';
export type OrderBy =
| 'title'
| 'message'
| 'created'
| 'topic'
| 'origin';
export type OrderByDirec = 'asc' | 'desc';
export type PageNumber = number;
export type PageSize = number;
export type Read = boolean;
export type User = string;
}
export interface QueryParameters {
pageSize?: Parameters.PageSize;
pageNumber?: Parameters.PageNumber;
orderBy?: Parameters.OrderBy;
orderByDirec?: Parameters.OrderByDirec;
containsText?: Parameters.ContainsText;
createdAfter?: Parameters.CreatedAfter /* date-time */;
messageScope?: Parameters.MessageScope;
user?: Parameters.User;
read?: Parameters.Read;
}
namespace Responses {
export type $200 = Components.Schemas.Notifications;
}
}
namespace GetNotificationsCount {
namespace Parameters {
export type ContainsText = string;
export type CreatedAfter = string; // date-time
export type MessageScope = 'all' | 'user' | 'system';
export type Read = boolean;
export type User = string;
}
export interface QueryParameters {
containsText?: Parameters.ContainsText;
createdAfter?: Parameters.CreatedAfter /* date-time */;
messageScope?: Parameters.MessageScope;
user?: Parameters.User;
read?: Parameters.Read;
}
namespace Responses {
export interface $200 {
count: number;
}
}
}
namespace SetRead {
namespace Parameters {
export type MessageId = string;
export type Read = boolean;
export type User = string;
}
export interface QueryParameters {
messageId: Parameters.MessageId;
user: Parameters.User;
read: Parameters.Read;
}
namespace Responses {
export interface $200 {}
}
}
}

export interface OperationMethods {
/**
* getNotifications - Gets notifications
*
* Gets notifications
*/
'getNotifications'(
parameters?: Parameters<Paths.GetNotifications.QueryParameters> | null,
data?: any,
config?: AxiosRequestConfig,
): OperationResponse<Paths.GetNotifications.Responses.$200>;
/**
* createNotification - Create notification
*
* Create notification
*/
'createNotification'(
parameters?: Parameters<UnknownParamsObject> | null,
data?: Paths.CreateNotification.RequestBody,
config?: AxiosRequestConfig,
): OperationResponse<Paths.CreateNotification.Responses.$200>;
/**
* getNotificationsCount - Get notifications count
*
* Gets notifications count
*/
'getNotificationsCount'(
parameters?: Parameters<Paths.GetNotificationsCount.QueryParameters> | null,
data?: any,
config?: AxiosRequestConfig,
): OperationResponse<Paths.GetNotificationsCount.Responses.$200>;
/**
* setRead - Set notification as read/unread
*
* Set notification as read/unread
*/
'setRead'(
parameters?: Parameters<Paths.SetRead.QueryParameters> | null,
data?: any,
config?: AxiosRequestConfig,
): OperationResponse<Paths.SetRead.Responses.$200>;
}

export interface PathsDictionary {
['/notifications']: {
/**
* createNotification - Create notification
*
* Create notification
*/
'post'(
parameters?: Parameters<UnknownParamsObject> | null,
data?: Paths.CreateNotification.RequestBody,
config?: AxiosRequestConfig,
): OperationResponse<Paths.CreateNotification.Responses.$200>;
/**
* getNotifications - Gets notifications
*
* Gets notifications
*/
'get'(
parameters?: Parameters<Paths.GetNotifications.QueryParameters> | null,
data?: any,
config?: AxiosRequestConfig,
): OperationResponse<Paths.GetNotifications.Responses.$200>;
};
['/notifications/count']: {
/**
* getNotificationsCount - Get notifications count
*
* Gets notifications count
*/
'get'(
parameters?: Parameters<Paths.GetNotificationsCount.QueryParameters> | null,
data?: any,
config?: AxiosRequestConfig,
): OperationResponse<Paths.GetNotificationsCount.Responses.$200>;
};
['/notifications/read']: {
/**
* setRead - Set notification as read/unread
*
* Set notification as read/unread
*/
'put'(
parameters?: Parameters<Paths.SetRead.QueryParameters> | null,
data?: any,
config?: AxiosRequestConfig,
): OperationResponse<Paths.SetRead.Responses.$200>;
};
}

export type Client = OpenAPIClient<OperationMethods, PathsDictionary>;
32 changes: 18 additions & 14 deletions plugins/notifications-backend/src/service/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import { CatalogClient } from '@backstage/catalog-client';

import { Knex } from 'knex';

import { Components, Paths } from '../openapi';
import { ActionsInsert, MessagesInsert } from './db';
import {
CreateNotificationRequest,
DefaultMessageScope,
DefaultOrderBy,
DefaultOrderDirection,
DefaultPageNumber,
DefaultPageSize,
DefaultUser,
Notification,
MessageScopes,
NotificationsFilterRequest,
NotificationsOrderByDirections,
NotificationsOrderByFields,
Expand All @@ -24,8 +24,8 @@ import {
export async function createNotification(
dbClient: Knex<any, any>,
catalogClient: CatalogClient,
req: CreateNotificationRequest,
): Promise<{ messageId: string }> {
req: Paths.CreateNotification.RequestBody,
): Promise<Paths.CreateNotification.Responses.$200> {
let isUser = false;

// validate users
Expand Down Expand Up @@ -138,7 +138,7 @@ export async function getNotifications(
pageSize: number = DefaultPageSize,
pageNumber: number = DefaultPageNumber,
sorting: NotificationsSortingRequest,
): Promise<Notification[]> {
): Promise<Paths.GetNotifications.Responses.$200> {
if (
pageSize < 0 ||
pageNumber < 0 ||
Expand All @@ -152,17 +152,21 @@ export async function getNotifications(

if (!filter.messageScope) {
filter.messageScope = DefaultMessageScope;
} else if (!MessageScopes.includes(filter.messageScope)) {
throw new Error(
`messageScope parameter must be one of ${MessageScopes.join()}`,
);
}

if (!filter.user) {
filter.user = DefaultUser;
}

const orderBy = sorting.fieldName || DefaultOrderBy;
const direction = sorting.direction || DefaultOrderDirection;
const orderBy = sorting.orderBy || DefaultOrderBy;
const orderByDirec = sorting.OrderByDirec || DefaultOrderDirection;
if (
!NotificationsOrderByFields.includes(orderBy) ||
!NotificationsOrderByDirections.includes(direction)
!NotificationsOrderByDirections.includes(orderByDirec)
) {
throw new Error(
`The orderBy parameter can be one of ${NotificationsOrderByFields.join(
Expand All @@ -177,15 +181,15 @@ export async function getNotifications(

const query = createQuery(dbClient, filter, userGroups);

query.orderBy(orderBy, direction);
query.orderBy(orderBy, orderByDirec);

if (pageNumber > 0) {
query.limit(pageSize).offset((pageNumber - 1) * pageSize);
}

const notifications: Notification[] = await query.select('*').then(messages =>
const notifications = await query.select('*').then(messages =>
messages.map((message: any) => {
const notification: Notification = {
const notification: Components.Schemas.Notification = {
id: message.id,
created: message.created,
isSystem: message.is_system,
Expand Down Expand Up @@ -225,7 +229,7 @@ export async function getNotificationsCount(
dbClient: Knex<any, any>,
catalogClient: CatalogClient,
filter: NotificationsFilterRequest,
): Promise<{ count: number }> {
): Promise<Paths.GetNotificationsCount.Responses.$200> {
if (!filter.messageScope) {
filter.messageScope = DefaultMessageScope;
}
Expand Down Expand Up @@ -364,10 +368,10 @@ function createQuery(

// filter by read/unread
switch (filter.read) {
case 'true':
case true:
query.andWhere('read', true);
break;
case 'false':
case false:
query.andWhere(function () {
this.where('read', false).orWhereNull('read');
});
Expand Down
Loading

0 comments on commit f954d00

Please sign in to comment.