diff --git a/package-lock.json b/package-lock.json index bf7b7ae..364c883 100644 --- a/package-lock.json +++ b/package-lock.json @@ -272,9 +272,9 @@ } }, "@types/hapi__joi": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@types/hapi__joi/-/hapi__joi-16.0.0.tgz", - "integrity": "sha512-WNQRIP65MmVIrLYk4JzRhwUm0jwN0cbeo7kzyQf8QuGAsQ6bgnRfRUipOY+b9RRtfA8xI9ZyM5E46rNSgUaB8g==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@types/hapi__joi/-/hapi__joi-16.0.1.tgz", + "integrity": "sha512-U4I9Pfk4qH6TCXlg55qOc3F730QndZi86hGWlZHPHY7b3J+MCDIAO6EejbkaLWk7U15v+jn2J3GRVKD4ruhS2Q==", "dev": true }, "@types/helmet": { @@ -357,9 +357,9 @@ } }, "@types/mongoose": { - "version": "5.5.19", - "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.5.19.tgz", - "integrity": "sha512-VIT+chW2nMHKK5vleT5PKOt8i72PwFbSHAKDKL0RQ9QcXvUex9LtR0Mq4nAI3WM8AwvvJqwFUCk4XY4r+PZlQA==", + "version": "5.5.20", + "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.5.20.tgz", + "integrity": "sha512-vaLVDjfuNp2LMfqHGMss4hN/K9puZfql/Rh6yXFvJvex3jKP646CXtvSKT8FM9uOUb2P4me266fmpPovBhEJXQ==", "dev": true, "requires": { "@types/mongodb": "*", @@ -781,9 +781,9 @@ "dev": true }, "automapper-nartc": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/automapper-nartc/-/automapper-nartc-3.0.23.tgz", - "integrity": "sha512-U9dwut2sKq8x5huEYZ7CHR+5Kv+ZFvxnO9p7QY9FF2Oy29HwyL57t5bwehrL/m/28Hefurt3powCHii9df3Ffw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/automapper-nartc/-/automapper-nartc-3.1.2.tgz", + "integrity": "sha512-zJhG8UFyuREAHi1fIb1tjLVCPbiFpde2jY8NZErvAgTzE79ggL/LY4Q9h0RJ04BZiV+SEAO58Bt0lb5X3LGGGg==", "requires": { "class-transformer": "0.2.3", "lodash.get": "4.4.2", @@ -5790,9 +5790,9 @@ } }, "mongoose": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.7.3.tgz", - "integrity": "sha512-CKCCCAhFnJRtmdmver8Ud9/NZ9m7D2H/xLgmrcL6cb9D4nril/idL8lsWWpBsJI81AOCVsktiZJ4X4vfo2S0fw==", + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.7.4.tgz", + "integrity": "sha512-IgqQS5HIaZ8tG2cib6QllfIw2Wc/A0QVOsdKLsSqRolqJFWOjI0se3vsKXLNkbEcuJ1xziW3e/jPhBs65678Hg==", "requires": { "bson": "~1.1.1", "kareem": "2.3.1", @@ -7958,9 +7958,9 @@ } }, "typescript": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz", - "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==", + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", + "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index a849daf..3513139 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "@hapi/joi": "^16.1.7", "@hasezoey/typegoose": "^5.9.2", "agenda": "^2.1.0", - "automapper-nartc": "^3.0.23", + "automapper-nartc": "^3.1.2", "bcrypt": "^3.0.6", "body-parser": "^1.19.0", "class-transformer": "^0.2.3", @@ -72,7 +72,7 @@ "jsonwebtoken": "^8.5.1", "mailgun-js": "^0.22.0", "method-override": "^3.0.0", - "mongoose": "^5.7.3", + "mongoose": "^5.7.4", "reflect-metadata": "^0.1.13", "tsconfig-paths": "^3.9.0", "winston": "^3.2.1", @@ -86,14 +86,14 @@ "@types/cors": "^2.8.6", "@types/dotenv": "^6.1.1", "@types/express": "^4.17.1", - "@types/hapi__joi": "^16.0.0", + "@types/hapi__joi": "^16.0.1", "@types/helmet": "0.0.44", "@types/http-status-codes": "^1.2.0", "@types/jsonwebtoken": "^8.3.4", "@types/mailgun-js": "^0.22.3", "@types/method-override": "0.0.31", "@types/mocha": "^5.2.7", - "@types/mongoose": "^5.5.19", + "@types/mongoose": "^5.5.20", "@types/supertest": "^2.0.8", "@types/winston": "^2.4.4", "@typescript-eslint/eslint-plugin": "^2.3.3", @@ -113,7 +113,7 @@ "supertest": "^4.0.2", "ts-mocha": "^6.0.0", "ts-node": "^8.4.1", - "typescript": "^3.6.3" + "typescript": "^3.6.4" }, "config": { "ghooks": { diff --git a/src/domain/interfaces/repositories.ts b/src/domain/interfaces/repositories.ts index e52f89c..4fab1c6 100644 --- a/src/domain/interfaces/repositories.ts +++ b/src/domain/interfaces/repositories.ts @@ -6,7 +6,7 @@ export type Query = { }; export interface IBaseRepository { - save(doc: T): Promise; + insertOrUpdate(doc: T): Promise; findAll(): Promise; findById(id: string): Promise; hardFindById(id: string): Promise; diff --git a/src/domain/model/base.ts b/src/domain/model/base.ts index 3033bff..26c1b7c 100644 --- a/src/domain/model/base.ts +++ b/src/domain/model/base.ts @@ -9,12 +9,13 @@ import { User } from "./user"; @pre("findOneAndUpdate", function(this: Query, next) { try { const entity = this.getUpdate(); - const user = global.currentUser.user as User; - entity.updatedBy = user.id; - - if (entity.isDeleted) { - entity.deletedBy = user.id; - entity.deletionTime = new Date(); + const user = (global.currentUser && global.currentUser.user) as User; + if (user) { + entity.updatedBy = user.id; + if (entity.isDeleted) { + entity.deletedBy = user.id; + entity.deletionTime = new Date(); + } } next(); } catch (error) { diff --git a/src/domain/utils/globals.ts b/src/domain/utils/globals.ts index 2453fbd..27a6a4d 100644 --- a/src/domain/utils/globals.ts +++ b/src/domain/utils/globals.ts @@ -3,7 +3,7 @@ import { User } from "../model/user"; import { Writable } from "./writable"; export class CurrentUser { - private static _instance: CurrentUser; + private static Instance: CurrentUser; readonly tenant: Tenant; readonly user?: User; private constructor(tenant: Tenant, user?: User) { @@ -11,9 +11,8 @@ export class CurrentUser { this.user = user; } static createInstance = (tenant: Tenant, user?: User): CurrentUser => { - if (CurrentUser._instance) return CurrentUser._instance; - const newInstance = new CurrentUser(tenant, user); - return newInstance; + if (CurrentUser.Instance) return CurrentUser.Instance; + return new CurrentUser(tenant, user); }; setUser = (user: User) => { (this as Writable).user = user; diff --git a/src/infrastructure/config/inversify.config.ts b/src/infrastructure/config/inversify.config.ts index b3dca2e..1582c67 100644 --- a/src/infrastructure/config/inversify.config.ts +++ b/src/infrastructure/config/inversify.config.ts @@ -16,7 +16,7 @@ import { ITenantService } from "../../ui/interfaces/tenant_service"; import { TenantProfile } from "../../ui/profiles/tenant_profile"; // Service implementations -import { LoggerService } from "../../domain/services/logger_service"; +import { LoggerService } from "../services/logger_service"; import { MailService } from "../services/mail_service"; import { AuthService } from "../../ui/services/auth_service"; import { TenantService } from "../../ui/services/tenant_service"; diff --git a/src/infrastructure/db/repositories/base_repository.ts b/src/infrastructure/db/repositories/base_repository.ts index 798093f..6b410f8 100644 --- a/src/infrastructure/db/repositories/base_repository.ts +++ b/src/infrastructure/db/repositories/base_repository.ts @@ -12,6 +12,8 @@ import { BaseEntity } from "../../../domain/model/base"; export class BaseRepository implements IBaseRepository { protected Model: Model; + protected CurrentTenantId?: string = + global.currentUser && global.currentUser.tenant.id; protected _constructor: () => TEntity; public constructor( @unmanaged() model: Model, @@ -21,14 +23,11 @@ export class BaseRepository this._constructor = constructor; } - // We wrap the mongoose API here so we can use async / await - public async findAll() { return new Promise((resolve, reject) => { this.Model.find((err, res) => { if (err) return reject(err); - let results = res.map(r => this.readMapper(r)); - results = results.filter(r => !r.isDeleted); + const results = res.map(r => this.readMapper(r)); return resolve(results); }); }); @@ -57,7 +56,7 @@ export class BaseRepository }); } - public async save(doc: TEntity): Promise { + public async insertOrUpdate(doc: TEntity): Promise { return new Promise((resolve, reject) => { if (doc.id) { this.Model.findByIdAndUpdate( @@ -119,7 +118,7 @@ export class BaseRepository const item = await this.findById(id); if (!item) return false; item.delete(); - await this.save(item); + await this.insertOrUpdate(item); return true; } // deleteOneByQuery(query: Query): Promise { diff --git a/src/domain/services/logger_service.ts b/src/infrastructure/services/logger_service.ts similarity index 86% rename from src/domain/services/logger_service.ts rename to src/infrastructure/services/logger_service.ts index e840a14..6395c77 100644 --- a/src/domain/services/logger_service.ts +++ b/src/infrastructure/services/logger_service.ts @@ -3,8 +3,8 @@ import { injectable } from "inversify"; import { winstonLoggerInstance, IWinstonLogger -} from "../../infrastructure/bootstrapping/loaders/logger"; -import { ILoggerService } from "../interfaces/services"; +} from "../bootstrapping/loaders/logger"; +import { ILoggerService } from "../../domain/interfaces/services"; @injectable() export class LoggerService implements ILoggerService { diff --git a/src/ui/profiles/tenant_profile.ts b/src/ui/profiles/tenant_profile.ts index 1ecabc2..14e51ec 100644 --- a/src/ui/profiles/tenant_profile.ts +++ b/src/ui/profiles/tenant_profile.ts @@ -1,4 +1,4 @@ -import { MappingProfileBase } from "automapper-nartc"; +import { MappingProfileBase, AutoMapper } from "automapper-nartc"; import { Tenant } from "../../domain/model/tenant"; import { TenantDto } from "../models/tenant_dto"; @@ -7,9 +7,11 @@ export class TenantProfile extends MappingProfileBase { super(); } - configure(): void { - this.createMap(Tenant, TenantDto) - .forMember("id", options => options.mapFrom(tenant => tenant.id.toString())); + configure(mapper: AutoMapper): void { + mapper + .createMap(Tenant, TenantDto) + .forMember("id", options => + options.mapFrom(tenant => tenant.id.toString()) + ); } - -} \ No newline at end of file +} diff --git a/src/ui/profiles/user_profile.ts b/src/ui/profiles/user_profile.ts index c4e54db..8c8b17b 100644 --- a/src/ui/profiles/user_profile.ts +++ b/src/ui/profiles/user_profile.ts @@ -1,4 +1,4 @@ -import { MappingProfileBase } from "automapper-nartc"; +import { MappingProfileBase, AutoMapper } from "automapper-nartc"; import { UserDto } from "../models/user_dto"; import { User } from "../../domain/model/user"; @@ -7,9 +7,11 @@ export class UserProfile extends MappingProfileBase { super(); } - configure(): void { - this.createMap(User, UserDto).forMember("id", options => - options.mapFrom(user => user.id.toString()) - ); + configure(mapper: AutoMapper): void { + mapper + .createMap(User, UserDto) + .forMember("id", options => + options.mapFrom(user => user.id.toString()) + ); } } diff --git a/src/ui/services/auth_service.ts b/src/ui/services/auth_service.ts index 328dcd7..f4857aa 100644 --- a/src/ui/services/auth_service.ts +++ b/src/ui/services/auth_service.ts @@ -47,7 +47,7 @@ export class AuthService implements IAuthService { }); if (user) throw new HttpError(httpStatus.CONFLICT); - user = await this._userRepository.save( + user = await this._userRepository.insertOrUpdate( User.createInstance({ ...dto, password: hashedPassword diff --git a/src/ui/services/tenant_service.ts b/src/ui/services/tenant_service.ts index 5a3fdc6..477f503 100644 --- a/src/ui/services/tenant_service.ts +++ b/src/ui/services/tenant_service.ts @@ -16,7 +16,7 @@ export class TenantService implements ITenantService { @tenantRepository public _tenantRepository: ITenantRepository; async create(name: string, description: string): Promise { - const tenant = await this._tenantRepository.save( + const tenant = await this._tenantRepository.insertOrUpdate( Tenant.createInstance(name, description) ); return this._mapper.map(tenant, TenantDto); diff --git a/test/infrastructure/db/repository/tenant_repository.test.ts b/test/infrastructure/db/repository/tenant_repository.test.ts new file mode 100644 index 0000000..01cf253 --- /dev/null +++ b/test/infrastructure/db/repository/tenant_repository.test.ts @@ -0,0 +1,30 @@ +import { TYPES } from "../../../../src/domain/constants/types"; +import { ITenantRepository } from "../../../../src/domain/interfaces/repositories"; +import { Tenant } from "../../../../src/domain/model/tenant"; +import { container } from "../../../../src/infrastructure/utils/ioc_container"; +import { cleanupDb } from "../../../setup"; + +describe("Tenant Repository", () => { + const tenants: Tenant[] = [ + Tenant.createInstance("T0", "T0"), + Tenant.createInstance("T1", "T1"), + Tenant.createInstance("T2", "T2") + ]; + let tenantRepository: ITenantRepository; + before("Create seed tenants", async () => { + await cleanupDb(); + tenantRepository = container.get( + TYPES.TenantRepository + ); + tenants.forEach(async (doc, i) => { + tenants[i] = await tenantRepository.insertOrUpdate(doc); + }); + }); + it("should get all the tenants without the deleted one", async () => { + tenants[0].delete(); + // console.log(tenants); + // await tenantRepository.insertOrUpdate(tenants[0]); + // const res = await tenantRepository.findAll(); + // expect(res.length).to.equal(2); + }); +}); diff --git a/test/setup.ts b/test/setup.ts index 3ec7f0f..34e7ade 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -1,12 +1,8 @@ import { Server } from "http"; -import { cleanUpMetadata } from "inversify-express-utils"; import { MongoMemoryServer } from "mongodb-memory-server"; import mongoose from "mongoose"; import supertest from "supertest"; -import { TYPES } from "../src/domain/constants/types"; -import { ITenantRepository } from "../src/domain/interfaces/repositories"; -import { Tenant } from "../src/domain/model/tenant"; import { bootstrap } from "../src/infrastructure/bootstrapping"; import { referenceDataIoCModule } from "../src/infrastructure/config/inversify.config"; import { container } from "../src/infrastructure/utils/ioc_container"; @@ -28,21 +24,9 @@ before("Setup", async () => { server = startAppServer(app); req = supertest(server); - cleanUpMetadata(); - - const tenantRepository = container.get( - TYPES.TenantRepository - ); - const tenant = await tenantRepository.findOneByQuery({ - name: "Default" - }); - if (!tenant) - await tenantRepository.save( - Tenant.createInstance("Default", "Default tenant") - ); }); -async function cleanupDb() { +export async function cleanupDb() { const collections = await mongoose.connection.db .listCollections(undefined, { nameOnly: true }) .toArray(); diff --git a/test/ui/api/auth_controller.test.ts b/test/ui/api/auth_controller.test.ts index 45749ad..dc71ad0 100644 --- a/test/ui/api/auth_controller.test.ts +++ b/test/ui/api/auth_controller.test.ts @@ -14,7 +14,7 @@ import { UserSignUpInput } from "../../../src/ui/models/user_dto"; import { TYPES } from "../../../src/domain/constants/types"; -import { req } from "../../setup"; +import { req, cleanupDb } from "../../setup"; const endpoint = `${config.api.prefix}/auth`; const tenantHeaderProp = "x-tenant-id"; @@ -25,17 +25,19 @@ describe("Auth controller", () => { let tenant1: Tenant; let tenant2: Tenant; before(async () => { + await cleanupDb(); + tenantRepository = container.get( TYPES.TenantRepository ); // Get first tenant because it already exists from the setup.ts file - tenant1 = await tenantRepository.findOneByQuery({ - name: "Default" - }); + tenant1 = await tenantRepository.insertOrUpdate( + Tenant.createInstance("Tenant1", "Second tenant") + ); // Create a second tenant - tenant2 = await tenantRepository.save( + tenant2 = await tenantRepository.insertOrUpdate( Tenant.createInstance("Tenant2", "Second tenant") ); tenant = tenant1; diff --git a/test/ui/api/tenant_controller.test.ts b/test/ui/api/tenant_controller.test.ts index eb986b9..8c556c4 100644 --- a/test/ui/api/tenant_controller.test.ts +++ b/test/ui/api/tenant_controller.test.ts @@ -13,7 +13,7 @@ import { config } from "../../../src/infrastructure/config"; import { container } from "../../../src/infrastructure/utils/ioc_container"; import { IAuthService } from "../../../src/ui/interfaces/auth_service"; import { CreateTenantInput } from "../../../src/ui/models/tenant_dto"; -import { req } from "../../setup"; +import { req, cleanupDb } from "../../setup"; const endpoint = `${config.api.prefix}/tenants`; let authService: IAuthService; @@ -27,12 +27,16 @@ describe("Tenant controller", async () => { let tenant: Tenant; before(async () => { + await cleanupDb(); + userRepository = container.get(TYPES.UserRepository); authService = container.get(TYPES.AuthService); tenantRepository = container.get( TYPES.TenantRepository ); - tenant = await tenantRepository.findOneByQuery({ name: "Default" }); + tenant = await tenantRepository.insertOrUpdate( + Tenant.createInstance("Default", "Default") + ); const hashedPw = await bcrypt.hash(password, 1); user = User.createInstance({ firstName: "Admin", @@ -42,7 +46,7 @@ describe("Tenant controller", async () => { username: "admin" }); user.setRole(UserRole.ADMIN); - await userRepository.save(user); + await userRepository.insertOrUpdate(user); const { token } = await authService.signIn({ password, emailOrUsername: user.username @@ -71,7 +75,7 @@ describe("Tenant controller", async () => { }); it("should return forbidden for non admin user that attempts to create tenant", async () => { user.setRole(UserRole.USER); - await userRepository.save(user); + await userRepository.insertOrUpdate(user); const { token } = await authService.signIn({ password, emailOrUsername: user.username @@ -99,7 +103,7 @@ describe("Tenant controller", async () => { }); it("should soft delete tenant by admin", async () => { user.setRole(UserRole.ADMIN); - await userRepository.save(user); + await userRepository.insertOrUpdate(user); const { token } = await authService.signIn({ password,