Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tenant management #82

Merged
merged 40 commits into from
Oct 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
cc2ac4b
Updated BaseEntity navigation properties to reference User type
ofuochi Oct 6, 2019
402d8c1
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 6, 2019
a435bb4
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 7, 2019
5f8d29c
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 7, 2019
2cc3314
Used auto mapper's array mapper for mapping array to DTO
ofuochi Oct 7, 2019
b4697ef
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 7, 2019
dd78def
Removed vscode local settings from version control
ofuochi Oct 7, 2019
06b1211
Updated createdBy field in user entity
ofuochi Oct 7, 2019
210a878
Added user auto mapper profile
ofuochi Oct 7, 2019
badf73e
Added user auto mapper profile
ofuochi Oct 7, 2019
0715bcb
Updated package.json and set tenant and user globally. Closes #16, cl…
ofuochi Oct 7, 2019
4f888a5
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 7, 2019
198c030
Implement save or update feature. Closes #11
ofuochi Oct 8, 2019
d9fa7a8
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 8, 2019
7b1aeb3
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 8, 2019
8d81c5a
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 8, 2019
641f509
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 8, 2019
48a9b72
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 8, 2019
ff62437
Moved hooks to base entity class and implemented soft-delete
ofuochi Oct 8, 2019
69d225d
Implement soft delete feature. Closes #56
ofuochi Oct 9, 2019
618cef8
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 9, 2019
0c4f979
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 9, 2019
d3776e2
Fixed #63, and refactor some part of test files (#14)
ofuochi Oct 10, 2019
875aa53
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 10, 2019
18d40e2
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 10, 2019
2748d20
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 13, 2019
e28852e
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 18, 2019
00116eb
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 18, 2019
c6756ba
Simplify returned entity in signUp
ofuochi Oct 18, 2019
d87688f
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 18, 2019
0bdd9ba
Removed global variables and wrote UserController tests
ofuochi Oct 19, 2019
9ee3a6b
Correct model class extensions
ofuochi Oct 19, 2019
cf92222
Implement user management by admin
ofuochi Oct 20, 2019
d2df905
Merge master
ofuochi Oct 20, 2019
5feb8cc
Merge user-management branch
ofuochi Oct 20, 2019
d99636c
User management
ofuochi Oct 21, 2019
91f0897
Merge branch 'master' of https://github.com/ofuochi/node-typescript-t…
ofuochi Oct 21, 2019
a8a0c7a
Merge remote master
ofuochi Oct 21, 2019
3391dac
Added paged response class
ofuochi Oct 21, 2019
4968ba6
Tenant management
ofuochi Oct 21, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/domain/model/tenant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ export class Tenant extends BaseEntity {

@instanceMethod
update(tenant: Partial<this>): void {
if (this.name) this.setName(tenant.name as string);
if (this.description) this.setDescription(tenant.description as string);
if (tenant.name)
this.setName(tenant.name
.replace(/\s/g, "")
.toUpperCase() as string);
if (tenant.description)
this.setDescription(tenant.description as string);
}
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import "reflect-metadata";
import { bootstrap } from "./infrastructure/bootstrapping";
import { winstonLoggerInstance } from "./infrastructure/bootstrapping/loaders/logger";
import { config } from "./infrastructure/config";
import { referenceDataIoCModule } from "./infrastructure/config/inversify.config";
import { iocContainer } from "./infrastructure/config/ioc";
Expand All @@ -16,6 +17,7 @@ export async function startServer(connStr: string, port: number) {
containerModules: [referenceDataIoCModule]
});
startAppServer(app, port);
winstonLoggerInstance.info(`✔️ Server listening on port: ${port}\n`);
} catch (error) {
exitProcess(error);
throw error;
Expand Down
1 change: 0 additions & 1 deletion src/infrastructure/utils/server_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export function startAppServer(app: App, serverPort?: number): Server {
return app.listen(port, (error: any) => {
if (error) exitProcess(error);
iocContainer.bind<App>(TYPES.App).toConstantValue(app);
log.info(`✔️ Server listening on port: ${port}\n`);
});
}
export function isIdValid(id: string): boolean {
Expand Down
36 changes: 34 additions & 2 deletions src/ui/api/controllers/tenant_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,26 @@ import http from "http";
import { plainToClass } from "class-transformer";
import httpStatus from "http-status-codes";
import { inject } from "inversify";
import { Body, Delete, Get, Post, Route, Security, Tags, Response } from "tsoa";
import {
Body,
Delete,
Get,
Post,
Route,
Security,
Tags,
Response,
Put
} from "tsoa";
import { provideSingleton } from "../../../infrastructure/config/ioc";
import { isIdValid } from "../../../infrastructure/utils/server_utils";
import { HttpError } from "../../error";
import { ITenantService } from "../../interfaces/tenant_service";
import { CreateTenantInput, TenantDto } from "../../models/tenant_dto";
import {
CreateTenantInput,
TenantDto,
TenantUpdateInput
} from "../../models/tenant_dto";
import { TenantService } from "../../services/tenant_service";
import { BaseController } from "./base_controller";

Expand All @@ -23,6 +37,11 @@ export class TenantController extends BaseController {
if (!tenant) throw new HttpError(httpStatus.NOT_FOUND);
return tenant;
}
@Get()
@Security("X-Auth-Token", ["admin"])
public async getAll(): Promise<TenantDto[]> {
return this._tenantService.getAll();
}
@Post()
@Security("X-Auth-Token", ["admin"])
@Response(httpStatus.FORBIDDEN, http.STATUS_CODES[httpStatus.FORBIDDEN])
Expand All @@ -31,6 +50,19 @@ export class TenantController extends BaseController {
await this.checkConflict(await this._tenantService.get(input.name));
return this._tenantService.create(input.name, input.description);
}
@Put("{id}")
@Security("X-Auth-Token", ["admin"])
public async update(
id: string,
@Body() input: TenantUpdateInput
): Promise<void> {
this.checkUUID(id);

if (!input) return this.setStatus(httpStatus.NO_CONTENT);
await this.checkBadRequest(plainToClass(TenantUpdateInput, input));
input = JSON.parse(JSON.stringify(input));
await this._tenantService.update({ ...input, id });
}
@Delete("{id}")
@Security("X-Auth-Token", ["admin"])
public async delete(id: string): Promise<void> {
Expand Down
61 changes: 61 additions & 0 deletions src/ui/api/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ const models: TsoaRoute.Models = {
"additionalProperties": false,
},
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
"TenantUpdateInput": {
"dataType": "refObject",
"properties": {
"name": { "dataType": "string" },
"description": { "dataType": "string" },
},
"additionalProperties": false,
},
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
"UserUpdateInput": {
"dataType": "refObject",
"properties": {
Expand Down Expand Up @@ -175,6 +184,31 @@ export function RegisterRoutes(app: express.Express) {
promiseHandler(controller, promise, response, next);
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/api/v1/tenants',
authenticateMiddleware([{ "X-Auth-Token": ["admin"] }]),
function(request: any, response: any, next: any) {
const args = {
};

// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa

let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request);
} catch (err) {
return next(err);
}

const controller = iocContainer.get<TenantController>(TenantController);
if (typeof controller['setStatus'] === 'function') {
(<any>controller).setStatus(undefined);
}


const promise = controller.getAll.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, next);
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/api/v1/tenants',
authenticateMiddleware([{ "X-Auth-Token": ["admin"] }]),
function(request: any, response: any, next: any) {
Expand All @@ -201,6 +235,33 @@ export function RegisterRoutes(app: express.Express) {
promiseHandler(controller, promise, response, next);
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.put('/api/v1/tenants/:id',
authenticateMiddleware([{ "X-Auth-Token": ["admin"] }]),
function(request: any, response: any, next: any) {
const args = {
id: { "in": "path", "name": "id", "required": true, "dataType": "string" },
input: { "in": "body", "name": "input", "required": true, "ref": "TenantUpdateInput" },
};

// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa

let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request);
} catch (err) {
return next(err);
}

const controller = iocContainer.get<TenantController>(TenantController);
if (typeof controller['setStatus'] === 'function') {
(<any>controller).setStatus(undefined);
}


const promise = controller.update.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, next);
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.delete('/api/v1/tenants/:id',
authenticateMiddleware([{ "X-Auth-Token": ["admin"] }]),
function(request: any, response: any, next: any) {
Expand Down
3 changes: 3 additions & 0 deletions src/ui/interfaces/tenant_service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { TenantDto } from "../models/tenant_dto";
import { Tenant } from "../../domain/model/tenant";

export interface ITenantService {
update(tenant: Partial<Tenant>): Promise<void>;
create(name: string, description: string): Promise<TenantDto>;
get(name: string): Promise<TenantDto | undefined>;
getAll(): Promise<TenantDto[]>;
delete(id: string): Promise<boolean>;
search(): Promise<TenantDto[]>;
}
4 changes: 4 additions & 0 deletions src/ui/models/base_dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ export abstract class BaseEntityDto {
@Expose()
id!: string;
}
export abstract class PagedResultDto<T> {
totalCount: number;
items: [T];
}
export abstract class BaseUpdateDto extends BaseEntityDto {}
19 changes: 18 additions & 1 deletion src/ui/models/tenant_dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { IsNotEmpty, MaxLength, IsString, IsBoolean } from "class-validator";
import {
IsNotEmpty,
MaxLength,
IsString,
IsBoolean,
IsOptional
} from "class-validator";
import { Expose } from "class-transformer";

import { MAX_NAME_LENGTH } from "../../domain/model/user";
Expand All @@ -13,6 +19,17 @@ export class CreateTenantInput extends BaseCreateEntityDto {
@IsNotEmpty()
description: string;
}
export class TenantUpdateInput {
@MaxLength(MAX_NAME_LENGTH)
@IsNotEmpty()
@IsOptional()
@IsString()
name?: string;
@MaxLength(MAX_NAME_LENGTH)
@IsOptional()
@IsNotEmpty()
description?: string;
}
export class TenantDto extends BaseEntityDto {
@MaxLength(MAX_NAME_LENGTH)
@IsNotEmpty()
Expand Down
22 changes: 22 additions & 0 deletions src/ui/services/tenant_service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import httpStatus from "http-status-codes";
import { plainToClass } from "class-transformer";
import { ITenantRepository } from "../../domain/interfaces/repositories";
import { Tenant } from "../../domain/model/tenant";
import { provideSingleton, inject } from "../../infrastructure/config/ioc";
import { ITenantService } from "../interfaces/tenant_service";
import { TenantDto } from "../models/tenant_dto";
import { TenantRepository } from "../../infrastructure/db/repositories/tenant_repository";
import { HttpError } from "../error";

@provideSingleton(TenantService)
export class TenantService implements ITenantService {
Expand All @@ -15,10 +17,30 @@ export class TenantService implements ITenantService {
Tenant.createInstance(name, description)
);
return plainToClass(TenantDto, tenant, {
enableImplicitConversion: true,
excludeExtraneousValues: true
});
}
async update(tenant: Partial<Tenant>): Promise<void> {
const tenantToUpdate = await this._tenantRepository.findById(tenant.id);
if (!tenantToUpdate)
throw new HttpError(
httpStatus.NOT_FOUND,
`Tenant with ID "${tenant.id}" does not exist`
);

// check that tenantToUpdate does not overwrite an existing tenant name

tenantToUpdate.update(tenant);
await this._tenantRepository.insertOrUpdate(tenantToUpdate);
}
async getAll(): Promise<TenantDto[]> {
const tenants = await this._tenantRepository.findAll();
return plainToClass<TenantDto, Tenant>(TenantDto, tenants, {
enableImplicitConversion: true,
excludeExtraneousValues: true
});
}
async get(name: string): Promise<TenantDto | undefined> {
const tenant = await this._tenantRepository.findOneByQuery({ name });
const tenantDto =
Expand Down
22 changes: 0 additions & 22 deletions src/ui/services/user_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,28 +72,6 @@ export class UserService implements IUserService {
`User with ID "${user.id}" does not exist`
);

// check that userToUpdate does not overwrite an existing email or username
let existingUser: User;
if (user.email) {
existingUser = await this._userRepository.findOneByQuery({
email: user.email
});
if (existingUser)
throw new HttpError(
httpStatus.CONFLICT,
`User with email "${user.email}" already exist`
);
}
if (user.username) {
existingUser = await this._userRepository.findOneByQuery({
username: user.username
});
if (existingUser)
throw new HttpError(
httpStatus.CONFLICT,
`User with username "${user.username}" already exist`
);
}
userToUpdate.update(user);
await this._userRepository.insertOrUpdate(userToUpdate);
}
Expand Down
Loading