From 40ac77c4f32eba5d075d524e7eef209b6f313c34 Mon Sep 17 00:00:00 2001
From: Jose Semedo <jose.semedo@cern.ch>
Date: Thu, 17 Mar 2022 17:40:35 +0100
Subject: [PATCH] [#85] Swagger notifications

---
 src/app.ts                                    |   6 +-
 src/controllers/devices/controller.ts         | 232 ++++++-------
 src/controllers/devices/dto.ts                | 316 ++++++++++++++----
 src/controllers/notifications-controller.ts   | 105 ------
 src/controllers/notifications/controller.ts   | 106 ++++++
 src/controllers/notifications/dto.ts          | 278 +++++++++++++++
 src/models/channel.ts                         |   2 +-
 src/models/notification-enums.ts              |  22 ++
 src/models/notification.ts                    |  35 +-
 src/models/preference.ts                      |  42 +--
 src/services/api-key-service.ts               |   2 +-
 src/services/devices-service.ts               |  14 +-
 src/services/impl/devices-service-impl.ts     |  67 ++--
 src/services/impl/devices/get-user-devices.ts |  41 ++-
 src/services/impl/devices/test-user-device.ts |   2 +-
 .../impl/devices/update-user-device.ts        |  54 +--
 .../impl/notifications-service-impl.ts        |  56 ++--
 src/services/impl/notifications/get-by-id.ts  |   7 +-
 .../impl/notifications/send-notification.ts   |  48 +--
 .../notifications/update-user-notification.ts |  68 ++--
 src/services/notifications-service.ts         |  21 +-
 src/utils/status-codes.ts                     | 116 +++++++
 22 files changed, 1085 insertions(+), 555 deletions(-)
 delete mode 100644 src/controllers/notifications-controller.ts
 create mode 100644 src/controllers/notifications/controller.ts
 create mode 100644 src/controllers/notifications/dto.ts
 create mode 100644 src/models/notification-enums.ts
 create mode 100644 src/utils/status-codes.ts

diff --git a/src/app.ts b/src/app.ts
index 2874037d..85857e02 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -9,7 +9,8 @@ import { AuthorizationChecker } from './middleware/authorizationChecker';
 import { Configuration } from './config/configuration';
 import * as sentry from './log/sentry';
 import * as swaggerUiExpress from 'swagger-ui-express';
-import { Controller } from './controllers/devices/controller';
+import { DevicesController } from './controllers/devices/controller';
+import { NotificationsController } from './controllers/notifications/controller';
 import * as fs from 'fs';
 import * as https from 'https';
 
@@ -18,7 +19,7 @@ Configuration.load();
 const { defaultMetadataStorage } = require('class-transformer/cjs/storage');
 
 const routingControllersOptions = {
-  controllers: [Controller],
+  controllers: [DevicesController, NotificationsController],
 };
 
 // Check ROLES are defined in ENV for controller Authorizations
@@ -96,6 +97,7 @@ const swaggerOptions = {
       // Use the app client-id instead, tha tmus tbe configure implicit but not public (see above)
       clientId: process.env.OAUTH_CLIENT_ID,
     },
+    filter: true,
   },
 };
 
diff --git a/src/controllers/devices/controller.ts b/src/controllers/devices/controller.ts
index cd609bee..e8c9dda0 100644
--- a/src/controllers/devices/controller.ts
+++ b/src/controllers/devices/controller.ts
@@ -1,124 +1,126 @@
 import {
-    Authorized,
-    BodyParam,
-    Delete,
-    Get,
-    JsonController,
-    OnUndefined,
-    Param, Patch,
-    Post,
-    Put,
-    Req,
-} from "routing-controllers";
-import {ServiceFactory} from "../../services/services-factory";
+  Authorized,
+  Body,
+  Delete,
+  Get,
+  JsonController,
+  OnUndefined,
+  Param,
+  Patch,
+  Post,
+  Req,
+} from 'routing-controllers';
+import { ServiceFactory } from '../../services/services-factory';
 
-import {DevicesServiceInterface} from "../../services/devices-service";
-import {OpenAPI, ResponseSchema} from "routing-controllers-openapi";
-import {DeviceRequest, DeviceResponse, GetDevicesResponse} from "./dto";
+import { DevicesServiceInterface } from '../../services/devices-service';
+import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
+import { DeviceRequest, DeviceResponse, DeviceValuesRequest, GetDevicesResponse } from './dto';
+import { StatusCodes, StatusCodeDescriptions } from '../../utils/status-codes';
 
