Commit 5937393b authored by Fabien Forestier's avatar Fabien Forestier
Browse files

Merge branch 'development' into 'master'

Development

See merge request refonte-data/service-organizations!1
parents e80f0aca 41acabeb
Pipeline #2385 passed with stage
in 13 seconds
......@@ -9,6 +9,8 @@ variables:
build_development:
stage: build
tags:
- build
only:
- development
script:
......@@ -21,6 +23,8 @@ build_development:
build_release:
stage: build
tags:
- build
only:
- tags
except:
......
favicon.ico

4.19 KB

This diff is collapsed.
{
"name": "service-organizations",
"version": "1.0.0",
"version": "1.1.0",
"description": "description",
"author": "",
"license": "MIT",
......@@ -38,6 +38,8 @@
"postgresql": "0.0.1",
"reflect-metadata": "^0.1.12",
"rxjs": "^6.2.2",
"serve-favicon": "^2.5.0",
"swagger-stats": "^0.95.7",
"typeorm": "^0.2.7",
"typescript": "^3.0.1"
},
......@@ -76,4 +78,4 @@
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
}
\ No newline at end of file
......@@ -6,4 +6,4 @@ import { LinksModule } from './links/links.module';
@Module({
imports: [TypeOrmModule.forRoot(), OrganizationsModule, LinksModule],
})
export class AppModule {}
export class AppModule { }
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
import { IsString, IsOptional } from 'class-validator';
import { Organization } from '../organizations/organization.entity';
import { ApiModelProperty } from '@nestjs/swagger';
@Entity('link')
export class LinkEntity {
@PrimaryGeneratedColumn()
@ApiModelProperty()
id: number;
@IsString()
@IsOptional()
@Column({ length: 100, nullable: true})
@Column({ length: 100, nullable: true })
@ApiModelProperty()
name: string;
@IsString()
@Column({ length: 300})
@Column({ length: 300 })
@ApiModelProperty()
url: string;
@Column({nullable: true})
@Column({ nullable: true })
@IsOptional()
@ApiModelProperty()
organizationId: string;
@ManyToOne(type => Organization, organization => organization.links, { onDelete: 'CASCADE'})
@JoinColumn({name: 'organizationId'})
@ManyToOne(type => Organization, organization => organization.links, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'organizationId' })
@ApiModelProperty()
organization: Organization;
}
......
......@@ -2,11 +2,17 @@ import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ValidationPipe } from '@nestjs/common';
import * as fs from 'fs';
import * as swStats from 'swagger-stats';
import * as favicon from 'serve-favicon';
import * as path from 'path';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({ credentials: true, origin: true, exposedHeaders: 'Content-range'});
app.enableCors({ credentials: true, origin: true, exposedHeaders: 'Content-range' });
app.use(favicon(path.join(__dirname, '../favicon.ico')));
const options = new DocumentBuilder()
.setBasePath('')
.setTitle('Organizations service API')
......@@ -14,9 +20,13 @@ async function bootstrap() {
.setVersion('0.1')
.build();
const document = SwaggerModule.createDocument(app, options);
fs.writeFileSync('./swagger-spec.json', JSON.stringify(document));
SwaggerModule.setup('api', app, document);
// Enable stats middleware based on the generated swagger document
app.use(swStats.getMiddleware(document));
// Set endpoint serving the swagger documentation
SwaggerModule.setup('api-doc', app, document);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
......
import { Organization } from './organization.entity';
import { Link } from '../links/link.dto';
import { ApiModelProperty, ApiModelPropertyOptional } from '@nestjs/swagger';
import { IsString, IsOptional, IsArray, MaxLength, IsDefined } from 'class-validator';
import { isString } from 'util';
import { IsString, IsOptional, IsArray, MaxLength } from 'class-validator';
export class OrganizationDTO {
@ApiModelProperty()
......@@ -23,7 +22,7 @@ export class OrganizationDTO {
@IsString()
logo?: string;
@ApiModelPropertyOptional({type: [Link]})
@ApiModelPropertyOptional({ type: [Link] })
@IsOptional()
@IsArray()
links?: [Link];
......
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, Generated } from 'typeorm';
import { LinkEntity } from '../links/link.entity';
import { ApiModelProperty } from '@nestjs/swagger';
@Entity('organization')
export class Organization {
@PrimaryGeneratedColumn('uuid')
@ApiModelProperty()
id: string;
@Column({ length: 200 })
@ApiModelProperty()
name: string;
@Column({ length: 100, nullable: true })
@ApiModelProperty()
elasticSearchName: string;
@Column('text')
@ApiModelProperty()
description: string;
@OneToMany(type => LinkEntity, link => link.organization, {
eager: true,
cascade: true,
})
})
@ApiModelProperty()
links: LinkEntity[];
@Column ('text', {nullable: true})
@Column('text', { nullable: true })
@ApiModelProperty()
logo: string;
}
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, UploadedFile, HttpCode } from '@nestjs/common';
import { OrganizationsService } from './organizations.service';
import { Organization } from './organization.entity';
import { ApiUseTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { OrganizationDTO } from './organization.dto';
import { Organization } from './organization.entity';
import { LinkEntity } from '../links/link.entity';
@ApiUseTags('organizations')
@Controller('organizations')
......@@ -13,9 +14,9 @@ export class OrganizationsController {
) { }
@ApiOperation({ title: 'Get all organizations' })
@ApiResponse({ status: 200, description: 'Return all organizations.' })
@ApiResponse({ status: 200, description: 'Return all organizations.', type: Organization, isArray: true })
@Get()
async findAll(@Query() query, @Response() res): Promise<OrganizationDTO[]> {
async findAll(@Query() query, @Response() res): Promise<Organization[]> {
const { organizations, organizationsCount } = await this.organizationsService.findAll(query);
res.append('Content-range', organizationsCount);
res.send(organizations);
......@@ -23,44 +24,38 @@ export class OrganizationsController {
}
@ApiOperation({ title: 'Get one organization' })
@ApiResponse({ status: 200, description: 'Return one organization.' })
@ApiResponse({ status: 200, description: 'Return one organization.', type: Organization })
@Get(':id')
findOne(@Param('id') id: number) {
findOne(@Param('id') id: number): Promise<Organization> {
return this.organizationsService.findOne(id);
}
@ApiResponse({ status: 200, description: 'Return the links of an organization.', type: LinkEntity, isArray: true })
@Get(':id/links')
findLinks(@Param('id') id: number) {
findLinks(@Param('id') id: number): Promise<LinkEntity[]> {
return this.organizationsService.findLinks(id);
}
@ApiOperation({ title: 'Create one organization' })
@ApiResponse({ status: 201, description: 'The record has been successfully created.' })
@ApiResponse({ status: 201, description: 'The record has been successfully created.', type: Organization })
@ApiResponse({ status: 400, description: 'The values of one or many fields are not correct.' })
@Post()
async create(@Body() organizationDTo: OrganizationDTO) {
return this.organizationsService.create(organizationDTo).then((organization) => {
return organization;
}).catch((err) => {
throw err;
});
create(@Body() organizationDTo: OrganizationDTO) {
return this.organizationsService.create(organizationDTo);
}
@ApiOperation({ title: 'Update one organization' })
@ApiResponse({ status: 201, description: 'The record has been successfully updated.' })
@ApiResponse({ status: 200, description: 'The record has been successfully updated.', type: Organization })
@ApiResponse({ status: 400, description: 'No item exists with the given id.' })
@Put(':id')
async update(@Param('id') id: number, @Body() organizationDTo: OrganizationDTO) {
return this.organizationsService.update(id, organizationDTo).then((organization) => {
return organization;
}).catch((err) => {
throw err;
});
update(@Param('id') id: number, @Body() organizationDTo: OrganizationDTO) {
return this.organizationsService.update(id, organizationDTo);
}
@ApiOperation({ title: 'Delete one organization' })
@ApiResponse({ status: 400, description: 'No item exists with the given id.' })
@ApiResponse({ status: 204, description: 'The organization has been deleted successfully.' })
@HttpCode(204)
@Delete(':id')
delete(@Param('id') id: number) {
return this.organizationsService.delete(id);
......
......@@ -7,8 +7,11 @@ import { LinksService } from '../links/links.service';
import { LinksModule } from '../links/links.module';
@Module({
imports: [TypeOrmModule.forFeature([Organization]), LinksModule],
imports: [
TypeOrmModule.forFeature([Organization]),
LinksModule,
],
controllers: [OrganizationsController],
providers: [OrganizationsService, LinksService],
})
export class OrganizationsModule {}
export class OrganizationsModule { }
import { Injectable, Logger, BadRequestException, HttpStatus } from '@nestjs/common';
import { Injectable, Logger, InternalServerErrorException, NotFoundException, HttpService } from '@nestjs/common';
import { Organization } from './organization.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { LinksService } from '../links/links.service';
......@@ -15,62 +15,140 @@ export class OrganizationsService {
) { }
async findAll(query?): Promise<OrganizationsRO> {
const qb = await this.organizationRepository
.createQueryBuilder('organization')
.leftJoinAndSelect('organization.links', 'links');
qb.where('1 = 1');
// Sorting
if ('sort_by' in query) {
const sort = query.sort_by ? query.sort_by.split('.') : ['name', 'ASC'];
sort[1] = sort[1].toUpperCase();
qb.orderBy('organization.' + sort[0], sort[1]);
try {
const qb = await this.organizationRepository
.createQueryBuilder('organization')
.leftJoinAndSelect('organization.links', 'links');
qb.where('1 = 1');
// Sorting
if ('sort_by' in query) {
const sort = query.sort_by ? query.sort_by.split('.') : ['name', 'ASC'];
sort[1] = sort[1].toUpperCase();
qb.orderBy('organization.' + sort[0], sort[1]);
}
const organizationsCount = await qb.getCount().catch((error) => {
Logger.error(`Error while getting the count of organizations.`);
Logger.error(error);
throw new InternalServerErrorException({ error, message: 'Error while getting the count of organizations.' });
});
if ('limit' in query) {
qb.limit(query.limit);
}
if ('offset' in query) {
qb.offset(query.offset);
}
const organizations = await qb.getMany().catch((error) => {
Logger.error(`Error while getting the list of organizations.`);
Logger.error(error);
throw new InternalServerErrorException({ error, message: 'Error while getting the list of organizations.' });
});
return { organizations, organizationsCount };
} catch (error) {
throw error;
}
const organizationsCount = await qb.getCount();
if ('limit' in query) {
qb.limit(query.limit);
}
if ('offset' in query) {
qb.offset(query.offset);
}
const organizations = await qb.getMany();
return {organizations, organizationsCount};
}
async findOne(id) {
return await this.organizationRepository.findOne(id);
try {
const organization = await this.organizationRepository.findOne(id).catch((error) => {
Logger.error(`Error while looking the organization with id ${id}.`);
Logger.error(error);
throw new InternalServerErrorException({ error, message: 'Error while looking the organization with id ${id}.' });
});
return organization;
} catch (error) {
throw error;
}
}
async findLinks(id) {
const organization = await this.organizationRepository.findOne(id);
return organization.links;
try {
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);
throw new InternalServerErrorException({ error, message: 'Error while looking for the link of the organization with id ${id}.' });
});
if (!organization) {
throw new NotFoundException({ message: 'No organization with such id have been found' });
}
return organization.links;
} catch (error) {
throw error;
}
}
async create(organization: OrganizationDTO) {
const organizationCreated = await this.organizationRepository.save(organization);
return organizationCreated;
async create(organization) {
try {
// 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);
throw new InternalServerErrorException({ error, message: 'Error saving the organization into the database.' });
});
Logger.log('Organization created:');
Logger.log(organizationCreated);
return organizationCreated;
} catch (error) {
Logger.error('Failed to create the organization.');
Logger.error(error);
throw error;
}
}
async update(id, organization: OrganizationDTO) {
const toUpdate = await this.organizationRepository.findOne(id);
if (! toUpdate) {
throw new BadRequestException('No item exists with the given id');
try {
const toUpdate = await this.organizationRepository.findOne(id).catch((error) => {
Logger.error(`Error while looking for the organization with the id ${id}.`);
Logger.error(error);
throw new InternalServerErrorException({ error, message: 'Error while looking for the organization.' });
});
if (!toUpdate) {
throw new NotFoundException({ message: 'No item exists with the given id' });
}
const updated = Object.assign(toUpdate, organization);
const saved = await this.organizationRepository.save(updated).catch((error) => {
Logger.error(`Error while updating the organization.`);
Logger.error(error);
throw new InternalServerErrorException({ error, message: 'Error while updating the organization.' });
});
this.linksService.deleteOrphans();
return saved;
} catch (error) {
throw error;
}
let updated = Object.assign(toUpdate, organization);
updated = await this.organizationRepository.save(updated);
this.linksService.deleteOrphans();
return updated;
}
async delete(id) {
return await this.organizationRepository.delete(id);
async delete(id): Promise<any> {
try {
const toDelete = await this.organizationRepository.findOne(id);
if (!toDelete) {
throw new NotFoundException({ message: 'No organization exists with the given id' });
}
return await this.organizationRepository.delete(id).catch((error) => {
Logger.error(`Error while deleting the organization with id ${id}.`);
Logger.error(error);
throw new InternalServerErrorException({ error, message: 'Error while deleting the organization with id ${id}.' });
});
} catch (error) {
throw error;
}
}
}
{"swagger":"2.0","info":{"description":"The actors API description","version":"1.0","title":"Actors example"},"basePath":"/localhost:3001","tags":[],"schemes":["http"],"paths":{"/organizations":{"get":{"summary":"Get all organizations","responses":{"200":{"description":""}},"tags":["organizations"],"produces":["application/json"],"consumes":["application/json"]},"post":{"summary":"Create one organization","parameters":[{"name":"Organization","required":true,"in":"body","schema":{"$ref":"#/definitions/Organization"}}],"responses":{"201":{"description":"The record has been successfully created."}},"tags":["organizations"],"produces":["application/json"],"consumes":["application/json"]}}},"definitions":{"Organization":{"type":"object","properties":{"id":{"type":"number"},"name":{"type":"string"},"description":{"type":"string"},"website":{"type":"string"},"logo":{"type":"string"}},"required":["id","name","description"]}}}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment