# CENOS Support service

Service description

## Installation

### Steps

- [Setup Slack app and workspace](#setup-slack-app-and-workspace)
- [Setup Amazon S3](#setup-amazon-s3)
- [Setup service](#setup-service)
- [Setup Slack Event Subscriptions](#setup-slack-event-subscriptions)

### Setup Slack app and workspace

First we need to set up Slack app and workspace where it will be integrated.

_**NOTE:** For development purposes a new workspace can be created. Functionality of this service does not require any
paid options of Slack._

#### Setup Slack App

1. Go to [Slack API website](https://api.slack.com/).
2. Press "Your Apps" in top-right corner.
    - If you haven't authorized in the Slack website in your browser before - there will be green "Go to Slack" button
      which will lead you to authorization page.
    - After you will get to "Welcome Back!" screen, you will need to go back
      to [Slack API website](https://api.slack.com/) and start from step 2.
3. Press green "Create New App" button.
4. Enter app name and select the workspace to integrate with.
    - _**NOTE:** This option **CANNOT** be changed in the future._
5. Press "Create App".
6. Now you should be presented with App management console. Press "OAuth & Permissions" in the left menu.
7. In the "Scope" section press "Add an OAuth scope" under "Bot Token Scopes" section.
8. Enter `channels:history` and press on the option appeared below.
9. `channels:history` should be added to the list under "Bot Token Scopes" section. Repeat steps 7-8 for these scopes:
    - `channels:read`,
    - `chat:write`,
    - `im:history`,
    - `remote_files:write`.
10. Repeat steps 7-8 but under "User Token Scopes" section and add `files:write` scope.
11. Don't close the page we were working on - we will need this later.
12. Under "OAuth Tokens for Your Team" section press "Reinstall to Workspace" button.
13. Press green "Allow" button.

There's one thing still missing ("Event Subscriptions" hook up) but this will be covered after an actual server will be
up and running.

#### Setup Slack workspace

The new channel should be created in the workspace. By default, service will search for a channel named `support`.

After creating the channel, Bot user should be added to it. To do this you just need to mention it in the chat.

### Setup Amazon S3

1. In AWS console create new S3 bucket. These settings should be applied:
    - Under "Block Public Access settings for this bucket" section untick "Block all public access".
    - Tick "Block public access to buckets and objects granted through any access control lists (ACLs)".
    - Tick "Block public access to buckets and objects granted through new public bucket or access point policies".
    - Tick "Block public and cross-account access to buckets and objects through any public bucket or access point
      policies".
2. Go to IAM console -> Users.
3. Create New user:
    - AWS access type: `Programmatic access`;
    - Permissions: "Attach existing policies directly":
        - "Create policy" - new browser tab will be opened;
        - "JSON" tab, clear contents of an input and paste this:
          ```
          {
              "Version": "2012-10-17",
              "Statement": [
                  {
                      "Sid": "VisualEditor0",
                      "Effect": "Allow",
                      "Action": [
                          "s3:Put*",
                          "s3:Get*",
                          "s3:List*",
                          "s3:Delete*"
                      ],
                      "Resource": [
                          "arn:aws:s3:::%BUCKET_NAME%/*",
                          "arn:aws:s3:::%BUCKET_NAME%"
                      ]
                  }
              ]
          }
          ```
        - in pasted text replace `%BUCKET_NAME%` with name of the bucket you've created;
        - press "Next" until the last step;
        - enter policy name and press "Create policy";
        - go back to previous tab, where we were creating the user (you can close tab where we were creating the policy)
          ;
        - press refresh button on top of policy list table (on the right);
        - input name of the policy you've just created and select it.
    - Press "Next" until "Create User" button appears and then press it.
4. "Success" screen will be shown: copy "Access key ID" and "Secret access key" and store it somewhere. We will need
   this later.

### Setup service

#### Prerequisites

Specified versions are not strict - it is the ones, that are tested and working, but you can install newer versions if
it won't break anything.

- NodeJS (15.10.0):
    - I recommend using NVM to install NodeJS. It can manage several installed versions, and you can easily switch
      between them.
- Redis (6.2.1):
    - On Windows you can either install Ubuntu WSL (Windows Subsystem for Linux) and install it there or use Docker.
    - Should be installed and running.
- MySQL (5.7.33):
    - separate database should be created;
    - if you have installed newer version of mysql, you may need to create a user without password or
      with `mysql_native_password`.

### For development

1. Clone the repository.
2. Open console/terminal window in the root directory of the repository.
3. Run `npm install`.
4. Run `npm start` to build and run the service.

### Dedicated server deployment

#### Server setup notes

- Ports specified in `.env` file as `CENOS_SUPPORT_API_PORT` and `CENOS_SUPPORT_SLACK_BOLT_PORT` should be exposed to
  the internet.
    - Default values are `3001` and `3002` respectively.
- All environment variables in `.env` file should edited before launching the service.
    - [Environment variable (`.env` file) explanation](#environment-variable-env-file-explanation).

#### Build and deploy

1. Clone the repository.
2. Open console/terminal window in the root directory of the repository.
3. Run `npm install`.
4. Run `npm run build`. This will build the project.
5. Transfer the contents of `dist` folder to the dedicated server.
6. Transfer `.env`, `package.json` and `package-lock.json` files to the same place on the dedicated server.
    - In the future automatic copying of these files to the `dist` folder should be implemented for build process.
7. Run `npm install` on the dedicated server in the folder where you've put the service files.
8. Launch `main.js` file through NodeJS.
    - For this PM2 library can be used. The service itself is stateless and does not setup any sort of clustering. PM2
      can handle clustering and load balancing.

## Environment variable (`.env` file) explanation

| Variable name                                                 | Description                                                                                                                                                       |
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| CENOS_SUPPORT_API_PORT                                        | Main API port                                                                                                                                                     |
| CENOS_SUPPORT_URL                                             | Public URL of service.                                                                                                                                            |
| CENOS_SUPPORT_DATABASE_HOST                                   | Database host                                                                                                                                                     |
| CENOS_SUPPORT_DATABASE_PORT                                   | Database port                                                                                                                                                     |
| CENOS_SUPPORT_DATABASE_USERNAME                               | Database username                                                                                                                                                 |
| CENOS_SUPPORT_DATABASE_PASSWORD                               | Database user password                                                                                                                                            |
| CENOS_SUPPORT_DATABASE_SCHEMA                                 | Database schema                                                                                                                                                   |
| CENOS_SUPPORT_SLACK_BOLT_PORT                                 | Slack Bolt port (must be different from `CENOS_SUPPORT_API_PORT`)                                                                                                 |
| CENOS_SUPPORT_SLACK_SIGNING_SECRET                            | Slack bot user signing secret. [Retrieving necessary Slack tokens](#retrieving-necessary-slack-tokens)                                                            |
| CENOS_SUPPORT_SLACK_BOT_TOKEN                                 | Slack bot user BOT token. [Retrieving necessary Slack tokens](#retrieving-necessary-slack-tokens)                                                                 |
| CENOS_SUPPORT_SLACK_USER_TOKEN                                | Slack bot user USER token. [Retrieving necessary Slack tokens](#retrieving-necessary-slack-tokens)                                                                |
| CENOS_SUPPORT_SLACK_WORKING_CHANNEL_NAME                      | Slack workspace channel name to integrate into                                                                                                                    |
| CENOS_SUPPORT_AMAZON_ACCESS_KEY_ID                            | AWS access key id                                                                                                                                                 |
| CENOS_SUPPORT_AMAZON_SECRET_ACCESS_KEY                        | AWS secret access key                                                                                                                                             |
| CENOS_SUPPORT_AMAZON_REGION                                   | AWS region                                                                                                                                                        |
| CENOS_SUPPORT_AMAZON_BUCKET                                   | AWS S3 bucket                                                                                                                                                     |
| CENOS_SUPPORT_REDIS_HOST                                      | Redis host                                                                                                                                                        |
| CENOS_SUPPORT_REDIS_PORT                                      | Redis port                                                                                                                                                        |
| CENOS_SUPPORT_TEMPORARY_ATTACHMENT_REMOVE_WORKER_DAYS_BEFORE  | The amount of days (from today) until which temporary Attachments will be deleted by the CRON. Examples: 1 - until yesterday, 2 - until the day before yesterday  |

**NOTE** that every time you change environment variable values or change `.env` file, you must restart the server!

## Retrieving necessary Slack tokens

Service, to work, requires 3 main Slack tokens assigned to their designated Environment Variables: Signing Secret, Bot
Token and User token. Here's how to get them:

- Signing secret:
    - go to Slack app dashboard ([Slack API website](https://api.slack.com/) -> Your Apps -> App);
    - `Basic Information` in the left menu;
    - `Signing Secret`, press `Show` button.
- Bot and User tokens:
    - go to Slack app dashboard ([Slack API website](https://api.slack.com/) -> Your Apps -> App);
    - `OAuth & Permissions` in the left menu;
    - under `OAuth Tokens for Your Team` sections `Bot User OAuth Token` and `User OAuth Token` respectively.

## Setup Slack Event Subscriptions

1. Go to [Slack API website](https://api.slack.com/) -> Your Apps -> App.
2. In the left menu go to `Event Subscriptions`.
3. Enable the switcher near `Enable Events`.
4. In `Request URL` input `%PUBLIC_SERVER_URL%:3002/slack/events`.
    - `%PUBLIC_SERVER_URL%` - public URL of the server.
    - Server MUST be up and running.
5. Expand `Subscribe to bot events` section (just click on it).
6. Add these events: `channel_created`, `message.channels`.
    - If the channel, with which the service will be working, is `private` - you need to add `message.groups` instead
      of `message.channels`.
7. Press `Save Changes` at the bottom.

# CRON workers
Currently, there is single CRON worker set up in the system. It's set up through `@nestjs/scheduler` library.

It will only run if the process name contains `master` string in it. It is done to support cluster mode and is supposed to work in conjunction with PM2.

## Worker list
 - Delete old, unused temporary Attachments.
    - Runs every day at 01:00 (24h format) server time.
    
# PM2 usage
The service is designed to run under [PM2](https://pm2.keymetrics.io/). 

There's `ecosystem.config.js` file in the project root folder, which is set up in a proper way to run on multiple threads
while supporting internal CRON workers in the project.

You're supposed to run the service using `pm2 start ecosystem.config.js` command. Same for `reload`, `restart`, etc

# Configuration DB table
In addition to Environment Variables, there's also a DB table that stores Configuration values.

Those values are stored in a DB, so they can be dynamically changed.

## Configuration values
###`working-hours`: string
####Description:
- String value. Must contain exactly 7 days (separated by `;`).
- Each day can contain indefinite amount of ranges (separated by `,`).
- Each range must contain 2 time values separated by `-`.
- Each time value consists of 2 numbers (can be prepended with `0` or not) separated by `:`, e.g.: `08:00`.
- Each time value is in UTC time zone.
- Each day can also contain one of modifier values instead of ranges: `+` or `-`.
    - `-` means day off.
    - `+` means that this day value will be the same as closest previous day value ignoring off days .
- At least one day MUST contain at least one valid range value (not modifiers).
- First day of the week is Sunday.

####Examples:
`-;05:00-14:00;+;+;+;+;-` - Each working day will be active from 05:00 until 14:00 UTC time.

`-;05:00-07:00,10:00-14:00;+;-;+;+;-` - Each working day, except for Wednesday, will be active from 05:00 until 07:00 and from 10:00 until 14:00 UTC time. 

###`motd-time`: string
####Description:
A string value, containing 2 ISO date-time strings separated by `|`. First value is date-time from and second is date-time to.

One of 2 or both of the date-time strings may be empty. In this case, following logic should apply (on frontend):
- Start value missing - MOTD should be shown at any time up until end value.
- End value missing - MOTD should be shown at any time AFTER start value.
- Both values missing (just '|' present) - MOTD should be shown always.

**NOTE:** Empty string `''` IS NOT a valid value - will trigger a validation error.

**NOTE 2:** `null` valie IS a valid value - message should be shown always

####Examples:
`2021-08-05T10:19:00.000Z|2021-08-07T10:19:25.113Z` - from 5 Aug 2021 10:19 UTC to 8 Aug 2021 10:19 UTC

`2021-08-05T10:19:00.000Z|` - from 5 Aug 2021 10:19 UTC until forever

`|2021-08-07T10:19:25.113Z` - forever until 8 Aug 2021 10:19 UTC

###`motd-body`: string
####Description:

A string value, containing a valid HTML string to show as a MOTD message.

**WARNING:** In a rare case, value might not be safe, so be sure to sanitize it before using!