diff options
Diffstat (limited to 'app/src')
| -rw-r--r-- | app/src/app.d.ts | 13 | ||||
| -rw-r--r-- | app/src/app.html | 14 | ||||
| -rw-r--r-- | app/src/lib/helpers.ts | 5 | ||||
| -rw-r--r-- | app/src/lib/server/actual.ts | 72 | ||||
| -rw-r--r-- | app/src/lib/server/db/index.ts | 6 | ||||
| -rw-r--r-- | app/src/lib/server/db/schema.ts | 36 | ||||
| -rw-r--r-- | app/src/lib/server/sb1.ts | 207 | ||||
| -rw-r--r-- | app/src/lib/server/session-log.ts | 14 | ||||
| -rw-r--r-- | app/src/lib/shared.ts | 154 | ||||
| -rw-r--r-- | app/src/lib/ui/button.svelte | 41 | ||||
| -rw-r--r-- | app/src/routes/+page.server.ts | 15 | ||||
| -rw-r--r-- | app/src/routes/+page.svelte | 94 | ||||
| -rw-r--r-- | app/src/routes/methods.remote.ts | 38 | ||||
| -rw-r--r-- | app/src/routes/sb1-authorize/+server.ts | 48 | ||||
| -rw-r--r-- | app/src/routes/status.svelte | 17 |
15 files changed, 0 insertions, 774 deletions
diff --git a/app/src/app.d.ts b/app/src/app.d.ts deleted file mode 100644 index da08e6d..0000000 --- a/app/src/app.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -// See https://svelte.dev/docs/kit/types#app.d.ts -// for information about these interfaces -declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} - } -} - -export {}; diff --git a/app/src/app.html b/app/src/app.html deleted file mode 100644 index 91f0602..0000000 --- a/app/src/app.html +++ /dev/null @@ -1,14 +0,0 @@ -<!doctype html> -<html lang="en"> - -<head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - %sveltekit.head% -</head> - -<body data-sveltekit-preload-data="hover"> - <div style="display: contents">%sveltekit.body%</div> -</body> - -</html>
\ No newline at end of file diff --git a/app/src/lib/helpers.ts b/app/src/lib/helpers.ts deleted file mode 100644 index 35b5f65..0000000 --- a/app/src/lib/helpers.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { Temporal } from "temporal-polyfill"; - -export function instantAsHtmlInputValueString(instant: Temporal.Instant) { - return instant.toString().split("T")[0] -}
\ No newline at end of file diff --git a/app/src/lib/server/actual.ts b/app/src/lib/server/actual.ts deleted file mode 100644 index ca4d9c4..0000000 --- a/app/src/lib/server/actual.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { ACTUAL_FILE_ID, ACTUAL_HOST, ACTUAL_PASS } from "$env/static/private"; -import * as actualApi from "@actual-app/api" -import { existsSync, mkdirSync } from "node:fs"; -import path from "node:path" -import process from "node:process"; -import type { ImportTransactionEntity } from "@actual-app/api/@types/loot-core/src/types/models/import-transaction"; -import { Temporal } from "temporal-polyfill"; -import type { Sb1Transaction } from "$lib/shared"; - -let inited = false - -async function init() { - if (inited) return - const dataDir = path.resolve(process.cwd(), "data/actualDataDir") - if (!existsSync(dataDir)) mkdirSync(dataDir, { recursive: true }); - await actualApi.init({ - password: ACTUAL_PASS, - serverURL: ACTUAL_HOST, - dataDir - }) - await actualApi.downloadBudget(ACTUAL_FILE_ID) - await actualApi.sync() - inited = true -} - -const budget = { - async get_budgets() { - await init() - return actualApi.getBudgets() - }, - - async get_accounts() { - await init() - return actualApi.getAccounts() - }, - - async import_transactions(account: string, transactions: Sb1Transaction[], dryRun: boolean) { - await init() - - function parsedDate(date: number) { - return Temporal.Instant.fromEpochMilliseconds(date) - .toString({ timeZone: "Europe/Oslo" }) - .split("T")[0] - } - - function notes(transaction: Sb1Transaction) { - const { description, cleanedDescription } = transaction - if (description?.toLowerCase().trim() === cleanedDescription?.toLowerCase().trim()) return undefined - return description - } - - function amount(amount: number) { - const res = Math.round(amount * 10000) - console.log(`${amount}->${res}`) - return res - } - - const mapped: ImportTransactionEntity[] = transactions - .filter(c => c.bookingStatus === "BOOKED") - .map(c => ({ - account, - date: parsedDate(c.date), - amount: amount(c.amount), - notes: notes(c), - payee_name: c.cleanedDescription - })) - - return actualApi.importTransactions(account, mapped, { dryRun }) - } -} - -export default { init, budget } diff --git a/app/src/lib/server/db/index.ts b/app/src/lib/server/db/index.ts deleted file mode 100644 index e477388..0000000 --- a/app/src/lib/server/db/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { drizzle } from 'drizzle-orm/node-postgres'; -import { env } from '$env/dynamic/private'; - -if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set'); - -export const db = drizzle(env.DATABASE_URL);
\ No newline at end of file diff --git a/app/src/lib/server/db/schema.ts b/app/src/lib/server/db/schema.ts deleted file mode 100644 index dbff3a2..0000000 --- a/app/src/lib/server/db/schema.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { relations, sql } from 'drizzle-orm'; -import { numeric, text, pgTable, uuid, jsonb } from "drizzle-orm/pg-core"; -import type { SessionLogType } from '../session-log'; -import type { Sb1Tokens, Sb1Transaction, Sb1TransactionDetails } from '$lib/shared'; - -export const SyncSessionTable = pgTable("session", { - id: uuid('id').primaryKey().default(sql`uuidv7()`), - authzState: text("authzState"), - accessTokenCreated: numeric("accessTokenCreated"), - refreshTokenCreated: numeric("refreshTokenCreated"), - tokens: jsonb("tokens").$type<Sb1Tokens>() -}) - -export const SyncLogTable = pgTable("session_log", { - id: uuid('id').primaryKey().default(sql`uuidv7()`), - sessionId: text("session_id"), - dateTime: text("date_time"), - type: text("type").$type<SessionLogType>(), - msg: text("msg") -}) - -export const TransactionsTable = pgTable("transactions", { - transaction: jsonb("transaction").$type<Sb1Transaction>(), - details: jsonb("details").$type<Sb1TransactionDetails>() -}) - -export const SyncLogRelation = relations(SyncLogTable, ({ one }) => ({ - author: one(SyncSessionTable, { - fields: [SyncLogTable.sessionId], - references: [SyncSessionTable.id], - }) -})) - -export const SyncSessionLogRelation = relations(SyncSessionTable, ({ many }) => ({ - logs: many(SyncLogTable) -})) diff --git a/app/src/lib/server/sb1.ts b/app/src/lib/server/sb1.ts deleted file mode 100644 index 0a51649..0000000 --- a/app/src/lib/server/sb1.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { SB1_FIN_INST, SB1_ID, SB1_REDIRECT_URI, SB1_SECRET } from "$env/static/private"; -import { eq, sql } from "drizzle-orm"; -import { Temporal } from "temporal-polyfill"; -import { randomUUID } from "node:crypto"; -import { db } from "./db"; -import { SyncSessionTable, TransactionsTable } from "./db/schema"; -import { add_session_log } from "./session-log"; -import type { Sb1Account, Sb1Tokens, Sb1Transaction } from "$lib/shared"; - -const auth = { - async is_ready(): Promise<boolean> { - const token = await this.get_access_token() - return token !== "" - }, - async get_auth_info() { - const entity = await db.select({ - refreshTokenCreated: SyncSessionTable.refreshTokenCreated, - accessTokenCreated: SyncSessionTable.accessTokenCreated, - tokens: SyncSessionTable.tokens - }).from(SyncSessionTable) - if (!entity[0]) return undefined - const { tokens, accessTokenCreated, refreshTokenCreated } = entity[0] - if (!tokens) return undefined - const refreshTokenExpires = Temporal.Instant.fromEpochMilliseconds(Number(refreshTokenCreated)).add({ seconds: tokens.refresh_token_expires_in }) - const accessTokenExpires = Temporal.Instant.fromEpochMilliseconds(Number(accessTokenCreated)).add({ seconds: tokens.expires_in }) - return { - refreshTokenExpires, - accessTokenExpires - } - }, - async init_auth_session(): Promise<string> { - const state = randomUUID() - - await db.insert(SyncSessionTable).values({ - authzState: state - }) - - const authorizeUrl = new URL("https://api.sparebank1.no/oauth/authorize"); - authorizeUrl.searchParams.set("client_id", SB1_ID); - authorizeUrl.searchParams.set("state", state); - authorizeUrl.searchParams.set("redirect_uri", SB1_REDIRECT_URI); - authorizeUrl.searchParams.set("finInst", SB1_FIN_INST); - authorizeUrl.searchParams.set("response_type", "code"); - return authorizeUrl.toString() - }, - async get_access_token() { - const result = await db.select({ - tokens: SyncSessionTable.tokens, - refreshTokenCreated: SyncSessionTable.refreshTokenCreated, - accessTokenCreated: SyncSessionTable.accessTokenCreated - }).from(SyncSessionTable) - - if (!result[0]) return undefined - - const { tokens, accessTokenCreated, refreshTokenCreated } = result[0] - - if (!tokens) return undefined - - const nowInstant = Temporal.Now.instant() - - const accessTokenExpiredInstant = Temporal.Instant.fromEpochMilliseconds(Number(accessTokenCreated)).add({ seconds: tokens.expires_in }) - if (Temporal.Instant.compare(nowInstant, accessTokenExpiredInstant) >= 0) { - const refreshedTokens = await this.refresh_token() - if (refreshedTokens) return refreshedTokens.access_token - } - - const refreshTokenExpiredInstant = Temporal.Instant.fromEpochMilliseconds(Number(refreshTokenCreated)).add({ seconds: tokens.refresh_token_expires_in }) - if (Temporal.Instant.compare(nowInstant, refreshTokenExpiredInstant) >= 0) { - return undefined - } - - return tokens?.access_token as string - }, - async refresh_token(): Promise<Sb1Tokens | null> { - console.log("Refreshing tokens") - const entity = await db.select({ - tokens: SyncSessionTable.tokens, - id: SyncSessionTable.id - }).from(SyncSessionTable) - - const { tokens: currentTokens, id } = entity[0] - - if (!currentTokens) return null - if (!currentTokens.refresh_token) throw new Error("No refresh token"); - - const params = new URLSearchParams(); - - params.set("client_id", SB1_ID); - params.set("client_secret", SB1_SECRET); - params.set("refresh_token", currentTokens.refresh_token); - params.set("grant_type", "refresh_token"); - - const res = await fetch("https://api.sparebank1.no/oauth/token", { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - method: "POST", - body: params, - }); - const tokens = await res.json() as Sb1Tokens - const epoch = Temporal.Now.instant().epochMilliseconds - if (res.ok) { - await db.update(SyncSessionTable).set({ tokens, accessTokenCreated: epoch.toString(), refreshTokenCreated: epoch.toString() }).where(eq(SyncSessionTable.id, id)) - await add_session_log(id, "REFRESH_SB1_TOKEN", "Done") - return tokens - } else { - console.error("Failed to refresh tokens", tokens) - await add_session_log(id, "REFRESH_SB1_TOKEN", "Failed: " + JSON.stringify(res)) - return null - } - } -} - -const data = { - async get_accounts() { - const token = await auth.get_access_token() - if (!token) return undefined - const url = new URL( - "https://api.sparebank1.no/personal/banking/accounts", - ); - const response = await fetch(url, { - headers: { - Authorization: `Bearer ${token}` - } - }) - if (response.ok) return await response.json() as { accounts: Array<Sb1Account> } - else console.error(await response.text()) - }, - async get_transactions(accountKey: string, delta?: Temporal.Instant) { - const token = await auth.get_access_token() - if (!token) return undefined - - const params = new URLSearchParams({ - "accountKey": accountKey, - }); - - if (delta) { - params.append("fromDate", formatInstant(delta, "yyyy-MM-dd")) - params.append("Transaction source", "ALL") - } - - const response = await fetch("https://api.sparebank1.no/personal/banking/transactions?" + params, { - headers: { - Authorization: `Bearer ${token}`, - Accept: "application/vnd.sparebank1.v1+json;charset=utf-8" - }, - }); - - const json = await response.json() - return json["transactions"] as Sb1Transaction[] - } -} - -export default { auth, data, init } - -let importInterval: NodeJS.Timeout -let inited = false - -async function init() { - if (inited) return - if (importInterval) clearInterval(importInterval) - await importTransactions() - importInterval = setInterval(async () => importTransactions, 60 * 60 * 1000) - inited = true -} - -async function importTransactions() { - console.log("Creating sb1 transactions indb") - const accounts = await data.get_accounts() - for (const account of accounts?.accounts ?? []) { - const transactions = await data.get_transactions(account.key) - for (const transaction of transactions ?? []) { - // if (await transactionExists(transaction.id)) continue - await db.insert(TransactionsTable).values({ transaction }) - } - } -} - -async function transactionExists(transactionId: string) { - const query = sql`select data ->>'id' as id from ${TransactionsTable} where id=${transactionId}`; - return (await db.execute(query)).rowCount ?? 0 > 0 -} - -function formatInstant( - instant: Temporal.Instant, - format: string, - timeZone: string = "UTC" -): string { - const zdt = instant.toZonedDateTimeISO(timeZone); - - const pad = (value: number, length = 2) => - value.toString().padStart(length, "0"); - - const replacements: Record<string, string> = { - yyyy: pad(zdt.year, 4), - MM: pad(zdt.month), - dd: pad(zdt.day), - HH: pad(zdt.hour), - mm: pad(zdt.minute), - ss: pad(zdt.second), - }; - - return format.replace( - /yyyy|MM|dd|HH|mm|ss/g, - (token) => replacements[token] - ); -}
\ No newline at end of file diff --git a/app/src/lib/server/session-log.ts b/app/src/lib/server/session-log.ts deleted file mode 100644 index 54498f3..0000000 --- a/app/src/lib/server/session-log.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Temporal } from "temporal-polyfill" -import { db } from "./db" -import { SyncLogTable } from "./db/schema" - -export type SessionLogType = "CREATED" | "SYNC_START" | "REFRESH_SB1_TOKEN" - -export async function add_session_log(id: string, type: SessionLogType, msg: string) { - db.insert(SyncLogTable).values({ - dateTime: String(Temporal.Now.instant().epochMilliseconds), - sessionId: id, - type: type, - msg: msg - }) -}
\ No newline at end of file diff --git a/app/src/lib/shared.ts b/app/src/lib/shared.ts deleted file mode 100644 index cc4472f..0000000 --- a/app/src/lib/shared.ts +++ /dev/null @@ -1,154 +0,0 @@ -import * as v from 'valibot' - -export type ImportForm = v.InferOutput<typeof ImportForm> -export const ImportForm = v.object({ - budgetId: v.string(), - mappings: v.array( - v.object({ - sb1Id: v.string(), - actualId: v.string() - }) - ), - dryRun: v.boolean() -}) -export type Sb1TransactionDetails = { - id: string; - date: Date; - type: string; - amount: number; - typeCode: string; - typeText: string; - valueDate: Date; - accountKey: string; - bookedDate: Date; - accountName: string; - description: string; - eInvoiceUrl: string; - nonUniqueId: string; - postingDate: Date; - currencyCode: string; - exchangeRate: number; - kidOrMessage: string; - accountNumber: number; - currencyAmount: number; - paymentDetails: Sb1PaymentDetails; - accountCurrency: string; - archiveReference: string; - paymentReference: string; - remoteAccountName: string; - cleanedDescription: string; - numericalReference: string; - classificationInput: Sb1ClassificationInput; - originalDescription: string; - remoteAccountNumber: string; -} - -export type Sb1PaymentDetails = { - amount: number; - message: string; - paymentCid: string; - payeeAddress: Sb1PayeeAddress; - payeeBankName: string; - payeeBicSwift: string; - amountCurrency: string; - serviceCharges: Sb1ServiceCharge[]; - payeeBankAddress: Sb1PayeeAddress; - paymentReference: string; - payeeEmailAddress: string; - internationalDetails: Sb1InternationalDetails; -} - -export type Sb1InternationalDetails = { - agreedRate: string; - agreedWith: string; - authorityReportCode: string; - authorityReportText: string; -} - -export type Sb1PayeeAddress = { - city: string; - line1: string; - line2: string; - line3: string; - zipCode: string; - countryCode: string; -} - -export type Sb1ServiceCharge = { - paidBy: string; - chargedAmount: number; - chargedAmountCurrency: string; -} - -export type Sb1Tokens = { - access_token: string - expires_in: number - refresh_token_expires_in: number - refresh_token_absolute_expires_in: number - token_type: string - refresh_token: string -} - -export type Sb1Transaction = { - id: string - nonUniqueId: string - description: string - cleanedDescription: string - accountNumber: Sb1AccountNumber - amount: number - date: number - interestDate: number - typeCode: string - typeText: string - currencyCode: string - canShowDetails: boolean - source: string - isConfidential: boolean - bookingStatus: string - accountName: string - accountKey: string - accountCurrency: string - isFromCurrencyAccount: boolean - classificationInput: Sb1ClassificationInput -} - -export type Sb1Account = { - key: string; - accountNumber: string; - iban: string; - name: string; - description: string; - balance: number; - availableBalance: number; - currencyCode: string; - owner: Sb1AccountOwner; - productType: string; - type: string; - productId: string; - descriptionCode: string; - accountProperties: { [key: string]: boolean }; -} - -export type Sb1AccountOwner = { - name: string; - firstName: string; - lastName: string; - type: string; - age: number; - customerKey: string; - ssnKey: string; -} - -export type Sb1AccountNumber = { - value: string - formatted: string - unformatted: string -} - -export type Sb1ClassificationInput = { - id: string - amount: number - type: string - text: string - date: string -} diff --git a/app/src/lib/ui/button.svelte b/app/src/lib/ui/button.svelte deleted file mode 100644 index ad82f57..0000000 --- a/app/src/lib/ui/button.svelte +++ /dev/null @@ -1,41 +0,0 @@ -<script lang="ts"> - import type { HTMLButtonAttributes } from "svelte/elements"; - let { children, loading, type = "button", ...restProps }: Props = $props(); - - type Props = { - loading?: boolean; - } & HTMLButtonAttributes; -</script> - -<button {...restProps} {type}> - {@render children?.()} - {#if loading} - ... - {/if} -</button> - -<style> - button { - border: 1px solid rgba(0, 0, 0, 0.3); - background-color: rgba(0, 0, 0, 0.1); - align-items: center; - border-radius: 3px; - padding: 2px 4px; - cursor: pointer; - display: flex; - gap: 3px; - transition: 0.075s all ease; - height: fit-content; - - &:hover, - &:focus { - background-color: rgba(0, 0, 0, 0.15); - } - - &:active { - background-color: rgba(0, 0, 0, 0.2); - transform: scale(0.96); - transition: 0.15s all ease; - } - } -</style> diff --git a/app/src/routes/+page.server.ts b/app/src/routes/+page.server.ts deleted file mode 100644 index df076d7..0000000 --- a/app/src/routes/+page.server.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { PageServerLoad } from './$types'; -import actual from '$lib/server/actual'; -import sb1 from "$lib/server/sb1" - -export const load = (async () => { - return { - actual: { - budgets: await actual.budget.get_budgets(), - accounts: await actual.budget.get_accounts(), - }, - sb1: { - accounts: (await sb1.data.get_accounts())?.accounts - } - }; -}) satisfies PageServerLoad;
\ No newline at end of file diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte deleted file mode 100644 index 693a430..0000000 --- a/app/src/routes/+page.svelte +++ /dev/null @@ -1,94 +0,0 @@ -<script lang="ts"> - import Button from "$lib/ui/button.svelte"; - import { - clear_auth_session, - init_auth_session, - do_import, - } from "./methods.remote"; - import type { PageProps } from "./$types"; - import type { ImportForm } from "$lib/shared"; - - let { data }: PageProps = $props(); - let navigating = $state(false); - let form = $state<ImportForm>({ - budgetId: "", - mappings: [], - dryRun: true, - }); - - async function run(e: SubmitEvent) { - e.preventDefault(); - if (!form.mappings.length) { - return; - } - await do_import(form); - } - - async function authorize() { - navigating = true; - location.href = await init_auth_session(); - } - - async function logout() { - navigating = true; - await clear_auth_session(); - location.reload(); - } - - function onMappingChanged(sb1Id: string, actualId: string) { - let mappings = form.mappings; - if (mappings.find((c) => c.sb1Id === sb1Id)) - mappings = mappings.filter((c) => c.sb1Id !== sb1Id); - mappings.push({ sb1Id, actualId }); - form.mappings = mappings; - } -</script> - -<main> - {#if data.sb1.accounts?.length} - <form onsubmit={run}> - <h3>Importer</h3> - <fieldset> - <h4>Kontoer</h4> - {#each data.sb1.accounts as account} - {@const actualId = `mapping-${account.key}-actual`} - <div> - <code>{account.name}</code> - <span>→</span> - <label for={actualId}>Actual</label> - <select - name={actualId} - id={actualId} - onchange={(e) => - onMappingChanged( - account.key, - e.currentTarget.value, - )} - > - <option value="-" selected>-</option> - {#each data.actual.accounts as actual} - <option value={actual.id}> - {actual.name} - </option> - {/each} - </select> - </div> - {/each} - <h4>Ellers</h4> - <input - type="checkbox" - id="dry" - bind:checked={form.dryRun} - /><label for="dry">Tørrkjøring</label><br /><br /> - <input type="submit" /> - </fieldset> - </form> - <h3>Annet</h3> - <Button onclick={logout} loading={navigating}>Logg ut</Button> - <div></div> - {:else} - <Button onclick={authorize} loading={navigating} - >Autentisér hos Sparebanken 1</Button - > - {/if} -</main> diff --git a/app/src/routes/methods.remote.ts b/app/src/routes/methods.remote.ts deleted file mode 100644 index d6fd908..0000000 --- a/app/src/routes/methods.remote.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { db } from "$lib/server/db"; -import { SyncSessionTable } from "$lib/server/db/schema"; -import { command, query } from "$app/server"; -import sb1 from "$lib/server/sb1"; -import actual from "$lib/server/actual"; -import { ImportForm } from "$lib/shared"; - -const init_auth_session = command(async () => { - return await sb1.auth.init_auth_session() -}) - -const clear_auth_session = query(async () => { - await db.delete(SyncSessionTable) -}) - -const do_import = command(ImportForm, async (form) => { - for (const mapping of form.mappings) { - const transactions = await sb1.data.get_transactions(mapping.sb1Id) - if (!transactions?.length) continue - console.log(await actual.budget.import_transactions(mapping.actualId, transactions, form.dryRun)) - } -}) - -const init_sb1 = command(async () => { - return await sb1.init() -}) - -const init_actual = command(async () => { - return await actual.init() -}) - -export { - init_auth_session, - do_import, - init_actual, - init_sb1, - clear_auth_session -} diff --git a/app/src/routes/sb1-authorize/+server.ts b/app/src/routes/sb1-authorize/+server.ts deleted file mode 100644 index d6b8fbf..0000000 --- a/app/src/routes/sb1-authorize/+server.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { error, redirect, json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { db } from '$lib/server/db'; -import { SyncSessionTable } from '$lib/server/db/schema'; -import { eq } from 'drizzle-orm'; -import { SB1_ID, SB1_REDIRECT_URI, SB1_SECRET } from '$env/static/private'; -import { Temporal } from "temporal-polyfill" -import sb1 from "$lib/server/sb1" - -export const GET: RequestHandler = async ({ url }) => { - const code = url.searchParams.get('code') - const state = url.searchParams.get('state'); - - if (!code) error(400, "?code is missing") - if (!state) error(400, "?state is missing") - - const session = await db.select().from(SyncSessionTable).where(eq(SyncSessionTable.authzState, state)) - const { id } = session[0] - if (!id) return error(500, "Ingen session") - - const fd = new URLSearchParams() - - fd.set("client_id", SB1_ID) - fd.set("client_secret", SB1_SECRET) - fd.set("redirect_uri", SB1_REDIRECT_URI) - fd.set("code", code) - fd.set("state", state) - fd.set("grant_type", "authorization_code") - - const response = await fetch("https://api.sparebank1.no/oauth/token", { - method: "post", - headers: { - "Content-Type": "application/x-www-form-urlencoded" - }, - body: fd - }) - - const responseJson = await response.json() - - if (response.ok) { - const epoch = Temporal.Now.instant().epochMilliseconds - await db.update(SyncSessionTable).set({ tokens: responseJson, accessTokenCreated: epoch.toString(), refreshTokenCreated: epoch.toString() }).where(eq(SyncSessionTable.id, id)) - await sb1.init() - redirect(302, "/") - } else { - return json(responseJson) - } -} diff --git a/app/src/routes/status.svelte b/app/src/routes/status.svelte deleted file mode 100644 index fe09193..0000000 --- a/app/src/routes/status.svelte +++ /dev/null @@ -1,17 +0,0 @@ -<script lang="ts"> - type Props = { - type: "sb1" | "actual"; - }; - import { onMount } from "svelte"; - let { type }: Props = $props(); - - onMount(() => {}); -</script> - -<div> - <span>{type}</span> - <div class="status"></div> - <div class="refresh">⟲</div> -</div> - -<style></style> |
