Compare commits
29 Commits
23abd0fa77
...
daefa63ea1
| Author | SHA1 | Date | |
|---|---|---|---|
|
daefa63ea1
|
|||
| 763c952cbe | |||
| fce669146f | |||
| 06219d1fd3 | |||
| 9fac68d7ae | |||
| 8afd48b981 | |||
|
25568591fd
|
|||
| e1551c1fa3 | |||
| e8b3d3e16c | |||
| 59288d4544 | |||
| a2d1f22d46 | |||
| 0e150b3d7d | |||
| 19567313bd | |||
| 42f1cfc07f | |||
| 0276bc6a4c | |||
| d6c8559d4c | |||
| 38cdd96b6f | |||
| cb49494e60 | |||
| 3ffe7514e2 | |||
| d13a387303 | |||
| a398d275f5 | |||
| 23b97a9cac | |||
| 4b74a9b6d4 | |||
| c67f232e9b | |||
| 93727ee49a | |||
| 1a79df9423 | |||
| 582d265fd5 | |||
| f094767582 | |||
| 440fed9ed1 |
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.24.4@sha256:10c131810f80a4802c49cab0961bbe18a16f4bb2fb99ef16deaa23e4246fc817 AS builder_go
|
FROM golang:1.24.5@sha256:ef5b4be1f94b36c90385abd9b6b4f201723ae28e71acacb76d00687333c17282 AS builder_go
|
||||||
WORKDIR /spend-sparrow
|
WORKDIR /spend-sparrow
|
||||||
RUN go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
|
RUN go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
|
||||||
RUN go install github.com/a-h/templ/cmd/templ@latest
|
RUN go install github.com/a-h/templ/cmd/templ@latest
|
||||||
@@ -13,7 +13,7 @@ RUN golangci-lint run ./...
|
|||||||
RUN go build -o /spend-sparrow/spend-sparrow .
|
RUN go build -o /spend-sparrow/spend-sparrow .
|
||||||
|
|
||||||
|
|
||||||
FROM node:22.17.0@sha256:0c0734eb7051babbb3e95cd74e684f940552b31472152edf0bb23e54ab44a0d7 AS builder_node
|
FROM node:22.17.1@sha256:37ff334612f77d8f999c10af8797727b731629c26f2e83caa6af390998bdc49c AS builder_node
|
||||||
WORKDIR /spend-sparrow
|
WORKDIR /spend-sparrow
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm clean-install
|
RUN npm clean-install
|
||||||
@@ -21,7 +21,7 @@ COPY . ./
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|
||||||
FROM debian:12.11@sha256:0d8498a0e9e6a60011df39aab78534cfe940785e7c59d19dfae1eb53ea59babe
|
FROM debian:12.11@sha256:b6507e340c43553136f5078284c8c68d86ec8262b1724dde73c325e8d3dcdeba
|
||||||
WORKDIR /spend-sparrow
|
WORKDIR /spend-sparrow
|
||||||
RUN apt-get update && apt-get install -y ca-certificates && echo "" > .env
|
RUN apt-get update && apt-get install -y ca-certificates && echo "" > .env
|
||||||
COPY migration ./migration
|
COPY migration ./migration
|
||||||
|
|||||||
10
Readme.md
10
Readme.md
@@ -3,6 +3,15 @@
|
|||||||
|
|
||||||
SpendSparrow is a web app to keep track of expenses and income. It is very opinionated by keeping an keen eye on disciplin of it's users. Every Expense needs to be mapped to a Piggy Bank. For emergencies, funds can be moved between Piggy Banks.
|
SpendSparrow is a web app to keep track of expenses and income. It is very opinionated by keeping an keen eye on disciplin of it's users. Every Expense needs to be mapped to a Piggy Bank. For emergencies, funds can be moved between Piggy Banks.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
|
||||||
|
go install github.com/a-h/templ/cmd/templ@latest
|
||||||
|
go install github.com/vektra/mockery/v2@latest
|
||||||
|
```
|
||||||
|
|
||||||
## Design priciples
|
## Design priciples
|
||||||
|
|
||||||
The State of the application can always be calculated on the fly. Even though it is not an Event Streaming Application, it is still important to be able to recalculate historic data.
|
The State of the application can always be calculated on the fly. Even though it is not an Event Streaming Application, it is still important to be able to recalculate historic data.
|
||||||
@@ -10,3 +19,4 @@ It may be applicable to do some sort of monthly snapshots to speed up calculatio
|
|||||||
|
|
||||||
This applications uses as little dependencies as feasible, especially on the front end.
|
This applications uses as little dependencies as feasible, especially on the front end.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
14
go.mod
14
go.mod
@@ -2,15 +2,15 @@ module spend-sparrow
|
|||||||
|
|
||||||
go 1.23.0
|
go 1.23.0
|
||||||
|
|
||||||
toolchain go1.24.4
|
toolchain go1.24.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/a-h/templ v0.3.906
|
github.com/a-h/templ v0.3.924
|
||||||
github.com/golang-migrate/migrate/v4 v4.18.3
|
github.com/golang-migrate/migrate/v4 v4.18.3
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/mattn/go-sqlite3 v1.14.28
|
github.com/mattn/go-sqlite3 v1.14.30
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2
|
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2
|
||||||
github.com/uptrace/opentelemetry-go-extra/otelsqlx v0.3.2
|
github.com/uptrace/opentelemetry-go-extra/otelsqlx v0.3.2
|
||||||
@@ -25,8 +25,8 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk/log v0.13.0
|
go.opentelemetry.io/otel/sdk/log v0.13.0
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.37.0
|
go.opentelemetry.io/otel/sdk/metric v1.37.0
|
||||||
go.opentelemetry.io/otel/trace v1.37.0
|
go.opentelemetry.io/otel/trace v1.37.0
|
||||||
golang.org/x/crypto v0.39.0
|
golang.org/x/crypto v0.40.0
|
||||||
golang.org/x/net v0.41.0
|
golang.org/x/net v0.42.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -45,8 +45,8 @@ require (
|
|||||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
golang.org/x/sys v0.34.0 // indirect
|
||||||
golang.org/x/text v0.26.0 // indirect
|
golang.org/x/text v0.27.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||||
google.golang.org/grpc v1.73.0 // indirect
|
google.golang.org/grpc v1.73.0 // indirect
|
||||||
|
|||||||
24
go.sum
24
go.sum
@@ -1,7 +1,7 @@
|
|||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/a-h/templ v0.3.906 h1:ZUThc8Q9n04UATaCwaG60pB1AqbulLmYEAMnWV63svg=
|
github.com/a-h/templ v0.3.924 h1:t5gZqTneXqvehpNZsgtnlOscnBboNh9aASBH2MgV/0k=
|
||||||
github.com/a-h/templ v0.3.906/go.mod h1:FFAu4dI//ESmEN7PQkJ7E7QfnSEMdcnu7QrAY8Dn334=
|
github.com/a-h/templ v0.3.924/go.mod h1:FFAu4dI//ESmEN7PQkJ7E7QfnSEMdcnu7QrAY8Dn334=
|
||||||
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
|
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
|
||||||
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -41,8 +41,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
github.com/mattn/go-sqlite3 v1.14.30 h1:bVreufq3EAIG1Quvws73du3/QgdeZ3myglJlrzSYYCY=
|
||||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.30/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
@@ -91,14 +91,14 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
|||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
type migrationLogger struct{}
|
type migrationLogger struct{}
|
||||||
|
|
||||||
func (l migrationLogger) Printf(format string, v ...any) {
|
func (l migrationLogger) Printf(format string, v ...any) {
|
||||||
|
//nolint:noctx
|
||||||
slog.Info(format, v...)
|
slog.Info(format, v...)
|
||||||
}
|
}
|
||||||
func (l migrationLogger) Verbose() bool {
|
func (l migrationLogger) Verbose() bool {
|
||||||
|
|||||||
@@ -32,30 +32,33 @@ templ Layout(slot templ.Component, user templ.Component, loggedIn bool, path str
|
|||||||
</head>
|
</head>
|
||||||
<body class="h-screen flex flex-col" hx-headers='{"Csrf-Token": "CSRF_TOKEN"}'>
|
<body class="h-screen flex flex-col" hx-headers='{"Csrf-Token": "CSRF_TOKEN"}'>
|
||||||
// Header
|
// Header
|
||||||
<nav class="flex bg-white items-center gap-2 py-1 px-2 h-12 md:gap-10 md:px-10 md:py-2">
|
<nav class="flex bg-blue-50 items-center gap-2 p-4 rounded-b-4xl">
|
||||||
<a href="/" class="flex gap-2 mr-20">
|
<a href="/" class="flex gap-2 mr-20">
|
||||||
<img class="w-6" src="/static/favicon.svg" alt="SpendSparrow logo"/>
|
<img class="w-6" src="/static/favicon.svg" alt="SpendSparrow logo"/>
|
||||||
<span class="text-4xl font-bold font-pirata">SpendSparrow</span>
|
<span class="text-4xl font-bold font-pirata">SpendSparrow</span>
|
||||||
</a>
|
</a>
|
||||||
if loggedIn {
|
if loggedIn {
|
||||||
<a class={ layoutLinkClass(path == "/dashboard") } href="/dashboard">Dashboard</a>
|
|
||||||
<a class={ layoutLinkClass(path == "/transaction") } href="/transaction">Transaction</a>
|
|
||||||
<a class={ layoutLinkClass(path == "/treasurechest") } href="/treasurechest">Treasure Chest</a>
|
|
||||||
<a class={ layoutLinkClass(path == "/account") } href="/account">Account</a>
|
|
||||||
}
|
}
|
||||||
<div class="ml-auto">
|
<div class="ml-auto">
|
||||||
@user
|
@user
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="h-12 fixed top-12 mr-4 inset-0 bg-linear-0 from-transparent to-white"></div>
|
|
||||||
// Content
|
// Content
|
||||||
<main class="flex-1 overflow-auto">
|
<div class="flex overflow-hidden">
|
||||||
if slot != nil {
|
<nav class="flex flex-col bg-gray-50 text-lg mt-5 px-5 pt-2 rounded-r-4xl">
|
||||||
@slot
|
<a class={ layoutLinkClass(path == "/dashboard") } href="/dashboard">Dashboard</a>
|
||||||
}
|
<a class={ layoutLinkClass(path == "/transaction") } href="/transaction">Transaction</a>
|
||||||
</main>
|
<a class={ layoutLinkClass(path == "/treasurechest") } href="/treasurechest">Treasure Chest</a>
|
||||||
|
<a class={ layoutLinkClass(path == "/account") } href="/account">Account</a>
|
||||||
|
</nav>
|
||||||
|
<main class="flex-1 overflow-auto">
|
||||||
|
if slot != nil {
|
||||||
|
@slot
|
||||||
|
}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
// Footer
|
// Footer
|
||||||
<!-- </div> -->
|
<div class="bg-red-900">MyFooter</div>
|
||||||
<div id="toasts" class="fixed bottom-4 right-4 ml-4 max-w-96 flex flex-col gap-2 z-50">
|
<div id="toasts" class="fixed bottom-4 right-4 ml-4 max-w-96 flex flex-col gap-2 z-50">
|
||||||
<div
|
<div
|
||||||
id="toast"
|
id="toast"
|
||||||
|
|||||||
Reference in New Issue
Block a user