Add backend API for personal finance management application

- Introduced a comprehensive backend API using TypeScript, Fastify, and PostgreSQL.
- Added essential files including architecture documentation, environment configuration, and Docker setup.
- Implemented RESTful routes for managing assets, liabilities, clients, invoices, and cashflow.
- Established a robust database schema with Prisma for data management.
- Integrated middleware for authentication and error handling.
- Created service and repository layers to adhere to SOLID principles and clean architecture.
- Included example environment variables for development, staging, and production setups.
This commit is contained in:
2025-12-07 12:59:09 -05:00
parent 9d493ba82f
commit cd93dcbfd2
70 changed files with 8649 additions and 6 deletions

View File

@@ -0,0 +1,88 @@
import {useState} from 'react';
import {Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle} from '@/components/ui/dialog';
import {Button} from '@/components/ui/button';
import {Input} from '@/components/ui/input';
import {Label} from '@/components/ui/label';
import {useAppDispatch, setUser} from '@/store';
interface Props {
open: boolean;
onOpenChange: (open: boolean) => void;
onSwitchToSignUp: () => void;
}
export default function LoginDialog({open, onOpenChange, onSwitchToSignUp}: Props) {
const dispatch = useAppDispatch();
const [form, setForm] = useState({
email: '',
password: '',
});
const [error, setError] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setError('');
// Mock login - in production this would validate against an API
if (!form.email || !form.password) {
setError('Please enter your email and password');
return;
}
// Mock successful login
dispatch(setUser({
id: crypto.randomUUID(),
email: form.email,
name: form.email.split('@')[0],
}));
onOpenChange(false);
setForm({email: '', password: ''});
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="card-elevated sm:max-w-md">
<DialogHeader>
<DialogTitle>Welcome back</DialogTitle>
<DialogDescription>Log in to your account</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit}>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="login-email">Email</Label>
<Input
id="login-email"
type="email"
placeholder="john@example.com"
value={form.email}
onChange={e => setForm({...form, email: e.target.value})}
className="input-depth"
required
/>
</div>
<div className="grid gap-2">
<Label htmlFor="login-password">Password</Label>
<Input
id="login-password"
type="password"
placeholder="••••••••"
value={form.password}
onChange={e => setForm({...form, password: e.target.value})}
className="input-depth"
required
/>
</div>
{error && <p className="text-sm text-red-400">{error}</p>}
</div>
<DialogFooter className="flex-col gap-2 sm:flex-col">
<Button type="submit" className="w-full">Log in</Button>
<Button type="button" variant="ghost" className="w-full" onClick={onSwitchToSignUp}>
Don't have an account? Sign up
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,122 @@
import {useState} from 'react';
import {Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle} from '@/components/ui/dialog';
import {Button} from '@/components/ui/button';
import {Input} from '@/components/ui/input';
import {Label} from '@/components/ui/label';
import {useAppDispatch, setUser} from '@/store';
interface Props {
open: boolean;
onOpenChange: (open: boolean) => void;
onSwitchToLogin: () => void;
}
export default function SignUpDialog({open, onOpenChange, onSwitchToLogin}: Props) {
const dispatch = useAppDispatch();
const [form, setForm] = useState({
name: '',
email: '',
password: '',
confirmPassword: '',
});
const [error, setError] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setError('');
if (form.password !== form.confirmPassword) {
setError('Passwords do not match');
return;
}
if (form.password.length < 6) {
setError('Password must be at least 6 characters');
return;
}
// Mock sign up - in production this would call an API
dispatch(setUser({
id: crypto.randomUUID(),
email: form.email,
name: form.name,
}));
onOpenChange(false);
setForm({name: '', email: '', password: '', confirmPassword: ''});
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="card-elevated sm:max-w-md">
<DialogHeader>
<DialogTitle>Create an account</DialogTitle>
<DialogDescription>Enter your details to get started</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit}>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="signup-name">Name</Label>
<Input
id="signup-name"
type="text"
placeholder="John Doe"
value={form.name}
onChange={e => setForm({...form, name: e.target.value})}
className="input-depth"
required
/>
</div>
<div className="grid gap-2">
<Label htmlFor="signup-email">Email</Label>
<Input
id="signup-email"
type="email"
placeholder="john@example.com"
value={form.email}
onChange={e => setForm({...form, email: e.target.value})}
className="input-depth"
required
/>
</div>
<div className="grid gap-2">
<Label htmlFor="signup-password">Password</Label>
<Input
id="signup-password"
type="password"
placeholder="••••••••"
value={form.password}
onChange={e => setForm({...form, password: e.target.value})}
className="input-depth"
required
/>
</div>
<div className="grid gap-2">
<Label htmlFor="signup-confirm">Confirm Password</Label>
<Input
id="signup-confirm"
type="password"
placeholder="••••••••"
value={form.confirmPassword}
onChange={e => setForm({...form, confirmPassword: e.target.value})}
className="input-depth"
required
/>
</div>
{error && <p className="text-sm text-red-400">{error}</p>}
<p className="text-xs text-muted-foreground">
By signing up, you agree to our Terms of Service and Privacy Policy.
</p>
</div>
<DialogFooter className="flex-col gap-2 sm:flex-col">
<Button type="submit" className="w-full">Create account</Button>
<Button type="button" variant="ghost" className="w-full" onClick={onSwitchToLogin}>
Already have an account? Log in
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}