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

DOMA-8614 News Item Sharing getRecipients proxy function #4374

Merged
merged 10 commits into from
Mar 13, 2024
31 changes: 31 additions & 0 deletions apps/condo/domains/news/access/GetNewsSharingRecipientsService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Generated by `createservice news.GetNewsItemSharingRecipientsService --type queries`
*/
const get = require('lodash/get')

const { throwAuthenticationError } = require('@open-condo/keystone/apolloErrorFormatter')
const { getById } = require('@open-condo/keystone/schema')

const { checkPermissionInUserOrganizationOrRelatedOrganization } = require('@condo/domains/organization/utils/accessSchema')

async function canGetNewsSharingRecipients ({ authentication: { item: user }, args: { data: { b2bAppContext: { id: b2bAppContextId } } } }) {
if (!user) return throwAuthenticationError()
if (user.deletedAt) return false
if (user.isAdmin || user.isSupport) return true

const b2bContext = await getById('B2BAppContext', b2bAppContextId)
if (!b2bContext) { return false }

const organizationId = get(b2bContext, 'organization', null)
if (!organizationId) { return false }

return await checkPermissionInUserOrganizationOrRelatedOrganization(user.id, organizationId, 'canManageNewsItems')
}

/*
Rules are logical functions that used for list access, and may return a boolean (meaning
all or no items are available) or a set of filters that limit the available items.
*/
module.exports = {
canGetNewsSharingRecipients,
}
7 changes: 7 additions & 0 deletions apps/condo/domains/news/gql.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ const NewsItemRecipientsExportTask = generateGqlQueries('NewsItemRecipientsExpor
const NEWS_ITEM_SHARING_FIELDS = `{ b2bAppContext { id } newsItem { id } sharingParams status statusMessage lastPostRequest ${COMMON_FIELDS} }`
const NewsItemSharing = generateGqlQueries('NewsItemSharing', NEWS_ITEM_SHARING_FIELDS)

const GET_NEWS_SHARING_RECIPIENTS_MUTATION = gql`
query getGetNewsSharingRecipients ($data: GetNewsSharingRecipientsInput!) {
result: getNewsSharingRecipients(data: $data) { id name receiversCount }
}
`

/* AUTOGENERATE MARKER <CONST> */

module.exports = {
Expand All @@ -46,5 +52,6 @@ module.exports = {
GET_NEWS_ITEMS_RECIPIENTS_COUNTERS_MUTATION,
NewsItemRecipientsExportTask,
NewsItemSharing,
GET_NEWS_SHARING_RECIPIENTS_MUTATION,
/* AUTOGENERATE MARKER <EXPORTS> */
}
176 changes: 176 additions & 0 deletions apps/condo/domains/news/schema/GetNewsSharingRecipientsService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/**
* Generated by `createservice news.GetNewsItemSharingRecipientsService --type queries`
*/

/**
* This service is part of "NewsSharing" functionality.
*
* --
*
* To make your miniapp able to show up on /news page you need to implement getRecipients method.
*
* GetRecipients should return a list of available recipients with relevant data:
*
* Request:
*
* GET <URL>?organizationId=string
*
* Where:
* - URL = getRecipients endpoint url provided in NewsItemSharingConfig
*
* Check response Payload in SCHEMA variable
* --
*
* This method is just a proxy between Condo and miniapp.
**
* <Condo /news> -> <Condo.GetNewsSharingRecipientsService> -> <Miniapp.GetRecipients>
*/

const Ajv = require('ajv')
const fetch = require('node-fetch')

const { GQLError, GQLErrorCode: { BAD_USER_INPUT, INTERNAL_ERROR } } = require('@open-condo/keystone/errors')
const { GQLCustomSchema, getById } = require('@open-condo/keystone/schema')

const { WRONG_VALUE, NETWORK_ERROR } = require('@condo/domains/common/constants/errors')
const access = require('@condo/domains/news/access/GetNewsSharingRecipientsService')

const SCHEMA = {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'id': { 'type': 'string' },
'name': { 'type': 'string' },
'receiversCount': { 'type': 'number' },
},
'required': ['id', 'name'],
'additionalProperties': false,
},
}

const ajv = new Ajv()
const validateSchema = ajv.compile(SCHEMA)

