Skip to content

Commit

Permalink
refactor(core): refactoring the extraction's internal functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Fabiopf02 committed Oct 8, 2023
1 parent a36e4d4 commit 6fff189
Show file tree
Hide file tree
Showing 22 changed files with 1,056 additions and 314 deletions.
53 changes: 53 additions & 0 deletions src/@types/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
export type MetaData = {
OFXHEADER: string
DATA: string
VERSION: string
SECURITY: string
ENCODING: string
CHARSET: string
COMPRESSION: string
OLDFILEUID: string
NEWFILEUID: string
}

export type DateResponse = {
datetime: string | null
date: string | null
time: string | null
offset: string | null
timezone: string | null
}

export type ConfigDate = {
/**
* @description supported keys:
* yy => year -> 2 digits,
* yyyy or y => year,
* MM or M => month,
* dd or d => day,
* hh or h => hour,
* mm or m => minute,
* ss or s => second,
* O => offset,
* TZ => timezone
* @example format: 'y-M-d h:m:s'
* @returns '2022-02-21 09:00:00'
*/
formatDate?: string
}

export type ConfigFitId = 'normal' | 'separated'

export type ExtractorConfig = ConfigDate & {
fitId?: ConfigFitId
nativeTypes?: boolean
}

export type TransactionsSummary = {
credit: number
debit: number
amountOfCredits: number
amountOfDebits: number
dateStart: string
dateEnd: string
}
74 changes: 8 additions & 66 deletions src/types.ts → src/@types/ofx.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,4 @@
export type OFXMetaData = {
OFXHEADER: string
DATA: string
VERSION: string
SECURITY: string
ENCODING: string
CHARSET: string
COMPRESSION: string
OLDFILEUID: string
NEWFILEUID: string
}

export type DateResponse = {
datetime: string | null
date: string | null
time: string | null
offset: string | null
timezone: string | null
}

/**
* separated - returns the separate transaction id:
* "date": string,
* "transactionCode": string,
* "protocol": string
*/
type ConfigFitId = 'normal' | 'separated'

export type ConfigDate = {
/**
* @description supported keys:
* yy => year -> 2 digits,
* yyyy or y => year,
* MM or M => month,
* dd or d => day,
* hh or h => hour,
* mm or m => minute,
* ss or s => second,
* O => offset,
* TZ => timezone
* @example format: 'y-M-d h:m:s'
* @returns '2022-02-21 09:00:00'
*/
formatDate?: string
}

type MapKeys<T> = {
[Property in keyof T]?: string
}

type KeysJson = MapKeys<
| Status
| BankAccountFrom
| STRTTRN
| BankTransferList
| LedGerBal
| FINANCIAL_INSTITUTION
| OfxStructure
>

export type OfxConfig = ConfigDate & {
fitId?: ConfigFitId
nativeTypes?: boolean
formatJson?: MapKeys<KeysJson>
}
import type { ConfigDate, ConfigFitId, DateResponse, MetaData } from './common'