-@JsonController("/devices")
-export class Controller {
-    devicesService: DevicesServiceInterface = ServiceFactory.getDevicesService();
+@JsonController('/devices')
+export class DevicesController {
+  devicesService: DevicesServiceInterface = ServiceFactory.getDevicesService();
 
-    @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
-    @OpenAPI({
-        description: "Creates a new device.",
-        operationId: "createDevice",
-        //security: [{oauth2: []}], Not usable through swagger UI
-        responses: {
-            '200': {description: "OK"},
-            '401': {description: "Unauthorized"},
-            '403': {description: "Forbidden"}
-        },
-    })
-    @ResponseSchema(DeviceResponse, {description: 'Created device'})
-    @Post()
-    createUserDevice(
-        @Req() req,
-        @BodyParam("device") device: DeviceRequest
-    ) {
-        return this.devicesService.createUserDevice(device, req.authorizationBag);
-    }
+  @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
+  @OpenAPI({
+    summary: 'Creates a new user device as specified in the body param. For internal use.',
+    description: 'Creates a new user device.',
+    operationId: 'createDevice',
+    //security: [{oauth2: []}], Not usable through swagger UI
+    responses: {
+      [StatusCodes.OK]: StatusCodeDescriptions[StatusCodes.OK],
+      [StatusCodes.Unauthorized]: StatusCodeDescriptions[StatusCodes.Unauthorized],
+      [StatusCodes.Forbidden]: StatusCodeDescriptions[StatusCodes.Forbidden],
+    },
+  })
+  @ResponseSchema(DeviceResponse, { description: 'Created device' })
+  @Post()
+  createUserDevice(@Req() req, @Body() device: DeviceRequest): Promise<DeviceResponse> {
+    return this.devicesService.createUserDevice(device, req.authorizationBag);
+  }
 
-    @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
-    @OpenAPI({
-        description: "Lists all of the user's devices.",
-        operationId: "listDevices",
-        security: [{oauth2: []}],
-        responses: {
-            '200': {description: "OK"},
-            '401': {description: "Unauthorized"},
-            '403': {description: "Forbidden"}
-        },
-    })
-    @ResponseSchema(GetDevicesResponse, {
-        description: 'A list of user devices'
-    })
-    @Get()
-    getUserDevices(@Req() req) {
-        return this.devicesService.getUserDevices(req.authorizationBag);
-    }
+  @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
+  @OpenAPI({
+    summary: "List user's devices",
+    description: "Lists all of the user's devices.",
+    operationId: 'listDevices',
+    security: [{ oauth2: [] }],
+    responses: {
+      [StatusCodes.OK]: StatusCodeDescriptions[StatusCodes.OK],
+      [StatusCodes.NotFound]: StatusCodeDescriptions[StatusCodes.NotFound],
+      [StatusCodes.Unauthorized]: StatusCodeDescriptions[StatusCodes.Unauthorized],
+      [StatusCodes.Forbidden]: StatusCodeDescriptions[StatusCodes.Forbidden],
+    },
+  })
+  @ResponseSchema(GetDevicesResponse, {
+    description: 'A list of user devices',
+  })
+  @Get()
+  getUserDevices(@Req() req): Promise<GetDevicesResponse> {
+    return this.devicesService.getUserDevices(req.authorizationBag);
+  }
 
-    @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
-    @OpenAPI({
-        description: "Delete user's device with with provided ID.",
-        operationId: "deleteDevice",
-        security: [{oauth2: []}],
-        responses: {
-            '204': {description: "No Content"},
-            '400': {description: "Bad Request"},
-            '401': {description: "Unauthorized"},
-            '403': {description: "Forbidden"}
-        },
-    })
-    @Delete("/:deviceId")
-    @OnUndefined(204)
-    deleteUserDeviceById(
-        @Req() req,
-        @Param("deviceId") deviceId: string
-    ) {
-        return this.devicesService.deleteUserDeviceById(deviceId, req.authorizationBag);
-    }
+  @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
+  @OpenAPI({
+    description: "Delete user's device with with provided device id.",
+    operationId: 'deleteDevice',
+    security: [{ oauth2: [] }],
+    responses: {
+      [StatusCodes.NoContent]: StatusCodeDescriptions[StatusCodes.NoContent],
+      [StatusCodes.NotFound]: StatusCodeDescriptions[StatusCodes.NotFound],
+      [StatusCodes.BadRequest]: StatusCodeDescriptions[StatusCodes.BadRequest],
+      [StatusCodes.Unauthorized]: StatusCodeDescriptions[StatusCodes.Unauthorized],
+      [StatusCodes.Forbidden]: StatusCodeDescriptions[StatusCodes.Forbidden],
+    },
+  })
+  @Delete('/:deviceId')
+  @OnUndefined(204)
+  deleteUserDeviceById(@Req() req, @Param('deviceId') deviceId: string): Promise<string> {
+    return this.devicesService.deleteUserDeviceById(deviceId, req.authorizationBag);
+  }
 
-    @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
-    @OpenAPI({
-        description: "Triggers a test notification on the device with the provided ID.",
-        operationId: "testDevice",
-        security: [{oauth2: []}],
-        responses: {
-            '204': {description: "No Content"},
-            '400': {description: "Bad Request"},
-            '401': {description: "Unauthorized"},
-            '403': {description: "Forbidden"}
-        },
-    })
-    @Post("/:deviceId")
-    @OnUndefined(204)
-    tryBrowserPushNotification(
-        @Req() req,
-        @Param("deviceId") deviceId: string
-    ) {
-        return this.devicesService.tryBrowserPushNotification(deviceId, req.authorizationBag);
-    }
+  @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
+  @OpenAPI({
+    summary: 'Send test notification',
+    description: "Sends a test notification to the user's device with the provided device id.",
+    operationId: 'testDevice',
+    security: [{ oauth2: [] }],
+    responses: {
+      [StatusCodes.NoContent]: StatusCodeDescriptions[StatusCodes.NoContent],
+      [StatusCodes.NotFound]: StatusCodeDescriptions[StatusCodes.NotFound],
+      [StatusCodes.BadRequest]: StatusCodeDescriptions[StatusCodes.BadRequest],
+      [StatusCodes.Unauthorized]: StatusCodeDescriptions[StatusCodes.Unauthorized],
+      [StatusCodes.Forbidden]: StatusCodeDescriptions[StatusCodes.Forbidden],
+    },
+  })
+  @Post('/:deviceId')
+  @OnUndefined(204)
+  tryBrowserPushNotification(@Req() req, @Param('deviceId') deviceId: string): Promise<void> {
+    return this.devicesService.tryBrowserPushNotification(deviceId, req.authorizationBag);
+  }
 
-    @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
-    @OpenAPI({
-        description: "Updates the device with the provided ID, using the provided name and info.",
-        operationId: "updateDevice",
-        security: [{oauth2: []}],
-        responses: {
-            '200': {description: "OK"},
-            '401': {description: "Unauthorized"},
-            '403': {description: "Forbidden"}
-        },
-    })
-    @Patch("/:deviceId")
-    updateUserDeviceById(
-        @Req() req,
-        @Param("deviceId") deviceId: string,
-        @BodyParam("name") name: string,
-        @BodyParam("info") info: string
-    ) {
-        return this.devicesService.updateUserDeviceById(deviceId, req.authorizationBag, name, info);
-    }
+  @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
+  @OpenAPI({
+    summary: 'Update device.',
+    description: 'Updates the device with the provided device ID, using the provided name and info.',
+    operationId: 'updateDevice',
+    security: [{ oauth2: [] }],
+    responses: {
+      [StatusCodes.OK]: StatusCodeDescriptions[StatusCodes.OK],
+      [StatusCodes.NotFound]: StatusCodeDescriptions[StatusCodes.NotFound],
+      [StatusCodes.Unauthorized]: StatusCodeDescriptions[StatusCodes.Unauthorized],
+      [StatusCodes.Forbidden]: StatusCodeDescriptions[StatusCodes.Forbidden],
+    },
+  })
+  @ResponseSchema(DeviceResponse, {
+    description: 'The updated device.',
+  })
+  @Patch('/:deviceId')
+  updateUserDeviceById(
+    @Req() req,
+    @Param('deviceId') deviceId: string,
+    @Body() newDeviceValues: DeviceValuesRequest,
+  ): Promise<DeviceResponse> {
+    return this.devicesService.updateUserDeviceById(deviceId, req.authorizationBag, newDeviceValues);
+  }
 }
diff --git a/src/controllers/devices/dto.ts b/src/controllers/devices/dto.ts
index c9bc0a74..9eb6f77c 100644
--- a/src/controllers/devices/dto.ts
+++ b/src/controllers/devices/dto.ts
@@ -1,77 +1,263 @@
-import {IsEnum, IsNotEmpty, IsOptional, IsString, IsUUID, ValidateNested} from "class-validator";
-import {Device, DeviceSubType, DeviceType} from "../../models/device";
-import {Type} from "class-transformer";
+import { IsEnum, IsNotEmpty, IsOptional, IsString, IsUUID, ValidateNested } from 'class-validator';
+import { Device, DeviceSubType, DeviceType } from '../../models/device';
+import { JSONSchema } from 'class-validator-jsonschema';
+import { Type } from 'class-transformer';
+import { v4 } from 'uuid';
 
-export class GetDevicesResponse {
-    @ValidateNested({each: true})
-    @Type(() => DeviceResponse)
-    userDevices: DeviceResponse[];
+@JSONSchema({
+  description: 'Device json return.',
+  example: {
+    name: 'Mac Firefox',
+    id: v4(),
+    info: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11) Firefox/97.0',
+    type: DeviceType.BROWSER,
+    subType: DeviceSubType.OTHER,
+    uuid: v4(),
+    token:
+      '{"endpoint":"https://updates.push.services.mozilla.com/wpush/v2/asfhijbsaSDArandomuuidasyrigbdsjyc","expirationTime":null,"keys":{"auth":"ahahyouwish","p256dh":"nopenopenope"}}',
+  },
+})
+export class DeviceResponse {
+  @IsNotEmpty()
+  @IsString()
+  @JSONSchema({
+    description: 'Name of the returned device.',
+    example: 'Mac Firefox',
+  })
+  name: string;
+
+  @IsUUID()
+  @IsNotEmpty()
+  @JSONSchema({
+    description: 'Id of the returned device.',
+    example: v4(),
+  })
+  id: string;
+
+  @IsNotEmpty()
+  @IsString()
+  @JSONSchema({
+    description: 'Info about the returned device.',
+    example: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11) Firefox/97.0',
+  })
+  info: string;
+
+  @IsNotEmpty()
+  @IsEnum(DeviceType)
+  @JSONSchema({
+    description: 'Type of the returned device.',
+    example: DeviceType.BROWSER,
+  })
+  type: DeviceType;
+
+  @IsOptional()
+  @IsEnum(DeviceSubType)
+  @JSONSchema({
+    description: 'Subtype of the returned device.',
+    example: DeviceSubType.OTHER,
+  })
+  subType: DeviceSubType;
+
+  @IsUUID()
+  @IsOptional()
+  @JSONSchema({
+    description: 'Apple device identifier needed for browser registration management in Apple cloud.',
+    example: v4(),
+  })
+  uuid: string;
 
-    constructor(list: DeviceResponse[]) {
-        this.userDevices = list;
-    }
+  @IsNotEmpty()
+  @IsString()
+  @JSONSchema({
+    description: 'Target identifier to use when sending a notification.',
+    example:
+      '{"endpoint":"https://updates.push.services.mozilla.com/wpush/v2/asfhijbsaSDArandomuuidasyrigbdsjyc","expirationTime":null,"keys":{"auth":"ahahyouwish","p256dh":"nopenopenope"}}',
+  })
+  token: string;
+
+  constructor(device: Device) {
+    this.id = device.id;
+    this.name = device.name;
+    this.info = device.info;
+    this.type = device.type;
+    this.subType = device.subType;
+    this.uuid = device.uuid;
+    this.token = device.token;
+  }
 }
+@JSONSchema({
+  description: 'Json response with list of devices.',
+  example: {
+    userDevices: [
+      new DeviceResponse(
+        new Device({
+          name: 'Mattermost',
+          id: v4(),
+          info: 'useremail@cern.ch',
+          type: DeviceType.APP,
+          subType: DeviceSubType.MATTERMOST,
+          uuid: null,
+          token: 'useremail@cern.ch',
+        }),
+      ),
+      new DeviceResponse(
+        new Device({
+          name: 'useremail@cern.ch',
+          id: v4(),
+          info: 'useremail@cern.ch',
+          type: DeviceType.MAIL,
+          subType: null,
+          uuid: null,
+          token: 'useremail@cern.ch',
+        }),
+      ),
+      new DeviceResponse(
+        new Device({
+          name: 'Max Firefox',
+          id: v4(),
+          info: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11) Firefox/97.0.',
+          type: DeviceType.BROWSER,
+          subType: DeviceSubType.OTHER,
+          uuid: v4(),
+          token:
+            '{"endpoint":"https://updates.push.services.mozilla.com/wpush/v2/asfhijbsaSDArandomuuidasyrigbdsjyc","expirationTime":null,"keys":{"auth":"ahahyouwish","p256dh":"nopenopenope"}}',
+        }),
+      ),
+    ],
+  },
+})
+export class GetDevicesResponse {
+  @ValidateNested({ each: true })
+  @Type(() => DeviceResponse)
+  @JSONSchema({
+    description: 'Device list ',
+    example: [
+      new DeviceResponse(
+        new Device({
+          name: 'Mattermost',
+          id: v4(),
+          info: 'useremail@cern.ch',
+          type: DeviceType.APP,
+          subType: DeviceSubType.MATTERMOST,
+          uuid: null,
+          token: 'useremail@cern.ch',
+        }),
+      ),
+      new DeviceResponse(
+        new Device({
+          name: 'useremail@cern.ch',
+          id: v4(),
+          info: 'useremail@cern.ch',
+          type: DeviceType.MAIL,
+          subType: null,
+          uuid: null,
+          token: 'useremail@cern.ch',
+        }),
+      ),
+      new DeviceResponse(
+        new Device({
+          name: 'Max Firefox',
+          id: v4(),
+          info: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11) Firefox/97.0.',
+          type: DeviceType.BROWSER,
+          subType: DeviceSubType.OTHER,
+          uuid: v4(),
+          token:
+            '{"endpoint":"https://updates.push.services.mozilla.com/wpush/v2/asfhijbsaSDArandomuuidasyrigbdsjyc","expirationTime":null,"keys":{"auth":"ahahyouwish","p256dh":"nopenopenope"}}',
+        }),
+      ),
+    ],
+  })
+  userDevices: DeviceResponse[];
 
+  constructor(list: DeviceResponse[]) {
+    this.userDevices = list;
+  }
+}
+@JSONSchema({
+  description: 'New Device json input.',
+  example: {
+    name: 'Mac Firefox',
+    info: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11) Firefox/97.0.',
+    type: DeviceType.BROWSER,
+    subType: DeviceSubType.OTHER,
+    uuid: v4(),
+    token:
+      '{"endpoint":"https://updates.push.services.mozilla.com/wpush/v2/asfhijbsaSDArandomuuidasyrigbdsjyc","expirationTime":null,"keys":{"auth":"ahahyouwish","p256dh":"nopenopenope"}}',
+  },
+})
 export class DeviceRequest {
-    @IsNotEmpty()
-    name: string;
+  @IsNotEmpty()
+  @IsString()
+  @JSONSchema({
+    description: 'Name of the device to be created.',
+    example: 'Mac Firefox',
+  })
+  name: string;
 
-    @IsNotEmpty()
-    @IsString()
-    info: string;
+  @IsNotEmpty()
+  @IsString()
+  @JSONSchema({
+    description: 'Info about the returned device.',
+    example: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11) Firefox/97.0',
+  })
+  info: string;
 
