From 2c73f0acaef6371bed36842dc024dee8b55626a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=BCndig?= Date: Sat, 16 Mar 2024 17:32:38 +0100 Subject: [PATCH] chore(functions): remove postfinance balance import (#774) --- functions/src/cron/index.ts | 2 - .../PostFinanceBalanceImporter.test.ts | 120 ------------------ .../PostFinanceBalanceImporter.ts | 94 -------------- .../cron/postfinance-balance-import/index.ts | 12 -- shared/src/types/bank-balance.ts | 12 -- 5 files changed, 240 deletions(-) delete mode 100644 functions/src/cron/postfinance-balance-import/PostFinanceBalanceImporter.test.ts delete mode 100644 functions/src/cron/postfinance-balance-import/PostFinanceBalanceImporter.ts delete mode 100644 functions/src/cron/postfinance-balance-import/index.ts delete mode 100644 shared/src/types/bank-balance.ts diff --git a/functions/src/cron/index.ts b/functions/src/cron/index.ts index 2a3f347d7..791b8dc8f 100644 --- a/functions/src/cron/index.ts +++ b/functions/src/cron/index.ts @@ -1,7 +1,5 @@ import importExchangeRatesFunction from './exchange-rate-import'; -import importBalanceMailFunction from './postfinance-balance-import'; import importPostfinancePaymentsFilesFunction from './postfinance-payments-files-import'; -export const importBalanceMail = importBalanceMailFunction; export const importPostfinancePaymentsFiles = importPostfinancePaymentsFilesFunction; export const importExchangeRates = importExchangeRatesFunction; diff --git a/functions/src/cron/postfinance-balance-import/PostFinanceBalanceImporter.test.ts b/functions/src/cron/postfinance-balance-import/PostFinanceBalanceImporter.test.ts deleted file mode 100644 index 18adacd5f..000000000 --- a/functions/src/cron/postfinance-balance-import/PostFinanceBalanceImporter.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { describe, expect, test } from '@jest/globals'; -import functions from 'firebase-functions-test'; -import { FirestoreAdmin } from '../../../../shared/src/firebase/admin/FirestoreAdmin'; -import { getOrInitializeFirebaseAdmin } from '../../../../shared/src/firebase/admin/app'; -import { - BANK_BALANCE_FIRESTORE_PATH, - BankBalance, - getIdFromBankBalance, -} from '../../../../shared/src/types/bank-balance'; -import { PostFinanceBalanceImporter } from './PostFinanceBalanceImporter'; - -describe('importPostfinanceBalance', () => { - const projectId = 'test' + new Date().getTime(); - const testEnv = functions({ projectId: projectId }); - const firestoreAdmin = new FirestoreAdmin(getOrInitializeFirebaseAdmin({ projectId: projectId })); - const postfinanceImporter = new PostFinanceBalanceImporter(); - - beforeEach(async () => { - await testEnv.firestore.clearFirestoreData({ projectId: projectId }); - }); - - test('extract data correctly', () => { - const testMail = - '
3D"PostFinance

Dear Sir or Madam

A credit of 80.00 has =\n' + - "been booked to the account Contributions. Current balance: CHF 50'993=\n" + - '97.53.


PostFinance
Do not reply to this automatically generated e-mail. Notificat=\n' + - 'ion settings can be modified in e-finance under =E2=80=9CSettings and profi=\n' + - 'le=E2=80=9D > =E2=80=9CNotifications=E2=80=9D.

If you have any =\n' + - 'questions: 0848 888 710 (max. CHF 0.08/Min. in Switzerland)
<=\n' + - 'div class=3D"right" style=3D"text-align:right;">Follow us on:3D"=\n'=C2=A03D"twitter"=C2=A03D"youtube"=\n' + - '
'; - - expect('contributions').toEqual(postfinanceImporter.extractAccount(testMail)); - expect(50).toEqual(postfinanceImporter.extractBalance(testMail)); - }); - - test('inserts balances into firestore', async () => { - const balances = [ - { - timestamp: 1663339392, - account: 'testAccount', - balance: 1000, - currency: 'CHF', - } as BankBalance, - ]; - - await postfinanceImporter.storeBalances(balances); - - const balance = balances[0]; - const snap = await firestoreAdmin - .doc(BANK_BALANCE_FIRESTORE_PATH, getIdFromBankBalance(balance)) - .get(); - expect(balance).toEqual(snap.data()); - }); - jest.setTimeout(30000); -}); diff --git a/functions/src/cron/postfinance-balance-import/PostFinanceBalanceImporter.ts b/functions/src/cron/postfinance-balance-import/PostFinanceBalanceImporter.ts deleted file mode 100644 index ff4f5952a..000000000 --- a/functions/src/cron/postfinance-balance-import/PostFinanceBalanceImporter.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { logger } from 'firebase-functions'; -import imaps from 'imap-simple'; -import _ from 'lodash'; -import { Source, simpleParser } from 'mailparser'; -import { FirestoreAdmin } from '../../../../shared/src/firebase/admin/FirestoreAdmin'; -import { - BANK_BALANCE_FIRESTORE_PATH, - BankBalance, - getIdFromBankBalance, -} from '../../../../shared/src/types/bank-balance'; -import { POSTFINANCE_EMAIL_PASSWORD, POSTFINANCE_EMAIL_USER } from '../../config'; - -export class PostFinanceBalanceImporter { - private readonly accountRegex = /(?<=account\W)(?.*?)(?=\W)/; // regex to retrieve the account name from the email - private readonly balanceRegex = /balance: CHF (?[0-9’.]*)/; // regex to retrieve the balance from the email - // we retrieve only unseen mails and mark them as seen once we imported the balance - private readonly searchCriteria = ['UNSEEN']; - private readonly fetchOptions = { bodies: ['HEADER', 'TEXT', ''], markSeen: true }; - private readonly firestoreAdmin: FirestoreAdmin; - - constructor() { - this.firestoreAdmin = new FirestoreAdmin(); - } - - extractBalance = (html: String) => { - return Number.parseFloat(html.match(this.balanceRegex)!.groups!['balance'].replace('’', '')); - }; - - storeBalances = async (balances: BankBalance[]): Promise => { - for await (const balance of balances) { - await this.firestoreAdmin - .doc(BANK_BALANCE_FIRESTORE_PATH, getIdFromBankBalance(balance)) - .set(balance); - } - }; - - extractAccount = (html: String) => { - return html.match(this.accountRegex)!.groups!['account'].toLowerCase(); - }; - - retrieveBalanceMails = async (): Promise => { - try { - logger.info('Start checking balance inbox'); - const config = { - imap: { - user: POSTFINANCE_EMAIL_USER, - password: POSTFINANCE_EMAIL_PASSWORD, - host: 'imap.gmail.com', - port: 993, - tls: true, - tlsOptions: { rejectUnauthorized: false }, - authTimeout: 10000, - }, - }; - const connection = await imaps.connect(config); - await connection.openBox('INBOX'); - logger.info('Connected to inbox'); - const messages = await connection.search(this.searchCriteria, this.fetchOptions); - const balances = await Promise.all( - messages.map(async (item: any) => { - const all = _.find(item.parts, { which: '' }); - const id = item.attributes.uid; - const idHeader = 'Imap-Id: ' + id + '\r\n'; - const source = idHeader + all?.body; - return this.parseEmail(source); - }), - ); - connection.end(); - logger.info('Retrieved balances'); - return balances.flat(); - } catch (error) { - logger.error('Could not ingest balance mails', error); - return []; - } - }; - - parseEmail = async (source: Source): Promise => { - const mail = await simpleParser(source); - if (!mail || !mail.html) return []; - try { - return [ - { - timestamp: mail.date!.getTime() / 1000, - account: this.extractAccount(mail.html), - balance: this.extractBalance(mail.html), - currency: 'CHF', - } as BankBalance, - ]; - } catch { - logger.info(`Could not parse email with subject ${mail.subject}`); - return []; - } - }; -} diff --git a/functions/src/cron/postfinance-balance-import/index.ts b/functions/src/cron/postfinance-balance-import/index.ts deleted file mode 100644 index 51f34bb39..000000000 --- a/functions/src/cron/postfinance-balance-import/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { onSchedule } from 'firebase-functions/v2/scheduler'; -import { PostFinanceBalanceImporter } from './PostFinanceBalanceImporter'; - -/** - * Function periodically connects to the gmail account where we send the postfinance balance statements, - * parses the emails and stores the current balances into firestore. - */ -export default onSchedule('0 * * * *', async () => { - const postFinanceImporter = new PostFinanceBalanceImporter(); - const balances = await postFinanceImporter.retrieveBalanceMails(); - await postFinanceImporter.storeBalances(balances); -}); diff --git a/shared/src/types/bank-balance.ts b/shared/src/types/bank-balance.ts deleted file mode 100644 index 8234f1b14..000000000 --- a/shared/src/types/bank-balance.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const BANK_BALANCE_FIRESTORE_PATH = 'postfinance-balances'; - -export type BankBalance = { - account: string; - balance: number; - currency: string; - timestamp: number; -}; - -export const getIdFromBankBalance = (balance: BankBalance) => { - return `${balance.account}_${balance.timestamp}`; -};