import { Injectable } from '@nestjs/common' import { classToPlain, plainToClass } from 'class-transformer' import { isEmpty, isNil, pick } from 'lodash' import { parseISO } from 'date-fns' import { Availability, AvailabilityTime, Roles, User } from '../models' import { UnprocessableEntityError } from '../errors' import { LanguageCountryCode } from '../utils/countries/language-country-code.enum' import { HttpService } from '../services/http' import { BaseRepository } from './base.repository' const INSERT = ` mutation insertUser($user: users_insert_input!) { user: insert_users_one(object: $user) { id } } ` const FIND_WITH_EXTERNAL_ID = ` query users { users(where: {external_id: {_is_null: false}}) { external_id country_code } } ` const GET_BY_WITH_EXTERNAL_ID = ` query users($external_id: String!) { users(limit: 1, where: {external_id: {_eq: $external_id}}) { external_id country_code } } ` const GET_ROLE = ` query user($id: String!) { users(where: {id: {_eq: $id}}) { role } } ` const GET_FINANCIAL_PROPERTIES = ` query GetFinancialProperties($id: String!) { users_by_pk(id: $id) { id remuneration_teleconsultation_plan remuneration_teleconsultation_single } } ` const GET = ` query user($id: String!) { users(where: {id: {_eq: $id}}) { id name external_id country_code role inactivated_at } } ` const GET_COUNTRY_CODE = ` query user($id: String!) { users(where: {id: {_eq: $id}}) { country_code } } ` const FIND_ALL = ` query user { users { id email inactivated_at } } ` const GET_GOALS_RESCTRICTIONS = ` query user($id: String!) { users(where: {id: {_eq: $id}}) { id users_goals { goal_id } users_restrictions { restriction_id } } } ` const SIMILAR_USER_COMMON_PROPERTIES = (findAvailableTimes: boolean) => ` id goals: users_goals_aggregate(where: {goal_id: {_eq: $goal}}) { aggregate { count } } ${ findAvailableTimes ? `availables: availabilities(where: {date: {_eq: $date}}) { date available_times(where: {status: {_eq: available}}) { id time } }` : '' } ` const FIND_SIMILAR_USERS_WITH_AVAILABLE_DATE_BY_GOAL_AND_RESTRICTIONS = (findAvailableTimes: boolean) => ` query findSimilarUsersByGoalAndRestrictions($userId: String!, $goal: uuid!, $restrictions: [uuid!], $countryCode: String!${ findAvailableTimes ? ', $date: date!' : '' }) { users(where: {users_goals: {goal_id: {_eq: $goal}}, users_restrictions: {restriction_id: {_in: $restrictions}}, id: {_neq: $userId}, inactivated_at: {_is_null: true}, country_code: {_eq: $countryCode}}) { restrictions: users_restrictions_aggregate(where: {restriction_id: {_in: $restrictions}}) { aggregate { count } } ${SIMILAR_USER_COMMON_PROPERTIES(findAvailableTimes)} } } ` const FIND_SIMILAR_USERS_WITH_AVAILABLE_DATE_BY_GOAL = (findAvailableTimes: boolean) => ` query findSimilarUsersByGoal($userId: String!, $goal: uuid!, $countryCode: String!${ findAvailableTimes ? ', $date: date!' : '' }) { users(where: {users_goals: {goal_id: {_eq: $goal}}, id: {_neq: $userId}, inactivated_at: {_is_null: true}, country_code: {_eq: $countryCode}}) { restrictions: users_restrictions_aggregate { aggregate { count } } ${SIMILAR_USER_COMMON_PROPERTIES(findAvailableTimes)} } } ` const FIND_SIMILAR_USERS = ` query findSimilarUsers($userId: String!, $goals: [uuid!]!, $restrictions: [uuid!]!) { users(where: {role: {_in: [user, nutritionist_manager]}, users_goals: {goal_id: {_in: $goals}}, users_restrictions: {restriction_id: {_in: $restrictions}}, id: {_neq: $userId}, inactivated_at: {_is_null: true}}, limit: 1) { id role goals: users_goals_aggregate(where: {goal_id: {_in: $goals}}) { aggregate { count } } restrictions: users_restrictions_aggregate(where: {restriction_id: {_in: $restrictions}}) { aggregate { count } } } ` const FIND_BY_ROLE_AND_COUNTRY_CODE = (attributes: string[]) => ` query findByRole($roles: [roles_enum!]!, $countryCode: [String!]!) { users(where: {role: {_in: $roles}, country_code: {_in: $countryCode}}) { ${attributes.join(' ')} } } ` const FIND_ALL_BY_COUNTRY_CODE_AND_CHECK_INACTIVITY = ` query user($onlyActive: Boolean, $countryCode: [String!]!) { users(where: {inactivated_at: {_is_null: $onlyActive}, country_code: {_in: $countryCode}}, order_by: {name: asc}) { id name email country_code } } ` export type AVAILABLE_TIMES_SIMILAR_USERS = { id: string; available_times: Array } @Injectable() export class UserRepository extends BaseRepository { constructor(protected readonly httpService: HttpService) { super(httpService, 'users') } async create(user: User): Promise { const qv = { user: classToPlain(user) } if (!isEmpty(qv.user['users_restrictions'])) qv.user['users_restrictions'] = { data: qv.user['users_restrictions'] } if (!isEmpty(qv.user['users_goals'])) qv.user['users_goals'] = { data: qv.user['users_goals'] } const { data, errors } = await this.fetchGraphQL({ body: { query: INSERT, variables: qv }, throwErrors: false, }) if (errors) { if (errors[0]?.extensions?.code === 'constraint-violation') { if (errors[0].message.indexOf('users_document_number_key') >= 0) throw new UnprocessableEntityError('CPF já cadastrado.') if (errors[0].message.indexOf('users_email_key') >= 0) throw new UnprocessableEntityError('E-mail já cadastrado.') } throw errors } return data.user.id } async findWithExternalId(): Promise> { const { data } = await this.fetchGraphQL({ body: { query: FIND_WITH_EXTERNAL_ID }, }) return data.users.map((user: any) => plainToClass(User, user)) } async get(id: string): Promise { const { data } = await this.fetchGraphQL({ body: { query: GET, variables: { id } }, }) return isEmpty(data.users) ? null : plainToClass(User, data.users[0]) } async getRole(id: string): Promise { const { data } = await this.fetchGraphQL({ body: { query: GET_ROLE, variables: { id } }, }) return isEmpty(data.users) ? null : plainToClass(User, data.users[0]).role } async getByExternalId(externalId: string): Promise { const { data } = await this.fetchGraphQL({ body: { query: GET_BY_WITH_EXTERNAL_ID, variables: { external_id: externalId, }, }, }) return plainToClass(User, data.users[0]) } async findAll(onlyActive = false, countryCodes?: LanguageCountryCode[]): Promise> { const { data } = await this.fetchGraphQL({ body: onlyActive || !isNil(countryCodes) ? { query: FIND_ALL_BY_COUNTRY_CODE_AND_CHECK_INACTIVITY, variables: { onlyActive, countryCode: Object.values(countryCodes || LanguageCountryCode), }, } : { query: FIND_ALL }, }) return data.users.map((user: any) => plainToClass(User, user)) } async findUsersByGoalAndRestrictions( userId: string, goalId: string, countryCode: string, restrictionsIds: Array = [], date?: Date, ): Promise> { const variables = { userId, goal: goalId, date, countryCode } if (restrictionsIds.length > 0) variables['restrictions'] = restrictionsIds const { data } = await this.fetchGraphQL({ body: { query: restrictionsIds.length > 0 ? FIND_SIMILAR_USERS_WITH_AVAILABLE_DATE_BY_GOAL_AND_RESTRICTIONS(!isNil(date)) : FIND_SIMILAR_USERS_WITH_AVAILABLE_DATE_BY_GOAL(!isNil(date)), variables, }, }) return data?.users.map(row => plainToClass(User, row)) || [] } async findUsersWithAvailableDateByGoalAndRestrictions( userId: string, date: Date, goalId: string, countryCode: string, restrictionsIds: Array = [], ): Promise { const users = await this.findUsersByGoalAndRestrictions(userId, goalId, countryCode, restrictionsIds, date) return ( users ?.filter(row => row.availables?.[0]?.available_times.length > 0) .map(row => ({ id: row.id, available_times: row.availables[0].available_times.map(available => AvailabilityTime.toInstance({ ...available, date: parseISO(`${row.availables[0].date}T00:00:00+00:00`) }), ), })) || [] ) } async getCountryCode(id: string): Promise { const { data } = await this.fetchGraphQL({ body: { query: GET_COUNTRY_CODE, variables: { id } }, }) return isEmpty(data.users) ? null : data.users[0].country_code } async findSimilarUsers(userId: string): Promise { const userDetails = await this.getGoalsAndRestrictons(userId) const { data } = await this.fetchGraphQL({ body: { query: FIND_SIMILAR_USERS, variables: { userId, goals: userDetails.goal_ids, restrictions: userDetails.restriction_ids }, }, }) return ( data?.users ?.filter( row => row.goals?.aggregate?.count >= userDetails.users_goals.length && row.restrictions?.aggregate?.count >= userDetails.users_restrictions.length, ) .map(row => plainToClass(User, pick(row, ['id', 'role']))) || [] ) } async findByRoleAndCountryCode({ roles, countryCodes, attributes = ['id'], }: { roles: Roles[] countryCodes?: LanguageCountryCode[] attributes?: string[] }): Promise { const { data } = await this.fetchGraphQL({ body: { query: FIND_BY_ROLE_AND_COUNTRY_CODE(attributes), variables: { roles, countryCode: Object.values(countryCodes || LanguageCountryCode), }, }, }) return data?.users?.map(row => plainToClass(User, pick(row, attributes))) || [] } async getFinancialProperties(id: string): Promise> { const { data } = await this.fetchGraphQL({ body: { query: GET_FINANCIAL_PROPERTIES, variables: { id } }, }) return isEmpty(data.users_by_pk) ? null : plainToClass(User, data.users_by_pk) } private async getGoalsAndRestrictons(id: string): Promise { const { data } = await this.fetchGraphQL({ body: { query: GET_GOALS_RESCTRICTIONS, variables: { id } }, }) return data?.users?.map(row => plainToClass(User, row)).shift() } }