way overdue
This commit is contained in:
6
config.js
Normal file
6
config.js
Normal file
@ -0,0 +1,6 @@
|
||||
export const STATUS_FINISHED = 'STATUS_FINISHED';
|
||||
export const STATUS_PROCESSING = 'STATUS_PROCESSING';
|
||||
export const STATUS_IDLE = 'STATUS_IDLE';
|
||||
export const STATUS_ERROR = 'STATUS_ERROR';
|
||||
|
||||
export const MESSAGE_START = 'START';
|
||||
5073
package-lock.json
generated
5073
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
@ -13,31 +13,28 @@
|
||||
"generate-pwa-assets": "pwa-assets-generator"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/adapter-node": "^5.2.13",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@vite-pwa/assets-generator": "^1.0.0",
|
||||
"@vite-pwa/sveltekit": "^1.0.0",
|
||||
"daisyui": "^5.0.43",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"svelte": "^5.0.0",
|
||||
"vite": "^6.2.6"
|
||||
"@sveltejs/adapter-auto": "^7.0.1",
|
||||
"@sveltejs/adapter-node": "^5.5.4",
|
||||
"@sveltejs/kit": "^2.57.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@vite-pwa/assets-generator": "^1.0.2",
|
||||
"@vite-pwa/sveltekit": "^1.1.0",
|
||||
"daisyui": "^5.5.19",
|
||||
"prettier": "^3.8.3",
|
||||
"prettier-plugin-svelte": "^3.5.1",
|
||||
"svelte": "^5.55.4",
|
||||
"vite": "^7.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/sveltekit": "^1.10.0",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"better-auth": "^1.3.4",
|
||||
"cron": "^4.3.2",
|
||||
"echarts": "^5.6.0",
|
||||
"patchright": "^1.52.5",
|
||||
"pg": "^8.16.3",
|
||||
"postgres": "^3.4.7",
|
||||
"puppeteer": "^24.14.0",
|
||||
"puppeteer-extra": "^3.3.6",
|
||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||
"tailwindcss": "^4.1.11"
|
||||
"@auth/sveltekit": "^1.11.2",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"better-auth": "^1.6.5",
|
||||
"cron": "^4.4.0",
|
||||
"echarts": "^6.0.0",
|
||||
"patchright": "^1.59.4",
|
||||
"pg": "^8.20.0",
|
||||
"postgres": "^3.4.9",
|
||||
"tailwindcss": "^4.2.2"
|
||||
}
|
||||
}
|
||||
@ -571,7 +571,7 @@ export async function getUnderallocatedTransactions() {
|
||||
export async function getOrphanedTransactions() {
|
||||
const result = await db`
|
||||
SELECT budget.name as budget_name,
|
||||
budget_transaction.id as trans_id,
|
||||
budget_transaction.id as budget_transaction_id,
|
||||
budget_transaction.budget_id as budget_id,
|
||||
budget_transaction.transaction_id as transaction_id,
|
||||
budget_transaction.amount as budget_amount,
|
||||
@ -586,10 +586,14 @@ export async function getOrphanedTransactions() {
|
||||
|
||||
export async function getLast30DaysTransactionsSums() {
|
||||
let result = await db`
|
||||
SELECT cast(date as date) as date, SUM(amount)
|
||||
from transaction
|
||||
where date >= (CURRENT_DATE - interval '30 days')
|
||||
GROUP BY cast(date as date)
|
||||
SELECT cast(date as date) as date,
|
||||
SUM(budget_transaction.amount) as total_amount,
|
||||
budget.name as budget_name
|
||||
from budget_transaction
|
||||
join transaction on budget_transaction.transaction_id = transaction.id
|
||||
join budget on budget_transaction.budget_id = budget.id
|
||||
where cast(date as date) >= (CURRENT_DATE - interval '30 days')
|
||||
GROUP BY cast(date as date), budget.name
|
||||
order by date desc;
|
||||
`;
|
||||
// result = Result [{ id: 1, amount: 100.00, notes: "Recent transaction" }, ...]
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
|
||||
import db from './db.js';
|
||||
import { chromium } from 'patchright';
|
||||
|
||||
@ -58,7 +59,7 @@ export async function pullData(amount = 100) {
|
||||
await new Promise((resolve) => setTimeout(resolve, Math.random() * 5000));
|
||||
await page.getByLabel('Login ID').fill('92830');
|
||||
await new Promise((resolve) => setTimeout(resolve, Math.random() * 5000));
|
||||
await page.getByLabel('Password').fill('Cmtjlt13');
|
||||
await page.getByLabel('Password').fill('u3T8F}$*fNdT$bK');
|
||||
await new Promise((resolve) => setTimeout(resolve, Math.random() * 5000));
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
@ -183,29 +184,9 @@ export async function pullData(amount = 100) {
|
||||
|
||||
const cardRegEx = /\d{4}$/;
|
||||
|
||||
// Check if any pending need to be updated
|
||||
// Check if any pending need to be update
|
||||
|
||||
state.push(`Checking for pending transactions for account ID: ${account.id}`);
|
||||
let currentPend =
|
||||
await db`SELECT id, amount, description, date from transaction where account_id = ${account.id} AND pending=true`;
|
||||
for (const pend of currentPend) {
|
||||
const found = transactions.find((t) => `${t.description}:${t.amount}` === pend.id && t.transactionType == "Memo");
|
||||
|
||||
if (found && found.transactionType != "Memo")
|
||||
{state.push(
|
||||
`I think I found an updated transaction: ${found.statementDescription} ${found.amount} for ${pend.description} ${pend.amount}`
|
||||
);
|
||||
await db`UPDATE budget_transaction SET transaction_id = ${found.transactionId} WHERE transaction_id = ${pend.id}`;
|
||||
await db`DELETE FROM transaction WHERE id = ${pend.id}`;
|
||||
} else if (!found)
|
||||
{
|
||||
state.push(`Orphaning no longer pending budget transaction with no new parent`);
|
||||
|
||||
await db`UPDATE budget_transaction SET transaction_id = null, notes = notes || ${pend.description} WHERE transaction_id = ${pend.id}`;
|
||||
state.push(`Removing pending transaction: ${pend.id}`);
|
||||
await db`DELETE FROM transaction WHERE id = ${pend.id}`;
|
||||
}
|
||||
}
|
||||
|
||||
for (const transaction of transactions) {
|
||||
const amount = Number(transaction.amount);
|
||||
@ -251,6 +232,33 @@ export async function pullData(amount = 100) {
|
||||
statement_description = EXCLUDED.statement_description,
|
||||
pending = EXCLUDED.pending`;
|
||||
}
|
||||
|
||||
state.push(`Checking for pending transactions for account ID: ${account.id}`);
|
||||
let currentPend =
|
||||
await db`SELECT id, amount, description, date from transaction where account_id = ${account.id} AND pending=true`;
|
||||
for (const pend of currentPend) {
|
||||
const found = transactions.find((t) => `${t.description}:${t.amount}` === pend.id && t.transactionType == "Memo");
|
||||
if (found)
|
||||
{
|
||||
state.push(`Looks like pending ${found.description} $${found.amount} still exsists`);
|
||||
} else {
|
||||
const canidate = transactions.find((t) => (t.description.toLowerCase().includes(pend.description.toLowerCase()) && t.amount == pend.amount));
|
||||
if (canidate)
|
||||
{
|
||||
state.push(
|
||||
`I think I found an updated transaction: ${canidate.statementDescription} ${canidate.amount} for ${pend.description} ${pend.amount}`
|
||||
);
|
||||
//await db`UPDATE budget_transaction SET transaction_id = ${canidate.transactionId} WHERE transaction_id = ${pend.id}`;
|
||||
//await db`DELETE FROM transaction WHERE id = ${pend.id}`;
|
||||
} else {
|
||||
state.push(`Orphaning no longer pending budget transaction with no new parent`);
|
||||
|
||||
await db`UPDATE budget_transaction SET transaction_id = null, notes = notes || ${pend.description} WHERE transaction_id = ${pend.id}`;
|
||||
state.push(`Removing pending transaction: ${pend.id}`);
|
||||
await db`DELETE FROM transaction WHERE id = ${pend.id}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
state.push('Orphaning transactions');
|
||||
|
||||
52
src/lib/waterfall.svelte
Normal file
52
src/lib/waterfall.svelte
Normal file
@ -0,0 +1,52 @@
|
||||
<script>
|
||||
import * as charts from 'echarts';
|
||||
let { last30days, budgets } = $props();
|
||||
|
||||
let dailyData = $derived(last30days.filter((e) => e.budget_name === 'Daily'));
|
||||
let dailyTotal = $derived(budgets.find((b) => b.name === 'Daily')?.sum || 0);
|
||||
|
||||
let data = $derived.by(() => {
|
||||
let accumulated = dailyTotal;
|
||||
let result = dailyData
|
||||
.map((entry) => {
|
||||
accumulated -= parseFloat(entry.total_amount);
|
||||
return { total: Math.round(accumulated), date: entry.date };
|
||||
})
|
||||
.reverse();
|
||||
result.push({ total: dailyTotal, date: 'Today' });
|
||||
return result;
|
||||
});
|
||||
|
||||
$inspect(data);
|
||||
|
||||
const option = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: data.map((e) => new Date(e.date).getDate())
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: data.map((e) => e.total),
|
||||
type: 'line',
|
||||
smooth: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
function echartsinit(node, option) {
|
||||
const chart = charts.init(node);
|
||||
chart.setOption(option);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container" use:echartsinit={option} />
|
||||
|
||||
<style>
|
||||
.container {
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
}
|
||||
</style>
|
||||
31
src/lib/workers/worker.js
Normal file
31
src/lib/workers/worker.js
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
import { CronJob } from 'cron';
|
||||
|
||||
|
||||
|
||||
const job = new CronJob(
|
||||
'0,31 * * * *', // cronTime
|
||||
async function () {
|
||||
notify();
|
||||
}, // onTick
|
||||
null, // onComplete
|
||||
true, // start
|
||||
'America/Detroit' // timeZone
|
||||
);
|
||||
|
||||
async function notify() {
|
||||
const res = await fetch("/api/budget");
|
||||
const budgets = await res.json();
|
||||
let text= '';
|
||||
if (budgets.length > 0) {
|
||||
text += `${budgets[0].name} - $${budgets[0].amount}\r\n`;
|
||||
}
|
||||
const notification = new Notification("Budgets",
|
||||
{
|
||||
body: text,
|
||||
icon: "favicon.ico",
|
||||
tag: "budget_update",
|
||||
silent: false,
|
||||
vibrate: [200, 100, 200, 100, 200, 100, 200]
|
||||
});
|
||||
}
|
||||
@ -1,7 +1,24 @@
|
||||
<script>
|
||||
import TransactionList from '$lib/transactionList.svelte';
|
||||
import Budgetlist from '$lib/budgetlist.svelte';
|
||||
import { echarts } from '$lib/echarts';
|
||||
import { browser } from '$app/environment';
|
||||
import Waterfall from '$lib/waterfall.svelte';
|
||||
let worker = $state();
|
||||
$effect(async () => {
|
||||
Notification.requestPermission().then((result) => {
|
||||
console.log(result);
|
||||
});
|
||||
if (browser) {
|
||||
// Check if we are in a browser
|
||||
if (window.Worker) {
|
||||
const MyWorker = await import('$workers/worker.js?worker');
|
||||
// And initiate the
|
||||
worker = new MyWorker.default();
|
||||
} else {
|
||||
console.warn('Web Workers are not supported in this browser.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let { data } = $props();
|
||||
let total = $derived(data.total);
|
||||
@ -9,7 +26,7 @@
|
||||
let underAllocatedTrans = $derived(data.underAllocatedTrans);
|
||||
let budgets = $derived(data.budgets);
|
||||
let budgetTransactions = $derived(data.budgetTransactions);
|
||||
let last30days = $derived(data.last30DaysTransactionsSums.reverse());
|
||||
let last30days = $derived(data.last30DaysTransactionsSums);
|
||||
let orphanedTransactions = $derived(data.orphanedTransactions);
|
||||
</script>
|
||||
|
||||
@ -38,3 +55,5 @@
|
||||
|
||||
<div class="text-xl divider">Orphans</div>
|
||||
<Budgetlist transactions={orphanedTransactions} />
|
||||
|
||||
<Waterfall {last30days} {budgets} />
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { addBudget, deleteBudget } from '$lib/db';
|
||||
import { addBudget, deleteBudget, getBudgets } from '$lib/db';
|
||||
|
||||
// In-memory store for demonstration (replace with a database in production)
|
||||
let budgets = [];
|
||||
@ -16,3 +16,13 @@ export async function POST({ request }) {
|
||||
|
||||
return json(res, { status: 201 });
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
// Fetch all budgets
|
||||
try {
|
||||
let res = await getBudgets();
|
||||
return json(res, { status: 200 });
|
||||
} catch (error) {
|
||||
return json({ error: 'Failed to fetch budgets' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,20 @@
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
kit: {
|
||||
alias: {
|
||||
$workers: 'src/lib/workers',
|
||||
$config: 'src/config.js',
|
||||
},
|
||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
}
|
||||
},
|
||||
preprocess: vitePreprocess(),
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
BIN
united-login.png
Normal file
BIN
united-login.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
Reference in New Issue
Block a user