From 38a6214de5be5b20e9c6a6e3857407658c0467ce Mon Sep 17 00:00:00 2001 From: Benjamin Piouffle Date: Thu, 12 Dec 2024 11:55:07 +0100 Subject: [PATCH] chore: Investigate on circular dependencies --- .github/workflows/ci.yml | 17 ++++++ package.json | 1 + reports/host-report.js | 3 +- server/graphql/v2/object/SearchResponse.ts | 7 --- server/lib/elastic-search/graphql-search.ts | 8 ++- server/lib/pdf.ts | 64 +++++++++++++++++++++ server/lib/utils.js | 64 --------------------- test/server/lib/utils.test.js | 3 +- 8 files changed, 93 insertions(+), 74 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36b12039220..f4846143d31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -395,3 +395,20 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} schema: 'main:server/graphql/schemaV2.graphql' fail-on-breaking: false + + circular-dependencies: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version-file: 'package.json' + cache: 'npm' + + - name: Check circular dependencies + run: npm run dependencies:check-circular diff --git a/package.json b/package.json index 28c0fa86947..98d6a031c46 100644 --- a/package.json +++ b/package.json @@ -225,6 +225,7 @@ "db:seed": "./scripts/sequelize.sh db:seed", "db:setup": "babel-node --extensions .js,.ts ./scripts/db_setup.js", "depcheck": "npx @opencollective/depcheck .", + "dependencies:check-circular": "npx madge --circular --extensions ts,js server", "deploy:production": "./scripts/pre-deploy.sh production && git push production main", "deploy:staging": "./scripts/pre-deploy.sh staging && git push -f staging main", "dev": "nodemon server/index.js -x \"babel-node --extensions .js,.ts\" . -e js,hbs,ts", diff --git a/reports/host-report.js b/reports/host-report.js index 70cebdc3bbf..7d5c5c843e8 100644 --- a/reports/host-report.js +++ b/reports/host-report.js @@ -10,10 +10,11 @@ import { generateHostFeeAmountForTransactionLoader } from '../server/graphql/loa import { getHostTransactionsCsvAsAdmin } from '../server/lib/csv'; import emailLib from '../server/lib/email'; import { getBackersStats, getHostedCollectives, sumTransactions } from '../server/lib/hostlib'; +import { exportToPDF } from '../server/lib/pdf.js'; import { stripHTML } from '../server/lib/sanitize-html'; import { reportErrorToSentry, reportMessageToSentry } from '../server/lib/sentry'; import { getPaidTaxTransactions, getTaxesSummary, getTransactions } from '../server/lib/transactions'; -import { exportToPDF, sumByWhen } from '../server/lib/utils'; +import { sumByWhen } from '../server/lib/utils'; import models, { Op, sequelize } from '../server/models'; const debug = debugLib('hostreport'); diff --git a/server/graphql/v2/object/SearchResponse.ts b/server/graphql/v2/object/SearchResponse.ts index be295ddd1db..f905b9e8a4d 100644 --- a/server/graphql/v2/object/SearchResponse.ts +++ b/server/graphql/v2/object/SearchResponse.ts @@ -1,13 +1,6 @@ import { GraphQLNonNull, GraphQLObjectType } from 'graphql'; import { getSearchResultFields } from '../../../lib/elastic-search/graphql-search'; -import { GraphQLAccountTypeKeys } from '../enum/AccountType'; - -export type SearchQueryAccountsResolverArgs = { - type: GraphQLAccountTypeKeys; - isHost: boolean; - tags: string[]; -}; const GraphQLSearchResults = new GraphQLObjectType({ name: 'SearchResults', diff --git a/server/lib/elastic-search/graphql-search.ts b/server/lib/elastic-search/graphql-search.ts index 3c29ac59e6e..f1d25ac14b8 100644 --- a/server/lib/elastic-search/graphql-search.ts +++ b/server/lib/elastic-search/graphql-search.ts @@ -26,8 +26,8 @@ import { GraphQLTierCollection } from '../../graphql/v2/collection/TierCollectio import { GraphQLTransactionCollection } from '../../graphql/v2/collection/TransactionCollection'; import { GraphQLUpdateCollection } from '../../graphql/v2/collection/UpdateCollection'; import { AccountTypeToModelMapping, GraphQLAccountType } from '../../graphql/v2/enum'; +import { GraphQLAccountTypeKeys } from '../../graphql/v2/enum/AccountType'; import { idEncode } from '../../graphql/v2/identifiers'; -import type { SearchQueryAccountsResolverArgs } from '../../graphql/v2/object/SearchResponse'; import { Collective, User } from '../../models'; import { ElasticSearchIndexName, ElasticSearchIndexParams } from './constants'; @@ -40,6 +40,12 @@ type GraphQLSearchParams = { host: Collective; }; +export type SearchQueryAccountsResolverArgs = { + type: GraphQLAccountTypeKeys; + isHost: boolean; + tags: string[]; +}; + /** * Returns a unique identifier for the ElasticSearch query, which can be used to batch multiple queries together. */ diff --git a/server/lib/pdf.ts b/server/lib/pdf.ts index 3525da7c156..0860a273d17 100644 --- a/server/lib/pdf.ts +++ b/server/lib/pdf.ts @@ -1,4 +1,8 @@ +import fs from 'fs'; +import path from 'path'; + import config from 'config'; +import pdf from 'html-pdf'; import { get } from 'lodash'; import moment from 'moment'; @@ -7,10 +11,70 @@ import { USTaxFormType } from '../models/LegalDocument'; import { TOKEN_EXPIRATION_PDF } from './auth'; import { fetchWithTimeout } from './fetch'; +import handlebars from './handlebars'; import logger from './logger'; import { reportErrorToSentry, reportMessageToSentry } from './sentry'; import { parseToBoolean } from './utils'; +/** + * export transactions to PDF + */ +export function exportToPDF(template, data, options) { + options = options || {}; + options.paper = options.paper || 'Letter'; // Letter for US or A4 for Europe + + let paperSize; + + switch (options.paper) { + case 'A4': + paperSize = { + width: '210mm', + height: '297mm', + margin: { + top: '10mm', + left: '10mm', + }, + }; + break; + case 'Letter': + default: + paperSize = { + width: '8.5in', + height: '11in', + margin: { + top: '0.4in', + left: '0.4in', + }, + }; + break; + } + + data.paperSize = paperSize; + options.paperSize = paperSize; + + const templateFilepath = path.resolve(__dirname, `../../templates/pdf/${template}.hbs`); + const source = fs.readFileSync(templateFilepath, 'utf8'); + const render = handlebars.compile(source); + + const html = render(data); + + if (options.format === 'html') { + return Promise.resolve(html); + } + options.format = options.paper; + + options.timeout = 60000; + + return new Promise((resolve, reject) => { + pdf.create(html, options).toBuffer((err, buffer) => { + if (err) { + return reject(err); + } + return resolve(buffer); + }); + }); +} + export const getTransactionPdf = async (transaction, user) => { if (parseToBoolean(config.pdfService.fetchTransactionsReceipts) === false) { return; diff --git a/server/lib/utils.js b/server/lib/utils.js index 2f5b12bc1b4..e12a6cf814f 100644 --- a/server/lib/utils.js +++ b/server/lib/utils.js @@ -1,19 +1,14 @@ import crypto from 'crypto'; -import fs from 'fs'; -import path from 'path'; import { URL } from 'url'; import config from 'config'; import fastRedact from 'fast-redact'; -import pdf from 'html-pdf'; import { filter, get, isObject, omit, padStart, sumBy } from 'lodash'; import moment from 'moment'; import pFilter from 'p-filter'; import { ZERO_DECIMAL_CURRENCIES } from '../constants/currencies'; -import handlebars from './handlebars'; - export function addParamsToUrl(url, obj) { const u = new URL(url); Object.keys(obj).forEach(key => { @@ -206,65 +201,6 @@ export function exportToCSV(data, attributes, getColumnName = attr => attr, proc return lines.join('\n'); } -/** - * export transactions to PDF - */ -export function exportToPDF(template, data, options) { - options = options || {}; - options.paper = options.paper || 'Letter'; // Letter for US or A4 for Europe - - let paperSize; - - switch (options.paper) { - case 'A4': - paperSize = { - width: '210mm', - height: '297mm', - margin: { - top: '10mm', - left: '10mm', - }, - }; - break; - case 'Letter': - default: - paperSize = { - width: '8.5in', - height: '11in', - margin: { - top: '0.4in', - left: '0.4in', - }, - }; - break; - } - - data.paperSize = paperSize; - options.paperSize = paperSize; - - const templateFilepath = path.resolve(__dirname, `../../templates/pdf/${template}.hbs`); - const source = fs.readFileSync(templateFilepath, 'utf8'); - const render = handlebars.compile(source); - - const html = render(data); - - if (options.format === 'html') { - return Promise.resolve(html); - } - options.format = options.paper; - - options.timeout = 60000; - - return new Promise((resolve, reject) => { - pdf.create(html, options).toBuffer((err, buffer) => { - if (err) { - return reject(err); - } - return resolve(buffer); - }); - }); -} - export const isValidEmail = email => { if (typeof email !== 'string') { return false; diff --git a/test/server/lib/utils.test.js b/test/server/lib/utils.test.js index c4f824e259f..6b4a5c04d79 100644 --- a/test/server/lib/utils.test.js +++ b/test/server/lib/utils.test.js @@ -1,6 +1,7 @@ import { assert, expect } from 'chai'; -import { exportToPDF, redactSensitiveFields } from '../../../server/lib/utils'; +import { exportToPDF } from '../../../server/lib/pdf'; +import { redactSensitiveFields } from '../../../server/lib/utils'; describe('server/lib/utils', () => { it('redacts sensitive fields', () => {