Skip to content

Instantly share code, notes, and snippets.

@duard
Created March 31, 2021 20:36
Show Gist options
  • Save duard/3ca1523db86b0ef520395c828caf1de7 to your computer and use it in GitHub Desktop.
Save duard/3ca1523db86b0ef520395c828caf1de7 to your computer and use it in GitHub Desktop.
filter users-domain
import AggregateRoot from '~sdk-ddd/aggregate-root'
import profiles from '~enum/profiles'
import KeycloakProvider, {
KeycloakAdapter
} from '~providers/keycloak-provider'
import { UploadFile } from '~handlers'
import { ResourceNotFound, Forbidden, ResourceDuplicated } from '~errors'
import HttpService from '~sdk-http/http-service'
// import { ioc } from '@adonisjs/fold'
const AWS = require('aws-sdk')
/**
* @class The UsersDomain is responsible for user problemns
*/
export default class UsersDomain extends AggregateRoot {
/**
* @constructs UsersDomain
* @param {Object} repository
*/
constructor () {
super()
this.aggregates(['keycloaks', 'products'])
this.repositories(['users', 'keycloaks', 'clients'])
}
convertSegment (user) {
if (
user.attributes['b2b-api-business-unit'] &&
user.attributes['b2b-api-business-unit'].includes('AUTOMOTIVA')
) {
user.attributes['b2b-api-business-unit'] = user.attributes['b2b-api-business-unit'].map((value) => {
if (value === 'AUTOMOTIVA') value = 'AUTOPECAS'
return value
})
}
}
/**
* Create a new user on hub and keycloak
* @param {Object} data - the user data
*/
async createUser (data) {
// get product to populare the email function
const selectedProduct = data.profile.products.find(
(element) => element.sendEmail === true
)
let product = null
if (selectedProduct && selectedProduct.product) {
product = await this.repository('products').findOne({
_id: selectedProduct.product
})
}
if (!product || !product.config.email.templates.length) {
product = await this.repository('products').findOne({
name: 'Portal Yandeh'
})
}
// convert products into clients -> roles
data.profile.clients = await this.aggregate('products').getProfiles(
data.profile.products
)
// buscamos o usuário no DW
let user = await this.repository('users').findOneAndPopulate({
username: data.username.trim()
})
// buscamos o usuário no keycloak
const keycloak = await new KeycloakProvider()
let [keycloakUser] = await keycloak.users.findOne({
username: data.username.trim()
})
// se usuário existe no DW sem a flag deleted=true e existe no keycloak lançamos erro
if (user && !user.deleted && keycloakUser) {
throw new ResourceDuplicated('user-exists')
}
// se usuário existe no DW com a flag deleted=true, restauramos o user
if (user && user.deleted) user = await this.restoreUser(user._id, data)
// se foi informado um ou mais endereços, validamos
if (data.addresses) {
data.addresses = this.aggregate('addresses').validateAddress(
data.addresses
)
}
if (
typeof data.templateVariables === 'object' &&
Object.keys(data.templateVariables).length
) {
for (const attribute of Object.keys(data.templateVariables)) {
this.addCustomAttribute(
data,
`template.${attribute}`,
data.templateVariables[attribute]
)
}
delete data.templateVariables
}
// se o usuário não existe no DW inserimos
if (!user) {
user = await this.repository('users').createUser(data)
}
// existindo atualizamos
delete user.created_at
delete user.__v
user = await this.repository('users').findByIdAndUpdatePopulate(user._id, {
...user,
...data
})
const userRep = await KeycloakAdapter.from(user).to('keycloak')
const kcUser = userRep.representation()
this.convertSegment(kcUser)
// se usuário existe no keycloak
if (keycloakUser) {
await keycloak.users.update({ id: keycloakUser.id }, kcUser)
} else {
keycloakUser = await keycloak.users.create(kcUser)
await this.enqueueWelcomeMail(user, product, false)
}
keycloakUser = await keycloak.users.findOne({ id: keycloakUser.id })
const updateData = {
keycloak: { id: keycloakUser.id }
}
// se veio imagem de perfil fazemos o upload
if (data.file) {
updateData.image = await UploadFile(data.file, `user/${user._id}`)
.Location
}
const clients = await userRep.roles()
const [resp] = await Promise.all([
this.repository('users').findByIdAndUpdatePopulate(user._id, updateData),
this.aggregate('keycloaks').addClientRole(keycloakUser.id, clients)
])
return resp
}
/**
* Restore a user that have been deleted
* @param {String} userId - User identifier
* @param {Object} data - User data to update
*/
async restoreUser (userId, data) {
return this.repository('users')
.findOneAndUpdate(
{ _id: userId, deleted: { $in: [true, false, null] } },
{ $set: { ...data, deleted: false } },
{ new: true }
)
.populate('profile.customers')
.populate('profile.clients.client')
.populate('segment')
.lean()
}
/**
* Update a user on hub
* @param {Object} data - the user data
* @param {Object} options - additional options to the user creation
*/
async changeUser (data, { id }) {
if (data.profile && data.profile.products) {
// @DEPRECATED - transform products to clients with their roles
data.profile.clients = await this.aggregate('products').getProfiles(
data.profile.products
)
}
// buscamos o usuário no DW
let user = await this.repository('users').findOneAndPopulate({ _id: id })
// buscamos o usuário no keycloak
const keycloak = await new KeycloakProvider()
let [keycloakUser] = await keycloak.users.findOne({
username: user.username.trim()
})
// se usuário existe no DW com a flag deleted=true, restauramos o user
if (user && user.deleted) user = await this.restoreUser(user._id, data)
if (
typeof data.templateVariables === 'object' &&
Object.keys(data.templateVariables).length
) {
for (const attribute of Object.keys(data.templateVariables)) {
this.addCustomAttribute(
data,
`template.${attribute}`,
data.templateVariables[attribute]
)
}
}
// eslint-disable-next-line
["__v", "created_at", "updated_at"].forEach((key) => delete user[key]);
user = await this.repository('users').findByIdAndUpdatePopulate(user._id, {
...user,
...data
})
const userRep = await KeycloakAdapter.from(user).to('keycloak')
const kcUser = userRep.representation()
this.convertSegment(kcUser)
// se usuário existe no keycloak
if (keycloakUser) {
await keycloak.users.update({ id: keycloakUser.id }, kcUser)
} else {
keycloakUser = await keycloak.users.create(kcUser)
}
keycloakUser = await keycloak.users.findOne({ id: keycloakUser.id })
if (keycloakUser.enabled !== data.active) {
await keycloak.users.update(
{ id: keycloakUser.id },
{ enabled: data.active }
)
}
const updateData = {
keycloak: { id: keycloakUser.id }
}
// se veio imagem de perfil fazemos o upload
if (data.file) {
updateData.image = (
await UploadFile(data.file, `user/${user._id}`)
).Location
}
const clients = await userRep.roles()
const products = await this.repository('products')
.find()
.populate('internals.clients.client')
const [updatedUser] = await Promise.all([
this.repository('users').findByIdAndUpdatePopulate(user._id, updateData),
this.aggregate('keycloaks').updateClientRole(
keycloakUser.id,
clients,
products
)
])
return updatedUser
}
/**
* Adiciona um novo atributo customizado ao usuário removendo duplicidades
*
* @param {Object} user - Objeto representacional do usuário
* @param {String} key - String da chave do atributo customizavel
* @param {Any} value - Valor
*/
addCustomAttribute (user, key, value) {
if (!user.custom_attrs) user.custom_attrs = []
const index = user.custom_attrs.findIndex((attr) => attr.key === key)
if (index > -1) {
if (!user.custom_attrs[index].value.includes(value)) {
user.custom_attrs[index].value += `,${value}`
}
return
}
user.custom_attrs.push({ key, value })
}
/**
* List all users according to authenticated user profile
* @param {Object} user - instanceof user collection
* @param {Object} options - limit and page params
* @param {Number} options.limit - pagination limit
* @param {Number} options.page - pagination page
*/
async getUsers (user, options) {
let { limit, page, sort } = {
...HttpService.defaultPagination,
...options
}
const roles = user.getRolesForClient(process.env.CLIENT_ID) // roles from logged user
// customer user can't list users
if (roles.includes(profiles.CUSTOMER_USER)) throw new Forbidden()
const params = await this.getFilterFromParams(options)
if (roles.includes(profiles.CUSTOMER_ADMIN)) {
params['profile.customers'] = {
$in: user.profile.customers.map((customer) => customer._id)
}
}
if (roles.includes(profiles.BUSINESS)) params.segment = user.segment
const usersPaginationPipeline = (filter = {}, skip = 0, limit = 10, sort = {}) => [
{ $lookup: { from: 'segments', localField: 'segment', foreignField: '_id', as: 'segments' } },
{ $lookup: { from: 'products', localField: 'profile.products.product', foreignField: '_id', as: 'products' } },
{ $lookup: { from: 'clients', localField: 'profile.clients.client', foreignField: '_id', as: 'clients' } },
{ $lookup: { from: 'customers', localField: 'profile.customers', foreignField: '_id', as: 'customers' } },
{
$match: {
...filter,
active: { '$in': [true, null] },
deleted: { '$in': [false, null] }
}
},
{ $sort: { ...sort, created_at: -1 } },
{
'$project': {
_id: 1,
name: 1,
segments: 1,
keycloak: 1,
username: 1,
email: 1,
token: 1,
custom_attrs: 1,
'profile.products.profile': 1,
'profile.products._id': 1,
'profile.products.product': { $arrayElemAt: ['$products', 0] },
'profile.clients.roles': 1,
'profile.clients._id': 1,
'profile.clients.client': { $arrayElemAt: ['$clients', 0] },
'profile.customers': { $arrayElemAt: ['$customers', 0] },
deleted: 1,
active: 1,
created_at: 1,
updated_at: 1,
// products: 1,
__v: 1
}
},
{
$facet: {
total: [{
$count: 'created_at'
}],
result: [{
$addFields: {
_id: '$_id'
}
}]
}
},
{ $unwind: '$total' },
{
$project: {
result: {
$slice: ['$result', skip, {
$ifNull: [limit, '$total.created_at']
}]
},
meta: {
total: '$total.created_at',
limit: { $literal: limit },
page: { $literal: ((skip / limit) + 1) },
pages: {
$ceil: {
$divide: ['$total.created_at', limit]
}
}
}
}
}
]
const skip = (limit * page - limit)
const executePagination = async () => {
return this.repository('users').aggregate(usersPaginationPipeline(params, skip, limit, sort))
}
const exec = await executePagination()
const data = exec.shift()
// let users = await this.repository('users').find({ '_id': '603d4c44505a51001c9b936f' })
// .populate('profile.customers')
// .populate('profile.clients.client')
// .populate('profile.products.product')
// .skip((limit * page) - limit)
// .limit(parseInt(limit))
// console.log('users', users)
return {
docs: data.result,
total: data.meta.total,
page: data.meta.page,
limit: data.meta.limit,
pages: data.meta.pages
}
}
/**
* Create a mongodb filter format from parameters
* @param {Object} params - filters to apply in key->value format
*/
async getFilterFromParams (params) {
let filter = {}
const searchParams = []
let searchFilter = {}
// old endpoint without filters
if (params.q) {
let queryString = params.q.replace(/(\+|\*|\.)/, '\\$1')
queryString = new RegExp(`${queryString}`, 'i')
filter['$or'] = [{ name: queryString }, { email: queryString }]
return filter
}
if (params.search) {
let queryString = params.search.replace(/(\+|\*|\.)/, '\\$1')
queryString = new RegExp(`${params.search}`, 'i')
searchFilter = {}
searchFilter[`${params.searchBy}`] = { '$in': [queryString] }
searchParams.push(searchFilter)
}
if (params.segment) {
let queryString = params.segment
searchFilter = {}
searchFilter[`segments.name`] = { '$in': queryString.split(',') }
searchParams.push(searchFilter)
}
if (params.search) {
let queryString = params.search.replace(/(\+|\*|\.)/, '\\$1')
queryString = new RegExp(`${params.search}`, 'i')
searchFilter = {}
searchFilter[`${params.searchBy}`] = { '$in': [queryString] }
searchParams.push(searchFilter)
}
let resultParams = {}
/*
if (params.profile) {
const criterias = []
for (const product of Object.keys(params.profile)) {
criterias.push({
$and: [
{ name: new RegExp(product, 'i') },
{
'internals.name': {
$in: params.profile[product].map(prof => new RegExp(prof, 'i'))
}
}
]
})
}
const products = await (ioc.use('repositories/products-repository')).find({ $or: criterias })
for (const product of products) {
const filter = { $and: [] }
filter['$and'].push({ 'profile.products.product': String(product._id) })
filter['$and'].push({
'profile.products.profile': {
'$in': product.internals
.filter(internal => params.profile[product.name].includes(internal.name))
.reduce((acc, profile) => {
acc.push(profile._id)
return acc
}, [])
}
})
console.log('\n', filter, '\n')
searchParams.push(filter)
}
if (!params['$or']) params['$or'] = []
searchParams['$or'].push(...searchParams)
}
*/
for (let index = 0; index < searchParams.length; index++) {
const element = searchParams[index]
resultParams = Object.assign(resultParams, element)
console.log(index, element)
}
return resultParams
}
/**
*
* @param {String} id
*/
async getUserById (id) {
const user = await this.repository('users').findByIdAndPopulate(id)
if (!user) throw new ResourceNotFound('User not found')
return user
}
/**
*
* @param {User} user
*/
async removeUser (user) {
await this.repository('users').removeUser(user._id)
await this.aggregate('keycloaks').removeUser(user)
}
/**
*
* @param {*} data
* @param {*} id
*/
async changeUserLogged (data, id) {
if (data.token && data.topic) {
await this.aggregate('notifications').subscribe(data.token, data.topic)
}
if (data.file) data.image = (await UploadFile(data.file, 'user')).Location
const user = await this.repository('users').findByIdAndUpdatePopulate(
id,
data
)
return user
}
/**
* Enqueue welcome mail to be sended to user
* @param {Object} user - User from hub
* @param {Object} product - Product with email template
* @param {Boolean} passwordDefinition - Forces password definition mail
*/
async enqueueWelcomeMail (user, product, passwordDefinition = false) {
AWS.config.update({ region: 'us-east-1' })
return new AWS.DynamoDB()
.putItem({
TableName: process.env.MAILER_TABLE,
Item: {
id: { S: `${user.email}-${new Date().getTime()}` },
email: { S: user.email },
payload: { S: JSON.stringify({ user, products: [product] }) },
createdAt: { S: new Date().toISOString() },
updatedAt: { S: new Date().toISOString() },
passwordDefinition: { BOOL: passwordDefinition || false },
status: { S: 'PENDING' }
}
})
.promise()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment