SQLite
Moved DB queries to separate file Moved Sparebank1 API call to separate file Create database tokens table if it doesn't exist Signed-off-by: Martin Berg Alstad <git@martials.no>
This commit is contained in:
parent
8854a22b40
commit
3bf354b4bf
4
.gitignore
vendored
4
.gitignore
vendored
@ -174,3 +174,7 @@ dist
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
|
||||
# SQLite
|
||||
*.sqlite-shm
|
||||
*.sqlite-wal
|
||||
|
BIN
default.sqlite
BIN
default.sqlite
Binary file not shown.
19
src/bank/db/queries.ts
Normal file
19
src/bank/db/queries.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { type Database } from "better-sqlite3"
|
||||
import { type OAuthTokenResponse } from "@/bank/sparebank1.ts"
|
||||
|
||||
const tokenKey = "sparebank1"
|
||||
|
||||
export function insertTokens(
|
||||
db: Database,
|
||||
oAuthToken: OAuthTokenResponse,
|
||||
): void {
|
||||
db.prepare("INSERT INTO tokens VALUES (?, ?)").run(tokenKey, oAuthToken)
|
||||
}
|
||||
|
||||
export function fetchTokens(db: Database): OAuthTokenResponse | null {
|
||||
return (
|
||||
(db
|
||||
.prepare("SELECT data FROM tokens WHERE key = ?")
|
||||
.get(tokenKey) as OAuthTokenResponse) ?? null
|
||||
)
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
// TODO move types
|
||||
import {
|
||||
BANK_INITIAL_REFRESH_TOKEN,
|
||||
BANK_OAUTH_CLIENT_ID,
|
||||
BANK_OAUTH_CLIENT_SECRET,
|
||||
} from "@/../config.ts"
|
||||
import { BANK_INITIAL_REFRESH_TOKEN } from "@/../config.ts"
|
||||
import logger from "@/logger.ts"
|
||||
import dayjs from "dayjs"
|
||||
import { Database } from "better-sqlite3"
|
||||
import { insertTokens } from "@/bank/db/queries.ts"
|
||||
import * as Api from "./sparebank1Api.ts"
|
||||
|
||||
export interface OAuthTokenResponse {
|
||||
access_token: string
|
||||
@ -49,6 +48,11 @@ export class Sparebank1Impl implements Sparebank1 {
|
||||
private static baseUrl = "https://api.sparebank1.no"
|
||||
private _accessToken: AccessToken | undefined
|
||||
private _refreshToken: RefreshToken | undefined
|
||||
private readonly db: Database
|
||||
|
||||
constructor(db: Database) {
|
||||
this.db = db
|
||||
}
|
||||
|
||||
private set accessToken(accessToken: AccessToken) {
|
||||
this._accessToken = accessToken
|
||||
@ -83,19 +87,15 @@ export class Sparebank1Impl implements Sparebank1 {
|
||||
|
||||
async fetchNewRefreshToken(): Promise<OAuthTokenResponse> {
|
||||
const refreshToken: string = await this.getRefreshToken()
|
||||
const queries = new URLSearchParams({
|
||||
client_id: BANK_OAUTH_CLIENT_ID,
|
||||
client_secret: BANK_OAUTH_CLIENT_SECRET,
|
||||
refresh_token: refreshToken,
|
||||
grant_type: "refresh_token",
|
||||
})
|
||||
const response = await fetch(`${Sparebank1Impl.baseUrl}/token?${queries}`)
|
||||
const result = await Api.refreshToken(refreshToken)
|
||||
|
||||
if (!response.ok) {
|
||||
if (result.status === "failure") {
|
||||
throw new Error("Failed to fetch refresh token")
|
||||
}
|
||||
const oAuthToken = result.data
|
||||
|
||||
insertTokens(this.db, oAuthToken)
|
||||
|
||||
const oAuthToken: OAuthTokenResponse = await response.json()
|
||||
this.accessToken = {
|
||||
access_token: oAuthToken.access_token,
|
||||
expires_in: oAuthToken.expires_in,
|
||||
|
32
src/bank/sparebank1Api.ts
Normal file
32
src/bank/sparebank1Api.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { BANK_OAUTH_CLIENT_ID, BANK_OAUTH_CLIENT_SECRET } from "../../config.ts"
|
||||
import { OAuthTokenResponse } from "@/bank/sparebank1.ts"
|
||||
|
||||
const baseUrl = "https://api.sparebank1.no"
|
||||
|
||||
type Success<T> = { status: "success"; data: T }
|
||||
type Failure<T> = { status: "failure"; data: T }
|
||||
type Result<OK, Err> = Success<OK> | Failure<Err>
|
||||
|
||||
function success<T>(data: T): Success<T> {
|
||||
return { status: "success", data: data }
|
||||
}
|
||||
|
||||
function failure<T>(data: T): Failure<T> {
|
||||
return { status: "failure", data: data }
|
||||
}
|
||||
|
||||
export async function refreshToken(
|
||||
refreshToken: string,
|
||||
): Promise<Result<OAuthTokenResponse, string>> {
|
||||
const queries = new URLSearchParams({
|
||||
client_id: BANK_OAUTH_CLIENT_ID,
|
||||
client_secret: BANK_OAUTH_CLIENT_SECRET,
|
||||
refresh_token: refreshToken,
|
||||
grant_type: "refresh_token",
|
||||
})
|
||||
const response = await fetch(`${baseUrl}/token?${queries}`)
|
||||
if (!response.ok) {
|
||||
return failure(response.statusText)
|
||||
}
|
||||
return success(await response.json())
|
||||
}
|
@ -13,7 +13,6 @@ import Database from "better-sqlite3"
|
||||
|
||||
// TODO Transports api for pino https://github.com/pinojs/pino/blob/HEAD/docs/transports.md
|
||||
// TODO create .cache if missing
|
||||
// TODO store oauth tokens in a SQLite db, use same dep as actual
|
||||
|
||||
export async function daily(actual: Actual, bank: Bank): Promise<void> {
|
||||
// Fetch transactions from the bank
|
||||
@ -52,13 +51,17 @@ async function main(): Promise<void> {
|
||||
const actual = await ActualImpl.init()
|
||||
const databaseFileName = "default.sqlite"
|
||||
const db = new Database(databaseFileName)
|
||||
db.pragma("journal_mode = WAL")
|
||||
db.exec(
|
||||
"CREATE TABLE IF NOT EXISTS tokens (key VARCHAR PRIMARY KEY, data JSON)",
|
||||
)
|
||||
logger.info(`Started SQLlite database with filename="${databaseFileName}"`)
|
||||
|
||||
logger.info("Waiting for CRON job to start")
|
||||
|
||||
cronJobDaily(async () => {
|
||||
logger.info("Running daily job")
|
||||
await daily(actual, new Sparebank1Impl())
|
||||
await daily(actual, new Sparebank1Impl(db))
|
||||
logger.info("Finished daily job")
|
||||
})
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user