async function fetchWithTimeout (url, options = {}, timeout = 5000) {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), timeout)

try {
const response = await fetch(url, { ...options, signal: controller.signal })
clearTimeout(timeoutId)
return response
} catch (error) {
clearTimeout(timeoutId)
throw error.name === 'AbortError' ? new Error('Timeout') : error
}
}


/**
* List of possible errors, that this custom schema can throw
* They will be rendered in documentation section in GraphiQL for this custom schema
*/
const ERRORS = {
NOT_NEWS_SHARING_APP: {
query: 'getNewsItemSharingRecipients',
variable: ['data', 'b2bAppContext'],
code: BAD_USER_INPUT,
type: WRONG_VALUE,
message: 'Provided b2bApp does not support NewsItemSharing',
messageForUser: 'api.newsItem.getNewsSharingRecipients.NOT_NEWS_SHARING_APP',
},
NEWS_SHARING_APP_REQUEST_FAILED: {
query: 'getNewsItemSharingRecipients',
variable: ['data'],
code: INTERNAL_ERROR,
type: NETWORK_ERROR,
message: 'Could not get a successful response from NewsSharing miniapp',
messageForUser: 'api.newsItem.getNewsSharingRecipients.NEWS_SHARING_APP_REQUEST_FAILED',
},
NEWS_SHARING_APP_REQUEST_BAD_RESPONSE: {
query: 'getNewsItemSharingRecipients',
variable: ['data'],
code: INTERNAL_ERROR,
type: WRONG_VALUE,
message: 'Response from NewsSharing miniapp was successful, but the data format was incorrect',
messageForUser: 'api.newsItem.getNewsSharingRecipients.NEWS_SHARING_APP_REQUEST_BAD_RESPONSE',
},
}

const GetNewsSharingRecipientsService = new GQLCustomSchema('GetNewsSharingRecipientsService', {
types: [
{
access: true,
type: 'input GetNewsSharingRecipientsInput { dv: Int!, sender: JSON!, b2bAppContext: B2BAppContextWhereUniqueInput! }',
},
{
access: true,
type: 'type GetNewsSharingRecipientsOutput { id: String!, name: String!, receiversCount: Int }',
},
],

queries: [
{
access: access.canGetNewsSharingRecipients,
schema: 'getNewsSharingRecipients (data: GetNewsSharingRecipientsInput!): [GetNewsSharingRecipientsOutput]',
resolver: async (parent, args, context, info, extra = {}) => {
const { data } = args

const { dv, sender, b2bAppContext } = data

const b2bAppContextData = await getById('B2BAppContext', b2bAppContext.id)
const b2bApp = await getById('B2BApp', b2bAppContextData.app)

if (!b2bApp.newsSharingConfig) {
throw new GQLError(ERRORS.NOT_NEWS_SHARING_APP)
}

const newsSharingConfig = await getById('B2BAppNewsSharingConfig', b2bApp.newsSharingConfig)

if (!newsSharingConfig) {
throw new GQLError(ERRORS.NOT_NEWS_SHARING_APP)
}

const getRecipientsUrl = newsSharingConfig.getRecipientsUrl
const organizationId = b2bAppContextData.organization

let getRecipientsResult

// Check that we can obtain result data
try {
getRecipientsResult = await fetchWithTimeout(`${getRecipientsUrl}?organizationId=${organizationId}`)
}
catch (err) {
throw new GQLError(ERRORS.NEWS_SHARING_APP_REQUEST_FAILED)
}

// If status code of response is not 200, we need to raise an error
if (getRecipientsResult.status !== 200) {
throw new GQLError(ERRORS.NEWS_SHARING_APP_REQUEST_FAILED)
}

// Check that result data is in good shape
let getRecipientsResultData

try {
getRecipientsResultData = await getRecipientsResult.json()
} catch (err) {
throw new GQLError(ERRORS.NEWS_SHARING_APP_REQUEST_BAD_RESPONSE)
}

if (!validateSchema(getRecipientsResultData)) {
throw new GQLError(ERRORS.NEWS_SHARING_APP_REQUEST_BAD_RESPONSE)
}

return getRecipientsResultData
},
},
],

})

module.exports = {
GetNewsSharingRecipientsService,
ERRORS,
}
Loading
Loading