## Common Lib:
A set of decorators and services that are commonly used in our applications.
### Installation:
```shell
npm install @alphaapps/nestjs-common
```
### Decorators:
- `GetUser`: Used in controllers to get the current authenticated user.
```ts
export default class UserController {
  getMe(@GetUser() user: User): User {
    return { user };
  }
}
```
- `InjectSentry`: Injects Sentry service so it can be called.
### Interceptors:
- `CrudWrapperInterceptor`: User to wrap the response of crud APIs with `data` key.
- `UserLanguageSetterInterceptor`: Updates the `language`, `devicePlatform` & `appVersion` of the user using values passed in headers in the request.
- `LocalizationSetter`: Used to update response with the correct language.
- `WinstonLoggingInterceptor`: Logs every request to `winston`
### Middlewares:
- `rewriteUrls`: A middleware for rewriting URLs. This is useful when want to add a param to the request URL without forcing the API consumer to do it.
```ts
export default class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer): void {
    consumer
      .apply(rewrite([{
        method: 'GET',
        from: '/notifications',
        to: '/notifications?limit=100&sort=createdAt,DESC'
      }]))
      .forRoutes({
        path: '*',
        method: RequestMethod.ALL
      });
  }
}
```
### Filters:
- `ExceptionsFilter`: An exception filter that catches all exceptions and check if these are business errors (i.e. User not verified, unique violations) and print them in a pretty way, when an exception is unknown (not one of our known errors) it reports it to Sentry.  
  This filter also logs the error to `winston`.

#### A note about errors:
Every error in the app must be an instance of `AlphaError` class.  
Application errors must be like this:
```ts
import { errors } from '@alphaapps/nestjs-common';
import _ from 'lodash';

const localErrors = {
  users: {
    invalidEmail: {
      code: 311,
      message: 'invalid_email_string',
      statusCode: 400
    },
  },
  files: {
    conflictingFiles: {
      code: 400,
      message: 'conflicting_files',
      statusCode: 400
    }
  },
};

export default _.merge(errors, localErrors);
```
Then every error should be fired like this: `throw new AlphaError('users.invalidEmail')`  
These errors must be provided in the `AppModule` so the `ExceptionsFilter` can identify them.
```ts
@Module({
  providers: [{
    provide: APP_INTERCEPTOR,
    useClass: WinstonLoggingInterceptor
  }, {
    provide: APP_FILTER,
    useClass: ExceptionsFilter
  }, {
    provide: ERRORS_TOKEN,
    useValue: Errors
  }]
})
export default class AppModule {}
```

### Services:
- `SentryService`: A service to encapsulate Sentry SDK with two methods `captureException` & `captureEvent`  
  This service can be injected with `@InjectSentry` decorator.
- `SequelizeCrudService`: A CRUD service for Sequelize ORM. More info can be viewed at [crud github repo](https://github.com/nestjsx/crud)  
  There's some few differences between the original crud service and ours:
  - `getMany` method calls 2 sub methods `createBuilder` & `executeQuery` to enable some customization in the extending service.
  - Joins are all allowed by default.
  - When calling `count` in `getMany` function the many associations to get correct results.
  - `getMany` uses `modelFindAll` & `modelCount` methods to enable more customisation.
- `FirebaseService`: Encapsulation for firebase push notification service.
- `MandrillService`: Encapsulation for Mandrill email service.
- `SesService`: Encapsulation for SES email service.
- `TwilioService`: Encapsulation for Twilio SMS service.

### Log Reader Service:
This service is used to query logs stored on disk and it's super useful in development environments.  
By default when using `WinstonLoggingInterceptor` and `ExceptionsFilter` all our requests and failed responses are saved via `winston` logging lib. So in `AppModule` we define the following `transports`
```ts
const env = process.env.NODE_ENV || 'development';
const winstonTransports: Transport[] = [
  new winston.transports.Console({
    level: 'debug',
    log: (info, callback) => {
      const {
        level = 'info', http = {}, message, duration
      } = info;
      const { method = 'GET', status_code: statusCode = 200 } = http;
      // eslint-disable-next-line no-console
      console.log(`${level}:  ${method}  ${message} [${statusCode}] ${duration ? `${duration / 1000000}ms` : ''}`);
      return callback();
    }
  })
];
// only add DatadogTransport if it is configured
if (config.has('datadog.apiKey')) {
  winstonTransports.push(new DatadogTransport({
    level: 'debug',
    apiKey: config.get('datadog.apiKey'),
    host: config.get('datadog.host'),
    port: config.get('datadog.port'),
    metadata: {
      env,
      host: `/esc/${env}-api`,
      service: 'api',
      ddsource: 'winston-datadog-transport'
    }
  }));
}
else {
  // if no datadog config is found then add a file transport
  winstonTransports.push(new winston.transports.DailyRotateFile({
    filename: 'logs/%DATE%-all.log',
    datePattern: 'YYYY-MM-DD',
    zippedArchive: true,
    maxSize: '20m',
    maxFiles: '14d'
  }));
}
@Module({
  imports: [
    WinstonModule.forRoot({
      format: winston.format.json(),
      transports: winstonTransports
    })
  ]
})
export default class AppModule {}
```
Basically what we're doing here is using `winston` to write our log entries, on `production` env where we usually use `DataDog` logs are sent there. However, on other envs we save logs to files on disk.  
In order to query these logs (the ones stored in files on disk) we use a small cli called `log-cli` which is included in the `@alphaapps/nestjs-common` package
```
sami@Samis-MacBook-Pro project % npx log-cli
Usage: log-cli read:logs

Commands:
  log-cli read:logs  Query logs from winston

Options:
  --help     Show help                                                 [boolean]
  --version  Show version number                                       [boolean]
  --dir      Folder where the logs are              [string] [default: "./logs"]
  --url      URL to match                                               [string]
  --method   Filter by HTTP method
                              [choices: "get", "post", "put", "patch", "delete"]
  --limit    Number of records returned                   [number] [default: 10]
  --sort     Sort the list            [choices: "desc", "asc"] [default: "desc"]
  --last     Show result from last period              [string] [default: "15d"]
  --from     From date (ISO Date)                                       [string]
  --to       To date (ISO Date)                                         [string]
  --userId   Filter by user id                                          [number]
  --level    Fetch only a specific level
                                    [string] [choices: "debug", "info", "error"]
  --status   Filter by response status code                             [number]

Examples:
  read:logs  npx log-cli read:logs --dir='./logs' --limit=20 --last=2d
             --to=2020-03-01T10:00:00

copyright Alpha-Apps@2020

Not enough non-option arguments: got 0, need at least 1
```
