Commit c6e76b81 authored by Fabien Forestier's avatar Fabien Forestier
Browse files

Merge branch 'development' into 'master'

Version 2.0.1

See merge request refonte-data/service-organizations!4
parents df0ddc83 ed21649e
Pipeline #2376 passed with stage
in 7 seconds
stages:
- build
- deploy
- db_migration
variables:
GROUP_HEADER: x-consumer-groups
......
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo_text.svg" width="320" alt="Nest Logo" /></a>
</p>
[travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master
[travis-url]: https://travis-ci.org/nestjs/nest
[linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
[linux-url]: https://travis-ci.org/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="blank">Node.js</a> framework for building efficient and scalable server-side applications, heavily inspired by <a href="https://angular.io" target="blank">Angular</a>.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore"><img src="https://img.shields.io/npm/dm/@nestjs/core.svg" alt="NPM Downloads" /></a>
<a href="https://travis-ci.org/nestjs/nest"><img src="https://api.travis-ci.org/nestjs/nest.svg?branch=master" alt="Travis" /></a>
<a href="https://travis-ci.org/nestjs/nest"><img src="https://img.shields.io/travis/nestjs/nest/master.svg?label=linux" alt="Linux" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#5" alt="Coverage" /></a>
<a href="https://gitter.im/nestjs/nestjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge"><img src="https://badges.gitter.im/nestjs/nestjs.svg" alt="Gitter" /></a>
<a href="https://opencollective.com/nest#backer"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec"><img src="https://img.shields.io/badge/Donate-PayPal-dc3d53.svg"/></a>
<a href="https://twitter.com/nestframework"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
......@@ -35,16 +5,19 @@ $ npm install
```
## Environment variables
To avoid to hard code some variables inside the .ts files (credentials, endpoints), we use environment variables.
Here is the flow:
- set the NODE_ENV variable inside the gitlab-ci.yml
- this will define the env_file used by docker-compose.yml (eg. configuration/dev.env or configuration/production.env)
- also this variable (NODE_ENV) is used by the Nestjs application to define the .env file (see config.module.ts)
For development purpose, the files dev.env and production.env are added to the Git project.
But this will have to be removed when the project goes to production.
In order to run the code, some environment variables are needed. They are specified in the `template.env` file at the root of the project.
For a local deployment:
1. `cp template.env .env`
2. Edit .env according to the chosen configuration
The values will be read from the file by default, but you can override any of those by exporting manually the variable before launching the service.
## Running the app without docker
## Running the app
You will need to provide a healthy connection to a database in order for the service to start.
```bash
# development
......@@ -53,15 +26,24 @@ $ npm run start
# watch mode
$ npm run start:dev
# incremental rebuild (webpack)
$ npm run webpack
$ npm run start:hmr
# production mode
$ npm run start:prod
```
## Test
## Running the app with docker
```bash
# build
$ docker-compose build
# deploy
$ docker-compose up [-d]
# build and deploy
$ docker-compose up --build [-d]
```
<!-- ## Test
```bash
# unit tests
......@@ -72,18 +54,6 @@ $ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
``` -->
## License
Nest is [MIT licensed](LICENSE).
......@@ -8,11 +8,11 @@ services:
ports:
- ${ORGANIZATIONS_SERVICE_BIND_PORT}:3000
environment:
NODE_ENV: ${NODE_ENV}
POSTGRES_USER: user
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: postgres
POSTGRES_HOST: database-organizations
POSTGRES_PORT: 5432
GROUP_HEADER: ${GROUP_HEADER}
ADMIN_GROUP_NAME: ${ADMIN_GROUP_NAME}
restart: unless-stopped
......
favicon.ico

4.19 KB | W: | H:

favicon.ico

31.3 KB | W: | H:

favicon.ico
favicon.ico
favicon.ico
favicon.ico
  • 2-up
  • Swipe
  • Onion skin
......@@ -10,28 +10,20 @@ if (process.env.PRODBUILD) {
root = 'dist';
}
// Only for development purpose, set environment variable based on the env file provided
if (process.env.NODE_ENV === 'LOCAL') {
const envConfig = dotenv.parse(fs.readFileSync(`./${process.env.NODE_ENV}.env`));
for (const k in envConfig) {
if (envConfig.hasOwnProperty(k)) {
process.env[k] = envConfig[k];
}
}
}
const ormConfig = {
type: 'postgres',
host: process.env.POSTGRES_HOST,
port: process.env.POSTGRES_PORT,
username: 'user',
username: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
database: 'postgres',
database: process.env.POSTGRES_DB,
entities: [`${root}/**/**.entity{.ts,.js}`],
synchronize: false,
migrations: [`${root}/migrations/*.ts`],
cli: { migrationsDir: `${root}/migrations` },
logging: true,
logging: [
'error',
],
};
module.exports = ormConfig;
{
"name": "service-organizations",
"version": "1.2.0",
"version": "2.0.1",
"description": "description",
"author": "",
"license": "MIT",
......
import { Logger } from '@nestjs/common';
export class AppLogger extends Logger {
log(message: string, context?: string) {
// add your tailored logic here
super.log(`[log] ${message}`, context);
}
warn(message: string, context?: string) {
// add your tailored logic here
super.warn(`[warn] ${message}`, context);
}
error(message: string, trace?: string, context?: string) {
// add your tailored logic here
super.error(`[error] ${message}`, trace, context);
}
}
\ No newline at end of file
......@@ -6,6 +6,7 @@ import { HealthModule } from './health/health.module';
import { ConfigModule } from './configuration/config.module';
import { APP_GUARD } from '@nestjs/core';
import { GroupsGuard } from './guards/groups.guards';
import { AppLogger } from './app-logger';
@Module({
imports: [
......@@ -16,6 +17,7 @@ import { GroupsGuard } from './guards/groups.guards';
HealthModule,
],
providers: [
AppLogger,
{
provide: APP_GUARD,
useClass: GroupsGuard,
......
......@@ -7,16 +7,6 @@ export class ConfigService {
private _config = config;
constructor() {
// Only for development purpose, set environment variable based on the env file provided
if (process.env.NODE_ENV === 'LOCAL') {
const envConfig = dotenv.parse(fs.readFileSync(`./src/configuration/${process.env.NODE_ENV}.env`));
for (const k in envConfig) {
if (envConfig.hasOwnProperty(k)) {
process.env[k] = envConfig[k];
}
}
}
// Initializing conf with values from var env
this._config.groupNames.admin = process.env.ADMIN_GROUP_NAME;
this._config.groupHeader = process.env.GROUP_HEADER;
......
GROUP_HEADER=
ADMIN_GROUP_NAME=
\ No newline at end of file
......@@ -4,10 +4,18 @@ import { ConfigService } from '../configuration/config.service';
@Injectable()
export class GroupsGuard implements CanActivate {
constructor(private readonly reflector: Reflector, private configService: ConfigService) { }
private logger: Logger;
constructor(
private readonly reflector: Reflector,
private configService: ConfigService,
) {
this.logger = new Logger(GroupsGuard.name);
}
canActivate(context: ExecutionContext): boolean {
Logger.log('[+] GroupMiddleware');
this.logger.log('Entering function', `${GroupsGuard.name} - ${this.canActivate.name}`);
// We get the groups specified on the endpoint with the @Groups annotation
// We need then to get the real group names associated to those keys from the config file
let groups = this.reflector.get<string[]>('groups', context.getHandler());
......@@ -31,13 +39,13 @@ export class GroupsGuard implements CanActivate {
const groupHeader = request.headers[this.configService.config.groupHeader];
if (!groupHeader) {
Logger.log(' [-] no group header');
this.logger.log('No group header found in the request');
return false;
}
const consumerGroups = groupHeader.split(',').map(e => e.trim());
Logger.log(` [-] consumer groups: ${consumerGroups}`);
this.logger.log(`Consumer groups: ${consumerGroups}`);
const hasGroup = () => consumerGroups.some((group) => groups.includes(group));
......
import { Controller, Get, Body, Post, Param, Delete, Put, Query, Response, Request, Req, Res } from '@nestjs/common';
import { Controller, Get, Body, Post, Param, Delete, Put, Query, Response } from '@nestjs/common';
import { LinksService } from './links.service';
import { LinkEntity } from './link.entity';
import { ApiUseTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
......@@ -35,11 +35,7 @@ export class LinksController {
@Post()
@Groups('admin')
async create(@Body() linkDTo: Link) {
return this.linksService.create(linkDTo).then((link) => {
return link;
}).catch((err) => {
return err;
});
return this.linksService.create(linkDTo);
}
@ApiOperation({ title: 'Update one link' })
......
import { Injectable, Logger } from '@nestjs/common';
import { Injectable, Logger, InternalServerErrorException, NotFoundException } from '@nestjs/common';
import { LinkEntity, LinksRO } from './link.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Link } from './link.dto';
......@@ -7,70 +7,144 @@ import { LinkRepository } from './link.repository';
@Injectable()
export class LinksService {
private logger: Logger;
constructor(
@InjectRepository(LinkEntity)
private linkRepository: LinkRepository,
) { }
) {
this.logger = new Logger(LinksService.name);
}
async findAll(query): Promise<LinksRO> {
const qb = await this.linkRepository
.createQueryBuilder('link');
qb.where('1 = 1');
if (query.where) {
qb.andWhere(query.where);
}
try {
this.logger.log('Entering function', `${LinksService.name} - ${this.findAll.name}`);
// Sorting
const sort = query.sort_by ? query.sort_by.split('.') : ['name', 'ASC'];
qb.orderBy(sort[0], sort[1]);
const qb = await this.linkRepository
.createQueryBuilder('link');
qb.where('1 = 1');
if ('filter' in query) {
const filterJSON = JSON.parse(query.filter);
if (filterJSON.organizationId) {
qb.where('"organizationId" = ' + filterJSON.organizationId);
if (query.where) {
qb.andWhere(query.where);
}
}
const linksCount = await qb.getCount();
// Sorting
const sort = query.sort_by ? query.sort_by.split('.') : ['name', 'ASC'];
qb.orderBy(sort[0], sort[1]);
if ('limit' in query) {
qb.limit(query.limit);
}
if ('filter' in query) {
const filterJSON = JSON.parse(query.filter);
if (filterJSON.organizationId) {
qb.where('"organizationId" = ' + filterJSON.organizationId);
}
}
if ('offset' in query) {
qb.offset(query.offset);
}
const linksCount = await qb.getCount().catch((error) => {
this.logger.error(`Error while getting the count of links.`, `${error}`, `${LinksService.name} - ${this.findAll.name}`);
throw new InternalServerErrorException({ error, message: 'Error while getting the count of links.' });
});
const links = await qb.getMany();
if ('limit' in query) {
qb.limit(query.limit);
}
if ('offset' in query) {
qb.offset(query.offset);
}
return {links, linksCount};
const links = await qb.getMany().catch((error) => {
this.logger.error(`Error while getting the list of link.`, `${error}`, `${LinksService.name} - ${this.findAll.name}`);
throw new InternalServerErrorException({ error, message: 'Error while getting the list of links.' });
});
return { links, linksCount };
} catch (error) {
throw error;
}
}
async findOne(id) {
return await this.linkRepository.findOne(id);
try {
this.logger.log('Entering function', `${LinksService.name} - ${this.findOne.name}`);
return await this.linkRepository.findOne(id).catch((error) => {
this.logger.error(`Error while looking for the link with id ${id}.`, `${error}`, `${LinksService.name} - ${this.findOne.name}`);
throw new InternalServerErrorException({ error, message: 'Error while looking for the link with id ${id}.' });
});
} catch (error) {
throw error;
}
}
async create(link: Link) {
return await this.linkRepository.save(link);
try {
this.logger.log('Entering function', `${LinksService.name} - ${this.create.name}`);
return await this.linkRepository.save(link).catch((error) => {
this.logger.error(`Error saving the link into the database.`, `${error}`, `${LinksService.name} - ${this.create.name}`);
throw new InternalServerErrorException({ error, message: 'Error saving the link into the database.' });
});
} catch (error) {
throw error;
}
}
async update(id, link: Link) {
const toUpdate = await this.linkRepository.findOne(id);
const updated = Object.assign(toUpdate, link);
return await this.linkRepository.save(updated);
try {
this.logger.log('Entering function', `${LinksService.name} - ${this.update.name}`);
const toUpdate = await this.linkRepository.findOne(id).catch((error) => {
this.logger.error(`Error while looking for the link with the id ${id}.`, `${error}`, `${LinksService.name} - ${this.update.name}`);
throw new InternalServerErrorException({ error, message: 'Error while looking for the link.' });
});
const updated = Object.assign(toUpdate, link);
return await this.linkRepository.save(updated).catch((error) => {
this.logger.error(`Error while updating the resource.`, `${error}`, `${LinksService.name} - ${this.update.name}`);
throw new InternalServerErrorException({ error, message: 'Error while updating the link.' });
});
} catch (error) {
throw error;
}
}
async delete(id) {
return await this.linkRepository.delete(id);
try {
this.logger.log('Entering function', `${LinksService.name} - ${this.delete.name}`);
const toDelete = await this.linkRepository.findOne(id);
if (!toDelete) {
throw new NotFoundException({ message: 'No link exists with the given id' });
}
return await this.linkRepository.delete(id).catch((error) => {
this.logger.error(`Error while deleting the link with id ${id}.`, `${error}`, `${LinksService.name} - ${this.delete.name}`);
throw new InternalServerErrorException({ error, message: `Error while deleting the link with id ${id}.` });
});
} catch (error) {
throw error;
}
}
async deleteOrphans() {
const orphanRO = await this.findAll({where: '"organizationId" IS NULL'});
orphanRO.links.forEach(orphan => {
this.linkRepository.delete(orphan);
});
try {
this.logger.log('Entering function', `${LinksService.name} - ${this.deleteOrphans.name}`);
const orphanRO = await this.findAll({ where: '"organizationId" IS NULL' }).catch((error) => {
this.logger.error(`Error while looking for orphans`, error, `${LinksService.name} - ${this.deleteOrphans.name}`);
throw new InternalServerErrorException({ error, message: `Error while looking for orphans` });
});
orphanRO.links.forEach(orphan => {
this.linkRepository.delete(orphan).catch((error) => {
this.logger.error(`Error while deleting orphan with id: ${orphan}`, `${error}`, `${LinksService.name} - ${this.deleteOrphans.name}`);
throw new InternalServerErrorException({ error, message: `Error while deleting orphan with id: ${orphan}` });
});
});
} catch (error) {
throw error;
}
}
}
......@@ -5,9 +5,12 @@ import { ValidationPipe } from '@nestjs/common';
import * as swStats from 'swagger-stats';
import * as favicon from 'serve-favicon';
import * as path from 'path';
import { AppLogger } from './app-logger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const app = await NestFactory.create(AppModule, {
logger: false,
});
app.enableCors({ credentials: true, origin: true, exposedHeaders: 'Content-range' });
......@@ -29,6 +32,8 @@ async function bootstrap() {
app.useGlobalPipes(new ValidationPipe());
app.useLogger(app.get(AppLogger));
await app.listen(3000);
}
bootstrap();
......@@ -8,14 +8,20 @@ import { OrganizationRepository } from './organization.repository';
@Injectable()
export class OrganizationsService {
private logger: Logger;
constructor(
@InjectRepository(Organization)
private organizationRepository: OrganizationRepository,
private linksService: LinksService,
) { }
) {
this.logger = new Logger(OrganizationsService.name);
}
async findAll(query?): Promise<OrganizationsRO> {
try {
this.logger.log('Entering function', `${OrganizationsService.name} - ${this.findAll.name}`);
const qb = await this.organizationRepository
.createQueryBuilder('organization')
.leftJoinAndSelect('organization.links', 'links');
......@@ -29,8 +35,7 @@ export class OrganizationsService {
}
const organizationsCount = await qb.getCount().catch((error) => {
Logger.error(`Error while getting the count of organizations.`);
Logger.error(error);
this.logger.error(`Error while getting the count of organizations.`, `${error}`, `${OrganizationsService.name} - ${this.findAll.name}`);
throw new InternalServerErrorException({ error, message: 'Error while getting the count of organizations.' });
});
......@@ -43,8 +48,7 @@ export class OrganizationsService {
}
const organizations = await qb.getMany().catch((error) => {
Logger.error(`Error while getting the list of organizations.`);
Logger.error(error);
this.logger.error(`Error while getting the list of organizations.`, `${error}`, `${OrganizationsService.name} - ${this.findAll.name}`);
throw new InternalServerErrorException({ error, message: 'Error while getting the list of organizations.' });
});
......@@ -56,9 +60,10 @@ export class OrganizationsService {
async findOne(id) {
try {
this.logger.log('Entering function', `${OrganizationsService.name} - ${this.findOne.name}`);
const organization = await this.organizationRepository.findOne(id).catch((error) => {
Logger.error(`Error while looking the organization with id ${id}.`);
Logger.error(error);
this.logger.error(`Error while looking the organization with id ${id}.`, `${error}`, `${OrganizationsService.name} - ${this.findOne.name}`);
throw new InternalServerErrorException({ error, message: 'Error while looking the organization with id ${id}.' });
});
......@@ -70,9 +75,12 @@ export class OrganizationsService {
async findLinks(id) {
try {
this.logger.log('Entering function', `${OrganizationsService.name} - ${this.findLinks.name}`);
const organization = await this.organizationRepository.findOne(id).catch((error) => {
Logger.error(`Error while looking for the link of the organization with id ${id}.`);
Logger.error(error);
this.logger.error(
`Error while looking for the link of the organization with id ${id}.`, `${error}`, `${OrganizationsService.name} - ${this.findLinks.name}`,
);
throw new InternalServerErrorException({ error, message: 'Error while looking for the link of the organization with id ${id}.' });
});
......@@ -87,31 +95,32 @@ export class OrganizationsService {
async create(organization) {
try {
this.logger.log('Entering function', `${OrganizationsService.name} - ${this.create.name}`);
// Only save the file name and not the full url of the logo
// organization.logo = await this._fileManagementService.upload('local-actors-logo', logoImage);
const organizationCreated = await this.organizationRepository.save(organization).catch((error) => {
Logger.error(`Error saving the organization into the database.`);
Logger.error(error);
this.logger.error(`Error saving the organization into the database.`, `${error}`, `${OrganizationsService.name} - ${this.create.name}`);
throw new InternalServerErrorException({ error, message: 'Error saving the organization into the database.' });
});
Logger.log('Organization created:');
Logger.log(organizationCreated);
this.logger