feat: fix issues in invite user.

feat: fix update last login at after login.
This commit is contained in:
Ahmed Bouhuolia
2020-09-05 22:18:53 +02:00
parent d35b124178
commit 750377ba86
16 changed files with 535 additions and 336 deletions

View File

@@ -2,6 +2,7 @@ import { Service, Inject, Container } from "typedi";
import JWT from 'jsonwebtoken';
import uniqid from 'uniqid';
import { omit } from 'lodash';
import moment from "moment";
import {
EventDispatcher,
EventDispatcherInterface,
@@ -76,6 +77,11 @@ export default class AuthenticationService {
this.logger.info('[login] generating JWT token.');
const token = this.generateToken(user);
this.logger.info('[login] updating user last login at.');
await SystemUser.query()
.where('id', user.id)
.patch({ last_login_at: moment().toMySqlDateTime() });
this.logger.info('[login] Logging success.', { user, token });
// Triggers `onLogin` event.

View File

@@ -1,11 +1,13 @@
import { Service, Inject } from "typedi";
import uniqid from 'uniqid';
import moment from 'moment';
import {
EventDispatcher,
EventDispatcherInterface,
} from '@/decorators/eventDispatcher';
import { ServiceError, ServiceErrors } from "@/exceptions";
import { SystemUser, Invite } from "@/system/models";
import { SystemUser, Invite, Tenant } from "@/system/models";
import { Option } from '@/models';
import { hashPassword } from '@/utils';
import TenancyService from '@/services/Tenancy/TenancyService';
import TenantsManager from "@/system/TenantsManager";
@@ -42,28 +44,28 @@ export default class InviteUserService {
*/
async acceptInvite(token: string, inviteUserInput: IInviteUserInput): Promise<void> {
const inviteToken = await this.getInviteOrThrowError(token);
await this.validateUserEmailAndPhone(inviteUserInput);
await this.validateUserPhoneNumber(inviteUserInput);
this.logger.info('[aceept_invite] trying to hash the user password.');
const hashedPassword = await hashPassword(inviteUserInput.password);
const user = SystemUser.query()
.where('email', inviteUserInput.email)
console.log(inviteToken);
this.logger.info('[accept_invite] trying to update user details.');
const updateUserOper = SystemUser.query()
.where('email', inviteToken.email)
.patch({
...inviteUserInput,
active: 1,
email: inviteToken.email,
invite_accepted_at: moment().format('YYYY/MM/DD'),
invite_accepted_at: moment().format('YYYY-MM-DD'),
password: hashedPassword,
tenant_id: inviteToken.tenantId,
});
this.logger.info('[accept_invite] trying to delete the given token.');
const deleteInviteTokenOper = Invite.query().where('token', inviteToken.token).delete();
await Promise.all([
insertUserOper,
deleteInviteTokenOper,
]);
// Await all async operations.
const [user] = await Promise.all([updateUserOper, deleteInviteTokenOper]);
// Triggers `onUserAcceptInvite` event.
this.eventDispatcher.dispatch(events.inviteUser.acceptInvite, {
@@ -80,20 +82,27 @@ export default class InviteUserService {
* @return {Promise<IInvite>}
*/
public async sendInvite(tenantId: number, email: string, authorizedUser: ISystemUser): Promise<IInvite> {
const { Option } = this.tenancy.models(tenantId);
await this.throwErrorIfUserEmailExists(email);
this.logger.info('[send_invite] trying to store invite token.');
const invite = await Invite.query().insert({
email,
tenant_id: authorizedUser.tenantId,
token: uniqid(),
});
this.logger.info('[send_invite] trying to store user with email and tenant.');
const user = await SystemUser.query().insert({
email,
tenant_id: authorizedUser.tenantId,
active: 1,
})
// Triggers `onUserSendInvite` event.
this.eventDispatcher.dispatch(events.inviteUser.sendInvite, {
invite,
});
return { invite };
return { invite, user };
}
/**
@@ -101,7 +110,7 @@ export default class InviteUserService {
* @param {string} token - the given token string.
* @throws {ServiceError}
*/
public async checkInvite(token: string) {
public async checkInvite(token: string): Promise<{ inviteToken: string, orgName: object}> {
const inviteToken = await this.getInviteOrThrowError(token)
// Find the tenant that associated to the given token.
@@ -109,16 +118,20 @@ export default class InviteUserService {
const tenantDb = this.tenantsManager.knexInstance(tenant.organizationId);
const organizationOptions = await Option.bindKnex(tenantDb).query()
.where('key', 'organization_name');
const orgName = await Option.bindKnex(tenantDb).query()
.findOne('key', 'organization_name')
// Triggers `onUserCheckInvite` event.
this.eventDispatcher.dispatch(events.inviteUser.checkInvite, {
inviteToken, organizationOptions,
inviteToken, orgName,
});
return { inviteToken, organizationOptions };
return { inviteToken, orgName };
}
/**
* Throws error in case the given user email not exists on the storage.
* @param {string} email
*/
private async throwErrorIfUserEmailExists(email: string) {
const foundUser = await SystemUser.query().findOne('email', email);
@@ -131,6 +144,7 @@ export default class InviteUserService {
* Retrieve invite model from the given token or throw error.
* @param {string} token - Then given token string.
* @throws {ServiceError}
* @returns {Invite}
*/
private async getInviteOrThrowError(token: string) {
const inviteToken = await Invite.query().findOne('token', token);
@@ -139,34 +153,22 @@ export default class InviteUserService {
this.logger.info('[aceept_invite] the invite token is invalid.');
throw new ServiceError('invite_token_invalid');
}
return inviteToken;
}
/**
* Validate the given user email and phone number uniquine.
* @param {IInviteUserInput} inviteUserInput
*/
private async validateUserEmailAndPhone(inviteUserInput: IInviteUserInput) {
private async validateUserPhoneNumber(inviteUserInput: IInviteUserInput) {
const foundUser = await SystemUser.query()
.onBuild(query => {
query.where('email', inviteUserInput.email);
if (inviteUserInput.phoneNumber) {
query.where('phone_number', inviteUserInput.phoneNumber);
}
});
const serviceErrors: ServiceError[] = [];
if (foundUser && foundUser.email === inviteUserInput.email) {
this.logger.info('[send_user_invite] the given email exists.');
serviceErrors.push(new ServiceError('email_exists'));
}
if (foundUser && foundUser.phoneNumber === inviteUserInput.phoneNumber) {
this.logger.info('[send_user_invite] the given phone number exists.');
serviceErrors.push(new ServiceError('phone_number_exists'));
}
if (serviceErrors.length > 0) {
throw new ServiceErrors(serviceErrors);
query.where('phone_number', inviteUserInput.phoneNumber);
query.first();
});
if (foundUser) {
throw new ServiceError('phone_number_exists');
}
}
}

View File

@@ -0,0 +1,147 @@
import { Inject, Service } from "typedi";
import TenancyService from '@/services/Tenancy/TenancyService';
import { SystemUser } from "@/system/models";
import { ServiceError, ServiceErrors } from "@/exceptions";
import { ISystemUser, ISystemUserDTO } from "@/interfaces";
import { ISystemUser } from "src/interfaces";
@Service()
export default class UsersService {
@Inject()
tenancy: TenancyService;
/**
* Creates a new user.
* @param {number} tenantId
* @param {number} userId
* @param {IUserDTO} userDTO
* @return {Promise<ISystemUser>}
*/
async editUser(tenantId: number, userId: number, userDTO: ISystemUserDTO): Promise<ISystemUser> {
const foundUsers = await SystemUser.query()
.whereNot('id', userId)
.andWhere((q) => {
q.where('email', userDTO.email);
q.orWhere('phone_number', userDTO.phoneNumber);
})
.where('tenant_id', tenantId);
const sameUserEmail = foundUsers
.some((u: ISystemUser) => u.email === userDTO.email);
const samePhoneNumber = foundUsers
.some((u: ISystemUser) => u.phoneNumber === userDTO.phone_number);
const serviceErrors: ServiceError[] = [];
if (sameUserEmail) {
serviceErrors.push(new ServiceError('email_already_exists'));
}
if (samePhoneNumber) {
serviceErrors.push(new ServiceError('phone_number_already_exist'));
}
if (serviceErrors.length > 0) {
throw new ServiceErrors(serviceErrors);
}
const updateSystemUser = await SystemUser.query()
.where('id', userId)
.update({
...userDTO,
});
return updateSystemUser;
}
/**
* Validate user existance throw error in case user was not found.,
* @param {number} tenantId -
* @param {number} userId -
* @returns {ISystemUser}
*/
async getUserOrThrowError(tenantId: number, userId: number): void {
const user = await SystemUser.query().findOne({
id: userId, tenant_id: tenantId,
});
if (!user) {
throw new ServiceError('user_not_found');
}
return user;
}
/**
* Deletes the given user id.
* @param {number} tenantId
* @param {number} userId
*/
async deleteUser(tenantId: number, userId: number): Promise<void> {
await this.getUserOrThrowError(tenantId, userId);
await SystemUser.query().where('id', userId).delete();
}
/**
* Activate the given user id.
* @param {number} tenantId
* @param {number} userId
*/
async activateUser(tenantId: number, userId: number): Promise<void> {
const user = await this.getUserOrThrowError(tenantId, userId);
this.throwErrorIfUserActive(user);
await SystemUser.query().findById(userId).update({ active: true });
}
/**
* Inactivate the given user id.
* @param {number} tenantId
* @param {number} userId
* @return {Promise<void>}
*/
async inactivateUser(tenantId: number, userId: number): Promise<void> {
const user = await this.getUserOrThrowError(tenantId, userId);
this.throwErrorIfUserInactive(user);
await SystemUser.query().findById(userId).update({ active: false });
}
/**
* Retrieve users list based on the given filter.
* @param {number} tenantId
* @param {object} filter
*/
async getList(tenantId: number) {
const users = await SystemUser.query()
.where('tenant_id', tenantId);
return users;
}
/**
* Retrieve the given user details.
* @param {number} tenantId - Tenant id.
* @param {number} userId - User id.
*/
async getUser(tenantId: number, userId: number) {
return this.getUserOrThrowError(tenantId, userId);
}
/**
* Throws service error in case the user was already active.
* @param {ISystemUser} user
* @throws {ServiceError}
*/
throwErrorIfUserActive(user: ISystemUser) {
if (user.active) {
throw new ServiceError('user_already_active');
}
}
/**
* Throws service error in case the user was already inactive.
* @param {ISystemUser} user
* @throws {ServiceError}
*/
throwErrorIfUserInactive(user: ISystemUser) {
if (!user.active) {
throw new ServiceError('user_already_inactive');
}
}
}