Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create router in BE based on openapi spec #45

Merged
merged 1 commit into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 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": "openapi 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 All @@ -42,6 +47,7 @@
"@types/supertest": "2.0.16",
"knex-mock-client": "2.0.0",
"msw": "1.3.2",
"openapicmd": "^2.1.0",
"supertest": "6.3.3"
},
"files": [
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 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to double-check: is this file generated without any further manual change?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course

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