Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: CI Build and Publish

on:
push: {}

permissions:
contents: read
packages: write

jobs:
lint-frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
run: |
cd frontend
npm ci

- name: Run Linting
run: |
cd frontend
npm run lint

- name: Check Type Build (Dry Run)
# Runs the build to verify no type/compilation errors, but doesn't keep artifacts
run: |
cd frontend
npm run build

build-and-push:
needs: [lint-frontend]
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v2

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Create short SHA output
id: sha
run: |
echo "short_sha=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT

- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push backend image
uses: docker/build-push-action@v4
with:
context: ./backend
file: ./backend/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/finance-manager-backend:latest
ghcr.io/${{ github.repository_owner }}/finance-manager-backend:${{ steps.sha.outputs.short_sha }}

- name: Build and push frontend image
uses: docker/build-push-action@v4
with:
context: ./frontend
file: ./frontend/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/finance-manager-frontend:latest
ghcr.io/${{ github.repository_owner }}/finance-manager-frontend:${{ steps.sha.outputs.short_sha }}
67 changes: 67 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Release Build and Publish

on:
push:
branches:
- main

permissions:
contents: read
packages: write

jobs:
release:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v2

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Read backend pyproject version
id: backend_ver
run: |
ver=$(python - <<'PY'
import re,sys
txt=open('backend/pyproject.toml').read()
m=re.search(r"version\s*=\s*[\"']([^\"']+)[\"']", txt)
if not m:
sys.exit(1)
print(m.group(1))
PY
)
echo "version=$ver" >> $GITHUB_OUTPUT

- name: Build and push backend image
uses: docker/build-push-action@v4
with:
context: ./backend
file: ./backend/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/finance-manager-backend:${{ steps.backend_ver.outputs.version }}
ghcr.io/${{ github.repository_owner }}/finance-manager-backend:latest

- name: Build and push frontend image
uses: docker/build-push-action@v4
with:
context: ./frontend
file: ./frontend/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/finance-manager-frontend:${{ steps.backend_ver.outputs.version }}
ghcr.io/${{ github.repository_owner }}/finance-manager-frontend:latest
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib64/
# lib/
# lib64/
parts/
sdist/
var/
Expand Down
2 changes: 2 additions & 0 deletions backend/app/api/v1/endpoints/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ async def login_access_token(
except HTTPException:
raise
except Exception as e:
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=str(e))
34 changes: 17 additions & 17 deletions frontend/src/app/(dashboard)/accounts/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client"

import { useEffect, useState } from "react"
import { useEffect, useState, useCallback } from "react"
import { useParams } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
Expand All @@ -12,7 +12,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Loader2, Plus } from "lucide-react"
import { Loader2 } from "lucide-react"
import { accountService, Account } from "@/services/accounts"
import { transactionService, Transaction } from "@/services/transactions"
import { categoryService } from "@/services/categories"
Expand Down Expand Up @@ -95,7 +95,7 @@ export default function AccountPage() {
}
}

const fetchAccount = async () => {
const fetchAccount = useCallback(async () => {
if (accountId && authService.isAuthenticated()) {
try {
const endDate = date?.to ? format(date.to, "yyyy-MM-dd") : undefined
Expand All @@ -105,9 +105,9 @@ export default function AccountPage() {
console.error("Failed to fetch account", error)
}
}
}
}, [accountId, date])

const fetchTransactions = async () => {
const fetchTransactions = useCallback(async () => {
if (accountId && authService.isAuthenticated() && date?.from && date?.to) {
try {
const data = await transactionService.getTransactions({
Expand All @@ -122,9 +122,9 @@ export default function AccountPage() {
console.error("Failed to fetch transactions", error)
}
}
}
}, [accountId, date, page, pageSize])

const fetchTargetAccountExpenses = async () => {
const fetchTargetAccountExpenses = useCallback(async () => {
if (!accountId || !authService.isAuthenticated() || !date?.from || !date?.to) {
return
}
Expand Down Expand Up @@ -188,9 +188,9 @@ export default function AccountPage() {
} finally {
setLoadingTargetAccounts(false)
}
}
}, [accountId, date, accountsMap])

const fetchCategoryBreakdown = async () => {
const fetchCategoryBreakdown = useCallback(async () => {
if (!accountId || !authService.isAuthenticated() || !date?.from || !date?.to) {
return
}
Expand Down Expand Up @@ -263,9 +263,9 @@ export default function AccountPage() {
} finally {
setLoadingCategoryChart(false)
}
}
}, [accountId, date, categoriesMap])

const fetchIncomeExpenseSplit = async () => {
const fetchIncomeExpenseSplit = useCallback(async () => {
if (!accountId || !authService.isAuthenticated() || !date?.from || !date?.to) {
return
}
Expand Down Expand Up @@ -309,11 +309,11 @@ export default function AccountPage() {
} finally {
setLoadingIncomeExpense(false)
}
}
}, [accountId, date])

useEffect(() => {
fetchAccount()
}, [accountId, date])
}, [fetchAccount])

// Reset page when date range changes
useEffect(() => {
Expand All @@ -322,19 +322,19 @@ export default function AccountPage() {

useEffect(() => {
fetchTransactions()
}, [accountId, date, page])
}, [fetchTransactions])

useEffect(() => {
fetchTargetAccountExpenses()
}, [accountId, date, accountsMap])
}, [fetchTargetAccountExpenses])

useEffect(() => {
fetchCategoryBreakdown()
}, [accountId, date, categoriesMap])
}, [fetchCategoryBreakdown])

