Initial commit: MyMidas personal finance tracker

Full-stack self-hosted finance app with FastAPI backend and React frontend.

Features:
- Accounts, transactions, budgets, investments with GBP base currency
- CSV import with auto-detection for 10 UK bank formats
- ML predictions: spending forecast, net worth projection, Monte Carlo
- 7 selectable themes (Obsidian, Arctic, Midnight, Vault, Terminal, Synthwave, Ledger)
- Receipt/document attachments on transactions (JPEG, PNG, WebP, PDF)
- AES-256-GCM field encryption, RS256 JWT, TOTP 2FA, RLS, audit log
- Encrypted nightly backups + key rotation script
- Mobile-responsive layout with bottom nav

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-04-21 11:56:10 +00:00
commit 61a7884ee5
127 changed files with 13323 additions and 0 deletions

View file

@ -0,0 +1,42 @@
import uuid
from datetime import date, datetime
from decimal import Decimal
from sqlalchemy import Boolean, Date, DateTime, ForeignKey, LargeBinary, Numeric, String, Text
from sqlalchemy.dialects.postgresql import ARRAY, JSONB, UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base
class Transaction(Base):
__tablename__ = "transactions"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
account_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("accounts.id"), nullable=False, index=True)
transfer_account_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("accounts.id"), nullable=True)
category_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("categories.id"), nullable=True, index=True)
type: Mapped[str] = mapped_column(String(20), nullable=False) # income|expense|transfer|investment
status: Mapped[str] = mapped_column(String(20), default="cleared", nullable=False)
amount: Mapped[Decimal] = mapped_column(Numeric(20, 8), nullable=False)
amount_base: Mapped[Decimal | None] = mapped_column(Numeric(20, 8), nullable=True)
currency: Mapped[str] = mapped_column(String(10), nullable=False)
base_currency: Mapped[str] = mapped_column(String(10), nullable=False)
exchange_rate: Mapped[Decimal | None] = mapped_column(Numeric(20, 10), nullable=True)
date: Mapped[date] = mapped_column(Date, nullable=False, index=True)
description_enc: Mapped[bytes] = mapped_column("description", LargeBinary, nullable=False)
merchant_enc: Mapped[bytes | None] = mapped_column("merchant", LargeBinary, nullable=True)
notes_enc: Mapped[bytes | None] = mapped_column("notes", LargeBinary, nullable=True)
tags: Mapped[list[str]] = mapped_column(ARRAY(Text), default=list, nullable=False)
is_recurring: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
recurring_rule: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
attachment_refs: Mapped[list] = mapped_column(JSONB, default=list, nullable=False)
import_hash: Mapped[str | None] = mapped_column(Text, nullable=True, index=True)
meta: Mapped[dict] = mapped_column(JSONB, default=dict, nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
account: Mapped["Account"] = relationship(foreign_keys=[account_id], back_populates="transactions", lazy="noload") # type: ignore[name-defined]
category: Mapped["Category | None"] = relationship(lazy="noload") # type: ignore[name-defined]