export type Status = {
CODE: string
Expand All @@ -77,6 +13,12 @@ export type BankAccountFrom = {

type TransferType = string

export type OfxConfig = ConfigDate & {
fitId?: ConfigFitId
nativeTypes?: boolean
// formatJson?: MapKeys<KeysJson>
}

export type STRTTRN = {
TRNTYPE: TransferType
DTPOSTED: DateResponse
Expand Down Expand Up @@ -129,4 +71,4 @@ export type OfxStructure = {
}
}

export type OfxResponse = OFXMetaData & OfxStructure
export type OfxResponse = MetaData & OfxStructure
81 changes: 81 additions & 0 deletions src/common/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { isValidNumberToConvert } from './parse'
import { formatDate } from './date'
import {
extractFinancialInstitutionTransactionId,
isDateField,
sanitizeCurrency,
} from './parse'
import { ExtractorConfig } from '../@types/common'
import { ELEMENT_CLOSURE_REGEX, ELEMENT_OPENING_REGEX } from './constants'
import { objectStartReplacer, objectEndReplacer, trim } from './parse'

export class Config {
private __config = {} as ExtractorConfig

constructor(private readonly config: ExtractorConfig) {
this.__config = config
}

getConfig() {
return this.__config
}

private configDate(dateString: string) {
const { formatDate: format } = this.__config
if (format) return formatDate(dateString, format)
return formatDate(dateString, 'y-M-d')
}

private configFinancialInstitutionTransactionId(fitString: string) {
const { fitId } = this.__config
if (fitId === 'separated')
return extractFinancialInstitutionTransactionId(fitString)
return `"${fitString}",`
}

private sanitizeValue(field: string, value: string) {
let fieldValue = value.replace(/[{]/g, '').replace(/(},)/g, '')
if (field.endsWith('AMT')) fieldValue = sanitizeCurrency(fieldValue)
if (isDateField(field)) fieldValue = this.configDate(fieldValue)
if (field === 'FITID')
return this.configFinancialInstitutionTransactionId(fieldValue)
if (
this.__config.nativeTypes &&
isValidNumberToConvert(field, fieldValue)
) {
return `${fieldValue},`
}
return `"${fieldValue}",`
}

sanitize(line: string) {
let sanitizedLine = line
const field = sanitizedLine.slice(0, sanitizedLine.indexOf(':'))
const sanitizeValue = this.sanitizeValue
const replacer = (matched: string) =>
sanitizeValue.call(this, field, matched)
if (line.match(/{(\w|\W)+/)) {
sanitizedLine = sanitizedLine.replace(/({(\w|\W)+)$/, replacer)
}
const matchedProperty = sanitizedLine.search(/(^\w+:)/)
if (matchedProperty < 0) return sanitizedLine
return sanitizedLine.replace(field + ':', `"${field}":`)
}

getPartialJsonData(data: string) {
const [_, ofxContentText] = data.split('<OFX>')
const ofxContent = '<OFX>' + ofxContentText
const { sanitize } = this
/**
* Use replace first for closing tag so there is no conflict in `objectStartReplacer`
*/
return ofxContent
.replace(ELEMENT_CLOSURE_REGEX, objectEndReplacer)
.replace(ELEMENT_OPENING_REGEX, objectStartReplacer)
.split('\n')
.map(trim)
.filter(Boolean)
.map(sanitize, this)
.join('')
}
}
File renamed without changes.
39 changes: 39 additions & 0 deletions src/common/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
function separatePartsOfDate(date: string) {
const year = date.slice(0, 4)
const month = date.slice(4, 6)
const day = date.slice(6, 8)
const hour = date.slice(8, 10)
const minutes = date.slice(10, 12)
const seconds = date.slice(12, 14)
const [offset, timezone] = date
.slice(14)
.replace('[', '')
.replace(']', '')
.split(':')
return {
yyyy: year,
yy: year.slice(2),
y: year,
MM: month,
M: month,
dd: day,
d: day,
hh: hour,
h: hour,
mm: minutes,
m: minutes,
ss: seconds,
s: seconds,
O: offset,
TZ: timezone,
}
}

export function formatDate(date: string, format: string) {
const parts = separatePartsOfDate(date)
let result = format
for (const [key, value] of Object.entries(parts)) {
result = result.replace(key, value)
}
return result
}
75 changes: 3 additions & 72 deletions src/helpers.ts → src/common/parse.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,6 @@
import { END_TEXT_BANK_TRANSFER, START_TEXT_BANK_TRANSFER } from './config'
import { STRTTRN as STRTTRNType } from './types'

function separatePartsOfDate(date: string) {
const year = date.slice(0, 4)
const month = date.slice(4, 6)
const day = date.slice(6, 8)
const hour = date.slice(8, 10)
const minutes = date.slice(10, 12)
const seconds = date.slice(12, 14)
const [offset, timezone] = date
.slice(14)
.replace('[', '')
.replace(']', '')
.split(':')
return {
yyyy: year,
yy: year.slice(2),
y: year,
MM: month,
M: month,
dd: day,
d: day,
hh: hour,
h: hour,
mm: minutes,
m: minutes,
ss: seconds,
s: seconds,
O: offset,
TZ: timezone,
}
}

export function formatDate(date: string, format: string) {
const parts = separatePartsOfDate(date)
let result = format
for (const [key, value] of Object.entries(parts)) {
result = result.replace(key, value)
}
return result
}
import { END_TEXT_BANK_TRANSFER, START_TEXT_BANK_TRANSFER } from './constants'
import type { STRTTRN as STRTTRNType } from '../@types/ofx'
import { formatDate } from './date'

export function fixJsonProblems(content: string) {
const result = content
Expand Down Expand Up @@ -140,33 +101,3 @@ export function getTransactionsSummary(STRTTRN: STRTTRNType[]) {
{ credit: 0, debit: 0, amountOfCredits: 0, amountOfDebits: 0 },
)
}

export function bufferToString(data: Buffer) {
return data.toString()
}

export async function fileFromPathToString(pathname: string) {
const fileData: string = await new Promise((resolve, reject) => {
import('fs').then(fs => {
return fs.readFile(pathname, (err, data) => {
if (err) reject(err)
else resolve(data.toString())
})
})
})
return fileData
}

export async function blobToString(blob: Blob): Promise<string> {
const data: string = await new Promise((resolve, reject) => {
if (typeof window !== 'undefined' && window.FileReader) {
const reader = new window.FileReader()
reader.onload = event => resolve(event.target!.result as string)
reader.onerror = event => reject(event.target!.error)
reader.readAsText(blob)
} else {
reject(new Error('FileReader is not available in this environment.'))
}
})
return data
}
29 changes: 29 additions & 0 deletions src/common/reader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export function bufferToString(data: Buffer) {
return data.toString()
}

export async function fileFromPathToString(pathname: string) {
const fileData: string = await new Promise((resolve, reject) => {
import('fs').then(fs => {
return fs.readFile(pathname, (err, data) => {
if (err) reject(err)
else resolve(data.toString())
})
})
})
return fileData
}

export async function blobToString(blob: Blob): Promise<string> {
const data: string = await new Promise((resolve, reject) => {
if (typeof window !== 'undefined' && window.FileReader) {
const reader = new window.FileReader()
reader.onload = event => resolve(event.target!.result as string)
reader.onerror = event => reject(event.target!.error)
reader.readAsText(blob)
} else {
reject(new Error('FileReader is not available in this environment.'))
}
})
return data
}
Loading

0 comments on commit 6fff189

Please sign in to comment.