-    @IsNotEmpty()
-    @IsEnum(DeviceType)
-    type: DeviceType;
+  @IsNotEmpty()
+  @IsEnum(DeviceType)
+  @JSONSchema({
+    description: 'Type of the returned device.',
+    example: DeviceType.BROWSER,
+  })
+  type: DeviceType;
 
-    @IsOptional()
-    @IsEnum(DeviceSubType)
-    subType: DeviceSubType;
+  @IsOptional()
+  @IsEnum(DeviceSubType)
+  @JSONSchema({
+    description: 'Subtype of the returned device.',
+    example: DeviceSubType.OTHER,
+  })
+  subType: DeviceSubType;
 
-    @IsUUID()
-    @IsOptional()
-    uuid: string;
+  @IsUUID()
+  @IsOptional()
+  @JSONSchema({
+    description: 'Apple device identifier needed for browser registration management in Apple cloud.',
+    example: v4(),
+  })
+  uuid: string;
 
-    @IsNotEmpty()
-    @IsString()
-    token: string;
+  @IsNotEmpty()
+  @IsString()
+  @JSONSchema({
+    description: 'Target identifier to use when sending a notification.',
+    example:
+      '{"endpoint":"https://updates.push.services.mozilla.com/wpush/v2/asfhijbsaSDArandomuuidasyrigbdsjyc","expirationTime":null,"keys":{"auth":"ahahyouwish","p256dh":"nopenopenope"}}',
+  })
+  token: string;
 }
 