useEffect(() => {
fetchIncomeExpenseSplit()
}, [accountId, date])
}, [fetchIncomeExpenseSplit])

if (!account) {
return <div className="p-8">Loading...</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/(dashboard)/categories/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Button } from "@/components/ui/button"
import { categoryService, Category } from "@/services/categories"
import { CategoryDialog } from "@/components/categories/CategoryDialog"
import { ImportCategoriesDialog } from "@/components/categories/ImportCategoriesDialog"
import { Loader2, Trash2 } from "lucide-react"
import { Trash2 } from "lucide-react"
import {
AlertDialog,
AlertDialogAction,
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/app/(dashboard)/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ export default function SettingsPage() {
setPasswordMessage({ type: 'success', text: 'Password updated successfully.' })
setPassword("")
setConfirmPassword("")
} catch (error: any) {
} catch (error: unknown) {
console.error("Failed to update password", error)
const msg = error.response?.data?.detail || "Failed to update password.";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const msg = (error as any).response?.data?.detail || "Failed to update password.";
setPasswordMessage({ type: 'error', text: msg })
} finally {
setUpdatingPassword(false)
Expand Down Expand Up @@ -130,9 +131,10 @@ export default function SettingsPage() {
text: `Restore successful! Processed ${result.counts.categories} categories, ${result.counts.accounts} accounts, and ${result.counts.transactions} transactions.`
})
if (fileInputRef.current) fileInputRef.current.value = ''
} catch (error: any) {
} catch (error: unknown) {
console.error("Failed to restore backup", error)
const msg = error.response?.data?.detail || "Failed to restore backup. Please check the file format.";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const msg = (error as any).response?.data?.detail || "Failed to restore backup. Please check the file format.";
setRestoreMessage({ type: 'error', text: msg })
} finally {
setRestoring(false)
Expand Down Expand Up @@ -287,7 +289,7 @@ export default function SettingsPage() {
<CardTitle>Restore Data</CardTitle>
<CardDescription>
Restore your data from a backup ZIP file.
<span className="block text-red-500 font-medium mt-1">Warning: This will add missing records and update existing ones. It does NOT delete existing data that isn't in the backup.</span>
<span className="block text-red-500 font-medium mt-1">Warning: This will add missing records and update existing ones. It does NOT delete existing data that isn&apos;t in the backup.</span>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/accounts/AccountSettingsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useState, useEffect } from "react"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
import { Loader2, Settings, Pencil } from "lucide-react"
import { Loader2, Pencil } from "lucide-react"

import { Button } from "@/components/ui/button"
import {
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/accounts/CreateAccountDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,9 @@ export function CreateAccountDialog({ onSuccess }: CreateAccountDialogProps) {
setOpen(false)
form.reset()
onSuccess()
} catch (error) {
} catch (error: unknown) {
console.error("Failed to create account", error)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const message = (error as any)?.response?.data?.detail || "Failed to create account. Please try again."
setSubmitError(message)
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ export function DeleteDestinationAccountDialog({
await accountService.deleteDestinationAccount(account.id)
setOpen(false)
onSuccess()
} catch (error: any) {
} catch (error: unknown) {
console.error("Failed to delete account", error)
setError(error.response?.data?.detail || "Failed to delete account")
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const msg = (error as any).response?.data?.detail || "Failed to delete account"
setError(msg)
} finally {
setLoading(false)
}
Expand All @@ -60,7 +62,7 @@ export function DeleteDestinationAccountDialog({
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the account
"{account.name}" and all its associated data.
&quot;{account.name}&quot; and all its associated data.
</AlertDialogDescription>
{error && (
<div className="text-sm text-destructive bg-destructive/10 p-3 rounded-md mt-2">
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/accounts/DestinationAccountDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,10 @@ export function DestinationAccountDialog({
}
setOpen(false)
onSuccess()
} catch (error: any) {
} catch (error: unknown) {
console.error("Failed to save account", error)
const errorMessage = error.response?.data?.detail || "Failed to save account. Please try again."
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const errorMessage = (error as any).response?.data?.detail || "Failed to save account. Please try again."
setError(errorMessage)
} finally {
setLoading(false)
Expand Down
Loading