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"
|
"generate-pwa-assets": "pwa-assets-generator"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^6.0.0",
|
"@sveltejs/adapter-auto": "^7.0.1",
|
||||||
"@sveltejs/adapter-node": "^5.2.13",
|
"@sveltejs/adapter-node": "^5.5.4",
|
||||||
"@sveltejs/kit": "^2.16.0",
|
"@sveltejs/kit": "^2.57.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@vite-pwa/assets-generator": "^1.0.0",
|
"@vite-pwa/assets-generator": "^1.0.2",
|
||||||
"@vite-pwa/sveltekit": "^1.0.0",
|
"@vite-pwa/sveltekit": "^1.1.0",
|
||||||
"daisyui": "^5.0.43",
|
"daisyui": "^5.5.19",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.8.3",
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "^3.5.1",
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.55.4",
|
||||||
"vite": "^6.2.6"
|
"vite": "^7.0.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/sveltekit": "^1.10.0",
|
"@auth/sveltekit": "^1.11.2",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"better-auth": "^1.3.4",
|
"better-auth": "^1.6.5",
|
||||||
"cron": "^4.3.2",
|
"cron": "^4.4.0",
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^6.0.0",
|
||||||
"patchright": "^1.52.5",
|
"patchright": "^1.59.4",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.20.0",
|
||||||
"postgres": "^3.4.7",
|
"postgres": "^3.4.9",
|
||||||
"puppeteer": "^24.14.0",
|
"tailwindcss": "^4.2.2"
|
||||||
"puppeteer-extra": "^3.3.6",
|
|
||||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
||||||
"tailwindcss": "^4.1.11"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -571,7 +571,7 @@ export async function getUnderallocatedTransactions() {
|
|||||||
export async function getOrphanedTransactions() {
|
export async function getOrphanedTransactions() {
|
||||||
const result = await db`
|
const result = await db`
|
||||||
SELECT budget.name as budget_name,
|
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.budget_id as budget_id,
|
||||||
budget_transaction.transaction_id as transaction_id,
|
budget_transaction.transaction_id as transaction_id,
|
||||||
budget_transaction.amount as budget_amount,
|
budget_transaction.amount as budget_amount,
|
||||||
@ -586,10 +586,14 @@ export async function getOrphanedTransactions() {
|
|||||||
|
|
||||||
export async function getLast30DaysTransactionsSums() {
|
export async function getLast30DaysTransactionsSums() {
|
||||||
let result = await db`
|
let result = await db`
|
||||||
SELECT cast(date as date) as date, SUM(amount)
|
SELECT cast(date as date) as date,
|
||||||
from transaction
|
SUM(budget_transaction.amount) as total_amount,
|
||||||
where date >= (CURRENT_DATE - interval '30 days')
|
budget.name as budget_name
|
||||||
GROUP BY cast(date as date)
|
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;
|
order by date desc;
|
||||||
`;
|
`;
|
||||||
// result = Result [{ id: 1, amount: 100.00, notes: "Recent transaction" }, ...]
|
// result = Result [{ id: 1, amount: 100.00, notes: "Recent transaction" }, ...]
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import db from './db.js';
|
import db from './db.js';
|
||||||
import { chromium } from 'patchright';
|
import { chromium } from 'patchright';
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ export async function pullData(amount = 100) {
|
|||||||
await new Promise((resolve) => setTimeout(resolve, Math.random() * 5000));
|
await new Promise((resolve) => setTimeout(resolve, Math.random() * 5000));
|
||||||
await page.getByLabel('Login ID').fill('92830');
|
await page.getByLabel('Login ID').fill('92830');
|
||||||
await new Promise((resolve) => setTimeout(resolve, Math.random() * 5000));
|
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 new Promise((resolve) => setTimeout(resolve, Math.random() * 5000));
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
|
|
||||||
@ -183,29 +184,9 @@ export async function pullData(amount = 100) {
|
|||||||
|
|
||||||
const cardRegEx = /\d{4}$/;
|
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) {
|
for (const transaction of transactions) {
|
||||||
const amount = Number(transaction.amount);
|
const amount = Number(transaction.amount);
|
||||||
@ -251,6 +232,33 @@ export async function pullData(amount = 100) {
|
|||||||
statement_description = EXCLUDED.statement_description,
|
statement_description = EXCLUDED.statement_description,
|
||||||
pending = EXCLUDED.pending`;
|
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');
|
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>
|
<script>
|
||||||
import TransactionList from '$lib/transactionList.svelte';
|
import TransactionList from '$lib/transactionList.svelte';
|
||||||
import Budgetlist from '$lib/budgetlist.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 { data } = $props();
|
||||||
let total = $derived(data.total);
|
let total = $derived(data.total);
|
||||||
@ -9,7 +26,7 @@
|
|||||||
let underAllocatedTrans = $derived(data.underAllocatedTrans);
|
let underAllocatedTrans = $derived(data.underAllocatedTrans);
|
||||||
let budgets = $derived(data.budgets);
|
let budgets = $derived(data.budgets);
|
||||||
let budgetTransactions = $derived(data.budgetTransactions);
|
let budgetTransactions = $derived(data.budgetTransactions);
|
||||||
let last30days = $derived(data.last30DaysTransactionsSums.reverse());
|
let last30days = $derived(data.last30DaysTransactionsSums);
|
||||||
let orphanedTransactions = $derived(data.orphanedTransactions);
|
let orphanedTransactions = $derived(data.orphanedTransactions);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -38,3 +55,5 @@
|
|||||||
|
|
||||||
<div class="text-xl divider">Orphans</div>
|
<div class="text-xl divider">Orphans</div>
|
||||||
<Budgetlist transactions={orphanedTransactions} />
|
<Budgetlist transactions={orphanedTransactions} />
|
||||||
|
|
||||||
|
<Waterfall {last30days} {budgets} />
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
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)
|
// In-memory store for demonstration (replace with a database in production)
|
||||||
let budgets = [];
|
let budgets = [];
|
||||||
@ -16,3 +16,13 @@ export async function POST({ request }) {
|
|||||||
|
|
||||||
return json(res, { status: 201 });
|
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 adapter from '@sveltejs/adapter-node';
|
||||||
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
kit: {
|
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.
|
// 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.
|
// 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.
|
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||||
adapter: adapter()
|
adapter: adapter()
|
||||||
}
|
},
|
||||||
|
preprocess: vitePreprocess(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
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