-// noinspection DuplicatedCode
-export class DeviceResponse {
-    @IsNotEmpty()
-    name: string;
-
-    @IsUUID()
-    id: string;
-
-    @IsNotEmpty()
-    @IsString()
-    info: string;
-
-    @IsNotEmpty()
-    @IsEnum(DeviceType)
-    type: DeviceType;
-
-    @IsOptional()
-    @IsEnum(DeviceSubType)
-    subType: DeviceSubType;
-
-    @IsUUID()
-    @IsOptional()
-    uuid: string;
-
-    @IsNotEmpty()
-    @IsString()
-    token: string;
-
-    constructor(device: Device) {
-        this.id = device.id;
-        this.name = device.name;
-        this.info = device.info;
-        this.type = device.type;
-        this.subType = device.subType;
-        this.uuid = device.uuid;
-        this.token = device.token;
-    }
-}
\ No newline at end of file
+@JSONSchema({
+  description: 'New Device json input.',
+  example: {
+    name: 'Mac Firefox',
+    info: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11) Firefox/97.0.',
+  },
+})
+export class DeviceValuesRequest {
+  @IsNotEmpty()
+  @IsString()
+  @JSONSchema({
+    description: 'Name the device is to be changed to.',
+    example: 'Mac Firefox',
+  })
+  name: string;
+
+  @IsNotEmpty()
+  @IsString()
+  @JSONSchema({
+    description: 'Info the device is to be changed to.',
+    example: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11) Firefox/97.0',
+  })
+  info: string;
+}
diff --git a/src/controllers/notifications-controller.ts b/src/controllers/notifications-controller.ts
deleted file mode 100644
index 7aab057c..00000000
--- a/src/controllers/notifications-controller.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import {
-  Get,
-  Post,
-  JsonController,
-  BodyParam,
-  Authorized,
-  Req,
-  Body,
-  Param,
-  QueryParams,
-  BadRequestError,
-  Put,
-  OnUndefined,
-  UnauthorizedError,
-} from 'routing-controllers';
-import { ServiceFactory } from '../services/services-factory';
-import { NotificationsService } from '../services/notifications-service';
-import { Notification } from '../models/notification';
-import { API_KEY_ACCESS_ROLE } from '../middleware/authorizationChecker';
-
-@JsonController('/notifications')
-export class NotificationsController {
-  notificationsService: NotificationsService =
-    ServiceFactory.getNotificationsService();
-
-  @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
-  @Get('/:id')
-  getById(
-    @Req() req,
-    @Param('id') notificationId: string,
-    @QueryParams() query,
-  ) {
-    return this.notificationsService.getById(
-      notificationId,
-      query,
-      req.authorizationBag,
-    );
-  }
-
-  @Authorized([
-    process.env.INTERNAL_ROLE,
-    process.env.VIEWER_ROLE,
-    API_KEY_ACCESS_ROLE,
-  ])
-  @Post()
-  sendNotification(
-    @Body() body,
-    @BodyParam('notification') notification: Notification,
-    @Req() req,
-  ) {
-    if (!notification)
-      throw new BadRequestError(
-        `The request body does not include a "notification" object. Body: ${JSON.stringify(
-          body,
-        )}`,
-      );
-
-    return this.notificationsService.sendNotification(
-      notification,
-      req.authorizationBag,
-    );
-  }
-
-  @OnUndefined(201)
-  @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
-  @Put('/:id/retry')
-  retryNotification(
-    @Param('id') notificationId: string,
-    @Req() req,
-  ): Promise<void> {
-    if (
-      !process.env.EXPOSE_UNAUTHENTICATED_ROUTES ||
-      process.env.EXPOSE_UNAUTHENTICATED_ROUTES === 'False'
-    ) {
-      throw new UnauthorizedError('Unauthenticated route is not enabled');
-    }
-
-    return this.notificationsService.retryNotification(notificationId, null);
-  }
-
-  // TODO: Fix calls from mail_gateway consumer by adding a bearer token or restricting on
-  @Post('/unauthenticated')
-  sendNotificationWithoutAuth(
-    @Body() body,
-    @BodyParam('notification') notification: Notification,
-    @Req() req,
-  ) {
-    if (
-      !process.env.EXPOSE_UNAUTHENTICATED_ROUTES ||
-      process.env.EXPOSE_UNAUTHENTICATED_ROUTES === 'False'
-    ) {
-      throw new UnauthorizedError('Unauthenticated route is not enabled');
-    }
-
-    if (!notification)
-      throw new BadRequestError(
-        `The request body does not include a "notification" object. Body: ${JSON.stringify(
-          body,
-        )}`,
-      );
-    // notification.from = req.service;
-
-    return this.notificationsService.sendNotification(notification, null);
-  }
-}
diff --git a/src/controllers/notifications/controller.ts b/src/controllers/notifications/controller.ts
new file mode 100644
index 00000000..8e03ef07
--- /dev/null
+++ b/src/controllers/notifications/controller.ts
@@ -0,0 +1,106 @@
+import {
+  Get,
+  Post,
+  JsonController,
+  Authorized,
+  Req,
+  Param,
+  Put,
+  OnUndefined,
+  ForbiddenError,
+  Body,
+} from 'routing-controllers';
+import { ServiceFactory } from '../../services/services-factory';
+import { NotificationsService } from '../../services/notifications-service';
+import { GetNotificationResponse, SendNotificationRequest } from './dto';
+import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
+import { API_KEY_ACCESS_ROLE } from '../../middleware/authorizationChecker';
+import { StatusCodeDescriptions, StatusCodes } from '../../utils/status-codes';
+
+@JsonController('/notifications')
+export class NotificationsController {
+  notificationsService: NotificationsService = ServiceFactory.getNotificationsService();
+
+  @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
+  @OpenAPI({
+    summary: 'Returns the Notification with the provided notification id.',
+    description: 'This endpoint returns the notification with the provided notification id, if requester has access.',
+    operationId: 'getNotification',
+    security: [{ oauth2: [] }],
+    responses: {
+      [StatusCodes.OK]: StatusCodeDescriptions[StatusCodes.OK],
+      [StatusCodes.NotFound]: StatusCodeDescriptions[StatusCodes.NotFound],
+      [StatusCodes.Forbidden]: StatusCodeDescriptions[StatusCodes.Forbidden],
+      [StatusCodes.Unauthorized]: StatusCodeDescriptions[StatusCodes.Unauthorized],
+    },
+  })
+  @ResponseSchema(GetNotificationResponse, { description: 'Notification requested.' })
+  @Get('/:id')
+  getById(@Req() req, @Param('id') notificationId: string): Promise<GetNotificationResponse> {
+    return this.notificationsService.getById(notificationId, req.authorizationBag);
+  }
+
+  @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE, API_KEY_ACCESS_ROLE])
+  @OpenAPI({
+    summary: 'Send a Notification.',
+    description: 'Sends the Notification to the Channel with the channel id provided.',
+    operationId: 'sendNotification',
+    security: [{ oauth2: [] }],
+    responses: {
+      [StatusCodes.OK]: StatusCodeDescriptions[StatusCodes.OK],
+      [StatusCodes.BadRequest]: StatusCodeDescriptions[StatusCodes.BadRequest],
+      [StatusCodes.NotFound]: StatusCodeDescriptions[StatusCodes.NotFound],
+      [StatusCodes.Forbidden]: StatusCodeDescriptions[StatusCodes.Forbidden],
+      [StatusCodes.Unauthorized]: StatusCodeDescriptions[StatusCodes.Unauthorized],
+    },
+  })
+  @ResponseSchema(GetNotificationResponse, { description: 'Notification sent.' })
+  @Post()
+  sendNotification(@Body() notification: SendNotificationRequest, @Req() req): Promise<GetNotificationResponse> {
+    return this.notificationsService.sendNotification(notification, req.authorizationBag);
+  }
+
+  @OnUndefined(201)
+  @Authorized([process.env.INTERNAL_ROLE, process.env.VIEWER_ROLE])
+  @OpenAPI({
+    summary: 'Retries to send Notification with provided notification id. For internal use.',
+    description: 'Retries to send Notification with provided notification id.',
+    operationId: 'retryNotification',
+    //security: [{ oauth2: [] }],
+    responses: {
+      [StatusCodes.OK]: StatusCodeDescriptions[StatusCodes.OK],
+      [StatusCodes.NotFound]: StatusCodeDescriptions[StatusCodes.NotFound],
+      [StatusCodes.Forbidden]: StatusCodeDescriptions[StatusCodes.Forbidden],
+      [StatusCodes.Unauthorized]: StatusCodeDescriptions[StatusCodes.Unauthorized],
+    },
+  })
+  @Put('/:id/retry')
+  retryNotification(@Param('id') notificationId: string, @Req() req): Promise<void> {
+    if (!process.env.EXPOSE_UNAUTHENTICATED_ROUTES || process.env.EXPOSE_UNAUTHENTICATED_ROUTES === 'False') {
+      throw new ForbiddenError('Unauthenticated route is not enabled');
+    }
+    return this.notificationsService.retryNotification(notificationId, null);
+  }
+
+  // TODO: Fix calls from mail_gateway consumer by adding a bearer token or restricting on
+  @OpenAPI({
+    summary: 'Sends Notification without the need for Auth. Internal use only.',
+    description: 'Sends a Notification without the need of Auth.',
+    operationId: 'sentNotificationUnauthenticated',
+    //security: [{ oauth2: [] }],
+    responses: {
+      [StatusCodes.OK]: StatusCodeDescriptions[StatusCodes.OK],
+      [StatusCodes.BadRequest]: StatusCodeDescriptions[StatusCodes.BadRequest],
+      [StatusCodes.Forbidden]: StatusCodeDescriptions[StatusCodes.Forbidden],
+      [StatusCodes.Unauthorized]: StatusCodeDescriptions[StatusCodes.Unauthorized],
+    },
+  })
+  @ResponseSchema(GetNotificationResponse, { description: 'Notification sent.' })
+  @Post('/unauthenticated')
+  sendNotificationWithoutAuth(@Body() notification: SendNotificationRequest): Promise<GetNotificationResponse> {
+    if (!process.env.EXPOSE_UNAUTHENTICATED_ROUTES || process.env.EXPOSE_UNAUTHENTICATED_ROUTES === 'False') {
+      throw new ForbiddenError('Unauthenticated route is not enabled');
+    }
+    return this.notificationsService.sendNotification(notification, null);
+  }
+}
diff --git a/src/controllers/notifications/dto.ts b/src/controllers/notifications/dto.ts
new file mode 100644
index 00000000..d5fd540f
--- /dev/null
+++ b/src/controllers/notifications/dto.ts
@@ -0,0 +1,278 @@
+import {
+  IsBoolean,
+  IsDate,
+  IsDateString,
+  IsEmail,
+  IsEnum,
+  IsNotEmpty,
+  IsOptional,
+  IsString,
+  IsUUID,
+  MaxDate,
+  MinDate,
+  ValidateNested,
+} from 'class-validator';
+import { Type } from 'class-transformer';
+import { Notification } from '../../models/notification';
+import { PriorityLevel, Source } from '../../models/notification-enums';
+import { JSONSchema } from 'class-validator-jsonschema';
+import { v4 } from 'uuid';
+
+@JSONSchema({
+  description: 'Notification json return',
+  example: {
+    id: v4(),
+    body: '<p>My body</p>',
+    description: 'Notification description.',
+    summary: 'My notification',
+    target: v4(),
+    sendAt: new Date(),
+    sentAt: new Date(),
+    priority: PriorityLevel.NORMAL,
+    link: 'https://notifications.web.cern.ch/',
+    imgUrl: 'https://home.cern/sites/default/files/logo/cern-logo.png',
+    source: Source.api,
+  },
+})
+export class GetNotificationResponse {
+  @IsString()
+  @IsNotEmpty()
+  @IsUUID('4')
+  @JSONSchema({
+    description: 'Notification id or the returned notification.',
+    example: v4(),
+  })
+  id: string;
+
+  @IsString()
+  @IsNotEmpty()
+  @JSONSchema({
+    description: 'Body of the returned notification.',
+    example: '<p>My body</p>',
+  })
+  body: string;
+
+  @IsDateString()
+  @MinDate(new Date())
+  @JSONSchema({
+    description: 'Date the returned notification is to be sent at.',
+    example: new Date(),
+  })
+  sendAt: Date;
+
+  @IsDateString()
+  @MaxDate(new Date())
+  @JSONSchema({
+    description: 'Date the returned notification was sent.',
+    example: new Date(),
+  })
+  sentAt: Date;
+
+  @IsString()
+  @JSONSchema({
+    description: 'Specify a target url to redirect when clicked.',
+    example: 'https://notifications.web.cern.ch/',
+  })
+  link: string;
+
+  @IsNotEmpty()
+  @IsString()
+  @JSONSchema({
+    description: 'Summary of the returned notification.',
+    example: 'My notification',
+  })
+  summary: string;
+
+  @IsString()
+  @JSONSchema({
+    description: 'Specify an image url to display as preview when possible, eg. push notifications',
+    example: 'https://home.cern/sites/default/files/logo/cern-logo.png',
+  })
+  imgUrl: string;
+
+  @IsEnum(Source)
+  @JSONSchema({
+    description: 'Internal use only. Will be ignored.',
+    example: Source.api,
+    default: Source.api,
+  })
+  source: Source;
+
+  constructor(notification: Notification) {
+    this.id = notification.id;
+    this.body = notification.body;
+    this.sendAt = notification.sendAt;
+    this.sentAt = notification.sentAt;
+    this.link = notification.link;
+    this.summary = notification.summary;
+    this.imgUrl = notification.imgUrl;
+    this.source = notification.source;
+  }
+}
+
+@JSONSchema({
+  description: 'New notification json input',
+  example: {
+    body: '<p>My body</p>',
+    summary: 'My notification',
+    target: v4(),
+    priority: PriorityLevel.NORMAL,
+    link: 'https://notifications.web.cern.ch/',
+    imgUrl: 'https://home.cern/sites/default/files/logo/cern-logo.png',
+    sendAt: new Date(),
+    targetUsers: [{ email: 'username' }],
+    targetGroups: [{ groupIdentifier: 'user-group' }],
+    intersection: true,
+    source: Source.api,
+  },
+})
+export class SendNotificationRequest {
+  @IsString()
+  @IsNotEmpty()
+  @JSONSchema({
+    description: 'Body of the notification to be sent. Supports raw text and HTML.',
+    example: '<p>My body</p>',
+  })
+  body: string;
+
+  @IsNotEmpty()
+  @IsUUID('4')
+  @JSONSchema({
+    description: 'Channel ID of the channel to where the notification is to be sent.',
+    example: v4(),
+  })
+  target: string;
+
+  @JSONSchema({
+    description:
+      'Supports mixed strings emails and group names (but degraded performance therefore not the ' +
+      'recommended option). Requires private set to "true".',
+    example: ['user@cern.ch', 'my-group'],
+  })
+  @IsString({ each: true })
+  @IsOptional()
+  targetData: string[];
+
+  @IsString()
+  @IsOptional()
+  @JSONSchema({ description: 'Internal use only. Will be ignored.' })
+  sender: string;
+
+  @IsNotEmpty()
+  @IsString()
+  @JSONSchema({
+    description: 'Summary of the notification to be sent.',
+    example: 'My notification',
+  })
+  summary: string;
+
+  @IsOptional()
+  @ValidateNested({ each: true })
+  @Type(() => SendNotificationRequestGroup)
+  @JSONSchema({
+    description: 'Groups of targeted users. Requires private set to "true".',
+    example: [{ groupIdentifier: 'user-group' }, { groupIdentifier: 'another-group' }],
+  })
+  targetGroups: SendNotificationRequestGroup[];
+
+  @IsOptional()
+  @ValidateNested({ each: true })
+  @Type(() => SendNotificationRequestUser)
+  @JSONSchema({
+    description: 'Emails or logins of targeted users. Requires private set to "true".',
+    example: [{ email: 'username' }, { email: 'user@cern.ch' }],
+  })
+  targetUsers: SendNotificationRequestUser[];
+
+  @IsString()
+  @IsOptional()
+  @JSONSchema({
+    description: 'Specify an image url to display as preview when possible, eg. push notifications',
+    example: 'https://home.cern/sites/default/files/logo/cern-logo.png',
+  })
+  imgUrl: string;
+
+  @IsNotEmpty()
+  @IsEnum(PriorityLevel)
+  @JSONSchema({
+    description: 'Priority with which the notification is sent.',
+    example: PriorityLevel.NORMAL,
+    default: PriorityLevel.NORMAL,
+  })
+  priority: PriorityLevel;
+
+  @IsEnum(Source)
+  @JSONSchema({
+    description: 'Internal use only. Will be ignored.',
+    example: Source.api,
+    default: Source.api,
+  })
+  source: Source;
+
+  @IsString()
+  @IsOptional()
+  @JSONSchema({
+    description: 'Specify a target url to redirect when clicked.',
+    example: 'https://notifications.web.cern.ch/',
+  })
+  link: string;
+
+  @IsBoolean()
+  @IsOptional()
+  @JSONSchema({
+    description: 'Enable to send targeted notifications.',
+    example: false,
+    default: false,
+  })
+  private: boolean;
+
+  @IsBoolean()
+  @IsOptional()
+  @JSONSchema({
+    description: 'Enable to send only to intersection between channel members/groups and targeted group.',
+    example: false,
+    default: false,
+  })
+  intersection: boolean;
+
+  @IsOptional()
+  @IsDateString()
+  @JSONSchema({
+    description: 'Send notification at this date.',
+    example: new Date(),
+    default: null,
+  })
+  sendAt: Date;
+}
+
+@JSONSchema({
+  description: 'Json object with target users.',
+  example: {
+    email: 'username',
+  },
+})
+class SendNotificationRequestUser {
+  @IsString()
+  @IsNotEmpty()
+  @JSONSchema({
+    description: 'Users to whom the notification is to be sent.',
+    example: '"email@cern.ch" or "username"',
+  })
+  email: string;
+}
+
+@JSONSchema({
+  description: 'Json object with target groups.',
+  example: {
+    groupIdentifier: 'user-group',
+  },
+})
+class SendNotificationRequestGroup {
+  @IsString()
+  @IsNotEmpty()
+  @JSONSchema({
+    description: 'Groups to which the notification is to be sent.',
+    example: 'user-group',
+  })
+  groupIdentifier: string;
+}
diff --git a/src/models/channel.ts b/src/models/channel.ts
index 0c98b39e..e8874fa6 100644
--- a/src/models/channel.ts
+++ b/src/models/channel.ts
@@ -326,7 +326,7 @@ export class Channel extends ApiKeyObject {
 
   // Checks if user has authorization to send Notification to the channel
   // via Form/API
-  async canSendByForm(authorizationBag: AuthorizationBag, targetedNotification: boolean = false) {
+  async canSendByForm(authorizationBag: AuthorizationBag, targetedNotification = false) {
     // No permission to send by Form set
     if (!this.submissionByForm) return false;
 
diff --git a/src/models/notification-enums.ts b/src/models/notification-enums.ts
new file mode 100644
index 00000000..c18f8cf0
--- /dev/null
+++ b/src/models/notification-enums.ts
@@ -0,0 +1,22 @@
+const SendDateFormat = 'YYYY-MM-DDTHH:mm:ss.sssZ';
+export { SendDateFormat };
+
+export enum PriorityLevel {
+  CRITICAL = 'CRITICAL',
+  IMPORTANT = 'IMPORTANT',
+  NORMAL = 'NORMAL',
+  LOW = 'LOW',
+}
+
+export enum Source {
+  email = 'EMAIL',
+  web = 'WEB',
+  api = 'API',
+}
+
+export enum Times {
+  morning = 9,
+  lunch = 13,
+  afternoon = 17,
+  night = 21,
+}
diff --git a/src/models/notification.ts b/src/models/notification.ts
index cd17027a..0c0a691a 100644
--- a/src/models/notification.ts
+++ b/src/models/notification.ts
@@ -4,29 +4,7 @@ import { Channel } from './channel';
 import { User } from './user';
 import { Group } from './group';
 import { AuthorizationBag } from './authorization-bag';
-
-export enum PriorityLevel {
-  CRITICAL = 'CRITICAL',
-  IMPORTANT = 'IMPORTANT',
-  NORMAL = 'NORMAL',
-  LOW = 'LOW',
-}
-
-export enum Source {
-  email = 'EMAIL',
-  web = 'WEB',
-  api = 'API',
-}
-
-export enum Times {
-  morning = 9,
-  lunch = 13,
-  afternoon = 17,
-  night = 21,
-}
-
-const SendDateFormat = 'YYYY-MM-DDTHH:mm:ss.sssZ';
-export { SendDateFormat };
+import { Source, PriorityLevel, Times } from './notification-enums';
 
 @Entity({ name: 'Notifications' })
 export class Notification {
@@ -52,19 +30,16 @@ export class Notification {
   @ManyToOne(type => Channel, channel => channel.notifications)
   target: Channel;
 
-  @Column('simple-array', { nullable: true })
-  tags: string[];
-
   @Column({ nullable: true }) link: string;
   @Column({ nullable: true }) summary: string;
-  @Column({ nullable: true }) contentType: string;
   @Column({ nullable: true }) imgUrl: string;
   @Column({ nullable: true }) sender: string;
+  @Column({ nullable: true }) contentType: string;
   @Column({ enum: PriorityLevel, default: PriorityLevel.NORMAL })
   priority: PriorityLevel;
 
   @Column({ enum: Source, default: Source.api })
-  source: string;
+  source: Source;
 
   @Column({ default: false })
   private: boolean;
@@ -91,10 +66,8 @@ export class Notification {
       this.sendAt = notification.sendAt;
       this.sentAt = notification.sendAt ? null : new Date();
       this.users = notification.users || [];
-      // this.tags = notification.tags;
       this.link = notification.link;
       this.summary = notification.summary;
-      // this.contentType = notification.contentType;
       this.imgUrl = notification.imgUrl;
       this.priority = notification.priority;
       this.source = notification.source;
@@ -113,7 +86,7 @@ export class Notification {
         return true;
       }
       if (!this.targetGroups) return false;
-      for (let g of this.targetGroups) {
+      for (const g of this.targetGroups) {
         if (await g.isMember(authorizationBag.user)) {
           return true;
         }
diff --git a/src/models/preference.ts b/src/models/preference.ts
index b3f05f63..d33504eb 100644
--- a/src/models/preference.ts
+++ b/src/models/preference.ts
@@ -12,7 +12,7 @@ import {
 } from 'typeorm';
 import { User } from './user';
 import { Channel } from './channel';
-import { PriorityLevel } from './notification';
+import { PriorityLevel } from './notification-enums';
 import { Device } from './device';
 import { BadRequestError } from 'routing-controllers';
 
@@ -63,9 +63,7 @@ export class Preference {
   async priorityToUpperCase(): Promise<void> {
     if (!this.notificationPriority) return;
     for (var i = 0; i < this.notificationPriority.length; i++)
-      this.notificationPriority[i] = this.notificationPriority[
-        i
-      ].toUpperCase() as PriorityLevel;
+      this.notificationPriority[i] = this.notificationPriority[i].toUpperCase() as PriorityLevel;
   }
 
   @Column({
@@ -95,8 +93,7 @@ export class Preference {
   @BeforeInsert()
   @AfterLoad()
   async bcScheduledTime(): Promise<void> {
-    if (this.type === 'DAILY' && !this.scheduledTime)
-      this.scheduledTime = allowedScheduledTimes[0];
+    if (this.type === 'DAILY' && !this.scheduledTime) this.scheduledTime = allowedScheduledTimes[0];
   }
 
   @ManyToMany(type => Channel, {
@@ -136,48 +133,31 @@ export class Preference {
     }
 
     if (this.notificationPriority.length === 0) {
-      throw new BadRequestError(
-        'Invalid Priority: at least one Priority is required',
-      );
+      throw new BadRequestError('Invalid Priority: at least one Priority is required');
     }
 
     if (this.rangeStart === this.rangeEnd && this.rangeStart && this.rangeEnd) {
-      throw new BadRequestError(
-        'Invalid Preference Time Range: start time and end time must be different',
-      );
-    } else if (
-      (this.rangeStart && !this.rangeEnd) ||
-      (this.rangeEnd && !this.rangeStart)
-    ) {
-      throw new BadRequestError(
-        "Invalid Preference Time Range: start time and end time can't be empty",
-      );
+      throw new BadRequestError('Invalid Preference Time Range: start time and end time must be different');
+    } else if ((this.rangeStart && !this.rangeEnd) || (this.rangeEnd && !this.rangeStart)) {
+      throw new BadRequestError("Invalid Preference Time Range: start time and end time can't be empty");
     }
 
     if (
-      (this.type === 'DAILY' ||
-        this.type === 'WEEKLY' ||
-        this.type === 'MONTHLY') &&
+      (this.type === 'DAILY' || this.type === 'WEEKLY' || this.type === 'MONTHLY') &&
       !allowedScheduledTimes.includes(this.scheduledTime)
     ) {
-      throw new BadRequestError(
-        'Invalid Scheduled Time: scheduled time can be 09:00, 13:00, 17:00 or 21:00',
-      );
+      throw new BadRequestError('Invalid Scheduled Time: scheduled time can be 09:00, 13:00, 17:00 or 21:00');
     }
 
     if (
       (this.type === 'WEEKLY' || this.type === 'MONTHLY') &&
       !Object.values(ScheduledDay).includes(this.scheduledDay)
     ) {
-      throw new BadRequestError(
-        'Invalid Scheduled Day: scheduled day can be 0, 1, 2, 3, 4, 5, 6',
-      );
+      throw new BadRequestError('Invalid Scheduled Day: scheduled day can be 0, 1, 2, 3, 4, 5, 6');
     }
 
     if (this.devices.length === 0) {
-      throw new BadRequestError(
-        'Invalid Target Device: at least one Target Device is required',
-      );
+      throw new BadRequestError('Invalid Target Device: at least one Target Device is required');
     }
 
     return true;
diff --git a/src/services/api-key-service.ts b/src/services/api-key-service.ts
index b58c84df..6df2f7a2 100644
--- a/src/services/api-key-service.ts
+++ b/src/services/api-key-service.ts
@@ -1,7 +1,7 @@
 import { AuthorizationBag } from '../models/authorization-bag';
 
 export interface ApiKeyService {
-  generateApiKey(id: string, authorizationBag: AuthorizationBag): Promise<string>;
+  generateApiKey(id: string, authorizationBag: AuthorizationBag): Promise<String>;
 
   verifyAPIKey(id: string, key: string): Promise<boolean>;
 }
diff --git a/src/services/devices-service.ts b/src/services/devices-service.ts
index b3408b56..994c66c2 100644
--- a/src/services/devices-service.ts
+++ b/src/services/devices-service.ts
@@ -1,14 +1,18 @@
-import {DeviceRequest, DeviceResponse} from "../controllers/devices/dto";
-import { AuthorizationBag } from "../models/authorization-bag";
+import { DeviceRequest, DeviceResponse, DeviceValuesRequest, GetDevicesResponse } from '../controllers/devices/dto';
+import { AuthorizationBag } from '../models/authorization-bag';
 
 export interface DevicesServiceInterface {
   createUserDevice(device: DeviceRequest, authorizationBag: AuthorizationBag): Promise<DeviceResponse>;
 
-  getUserDevices(authorizationBag: AuthorizationBag): Promise<DeviceResponse[]>;
+  getUserDevices(authorizationBag: AuthorizationBag): Promise<GetDevicesResponse>;
 
   deleteUserDeviceById(deviceId: string, authorizationBag: AuthorizationBag): Promise<string>;
 
-  tryBrowserPushNotification(deviceId: string, authorizationBag: AuthorizationBag): Promise<string>;
+  tryBrowserPushNotification(deviceId: string, authorizationBag: AuthorizationBag): Promise<void>;
 
-  updateUserDeviceById(deviceId: string, authorizationBag: AuthorizationBag, name: string, info: string): Promise<DeviceResponse>;
+  updateUserDeviceById(
+    deviceId: string,
+    authorizationBag: AuthorizationBag,
+    newDeviceValues: DeviceValuesRequest,
+  ): Promise<DeviceResponse>;
 }
diff --git a/src/services/impl/devices-service-impl.ts b/src/services/impl/devices-service-impl.ts
index c28d5c2a..8c363984 100644
--- a/src/services/impl/devices-service-impl.ts
+++ b/src/services/impl/devices-service-impl.ts
@@ -1,61 +1,36 @@
-import { AbstractService } from "./abstract-service";
-import { DevicesServiceInterface } from "../devices-service";
-import { Device } from "../../models/device";
-import { CreateUserDevice } from "./devices/create-user-device";
-import { GetUserDevices } from "./devices/get-user-devices";
-import { DeleteUserDevice } from "./devices/delete-user-device";
-import { TestUserDevice } from "./devices/test-user-device";
-import { UpdateUserDevice } from "./devices/update-user-device";
-import { AuthorizationBag } from "../../models/authorization-bag";
-import {DeviceRequest, DeviceResponse} from "../../controllers/devices/dto";
+import { AbstractService } from './abstract-service';
+import { DevicesServiceInterface } from '../devices-service';
+import { Device } from '../../models/device';
+import { CreateUserDevice } from './devices/create-user-device';
+import { GetUserDevices } from './devices/get-user-devices';
+import { DeleteUserDevice } from './devices/delete-user-device';
+import { TestUserDevice } from './devices/test-user-device';
+import { UpdateUserDevice } from './devices/update-user-device';
+import { AuthorizationBag } from '../../models/authorization-bag';
+import { DeviceRequest, DeviceResponse, DeviceValuesRequest, GetDevicesResponse } from '../../controllers/devices/dto';
 
-export class DevicesService
-  extends AbstractService
-  implements DevicesServiceInterface {
-  createUserDevice(
-    device: DeviceRequest,
-    authorizationBag: AuthorizationBag
-  ): Promise<DeviceResponse> {
-    return this.commandExecutor.execute(
-      new CreateUserDevice(device, authorizationBag)
-    );
+export class DevicesService extends AbstractService implements DevicesServiceInterface {
+  createUserDevice(device: DeviceRequest, authorizationBag: AuthorizationBag): Promise<DeviceResponse> {
+    return this.commandExecutor.execute(new CreateUserDevice(device, authorizationBag));
   }
 
-  getUserDevices(
-    authorizationBag: AuthorizationBag
-  ): Promise<DeviceResponse[]> {
-    return this.commandExecutor.execute(
-      new GetUserDevices(authorizationBag)
-    );
+  getUserDevices(authorizationBag: AuthorizationBag): Promise<GetDevicesResponse> {
+    return this.commandExecutor.execute(new GetUserDevices(authorizationBag));
   }
 
-  deleteUserDeviceById(
-    deviceId: string,
-    authorizationBag: AuthorizationBag
-  ): Promise<string> {
-    return this.commandExecutor.execute(
-      new DeleteUserDevice(deviceId, authorizationBag)
-    );
+  deleteUserDeviceById(deviceId: string, authorizationBag: AuthorizationBag): Promise<string> {
+    return this.commandExecutor.execute(new DeleteUserDevice(deviceId, authorizationBag));
   }
 
-  tryBrowserPushNotification(
-    deviceId: string,
-    authorizationBag: AuthorizationBag
-  ): Promise<string> {
-    return this.commandExecutor.execute(
-      new TestUserDevice(deviceId, authorizationBag)
-    );
+  tryBrowserPushNotification(deviceId: string, authorizationBag: AuthorizationBag): Promise<void> {
+    return this.commandExecutor.execute(new TestUserDevice(deviceId, authorizationBag));
   }
 
   updateUserDeviceById(
     deviceId: string,
     authorizationBag: AuthorizationBag,
-    name: string,
-    info: string,
+    newDeviceValues: DeviceValuesRequest,
   ): Promise<DeviceResponse> {
-    return this.commandExecutor.execute(
-      new UpdateUserDevice(deviceId, authorizationBag, name, info)
-    );
+    return this.commandExecutor.execute(new UpdateUserDevice(deviceId, authorizationBag, newDeviceValues));
   }
-
 }
diff --git a/src/services/impl/devices/get-user-devices.ts b/src/services/impl/devices/get-user-devices.ts
index 280dd50e..57b6553f 100644
--- a/src/services/impl/devices/get-user-devices.ts
+++ b/src/services/impl/devices/get-user-devices.ts
@@ -1,26 +1,25 @@
-import {Command} from "../command";
-import {EntityManager} from "typeorm";
-import {Device} from "../../../models/device";
-import {AuthorizationBag} from "../../../models/authorization-bag";
-import {DeviceResponse, GetDevicesResponse} from "../../../controllers/devices/dto";
+import { Command } from '../command';
+import { EntityManager } from 'typeorm';
+import { Device } from '../../../models/device';
+import { AuthorizationBag } from '../../../models/authorization-bag';
+import { DeviceResponse, GetDevicesResponse } from '../../../controllers/devices/dto';
 
 export class GetUserDevices implements Command {
-    constructor(private authorizationBag: AuthorizationBag) {
-    }
+  constructor(private authorizationBag: AuthorizationBag) {}
 
-    private async fetchUserDevices(transactionManager: EntityManager) {
-        return await transactionManager
-            .getRepository(Device)
-            .createQueryBuilder("device")
-            .leftJoin("device.user", "user")
-            .where("user.id = :userid", {
-                userid: this.authorizationBag.userId
-            })
-            .getMany();
-    }
+  private async fetchUserDevices(transactionManager: EntityManager) {
+    return await transactionManager
+      .getRepository(Device)
+      .createQueryBuilder('device')
+      .leftJoin('device.user', 'user')
+      .where('user.id = :userid', {
+        userid: this.authorizationBag.userId,
+      })
+      .getMany();
+  }
 
-    async execute(transactionManager: EntityManager) {
-        const devices = await this.fetchUserDevices(transactionManager);
-        return new GetDevicesResponse(devices.map(device => new DeviceResponse(device)));
-    }
+  async execute(transactionManager: EntityManager): Promise<GetDevicesResponse> {
+    const devices = await this.fetchUserDevices(transactionManager);
+    return new GetDevicesResponse(devices.map(device => new DeviceResponse(device)));
+  }
 }
diff --git a/src/services/impl/devices/test-user-device.ts b/src/services/impl/devices/test-user-device.ts
index 73b9f379..9db72b51 100644
--- a/src/services/impl/devices/test-user-device.ts
+++ b/src/services/impl/devices/test-user-device.ts
@@ -10,7 +10,7 @@ import { BadRequestError, ForbiddenError } from 'routing-controllers';
 export class TestUserDevice implements Command {
   constructor(private deviceId: string, private authorizationBag: AuthorizationBag) {}
 
-  async execute(transactionManager: EntityManager) {
+  async execute(transactionManager: EntityManager): Promise<void> {
     let selectedDevice = await transactionManager
       .getRepository(Device)
       .createQueryBuilder('device')
diff --git a/src/services/impl/devices/update-user-device.ts b/src/services/impl/devices/update-user-device.ts
index ec2b2adb..631716b2 100644
--- a/src/services/impl/devices/update-user-device.ts
+++ b/src/services/impl/devices/update-user-device.ts
@@ -1,34 +1,34 @@
-import {Command} from "../command";
-import {EntityManager} from "typeorm";
-import {Device} from "../../../models/device";
-import {AuthorizationBag} from "../../../models/authorization-bag";
-import {ForbiddenError} from "routing-controllers";
-import {DeviceResponse} from "../../../controllers/devices/dto";
+import { Command } from '../command';
+import { EntityManager } from 'typeorm';
+import { Device } from '../../../models/device';
+import { AuthorizationBag } from '../../../models/authorization-bag';
+import { ForbiddenError } from 'routing-controllers';
+import { DeviceResponse, DeviceValuesRequest } from '../../../controllers/devices/dto';
 
 export class UpdateUserDevice implements Command {
-    constructor(private deviceId: string, private authorizationBag: AuthorizationBag, private name: string, private info: string) {
-    }
+  constructor(
+    private deviceId: string,
+    private authorizationBag: AuthorizationBag,
+    private newDeviceValues: DeviceValuesRequest,
+  ) {}
 
-    async execute(transactionManager: EntityManager) {
-        let result = await transactionManager.update(
-            Device,
-            {
-                id: this.deviceId,
-                user: this.authorizationBag.userId,
-            },
-            {
-                name: this.name,
-                info: this.info,
-            });
+  async execute(transactionManager: EntityManager): Promise<DeviceResponse> {
+    let result = await transactionManager.update(
+      Device,
+      {
+        id: this.deviceId,
+        user: this.authorizationBag.userId,
+      },
+      {
+        name: this.newDeviceValues.name,
+        info: this.newDeviceValues.info,
+      },
+    );
 
-        if (result.affected !== 1)
-            throw new ForbiddenError("The device does not exist or you are not the owner");
+    if (result.affected !== 1) throw new ForbiddenError('The device does not exist or you are not the owner');
 
-        const response = await transactionManager.findOne(
-            Device,
-            {id: this.deviceId}
-        );
+    const response = await transactionManager.findOne(Device, { id: this.deviceId });
 
-        return new DeviceResponse(response)
-    }
+    return new DeviceResponse(response);
+  }
 }
diff --git a/src/services/impl/notifications-service-impl.ts b/src/services/impl/notifications-service-impl.ts
index 4cfcd08f..f172f803 100644
--- a/src/services/impl/notifications-service-impl.ts
+++ b/src/services/impl/notifications-service-impl.ts
@@ -1,46 +1,36 @@
-import { NotificationsService } from "../notifications-service";
-import { Notification } from "../../models/notification";
-import { SendNotification } from "./notifications/send-notification";
-import { FindAllNotifications } from "./notifications/find-all-notifications";
-import { GetById } from "./notifications/get-by-id";
-import { AbstractService } from "./abstract-service";
-import { UserNotification } from "../../models/user-notification";
-import { UpdateUserNotification } from "./notifications/update-user-notification";
-import { AuthorizationBag } from "../../models/authorization-bag";
-import {RetryNotification} from "./notifications/retry-notification";
+import { NotificationsService } from '../notifications-service';
+import { Notification } from '../../models/notification';
+import { SendNotification } from './notifications/send-notification';
+import { FindAllNotifications } from './notifications/find-all-notifications';
+import { GetById } from './notifications/get-by-id';
+import { AbstractService } from './abstract-service';
+import { UserNotification } from '../../models/user-notification';
+import { UpdateUserNotification } from './notifications/update-user-notification';
+import { AuthorizationBag } from '../../models/authorization-bag';
+import { RetryNotification } from './notifications/retry-notification';
+import { GetNotificationResponse, SendNotificationRequest } from '../../controllers/notifications/dto';
 
-export class NotificationsServiceImpl extends AbstractService
-  implements NotificationsService {
-  sendNotification(notification: Notification, authorizationBag: AuthorizationBag): Promise<Notification> {
-    return this.commandExecutor.execute(
-      new SendNotification(notification, authorizationBag)
-    );
+export class NotificationsServiceImpl extends AbstractService implements NotificationsService {
+  sendNotification(
+    notification: SendNotificationRequest,
+    authorizationBag: AuthorizationBag,
+  ): Promise<GetNotificationResponse> {
+    return this.commandExecutor.execute(new SendNotification(notification, authorizationBag));
   }
 
   retryNotification(notificationId: string, authorizationBag: AuthorizationBag): Promise<void> {
-    return this.commandExecutor.execute(
-      new RetryNotification(notificationId, authorizationBag)
-    );
+    return this.commandExecutor.execute(new RetryNotification(notificationId, authorizationBag));
   }
 
   findAllNotifications(channelId: string, query: any, authorizationBag: AuthorizationBag): Promise<Notification[]> {
-    return this.commandExecutor.execute(
-      new FindAllNotifications(channelId, query, authorizationBag)
-    );
+    return this.commandExecutor.execute(new FindAllNotifications(channelId, query, authorizationBag));
   }
 
-  getById(notificationId: string, query: any, authorizationBag: AuthorizationBag): Promise<Notification> {
-    return this.commandExecutor.execute(
-      new GetById(notificationId, query, authorizationBag)
-    );
+  getById(notificationId: string, authorizationBag: AuthorizationBag): Promise<GetNotificationResponse> {
+    return this.commandExecutor.execute(new GetById(notificationId, authorizationBag));
   }
 
-  updateUserNotification(
-    notification: UserNotification,
-    authorizationBag: AuthorizationBag
-  ): Promise<any> {
-    return this.commandExecutor.execute(
-      new UpdateUserNotification(notification, authorizationBag)
-    );
+  updateUserNotification(notification: UserNotification, authorizationBag: AuthorizationBag): Promise<any> {
+    return this.commandExecutor.execute(new UpdateUserNotification(notification, authorizationBag));
   }
 }
diff --git a/src/services/impl/notifications/get-by-id.ts b/src/services/impl/notifications/get-by-id.ts
index f8a3b516..709160d5 100644
--- a/src/services/impl/notifications/get-by-id.ts
+++ b/src/services/impl/notifications/get-by-id.ts
@@ -3,11 +3,12 @@ import { EntityManager } from 'typeorm';
 import { Notification } from '../../../models/notification';
 import { ForbiddenError, NotFoundError } from 'routing-controllers';
 import { AuthorizationBag } from '../../../models/authorization-bag';
+import { GetNotificationResponse } from '../../../controllers/notifications/dto';
 
 export class GetById implements Command {
-  constructor(private notificationId: string, private query: any, private authorizationBag: AuthorizationBag) {}
+  constructor(private notificationId: string, private authorizationBag: AuthorizationBag) {}
 
-  async execute(transactionManager: EntityManager) {
+  async execute(transactionManager: EntityManager): Promise<GetNotificationResponse> {
     const notification = await transactionManager.findOne(Notification, {
       relations: [
         'target',
@@ -40,6 +41,6 @@ export class GetById implements Command {
       throw new ForbiddenError('Access to notification not authorized.');
     }
 
-    return notification;
+    return new GetNotificationResponse(notification);
   }
 }
diff --git a/src/services/impl/notifications/send-notification.ts b/src/services/impl/notifications/send-notification.ts
index 017a2f35..8cf25e6e 100644
--- a/src/services/impl/notifications/send-notification.ts
+++ b/src/services/impl/notifications/send-notification.ts
@@ -3,7 +3,8 @@ import * as stompit from 'stompit';
 import { EntityManager } from 'typeorm';
 import { BadRequestError, ForbiddenError, NotFoundError } from 'routing-controllers';
 import { Command } from '../command';
-import { Notification, PriorityLevel, SendDateFormat, Times } from '../../../models/notification';
+import { Notification } from '../../../models/notification';
+import { SendDateFormat, Times, Source, PriorityLevel } from '../../../models/notification-enums';
 import { Channel } from '../../../models/channel';
 import { User } from '../../../models/user';
 import { Group } from '../../../models/group';
@@ -16,15 +17,15 @@ import { UsersServiceInterface } from '../../users-service';
 import { GroupsServiceInterface } from '../../groups-service';
 import * as memoize from 'memoizee';
 import { AuditNotifications } from '../../../log/auditing';
-import { ChannelFlags } from '../../../models/channel-enums';
+import { GetNotificationResponse, SendNotificationRequest } from '../../../controllers/notifications/dto';
 
 export class SendNotification implements Command {
   private usersService: UsersServiceInterface = ServiceFactory.getUserService();
   private groupsService: GroupsServiceInterface = ServiceFactory.getGroupService();
 
-  constructor(private notification, private authorizationBag: AuthorizationBag) {}
+  constructor(private notification: SendNotificationRequest, private authorizationBag: AuthorizationBag) {}
 
-  async execute(transactionManager: EntityManager) {
+  async execute(transactionManager: EntityManager): Promise<GetNotificationResponse> {
     const targetChannel = await transactionManager.findOne(Channel, {
       relations: ['members', 'groups', 'owner', 'adminGroup', 'category'],
       where: { id: this.notification.target },
@@ -36,7 +37,9 @@ export class SendNotification implements Command {
       if (
         !(await targetChannel.canSendByForm(
           this.authorizationBag,
-          this.notification.targetUsers || this.notification.targetGroups || this.notification.targetData,
+          this.notification.targetUsers !== undefined ||
+            this.notification.targetGroups !== undefined ||
+            this.notification.targetData !== undefined,
         ))
       )
         throw new ForbiddenError('Sending to Channel not Authorized !');
@@ -66,14 +69,14 @@ export class SendNotification implements Command {
         if (!id) return;
         const identifier = id.toLowerCase();
         // no @ mean it's a group name (or a mistake that we'll ignore)
-        if (!identifier.includes('@')) this.notification.targetGroups.push({ groupIdentifier: identifier });
+        if (!identifier.includes('@')) this.notification.targetGroups.push(new Group({ groupIdentifier: identifier }));
         // @domain is not @cern.ch then it's an external user
-        else if (!identifier.includes('@cern.ch')) this.notification.targetUsers.push({ email: identifier });
+        else if (!identifier.includes('@cern.ch')) this.notification.targetUsers.push(new User({ email: identifier }));
         // email@cern.ch contains a - so it's a group
         else if (identifier.includes('-'))
-          this.notification.targetGroups.push({ groupIdentifier: identifier.replace('@cern.ch', '') });
+          this.notification.targetGroups.push(new Group({ groupIdentifier: identifier.replace('@cern.ch', '') }));
         // And the rest is handled as user email
-        else this.notification.targetUsers.push({ email: identifier });
+        else this.notification.targetUsers.push(new User({ email: identifier }));
       });
     }
 
@@ -97,18 +100,17 @@ export class SendNotification implements Command {
       );
     }
 
-    const notificationInDB = await transactionManager.findOne(Notification, {
-      id: this.notification.id,
-    });
-    if (notificationInDB) throw new BadRequestError('The notification already exists in the database');
+    const source =
+      this.authorizationBag?.isAnonymous || this.authorizationBag?.isApiKey ? Source.api : this.notification.source;
 
     const newNotification = await transactionManager.save(
       new Notification({
         ...this.notification,
+        source,
         target: targetChannel,
-        sender: this.notification.sender || this.authorizationBag.email,
-        targetUsers: targetUsers,
-        targetGroups: targetGroups,
+        sender: this.authorizationBag?.email || this.notification.sender,
+        targetUsers,
+        targetGroups,
       }),
     );
 
@@ -123,15 +125,17 @@ export class SendNotification implements Command {
 
     await AuditNotifications.setValue(newNotification.id, {
       event: 'Sent',
-      user: this.authorizationBag.email,
+      user: this.authorizationBag?.email || this.notification.sender,
       from: newNotification.source,
     });
 
     // Get back object from DB, without full info about target Channel
-    return await transactionManager.findOne(Notification, {
-      relations: ['targetUsers', 'targetGroups'],
-      where: { id: newNotification.id },
-    });
+    return new GetNotificationResponse(
+      await transactionManager.findOne(Notification, {
+        relations: ['targetUsers', 'targetGroups'],
+        where: { id: newNotification.id },
+      }),
+    );
   }
 
   async sendToQueue(notification: Notification): Promise<void> {
@@ -293,7 +297,7 @@ export class SendNotification implements Command {
 
   // Update lastActivityDate from the Channel with the current Date
   static async _updateLastActivityNoCache(channel_id: string, transactionManager: EntityManager, channel: Channel) {
-    let emptyChannelObject = channel.getEmptyChannelObjectForQuickUpdate();
+    const emptyChannelObject = channel.getEmptyChannelObjectForQuickUpdate();
     emptyChannelObject.lastActivityDate = new Date();
     return await transactionManager.save(emptyChannelObject);
   }
diff --git a/src/services/impl/notifications/update-user-notification.ts b/src/services/impl/notifications/update-user-notification.ts
index 22569e29..6e037de7 100644
--- a/src/services/impl/notifications/update-user-notification.ts
+++ b/src/services/impl/notifications/update-user-notification.ts
@@ -1,42 +1,40 @@
-import { EntityManager } from "typeorm";
-import { Command } from "../command";
-import { Notification } from "../../../models/notification";
-import { UserNotification } from "../../../models/user-notification";
-import { AuthorizationBag } from "../../../models/authorization-bag";
+import { EntityManager } from 'typeorm';
+import { Command } from '../command';
+import { Notification } from '../../../models/notification';
+import { UserNotification } from '../../../models/user-notification';
+import { AuthorizationBag } from '../../../models/authorization-bag';
 
+//TODO: Legacy cleanup?
 export class UpdateUserNotification implements Command {
-    constructor(private notification, private authorizationBag: AuthorizationBag) {
-        delete this.notification.id;
-    }
+  constructor(private notification, private authorizationBag: AuthorizationBag) {
+    delete this.notification.id;
+  }
 
-    async execute(transactionManager: EntityManager): Promise<any> {
-        if (await this.isNotificationOfUser(transactionManager)) {
-            let userNotification = await transactionManager.findOne(
-                UserNotification,
-                {
-                    user: { id: this.authorizationBag.userId },
-                    notification: this.notification.notification
-                }
-            );
+  async execute(transactionManager: EntityManager): Promise<any> {
+    if (await this.isNotificationOfUser(transactionManager)) {
+      let userNotification = await transactionManager.findOne(UserNotification, {
+        user: { id: this.authorizationBag.userId },
+        notification: this.notification.notification,
+      });
 
-            userNotification = await transactionManager.save(UserNotification, { ...userNotification, ...this.notification });
-            delete userNotification.id;
-            delete userNotification.notification;
-            delete userNotification.user;
+      userNotification = await transactionManager.save(UserNotification, { ...userNotification, ...this.notification });
+      delete userNotification.id;
+      delete userNotification.notification;
+      delete userNotification.user;
 
-            return {
-                ...await transactionManager.findOne(
-                    Notification,
-                    { id: this.notification.notification }), ...userNotification
-            };
-        }
+      return {
+        ...(await transactionManager.findOne(Notification, { id: this.notification.notification })),
+        ...userNotification,
+      };
     }
+  }
 
-    async isNotificationOfUser(transactionManager: EntityManager): Promise<boolean> {
-        const notification = await transactionManager.findOne(
-            Notification,
-            { id: this.notification.notification }, { relations: ['users', 'users.user'] }
-        );
-        return notification.users.filter(u => u.user.id === this.authorizationBag.userId).length === 1;
-    }
-}
\ No newline at end of file
+  async isNotificationOfUser(transactionManager: EntityManager): Promise<boolean> {
+    const notification = await transactionManager.findOne(
+      Notification,
+      { id: this.notification.notification },
+      { relations: ['users', 'users.user'] },
+    );
+    return notification.users.filter(u => u.user.id === this.authorizationBag.userId).length === 1;
+  }
+}
diff --git a/src/services/notifications-service.ts b/src/services/notifications-service.ts
index 3e218063..46cce9ed 100644
--- a/src/services/notifications-service.ts
+++ b/src/services/notifications-service.ts
@@ -1,20 +1,19 @@
-import { Notification } from "../models/notification";
-import { UserNotification } from "../models/user-notification";
-import { AuthorizationBag } from "../models/authorization-bag";
+import { Notification } from '../models/notification';
+import { UserNotification } from '../models/user-notification';
+import { AuthorizationBag } from '../models/authorization-bag';
+import { GetNotificationResponse, SendNotificationRequest } from '../controllers/notifications/dto';
 
 export interface NotificationsService {
-
-  sendNotification(notification: Notification, authorizationBag: AuthorizationBag): Promise<Notification>;
+  sendNotification(
+    notification: SendNotificationRequest,
+    authorizationBag: AuthorizationBag,
+  ): Promise<GetNotificationResponse>;
 
   retryNotification(notificationId: string, authorizationBag: AuthorizationBag): Promise<void>;
 
   findAllNotifications(channelId: string, query: any, authorizationBag: AuthorizationBag): Promise<Notification[]>;
 
-  getById(notificationId: string, query: any, authorizationBag: AuthorizationBag): Promise<Notification>;
-
-  updateUserNotification(
-    notification: UserNotification,
-    authorizationBag: AuthorizationBag,
-  ): Promise<any>;
+  getById(notificationId: string, authorizationBag: AuthorizationBag): Promise<GetNotificationResponse>;
 
+  updateUserNotification(notification: UserNotification, authorizationBag: AuthorizationBag): Promise<any>;
 }
diff --git a/src/utils/status-codes.ts b/src/utils/status-codes.ts
new file mode 100644
index 00000000..f5648507
--- /dev/null
+++ b/src/utils/status-codes.ts
@@ -0,0 +1,116 @@
+export const enum StatusCodes {
+  Accepted = '202',
+  BadGateway = '502',
+  BadRequest = '400',
+  Conflict = '409',
+  Continue = '100',
+  Created = '201',
+  ExpectationFailed = '417',
+  FailedDependency = '424',
+  Forbidden = '403',
+  GatewayTimeout = '504',
+  Gone = '410',
+  HTTPVersionNotSupported = '505',
+  ImATeapot = '418',
+  InsufficientSpaceOnResource = '419',
+  InsufficientStorage = '507',
+  InternalServerError = '500',
+  LengthRequired = '411',
+  Locked = '423',
+  MethodFailure = '420',
+  MethodNotAllowed = '405',
+  MovedPermanently = '301',
+  MovedTemporarily = '302',
+  MultiStatus = '207',
+  MultipleChoices = '300',
+  NetworkAuthenticationRequired = '511',
+  NoContent = '204',
+  NonAuthoritativeInformation = '203',
+  NotAcceptable = '406',
+  NotFound = '404',
+  NotImplemented = '501',
+  NotModified = '304',
+  OK = '200',
+  PartialContent = '206',
+  PaymentRequired = '402',
+  PermanentRedirect = '308',
+  PreconditionFailed = '412',
+  PreconditionRequired = '428',
+  Processing = '102',
+  ProxyAuthenticationRequired = '407',
+  RequestHeaderFieldsTooLarge = '431',
+  RequestTimeout = '408',
+  RequestEntityTooLarge = '413',
+  RequestURITooLong = '414',
+  RequestedRangeNotSatisfiable = '416',
+  ResetContent = '205',
+  SeeOther = '303',
+  ServiceUnavailable = '503',
+  SwitchingProtocols = '101',
+  TemporaryRedirect = '307',
+  TooManyRequests = '429',
+  Unauthorized = '401',
+  UnavailableForLegalReasons = '451',
+  UnprocessableEntity = '422',
+  UnsupportedMediaType = '415',
+  UseProxy = '305',
+  MisdirectedRequest = '421',
+}
+
+export const StatusCodeDescriptions: Record<string, object> = {
+  [StatusCodes.Accepted]: { description: 'Accepted' },
+  [StatusCodes.BadGateway]: { description: 'Bad Gateway' },
+  [StatusCodes.BadRequest]: { description: 'Bad Request' },
+  [StatusCodes.Conflict]: { description: 'Conflict' },
+  [StatusCodes.Continue]: { description: 'Continue' },
+  [StatusCodes.Created]: { description: 'Created' },
+  [StatusCodes.ExpectationFailed]: { description: 'Expectation Failed' },
+  [StatusCodes.FailedDependency]: { description: 'Failed Dependency' },
+  [StatusCodes.Forbidden]: { description: 'Forbidden' },
+  [StatusCodes.GatewayTimeout]: { description: 'Gateway Timeout' },
+  [StatusCodes.Gone]: { description: 'Gone' },
+  [StatusCodes.HTTPVersionNotSupported]: { description: 'HTTP Version Not Supported' },
+  [StatusCodes.ImATeapot]: { description: "I'm a teapot" },
+  [StatusCodes.InsufficientSpaceOnResource]: { description: 'Insufficient Space on Resource' },
+  [StatusCodes.InsufficientStorage]: { description: 'Insufficient Storage' },
+  [StatusCodes.InternalServerError]: { description: 'Internal Server Error' },
+  [StatusCodes.LengthRequired]: { description: 'Length Required' },
+  [StatusCodes.Locked]: { description: 'Locked' },
+  [StatusCodes.MethodFailure]: { description: 'Method Failure' },
+  [StatusCodes.MethodNotAllowed]: { description: 'Method Not Allowed' },
+  [StatusCodes.MovedPermanently]: { description: 'Moved Permanently' },
+  [StatusCodes.MovedTemporarily]: { description: 'Moved Temporarily' },
+  [StatusCodes.MultiStatus]: { description: 'Multi - Status' },
+  [StatusCodes.MultipleChoices]: { description: 'Multiple Choices' },
+  [StatusCodes.NetworkAuthenticationRequired]: { description: 'Network Authentication Required' },
+  [StatusCodes.NoContent]: { description: 'No Content' },
+  [StatusCodes.NonAuthoritativeInformation]: { description: 'Non Authoritative Information' },
+  [StatusCodes.NotAcceptable]: { description: 'Not Acceptable' },
+  [StatusCodes.NotFound]: { description: 'Not Found' },
+  [StatusCodes.NotImplemented]: { description: 'Not Implemented' },
+  [StatusCodes.NotModified]: { description: 'Not Modified' },
+  [StatusCodes.PartialContent]: { description: 'Partial Content' },
+  [StatusCodes.PaymentRequired]: { description: 'Payment Required' },
+  [StatusCodes.PermanentRedirect]: { description: 'Permanent Redirect' },
+  [StatusCodes.PreconditionFailed]: { description: 'Precondition Failed' },
+  [StatusCodes.PreconditionRequired]: { description: 'Precondition Required' },
+  [StatusCodes.Processing]: { description: 'Processing' },
+  [StatusCodes.ProxyAuthenticationRequired]: { description: 'Proxy Authentication Required' },
+  [StatusCodes.RequestHeaderFieldsTooLarge]: { description: 'Request Header Fields Too Large' },
+  [StatusCodes.RequestTimeout]: { description: 'Request Timeout' },
+  [StatusCodes.RequestEntityTooLarge]: { description: 'Request Entity Too Large' },
+  [StatusCodes.RequestURITooLong]: { description: 'Request - URI Too Long' },
+  [StatusCodes.RequestedRangeNotSatisfiable]: { description: 'Requested Range Not Satisfiable' },
+  [StatusCodes.ResetContent]: { description: 'Reset Content' },
+  [StatusCodes.SeeOther]: { description: 'See Other' },
+  [StatusCodes.ServiceUnavailable]: { description: 'Service Unavailable' },
+  [StatusCodes.SwitchingProtocols]: { description: 'Switching Protocols' },
+  [StatusCodes.TemporaryRedirect]: { description: 'Temporary Redirect' },
+  [StatusCodes.TooManyRequests]: { description: 'Too Many Requests' },
+  [StatusCodes.Unauthorized]: { description: 'Unauthorized' },
+  [StatusCodes.UnavailableForLegalReasons]: { description: 'Unavailable For Legal Reasons' },
+  [StatusCodes.UnprocessableEntity]: { description: 'Unprocessable Entity' },
+  [StatusCodes.UnsupportedMediaType]: { description: 'Unsupported Media Type' },
+  [StatusCodes.UseProxy]: { description: 'Use Proxy' },
+  [StatusCodes.MisdirectedRequest]: { description: 'Misdirected Request' },
+};
-- 
GitLab