🧹 Moved Sb1 API to separate workspace and created common workspace
- Created common workspace - Create Sparebank1Api workspace - Moved logger to common - Moved SB1 types to types.ts - Logger will avoid duplicating first line when capturing console.logs - Updated imports and added type keyword - Added nonUniqueId type
This commit is contained in:
3
packages/common/date.ts
Normal file
3
packages/common/date.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { type Dayjs } from "dayjs"
|
||||
|
||||
export const toISODateString = (day: Dayjs): string => day.format("YYYY-MM-DD")
|
21
packages/common/logger.ts
Normal file
21
packages/common/logger.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import pino from "pino"
|
||||
import { LOG_LEVEL } from "@/../config.ts"
|
||||
|
||||
/**
|
||||
* / Returns a logging instance with the default log-level "info"
|
||||
*/
|
||||
const logger = pino(
|
||||
pino.destination({
|
||||
level: LOG_LEVEL,
|
||||
}),
|
||||
)
|
||||
|
||||
console.log = function (...args): void {
|
||||
if (args.length > 1) {
|
||||
logger.info(args?.slice(1), args?.[0])
|
||||
} else {
|
||||
logger.info(args?.[0])
|
||||
}
|
||||
}
|
||||
|
||||
export default logger
|
12
packages/common/package.json
Normal file
12
packages/common/package.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "common",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"pino": "^9.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"pino-pretty": "^13.0.0"
|
||||
}
|
||||
}
|
12
packages/sparebank1Api/common.ts
Normal file
12
packages/sparebank1Api/common.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { Failure, Success } from "./types"
|
||||
|
||||
export const baseUrl = "https://api.sparebank1.no"
|
||||
|
||||
export const success = <T>(data: T): Success<T> => ({
|
||||
status: "success",
|
||||
data: data,
|
||||
})
|
||||
export const failure = <T>(data: T): Failure<T> => ({
|
||||
status: "failure",
|
||||
data: data,
|
||||
})
|
30
packages/sparebank1Api/oauth.ts
Normal file
30
packages/sparebank1Api/oauth.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { OAuthTokenResponse, Result } from "./types"
|
||||
import * as querystring from "node:querystring"
|
||||
import { baseUrl, failure, success } from "./common"
|
||||
import logger from "@common/logger"
|
||||
|
||||
export async function refreshToken(
|
||||
clientId: string,
|
||||
clientSecret: string,
|
||||
refreshToken: string,
|
||||
): Promise<Result<OAuthTokenResponse, string>> {
|
||||
const queries = querystring.stringify({
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
refresh_token: refreshToken,
|
||||
grant_type: "refresh_token",
|
||||
})
|
||||
const url = `${baseUrl}/oauth/token?${queries}`
|
||||
logger.debug(`Sending POST request to url: '${url}'`)
|
||||
const response = await fetch(url, {
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
})
|
||||
logger.debug(`Received response with status '${response.status}'`)
|
||||
if (!response.ok) {
|
||||
return failure(await response.text())
|
||||
}
|
||||
return success(await response.json())
|
||||
}
|
6
packages/sparebank1Api/package.json
Normal file
6
packages/sparebank1Api/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "packages",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"license": "ISC"
|
||||
}
|
35
packages/sparebank1Api/transactions.ts
Normal file
35
packages/sparebank1Api/transactions.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import type { Interval, TransactionResponse } from "./types"
|
||||
import * as querystring from "node:querystring"
|
||||
import { toISODateString } from "@common/date"
|
||||
import logger from "@common/logger"
|
||||
import { baseUrl } from "./common"
|
||||
|
||||
export async function list(
|
||||
accessToken: string,
|
||||
accountKeys: string | ReadonlyArray<string>,
|
||||
interval?: Interval,
|
||||
): Promise<TransactionResponse> {
|
||||
const queryString = querystring.stringify({
|
||||
accountKey: accountKeys,
|
||||
...(interval && {
|
||||
fromDate: toISODateString(interval.fromDate),
|
||||
toDate: toISODateString(interval.toDate),
|
||||
}),
|
||||
})
|
||||
|
||||
const url = `${baseUrl}/personal/banking/transactions?${queryString}`
|
||||
logger.debug(`Sending GET request to '${url}'`)
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/vnd.sparebank1.v1+json;charset=utf-8",
|
||||
},
|
||||
})
|
||||
logger.debug(`Received response with status '${response.status}'`)
|
||||
if (response.ok) {
|
||||
return response.json()
|
||||
} else {
|
||||
logger.warn(await response.json())
|
||||
return { transactions: [] }
|
||||
}
|
||||
}
|
43
packages/sparebank1Api/types.ts
Normal file
43
packages/sparebank1Api/types.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import type { Dayjs } from "dayjs"
|
||||
|
||||
export type Success<T> = { status: "success"; data: T }
|
||||
export type Failure<T> = { status: "failure"; data: T }
|
||||
export type Result<OK, Err> = Success<OK> | Failure<Err>
|
||||
|
||||
export interface Interval {
|
||||
fromDate: Dayjs
|
||||
toDate: Dayjs
|
||||
}
|
||||
|
||||
export interface OAuthTokenResponse {
|
||||
access_token: string
|
||||
expires_in: number
|
||||
refresh_token_expires_in: number
|
||||
refresh_token_absolute_expires_in: number
|
||||
token_type: "Bearer"
|
||||
refresh_token: string
|
||||
}
|
||||
|
||||
export type BookingStatus = "PENDING" | "BOOKED"
|
||||
|
||||
export type NonUniqueId = "000000000000000000" | `${number}`
|
||||
|
||||
export interface SB1Transaction {
|
||||
id: string
|
||||
nonUniqueId: NonUniqueId
|
||||
// The Id of the account
|
||||
accountKey: string
|
||||
// Unix time
|
||||
date: number
|
||||
// Amount in NOK
|
||||
amount: number
|
||||
cleanedDescription: string
|
||||
remoteAccountName: string
|
||||
bookingStatus: BookingStatus
|
||||
|
||||
[key: string]: string | number | boolean | unknown
|
||||
}
|
||||
|
||||
export interface TransactionResponse {
|
||||
transactions: ReadonlyArray<SB1Transaction>
|
||||
}
|
Reference in New Issue
Block a user