Compare commits

80 Commits

Author SHA1 Message Date
061d63a8ad feat(ui): #111 draft for (unfinished) mobile transaction-ui
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 6m43s
2025-08-24 15:35:00 +02:00
d0faee2950 chore(deps): update golang:1.25.0 docker digest to 4859242
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 8m24s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m39s
2025-08-22 21:05:43 +00:00
01d459e913 fix: add "?" to validateString
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m52s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m36s
2025-08-20 04:41:29 +02:00
cee533694c chore(deps): update golang:1.25.0 docker digest to 91e2cd4
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 8m40s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 2m40s
2025-08-18 12:05:26 +00:00
f6283c6ab3 fix(deps): update module github.com/a-h/templ to v0.3.943
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m51s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m33s
2025-08-15 16:05:55 +00:00
e48d11b818 chore(deps): update node.js to v24.6.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m55s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m38s
2025-08-14 22:06:46 +00:00
b9150334ee chore(deps): update node.js to 3266bc9
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m47s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m41s
2025-08-14 16:06:16 +00:00
d20981beaa fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.32
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m0s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m34s
2025-08-14 15:06:41 +00:00
3c95abe59c fix(deps): update module github.com/a-h/templ to v0.3.937
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m6s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m30s
2025-08-14 14:10:03 +00:00
fad2bd3928 chore(deps): update tailwindcss monorepo to v4.1.12
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m55s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m34s
2025-08-14 13:06:29 +00:00
c75b99ea9d chore(deps): update node.js to 73297e2
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m46s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m31s
2025-08-14 07:05:57 +00:00
56737a4156 chore(deps): update golang:1.25.0 docker digest to 9e56f0d
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m58s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m46s
2025-08-14 06:09:06 +00:00
ab425d759c chore(deps): update node.js to 58a2604
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m55s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m42s
2025-08-14 01:16:40 +00:00
283679fc4f chore(deps): update golang:1.25.0 docker digest to 10a15b9
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m49s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m38s
2025-08-14 00:06:49 +00:00
e0802cf232 chore(deps): update dependency go to v1.25.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m19s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m43s
2025-08-13 23:10:18 +00:00
6577dbb297 chore(deps): update golang docker tag to v1.25.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m49s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m41s
2025-08-13 22:11:10 +00:00
7e37c24b07 chore(deps): update golang:1.24.6 docker digest to 746a0e9
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 8m30s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m41s
2025-08-13 21:06:28 +00:00
3f3edbb8ad chore(deps): update golang:1.24.6 docker digest to 86a999d
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m37s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m22s
2025-08-13 18:06:20 +00:00
16429f1950 chore(deps): update debian docker tag to v13
All checks were successful
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m55s
2025-08-13 15:25:14 +00:00
82a9fd8220 chore(deps): update golang:1.24.6 docker digest to 370491a
Some checks failed
Build Docker Image / Build-Docker-Image (push) Successful in 4m50s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Has been cancelled
2025-08-13 15:08:39 +00:00
192e6b7f50 chore(deps): update node.js to 5cc5271
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m37s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m33s
2025-08-13 02:06:22 +00:00
57377f9c27 chore(deps): update debian:12.11 docker digest to 731dd13
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m8s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m58s
2025-08-13 01:16:08 +00:00
66227c5818 chore(deps): update golang:1.24.6 docker digest to 0348485
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 8m32s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m44s
2025-08-13 00:08:49 +00:00
6e51e3c8b3 chore(deps): update debian:12.11 docker digest to 5626655
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m57s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m39s
2025-08-12 22:06:39 +00:00
6c916aecb4 fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.31
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m0s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m39s
2025-08-11 15:06:14 +00:00
8575fbf56e chore(deps): update actions/checkout digest to 08eba0b
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m46s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m38s
2025-08-11 11:05:53 +00:00
f820fcdfeb fix(deps): update module golang.org/x/net to v0.43.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m15s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m8s
2025-08-09 20:35:22 +00:00
f6e58b7afc fix(deps): update module golang.org/x/crypto to v0.41.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m44s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m2s
2025-08-09 02:06:26 +00:00
93e669b038 chore(deps): update golang docker tag to v1.24.6
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m28s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m55s
2025-08-09 01:10:39 +00:00
d037317aab chore(deps): update dependency go to v1.24.6
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 3m27s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m50s
2025-08-08 23:09:13 +00:00
0517e7ec89 feat(transaction): #243 add pagination to transactions
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 2m28s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 2m34s
2025-08-09 00:36:50 +02:00
867c0ca1cd chore(deps): update node.js to 3218f0d
Some checks failed
Build Docker Image / Build-Docker-Image (push) Successful in 4m3s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Failing after 2m50s
2025-08-04 16:07:53 +00:00
ddcbfaa075 chore(deps): update node.js to 0d98a9f
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 3m44s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m41s
2025-08-04 13:04:59 +00:00
4583c0a70e chore(deps): update node.js to v22.18.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m14s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m43s
2025-08-04 10:05:10 +00:00
380854272a chore(deps): update dependency echarts to v6
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m9s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m48s
2025-08-01 22:42:20 +02:00
6bc9e0666b feat(layout): #211 optimize the overall layout for mobile
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m10s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m18s
move navigation to aside
proper mobile handling
update logo.svg
remove pirata-one/only use it for the logo
2025-08-01 22:26:17 +02:00
9fa554c60a chore(deps): update node.js to v24.5.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m48s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m37s
2025-07-31 22:08:19 +00:00
763c952cbe fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.30
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m44s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m7s
2025-07-30 13:06:28 +00:00
fce669146f fix(deps): update module github.com/a-h/templ to v0.3.924
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m16s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m5s
2025-07-26 16:05:39 +00:00
06219d1fd3 fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.29
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 3m19s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m42s
2025-07-25 23:05:50 +00:00
9fac68d7ae chore(deps): update node.js to 37ff334
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 7m58s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 2m47s
2025-07-25 22:07:02 +00:00
8afd48b981 chore(deps): update golang:1.24.5 docker digest to ef5b4be
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 7m35s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m47s
2025-07-25 21:05:10 +00:00
25568591fd fix: build
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m8s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m39s
2025-07-25 22:47:07 +02:00
e1551c1fa3 chore(deps): update debian:12.11 docker digest to b6507e3
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m8s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m49s
2025-07-22 04:06:22 +00:00
e8b3d3e16c fix(deps): update module github.com/a-h/templ to v0.3.920
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m5s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m26s
2025-07-20 19:06:40 +00:00
59288d4544 chore(deps): update node.js to 9e6918e
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m59s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m27s
2025-07-17 01:06:01 +00:00
a2d1f22d46 chore(deps): update node.js to 414e20e
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m46s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m40s
2025-07-16 22:09:53 +00:00
0e150b3d7d chore(deps): update node.js to v22.17.1
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m24s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m54s
2025-07-16 19:08:44 +00:00
19567313bd chore(deps): update node.js to v24.4.1
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m36s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m14s
2025-07-15 23:08:46 +00:00
42f1cfc07f fix(deps): update module golang.org/x/net to v0.42.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m3s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m30s
2025-07-10 20:06:21 +00:00
0276bc6a4c fix(deps): update module golang.org/x/crypto to v0.40.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m10s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m28s
2025-07-10 18:06:59 +00:00
d6c8559d4c chore(deps): update golang:1.24.5 docker digest to 14fd8a5
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m2s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 5m2s
2025-07-09 21:05:29 +00:00
38cdd96b6f chore(deps): update golang docker tag to v1.24.5
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 8m3s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m0s
2025-07-09 19:05:32 +00:00
cb49494e60 chore(deps): update node.js to v24.4.0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m57s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m33s
2025-07-09 13:08:35 +00:00
3ffe7514e2 chore(deps): update dependency go to v1.24.5
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m0s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m34s
2025-07-08 17:15:13 +00:00
d13a387303 chore(deps): update node.js to 2fa6c97
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m42s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m32s
2025-07-08 04:05:16 +00:00
a398d275f5 chore(deps): update node.js to 5307f5f
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m42s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m28s
2025-07-08 01:39:32 +00:00
23b97a9cac chore(deps): update node.js to df39165
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m55s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m32s
2025-07-07 22:06:05 +00:00
4b74a9b6d4 chore(deps): update golang:1.24.4 docker digest to 20a022e
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m6s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m6s
2025-07-02 12:04:58 +00:00
c67f232e9b chore(deps): update golang:1.24.4 docker digest to 764d7e0
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 3m47s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m4s
2025-07-02 00:06:32 +00:00
93727ee49a chore(deps): update golang:1.24.4 docker digest to a92f3b1
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m1s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m7s
2025-07-01 21:05:23 +00:00
1a79df9423 chore(deps): update golang:1.24.4 docker digest to 1aa97dd
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 3m50s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m48s
2025-07-01 18:05:24 +00:00
582d265fd5 chore(deps): update golang:1.24.4 docker digest to 1bb140b
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 3m55s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m56s
2025-07-01 15:05:25 +00:00
f094767582 chore(deps): update golang:1.24.4 docker digest to 270cd53
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 6m39s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m18s
2025-07-01 06:54:12 +00:00
440fed9ed1 chore(deps): update debian:12.11 docker digest to d42b86d
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m12s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m55s
2025-07-01 04:06:08 +00:00
6e1d24eef7 feat(dashboard): #191 add development of treasurechests
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 12m24s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 2m53s
2025-06-30 00:39:14 +02:00
f37b50515b fix(deps): update opentelemetry-go-contrib monorepo
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m58s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 5m25s
2025-06-27 19:06:53 +00:00
b2a512d186 fix(deps): update opentelemetry-go monorepo
All checks were successful
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m58s
2025-06-27 18:28:27 +00:00
4a28fb5ca4 chore(deps): update node.js to v24.3.0
Some checks failed
Build Docker Image / Build-Docker-Image (push) Successful in 5m19s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Has been cancelled
2025-06-27 18:11:32 +00:00
e5f98c1fb0 chore(deps): update node.js to v22.17.0
All checks were successful
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 5m13s
2025-06-27 17:48:00 +00:00
3072df6507 fix(deps): update module github.com/a-h/templ to v0.3.906
Some checks failed
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Has been cancelled
2025-06-27 17:42:29 +00:00
9f35ca7476 chore(deps): update tailwindcss monorepo to v4.1.11
Some checks failed
Build Docker Image / Build-Docker-Image (push) Successful in 5m3s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Has been cancelled
2025-06-27 17:24:55 +00:00
472ab68986 chore(deps): update dependency htmx.org to v2.0.6
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m40s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 5m21s
2025-06-27 16:33:51 +00:00
2fd2200ac2 fix: enable dot in stings
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m59s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m57s
2025-06-22 19:40:59 +02:00
a58ddb7a1d chore(deps): update dependency htmx.org to v2.0.5
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m34s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m5s
2025-06-20 22:06:46 +00:00
147d57f6e5 feat: #194 enable sqlite wal mode for better performace
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 3m40s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m57s
2025-06-20 22:55:59 +02:00
d064626197 feat: #193 disable session handling for static content
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 3m48s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m41s
2025-06-20 22:46:13 +02:00
72869e5c68 feat(dashboard): #192 include treemap of treasure chests
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m47s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 3m36s
2025-06-20 21:31:37 +02:00
3120c19669 feat(dashboard): #82 add chart for sum of account and savings
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m21s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 5m33s
2025-06-19 18:05:36 +02:00
c9bf320611 chore: include node version manager
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m28s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 4m51s
2025-06-19 12:31:55 +02:00
45 changed files with 1368 additions and 579 deletions

View File

@@ -10,6 +10,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- run: docker build . -t spend-sparrow-test - run: docker build . -t spend-sparrow-test
- run: docker rmi spend-sparrow-test - run: docker rmi spend-sparrow-test

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- run: docker login git.wundenbergs.de -u tim -p ${{ secrets.DOCKER_GITEA_TOKEN }} - run: docker login git.wundenbergs.de -u tim -p ${{ secrets.DOCKER_GITEA_TOKEN }}
- run: docker build . -t git.wundenbergs.de/x/spend-sparrow:latest -t git.wundenbergs.de/x/spend-sparrow:$GITHUB_SHA - run: docker build . -t git.wundenbergs.de/x/spend-sparrow:latest -t git.wundenbergs.de/x/spend-sparrow:$GITHUB_SHA
- run: docker push git.wundenbergs.de/x/spend-sparrow:latest - run: docker push git.wundenbergs.de/x/spend-sparrow:latest

2
.gitignore vendored
View File

@@ -25,7 +25,7 @@ go.work.sum
# env file # env file
.env .env
*.db data/
secrets/ secrets/
node_modules/ node_modules/

View File

@@ -24,6 +24,8 @@ linters:
- cyclop - cyclop
- contextcheck - contextcheck
- bodyclose # i don't care in the tests, the implementation itself doesn't do http requests - bodyclose # i don't care in the tests, the implementation itself doesn't do http requests
- wsl_v5
- noinlineerr
settings: settings:
nestif: nestif:
min-complexity: 6 min-complexity: 6

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
24.6.0

View File

@@ -1,4 +1,4 @@
FROM golang:1.24.4@sha256:10c131810f80a4802c49cab0961bbe18a16f4bb2fb99ef16deaa23e4246fc817 AS builder_go FROM golang:1.25.0@sha256:4859242e2c392ddc9d3225fd41181c00a443d9cc005b8e5131ce164106fbc676 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.16.0@sha256:71bcbb3b215b3fa84b5b167585675072f4c270855e37a599803f1a58141a0716 AS builder_node FROM node:22.18.0@sha256:3266bc9e8bee1acc8a77386eefaf574987d2729b8c5ec35b0dbd6ddbc40b0ce2 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:13.0@sha256:6d87375016340817ac2391e670971725a9981cfc24e221c47734681ed0f6c0f5
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

View File

@@ -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.

View File

@@ -0,0 +1,188 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg1"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="logo-inkscape.svg"
inkscape:export-filename="static/logo.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="2.5914723"
inkscape:cx="385.10927"
inkscape:cy="275.712"
inkscape:window-width="2252"
inkscape:window-height="1450"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer2" />
<defs
id="defs1">
<rect
x="115.37843"
y="80.263254"
width="470.38898"
height="197.18521"
id="rect2" />
<rect
x="175.18448"
y="463.06726"
width="253.29221"
height="303.50433"
id="rect6" />
</defs>
<g
inkscape:label="Faviocon"
inkscape:groupmode="layer"
id="layer1">
<path
d="m 59.240389,97.978247 c 1.775354,-0.394229 4.087813,-2.156354 4.439709,-3.024187 0.206375,-0.508 -0.822855,-1.30175 -1.098021,-1.621896 -0.629709,-0.73025 -0.375709,-1.090083 -0.132292,-1.960562 0.277813,-0.989542 -0.381,-2.082271 -1.314979,-2.510896 -0.933979,-0.428625 -2.050521,-0.293688 -2.989792,0.124354 -0.939271,0.418042 -1.740958,1.090083 -2.526771,1.751542 -0.574145,-0.36248 -1.489604,-1.963209 -2.97127,-0.923396 -1.023938,0.717021 -1.116542,2.278062 -0.98425,3.52425 0.309562,2.876021 1.018645,4.368271 2.354791,4.770437 1.688042,0.508 3.556,0.240771 5.222875,-0.129646"
style="fill:#ffca28;stroke-width:0.264583"
id="path1-3"
inkscape:export-filename="static/favicon.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<path
d="m 62.124348,89.704727 c -0.224896,3.876145 -4.005792,6.447895 -5.799667,7.580312 l 1.164167,1.000125 c 0,0 0.738187,0.01588 1.748895,-0.306917 1.733021,-0.550333 4.265084,-2.106083 4.439709,-3.024187 0.256646,-1.336146 -1.113896,-1.045104 -1.423459,-2.100792 -0.161395,-0.558271 0.785813,-1.613958 -0.129645,-3.148541 m -6.503459,1.03452 c 0,0 -0.674687,-0.690562 -1.17475,-1.005416 -0.248708,0.468312 -0.425979,0.976312 -0.513291,1.500187 -0.156105,0.92075 0,2.227792 0.36777,3.201459 0.05821,0.150812 0.275167,0.127 0.29898,-0.03175 0.3175,-2.092855 1.021291,-3.66448 1.021291,-3.66448"
style="fill:#e2a610;stroke-width:0.264583"
id="path2-6" />
<path
d="m 50.906014,97.636935 c 0,0 -8.252354,0.891646 -11.975042,8.056565 -3.722687,7.16492 -0.558271,11.50937 2.791354,13.09158 3.349626,1.58221 11.789834,2.14048 17.279938,0.83873 5.490104,-1.30175 6.863292,-4.0005 6.606646,-6.60664 -0.373062,-3.80471 -3.907896,-6.14363 -3.907896,-6.14363 0,0 0.140229,-4.699 -3.505729,-7.749647 -3.235854,-2.709333 -7.289271,-1.486958 -7.289271,-1.486958"
style="fill:#ffca28;stroke-width:0.264583"
id="path3-0" />
<path
d="m 56.120952,95.996518 c 2.233083,0.727604 2.727854,2.746375 2.566458,3.296709 -0.193146,0.645583 -2.667,-1.867959 -6.344708,-1.717146 -1.285875,0.05292 -0.912813,-0.735542 -0.3175,-1.190625 0.785812,-0.600604 2.106083,-1.034521 4.09575,-0.388938"
style="fill:#6d4c41;stroke-width:0.264583"
id="path6-6" />
<path
d="m 56.120952,95.996518 c 2.233083,0.727604 2.727854,2.746375 2.566458,3.296709 -0.193146,0.645583 -2.667,-1.867959 -6.344708,-1.717146 -1.285875,0.05292 -0.912813,-0.735542 -0.3175,-1.190625 0.785812,-0.600604 2.106083,-1.034521 4.09575,-0.388938"
style="fill:#6b4b46;stroke-width:0.264583"
id="path7-2" />
<path
d="m 60.042077,103.11381 c 0.280458,-0.19314 1.222375,0.14023 1.486958,1.98438 0.129646,0.90223 0.169333,1.77535 0.169333,1.77535 0,0 -1.11125,-0.99748 -1.47902,-1.69862 -0.463021,-0.88636 -0.642938,-1.74361 -0.177271,-2.06111"
style="fill:#e2a610;stroke-width:0.264583"
id="path8-6" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:19.7556px;font-family:'Pirata One';-inkscape-font-specification:'Pirata One, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;letter-spacing:-0.529167px;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000000;stroke-width:0.264583"
x="82.355011"
y="90.66716"
id="text4-9"
inkscape:label="$"
transform="rotate(20.578693)"><tspan
sodipodi:role="line"
id="tspan4-2"
style="font-size:19.7556px;fill:#4d4d4d;stroke-width:0.264583"
x="82.355011"
y="90.66716">$</tspan></text>
</g>
<g
inkscape:label="Logo"
inkscape:groupmode="layer"
id="g7"
transform="translate(1.4293676,-48.496402)">
<g
id="g8"
inkscape:label="Favicon"
transform="translate(-38.797122,-28.178962)"
inkscape:export-filename="../static/logo.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<path
d="m 98.874384,115.02659 c 1.775356,-0.39423 4.087816,-2.15635 4.439706,-3.02419 0.20638,-0.508 -0.82285,-1.30175 -1.09802,-1.62189 -0.62971,-0.73025 -0.37571,-1.09009 -0.13229,-1.96057 0.27781,-0.98954 -0.381,-2.08227 -1.31498,-2.51089 -0.933978,-0.42863 -2.05052,-0.29369 -2.989791,0.12435 -0.939271,0.41804 -1.740958,1.09009 -2.526771,1.75154 -0.574145,-0.36248 -1.489604,-1.96321 -2.97127,-0.92339 -1.023938,0.71702 -1.116542,2.27806 -0.98425,3.52425 0.309562,2.87602 1.018645,4.36827 2.354791,4.77043 1.688042,0.508 3.556,0.24078 5.222875,-0.12964"
style="fill:#ffca28;stroke-width:0.264583"
id="path1-3-3" />
<path
d="m 101.75834,106.75307 c -0.22489,3.87614 -4.005789,6.44789 -5.799664,7.58031 l 1.164167,1.00013 c 0,0 0.738187,0.0159 1.748895,-0.30692 1.733022,-0.55033 4.265082,-2.10608 4.439712,-3.02419 0.25664,-1.33614 -1.1139,-1.0451 -1.42346,-2.10079 -0.1614,-0.55827 0.78581,-1.61396 -0.12965,-3.14854 m -6.503456,1.03452 c 0,0 -0.674687,-0.69056 -1.17475,-1.00542 -0.248708,0.46832 -0.425979,0.97632 -0.513291,1.50019 -0.156105,0.92075 0,2.22779 0.36777,3.20146 0.05821,0.15081 0.275167,0.127 0.29898,-0.0317 0.3175,-2.09286 1.021291,-3.66448 1.021291,-3.66448"
style="fill:#e2a610;stroke-width:0.264583"
id="path2-6-6" />
<path
d="m 90.540009,114.68528 c 0,0 -8.252354,0.89164 -11.975042,8.05656 -3.722687,7.16492 -0.558271,11.50937 2.791354,13.09158 3.349626,1.58221 11.789834,2.14048 17.279938,0.83873 5.490101,-1.30175 6.863291,-4.0005 6.606641,-6.60664 -0.37306,-3.80471 -3.90789,-6.14363 -3.90789,-6.14363 0,0 0.14023,-4.699 -3.50573,-7.74964 -3.235854,-2.70934 -7.289271,-1.48696 -7.289271,-1.48696"
style="fill:#ffca28;stroke-width:0.264583"
id="path3-0-1" />
<path
d="m 95.754947,113.04486 c 2.233083,0.7276 2.727854,2.74638 2.566458,3.29671 -0.193146,0.64558 -2.667,-1.86796 -6.344708,-1.71715 -1.285875,0.0529 -0.912813,-0.73554 -0.3175,-1.19062 0.785812,-0.60061 2.106083,-1.03452 4.09575,-0.38894"
style="fill:#6d4c41;stroke-width:0.264583"
id="path6-6-2" />
<path
d="m 95.754947,113.04486 c 2.233083,0.7276 2.727854,2.74638 2.566458,3.29671 -0.193146,0.64558 -2.667,-1.86796 -6.344708,-1.71715 -1.285875,0.0529 -0.912813,-0.73554 -0.3175,-1.19062 0.785812,-0.60061 2.106083,-1.03452 4.09575,-0.38894"
style="fill:#6b4b46;stroke-width:0.264583"
id="path7-2-9" />
<path
d="m 99.676072,120.16215 c 0.280458,-0.19314 1.222378,0.14023 1.486958,1.98438 0.12965,0.90223 0.16933,1.77535 0.16933,1.77535 0,0 -1.11125,-0.99748 -1.479017,-1.69862 -0.463021,-0.88636 -0.642938,-1.74361 -0.177271,-2.06111"
style="fill:#e2a610;stroke-width:0.264583"
id="path8-6-3" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:19.7556px;font-family:'Pirata One';-inkscape-font-specification:'Pirata One, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;letter-spacing:-0.529167px;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000000;stroke-width:0.264583"
x="125.45235"
y="92.696564"
id="text4-9-1"
inkscape:label="$"
transform="rotate(20.578693)"><tspan
sodipodi:role="line"
id="tspan4-2-9"
style="font-size:19.7556px;fill:#4d4d4d;stroke-width:0.264583"
x="125.45235"
y="92.696564">$</tspan></text>
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Text"
transform="translate(-1.4293676,48.496402)">
<text
xml:space="preserve"
style="font-size:17.6389px;text-align:start;letter-spacing:-0.529167px;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000000;stroke-width:0.264583"
x="57.635151"
y="55.655094"
id="text1"
inkscape:label="SpendSparrow"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17.6389px;font-family:'Pirata One';-inkscape-font-specification:'Pirata One, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:-0.529167px;fill:#4d4d4d;stroke:none;stroke-width:0.264583"
x="57.635151"
y="55.655094">pendSparrow</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:19.7556px;font-family:'Pirata One';-inkscape-font-specification:'Pirata One, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;letter-spacing:-0.529167px;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#1a1a1a;stroke-width:0.264583"
x="93.314896"
y="91.227318"
id="text5"><tspan
sodipodi:role="line"
id="tspan5"
style="stroke-width:0.264583"
x="93.314896"
y="91.227318" /></text>
<text
xml:space="preserve"
transform="scale(0.26458333)"
id="text6"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:74.6667px;font-family:'Pirata One';-inkscape-font-specification:'Pirata One, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;letter-spacing:-2px;writing-mode:lr-tb;direction:ltr;white-space:pre;shape-inside:url(#rect6);display:inline;fill:#1a1a1a;stroke:none" />
<text
xml:space="preserve"
transform="scale(0.26458333)"
id="text2"
style="fill:#4d4d4d;text-orientation:auto;-inkscape-font-specification:'Pirata One, Normal';font-family:'Pirata One';font-size:74.66666667px;letter-spacing:-2px;text-align:start;writing-mode:lr-tb;direction:ltr;white-space:pre;shape-inside:url(#rect2)" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

6
dev.sh
View File

@@ -1,3 +1,9 @@
#!/bin/sh
set -e
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
templ generate --watch --proxy="http://localhost:8080" --cmd="go run ." & templ generate --watch --proxy="http://localhost:8080" --cmd="go run ." &
npm run watch npm run watch

54
go.mod
View File

@@ -2,54 +2,54 @@ module spend-sparrow
go 1.23.0 go 1.23.0
toolchain go1.24.4 toolchain go1.25.0
require ( require (
github.com/a-h/templ v0.3.898 github.com/a-h/templ v0.3.943
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.32
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/otelsqlx v0.3.2 github.com/uptrace/opentelemetry-go-extra/otelsqlx v0.3.2
go.opentelemetry.io/contrib/bridges/otelslog v0.11.0 go.opentelemetry.io/contrib/bridges/otelslog v0.12.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0
go.opentelemetry.io/otel v1.36.0 go.opentelemetry.io/otel v1.37.0
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.13.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0
go.opentelemetry.io/otel/log v0.12.2 go.opentelemetry.io/otel/log v0.13.0
go.opentelemetry.io/otel/sdk v1.36.0 go.opentelemetry.io/otel/sdk v1.37.0
go.opentelemetry.io/otel/sdk/log v0.12.2 go.opentelemetry.io/otel/sdk/log v0.13.0
go.opentelemetry.io/otel/sdk/metric v1.36.0 go.opentelemetry.io/otel/sdk/metric v1.37.0
go.opentelemetry.io/otel/trace v1.36.0 go.opentelemetry.io/otel/trace v1.37.0
golang.org/x/crypto v0.39.0 golang.org/x/crypto v0.41.0
golang.org/x/net v0.41.0 golang.org/x/net v0.43.0
) )
require ( require (
github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.6.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.35.0 // indirect
golang.org/x/text v0.26.0 // indirect golang.org/x/text v0.28.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.72.1 // indirect google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

104
go.sum
View File

@@ -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.898 h1:g9oxL/dmM6tvwRe2egJS8hBDQTncokbMoOFk1oJMX7s= github.com/a-h/templ v0.3.943 h1:o+mT/4yqhZ33F3ootBiHwaY4HM5EVaOJfIshvd5UNTY=
github.com/a-h/templ v0.3.898/go.mod h1:oLBbZVQ6//Q6zpvSMPTuBK0F3qOtBdFBcGRspcT+VNQ= github.com/a-h/templ v0.3.943/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
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=
@@ -9,8 +9,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
@@ -23,8 +23,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -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.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.32/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=
@@ -57,54 +57,54 @@ github.com/uptrace/opentelemetry-go-extra/otelsqlx v0.3.2 h1:zA9ZXfdtowo0EKt+t7u
github.com/uptrace/opentelemetry-go-extra/otelsqlx v0.3.2/go.mod h1:ySXmuW9JLCm/TjsQksuMY/7MNiWqfHnhH2xeT34uOLU= github.com/uptrace/opentelemetry-go-extra/otelsqlx v0.3.2/go.mod h1:ySXmuW9JLCm/TjsQksuMY/7MNiWqfHnhH2xeT34uOLU=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/bridges/otelslog v0.11.0 h1:EMIiYTms4Z4m3bBuKp1VmMNRLZcl6j4YbvOPL1IhlWo= go.opentelemetry.io/contrib/bridges/otelslog v0.12.0 h1:lFM7SZo8Ce01RzRfnUFQZEYeWRf/MtOA3A5MobOqk2g=
go.opentelemetry.io/contrib/bridges/otelslog v0.11.0/go.mod h1:DIEZmUR7tzuOOVUTDKvkGWtYWSHFV18Qg8+GMb8wPJw= go.opentelemetry.io/contrib/bridges/otelslog v0.12.0/go.mod h1:Dw05mhFtrKAYu72Tkb3YBYeQpRUJ4quDgo2DQw3No5A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.13.0 h1:z6lNIajgEBVtQZHjfw2hAccPEBDs+nx58VemmXWa2ec=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.13.0/go.mod h1:+kyc3bRx/Qkq05P6OCu3mTEIOxYRYzoIg+JsUp5X+PM=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 h1:zwdo1gS2eH26Rg+CoqVQpEK1h8gvt5qyU5Kk5Bixvow= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0/go.mod h1:rUKCPscaRWWcqGT6HnEmYrK+YNe5+Sw64xgQTOJ5b30= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI=
go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= go.opentelemetry.io/otel/log v0.13.0 h1:yoxRoIZcohB6Xf0lNv9QIyCzQvrtGZklVbdCoyb7dls=
go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= go.opentelemetry.io/otel/log v0.13.0/go.mod h1:INKfG4k1O9CL25BaM1qLe0zIedOpvlS5Z7XgSbmN83E=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= go.opentelemetry.io/otel/sdk/log v0.13.0 h1:I3CGUszjM926OphK8ZdzF+kLqFvfRY/IIoFq/TjwfaQ=
go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= go.opentelemetry.io/otel/sdk/log v0.13.0/go.mod h1:lOrQyCCXmpZdN7NchXb6DOZZa1N5G1R2tm5GMMTpDBw=
go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0= go.opentelemetry.io/otel/sdk/log/logtest v0.13.0 h1:9yio6AFZ3QD9j9oqshV1Ibm9gPLlHNxurno5BreMtIA=
go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE= go.opentelemetry.io/otel/sdk/log/logtest v0.13.0/go.mod h1:QOGiAJHl+fob8Nu85ifXfuQYmJTFAvcrxL6w5/tu168=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= 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-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -3,25 +3,22 @@
@source './static/**/*.js'; @source './static/**/*.js';
@source './template/**/*.templ'; @source './template/**/*.templ';
body {
@apply font-garamond text-gray-700;
}
input:focus {
@apply outline-none ring-0;
}
@font-face {
font-family: "Pirata One";
src: url("/static/font/PirataOne-Regular.woff2") format("woff2");
}
@font-face { @font-face {
font-family: "EB Garamond"; font-family: "EB Garamond";
src: url("/static/font/EBGaramond-VariableFont_wght.woff2") format("woff2"); src: url("/static/font/EBGaramond-VariableFont_wght.woff2") format("woff2");
} }
@theme { body {
--font-pirata: "Pirata One", serif; font-family: "EB Garamond", serif;
--font-garamond: "EB Garamond", serif; @apply text-gray-700;
}
input:focus {
@apply outline-none ring-0;
}
button {
@apply cursor-pointer;
} }
/* Button */ /* Button */
@@ -61,3 +58,5 @@ input:focus {
box-shadow: 0 0 0 2px var(--color-gray-200); box-shadow: 0 0 0 2px var(--color-gray-200);
} }

View File

@@ -121,7 +121,7 @@ func createHandlerWithServices(ctx context.Context, d *sqlx.DB, serverSettings *
render := handler.NewRender() render := handler.NewRender()
indexHandler := handler.NewIndex(render, clockService) indexHandler := handler.NewIndex(render, clockService)
dashboardHandler := handler.NewDashboard(render, dashboardService) dashboardHandler := handler.NewDashboard(render, dashboardService, treasureChestService)
authHandler := handler.NewAuth(authService, render) authHandler := handler.NewAuth(authService, render)
accountHandler := handler.NewAccount(accountService, render) accountHandler := handler.NewAccount(accountService, render)
treasureChestHandler := handler.NewTreasureChest(treasureChestService, transactionRecurringService, render) treasureChestHandler := handler.NewTreasureChest(treasureChestService, transactionRecurringService, render)

View File

@@ -9,6 +9,8 @@ import (
"spend-sparrow/internal/template/dashboard" "spend-sparrow/internal/template/dashboard"
"spend-sparrow/internal/utils" "spend-sparrow/internal/utils"
"time" "time"
"github.com/google/uuid"
) )
type Dashboard interface { type Dashboard interface {
@@ -18,18 +20,22 @@ type Dashboard interface {
type DashboardImpl struct { type DashboardImpl struct {
r *Render r *Render
d *service.Dashboard d *service.Dashboard
treasureChest service.TreasureChest
} }
func NewDashboard(r *Render, d *service.Dashboard) Dashboard { func NewDashboard(r *Render, d *service.Dashboard, treasureChest service.TreasureChest) Dashboard {
return DashboardImpl{ return DashboardImpl{
r: r, r: r,
d: d, d: d,
treasureChest: treasureChest,
} }
} }
func (handler DashboardImpl) Handle(router *http.ServeMux) { func (handler DashboardImpl) Handle(router *http.ServeMux) {
router.Handle("GET /dashboard", handler.handleDashboard()) router.Handle("GET /dashboard", handler.handleDashboard())
router.Handle("GET /dashboard/dataset", handler.handleDashboardDataset()) router.Handle("GET /dashboard/main-chart", handler.handleDashboardMainChart())
router.Handle("GET /dashboard/treasure-chests", handler.handleDashboardTreasureChests())
router.Handle("GET /dashboard/treasure-chest", handler.handleDashboardTreasureChest())
} }
func (handler DashboardImpl) handleDashboard() http.HandlerFunc { func (handler DashboardImpl) handleDashboard() http.HandlerFunc {
@@ -42,12 +48,18 @@ func (handler DashboardImpl) handleDashboard() http.HandlerFunc {
return return
} }
comp := dashboard.Dashboard() treasureChests, err := handler.treasureChest.GetAll(r.Context(), user)
if err != nil {
handleError(w, r, err)
return
}
comp := dashboard.Dashboard(treasureChests)
handler.r.RenderLayoutWithStatus(r, w, comp, user, http.StatusOK) handler.r.RenderLayoutWithStatus(r, w, comp, user, http.StatusOK)
} }
} }
func (handler DashboardImpl) handleDashboardDataset() http.HandlerFunc { func (handler DashboardImpl) handleDashboardMainChart() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
updateSpan(r) updateSpan(r)
@@ -69,11 +81,15 @@ func (handler DashboardImpl) handleDashboardDataset() http.HandlerFunc {
account += fmt.Sprintf(`["%s",%.2f],`, entry.Day.Format(time.RFC3339), float64(entry.Value)/100) account += fmt.Sprintf(`["%s",%.2f],`, entry.Day.Format(time.RFC3339), float64(entry.Value)/100)
savings += fmt.Sprintf(`["%s",%.2f],`, entry.Day.Format(time.RFC3339), float64(entry.Savings)/100) savings += fmt.Sprintf(`["%s",%.2f],`, entry.Day.Format(time.RFC3339), float64(entry.Savings)/100)
} }
account = account[:len(account)-1] account = account[:len(account)-1]
savings = savings[:len(savings)-1] savings = savings[:len(savings)-1]
_, err = fmt.Fprintf(w, ` _, err = fmt.Fprintf(w, `
{ {
"aria": {
"enabled": true
},
"tooltip": { "tooltip": {
"trigger": "axis", "trigger": "axis",
"formatter": "<updated by client>" "formatter": "<updated by client>"
@@ -105,3 +121,127 @@ func (handler DashboardImpl) handleDashboardDataset() http.HandlerFunc {
} }
} }
} }
func (handler DashboardImpl) handleDashboardTreasureChests() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
updateSpan(r)
user := middleware.GetUser(r)
treeList, err := handler.d.TreasureChests(r.Context(), user)
if err != nil {
handleError(w, r, err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
data := ""
for _, item := range treeList {
children := ""
for _, child := range item.Children {
if child.Value < 0 {
children += fmt.Sprintf(`{"name":"%s\n%.2f €","value":%d},`, child.Name, float64(child.Value)/100, -child.Value)
} else {
children += fmt.Sprintf(`{"name":"%s\n%.2f €","value":%d},`, child.Name, float64(child.Value)/100, child.Value)
}
}
children = children[:len(children)-1]
data += fmt.Sprintf(`{"name":"%s","children":[%s]},`, item.Name, children)
}
data = data[:len(data)-1]
_, err = fmt.Fprintf(w, `
{
"aria": {
"enabled": true
},
"series": [
{
"data": [%s],
"type": "treemap",
"name": "Savings"
}
]
}
`, data)
if err != nil {
slog.InfoContext(r.Context(), "could not write response", "err", err)
}
}
}
func (handler DashboardImpl) handleDashboardTreasureChest() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
updateSpan(r)
user := middleware.GetUser(r)
var treasureChestId *uuid.UUID
treasureChestStr := r.URL.Query().Get("id")
if treasureChestStr != "" {
id, err := uuid.Parse(treasureChestStr)
if err != nil {
handleError(w, r, fmt.Errorf("could not parse treasure chest: %w", service.ErrBadRequest))
return
}
treasureChestId = &id
}
series, err := handler.d.TreasureChest(r.Context(), user, treasureChestId)
if err != nil {
handleError(w, r, err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
value := ""
for _, entry := range series {
value += fmt.Sprintf(`["%s",%.2f],`, entry.Day.Format(time.RFC3339), float64(entry.Value)/100)
}
if len(value) > 0 {
value = value[:len(value)-1]
}
_, err = fmt.Fprintf(w, `
{
"aria": {
"enabled": true
},
"tooltip": {
"trigger": "axis",
"formatter": "<updated by client>"
},
"xAxis": {
"type": "time"
},
"yAxis": {
"axisLabel": {
"formatter": "{value} €"
}
},
"series": [
{
"data": [%s],
"type": "line",
"name": "Treasure Chest Value"
}
]
}
`, value)
if err != nil {
slog.InfoContext(r.Context(), "could not write response", "err", err)
}
}
}

View File

@@ -3,6 +3,7 @@ package middleware
import ( import (
"context" "context"
"net/http" "net/http"
"strings"
"spend-sparrow/internal/service" "spend-sparrow/internal/service"
"spend-sparrow/internal/types" "spend-sparrow/internal/types"
@@ -16,6 +17,13 @@ var UserKey ContextKey = "user"
func Authenticate(service service.Auth) func(http.Handler) http.Handler { func Authenticate(service service.Auth) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if strings.Contains(r.URL.Path, "/static/") {
next.ServeHTTP(w, r.WithContext(ctx))
return
}
sessionId := getSessionID(r) sessionId := getSessionID(r)
session, user, _ := service.SignInSession(r.Context(), sessionId) session, user, _ := service.SignInSession(r.Context(), sessionId)
@@ -33,7 +41,6 @@ func Authenticate(service service.Auth) func(http.Handler) http.Handler {
http.SetCookie(w, &cookie) http.SetCookie(w, &cookie)
} }
ctx := r.Context()
ctx = context.WithValue(ctx, UserKey, user) ctx = context.WithValue(ctx, UserKey, user)
ctx = context.WithValue(ctx, SessionKey, session) ctx = context.WithValue(ctx, SessionKey, session)

View File

@@ -10,6 +10,7 @@ import (
type csrfResponseWriter struct { type csrfResponseWriter struct {
http.ResponseWriter http.ResponseWriter
csrfToken string csrfToken string
} }
@@ -32,9 +33,15 @@ func (rr *csrfResponseWriter) Write(data []byte) (int, error) {
func CrossSiteRequestForgery(auth service.Auth) func(http.Handler) http.Handler { func CrossSiteRequestForgery(auth service.Auth) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session := GetSession(r)
ctx := r.Context() ctx := r.Context()
if strings.Contains(r.URL.Path, "/static/") {
next.ServeHTTP(w, r.WithContext(ctx))
return
}
session := GetSession(r)
if r.Method == http.MethodPost || if r.Method == http.MethodPost ||
r.Method == http.MethodPut || r.Method == http.MethodPut ||
r.Method == http.MethodDelete || r.Method == http.MethodDelete ||

View File

@@ -8,6 +8,7 @@ import (
type WrappedWriter struct { type WrappedWriter struct {
http.ResponseWriter http.ResponseWriter
StatusCode int StatusCode int
} }

View File

@@ -65,6 +65,8 @@ func (handler IndexImpl) handleRootAnd404() http.HandlerFunc {
func (handler IndexImpl) handleEmpty() http.HandlerFunc { func (handler IndexImpl) handleEmpty() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
// do nothing updateSpan(r)
// Return nothing
} }
} }

View File

@@ -58,6 +58,7 @@ func (h TransactionImpl) handleTransactionPage() http.HandlerFunc {
AccountId: r.URL.Query().Get("account-id"), AccountId: r.URL.Query().Get("account-id"),
TreasureChestId: r.URL.Query().Get("treasure-chest-id"), TreasureChestId: r.URL.Query().Get("treasure-chest-id"),
Error: r.URL.Query().Get("error"), Error: r.URL.Query().Get("error"),
Page: r.URL.Query().Get("page"),
} }
transactions, err := h.s.GetAll(r.Context(), user, filter) transactions, err := h.s.GetAll(r.Context(), user, filter)

View File

@@ -6,6 +6,7 @@ import (
"spend-sparrow/internal/types" "spend-sparrow/internal/types"
"time" "time"
"github.com/google/uuid"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )
@@ -39,7 +40,9 @@ func (s Dashboard) MainChart(
} }
timeEntries := make([]types.DashboardMainChartEntry, 0) timeEntries := make([]types.DashboardMainChartEntry, 0)
var lastEntry *types.DashboardMainChartEntry var lastEntry *types.DashboardMainChartEntry
for _, t := range transactions { for _, t := range transactions {
if t.Error != nil { if t.Error != nil {
continue continue
@@ -64,6 +67,7 @@ func (s Dashboard) MainChart(
if t.AccountId != nil { if t.AccountId != nil {
lastEntry.Value += t.Value lastEntry.Value += t.Value
} }
if t.TreasureChestId != nil { if t.TreasureChestId != nil {
lastEntry.Savings += t.Value lastEntry.Savings += t.Value
} }
@@ -75,3 +79,97 @@ func (s Dashboard) MainChart(
return timeEntries, nil return timeEntries, nil
} }
func (s Dashboard) TreasureChests(
ctx context.Context,
user *types.User,
) ([]*types.DashboardTreasureChest, error) {
if user == nil {
return nil, ErrUnauthorized
}
treasureChests := make([]*types.TreasureChest, 0)
err := s.db.SelectContext(ctx, &treasureChests, `SELECT * FROM treasure_chest WHERE user_id = ?`, user.Id)
err = db.TransformAndLogDbError(ctx, "dashboard TreasureChests", nil, err)
if err != nil {
return nil, err
}
treasureChests = sortTreasureChests(treasureChests)
result := make([]*types.DashboardTreasureChest, 0)
for _, t := range treasureChests {
if t.ParentId == nil {
result = append(result, &types.DashboardTreasureChest{
Name: t.Name,
Value: t.CurrentBalance,
Children: make([]types.DashboardTreasureChest, 0),
})
} else {
result[len(result)-1].Children = append(result[len(result)-1].Children, types.DashboardTreasureChest{
Name: t.Name,
Value: t.CurrentBalance,
Children: make([]types.DashboardTreasureChest, 0),
})
}
}
return result, nil
}
func (s Dashboard) TreasureChest(
ctx context.Context,
user *types.User,
treausureChestId *uuid.UUID,
) ([]types.DashboardMainChartEntry, error) {
if user == nil {
return nil, ErrUnauthorized
}
transactions := make([]types.Transaction, 0)
err := s.db.SelectContext(ctx, &transactions, `
SELECT *
FROM "transaction"
WHERE user_id = ?
AND treasure_chest_id = ?
ORDER BY timestamp`, user.Id, treausureChestId)
err = db.TransformAndLogDbError(ctx, "dashboard Chart", nil, err)
if err != nil {
return nil, err
}
timeEntries := make([]types.DashboardMainChartEntry, 0)
var lastEntry *types.DashboardMainChartEntry
for _, t := range transactions {
if t.Error != nil {
continue
}
newDay := t.Timestamp.Truncate(24 * time.Hour)
if lastEntry == nil {
lastEntry = &types.DashboardMainChartEntry{
Day: newDay,
Value: 0,
}
} else if lastEntry.Day != newDay {
timeEntries = append(timeEntries, *lastEntry)
lastEntry = &types.DashboardMainChartEntry{
Day: newDay,
Value: lastEntry.Value,
}
}
if t.TreasureChestId != nil {
lastEntry.Value += t.Value
}
}
if lastEntry != nil {
timeEntries = append(timeEntries, *lastEntry)
}
return timeEntries, nil
}

View File

@@ -10,7 +10,7 @@ const (
) )
var ( var (
safeInputRegex = regexp.MustCompile(`^[a-zA-Z0-9ÄÖÜäöüß,&'" -]+$`) safeInputRegex = regexp.MustCompile(`^[a-zA-Z0-9ÄÖÜäöüß,&'". \-\?]+$`)
) )
func validateString(value string, fieldName string) error { func validateString(value string, fieldName string) error {

View File

@@ -7,12 +7,15 @@ import (
"log/slog" "log/slog"
"spend-sparrow/internal/db" "spend-sparrow/internal/db"
"spend-sparrow/internal/types" "spend-sparrow/internal/types"
"strconv"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )
const page_size = 25
type Transaction interface { type Transaction interface {
Add(ctx context.Context, tx *sqlx.Tx, user *types.User, transaction types.Transaction) (*types.Transaction, error) Add(ctx context.Context, tx *sqlx.Tx, user *types.User, transaction types.Transaction) (*types.Transaction, error)
Update(ctx context.Context, user *types.User, transaction types.Transaction) (*types.Transaction, error) Update(ctx context.Context, user *types.User, transaction types.Transaction) (*types.Transaction, error)
@@ -231,22 +234,41 @@ func (s TransactionImpl) GetAll(ctx context.Context, user *types.User, filter ty
return nil, ErrUnauthorized return nil, ErrUnauthorized
} }
var (
page int64
offset int64
err error
)
if filter.Page != "" {
page, err = strconv.ParseInt(filter.Page, 10, 64)
if err != nil {
offset = 0
} else {
offset = page - 1
offset *= page_size
}
}
transactions := make([]*types.Transaction, 0) transactions := make([]*types.Transaction, 0)
err := s.db.SelectContext(ctx, &transactions, ` err = s.db.SelectContext(ctx, &transactions, `
SELECT * SELECT *
FROM "transaction" FROM "transaction"
WHERE user_id = ? WHERE user_id = ?
AND (? = '' OR account_id = ?) AND ($1 = '' OR account_id = $1)
AND (? = '' OR treasure_chest_id = ?) AND ($2 = '' OR treasure_chest_id = $2)
AND (? = '' AND ($3 = ''
OR (? = "true" AND error IS NOT NULL) OR ($3 = "true" AND error IS NOT NULL)
OR (? = "false" AND error IS NULL) OR ($3 = "false" AND error IS NULL)
) )
ORDER BY timestamp DESC, created_at DESC`, ORDER BY timestamp DESC, created_at DESC
LIMIT $4 OFFSET $5
`,
user.Id, user.Id,
filter.AccountId, filter.AccountId, filter.AccountId,
filter.TreasureChestId, filter.TreasureChestId, filter.TreasureChestId,
filter.Error, filter.Error, filter.Error) filter.Error,
page_size,
offset)
err = db.TransformAndLogDbError(ctx, "transaction GetAll", nil, err) err = db.TransformAndLogDbError(ctx, "transaction GetAll", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -205,7 +205,7 @@ func (s TreasureChestImpl) GetAll(ctx context.Context, user *types.User) ([]*typ
return nil, err return nil, err
} }
return sortTree(treasureChests), nil return sortTreasureChests(treasureChests), nil
} }
func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr string) error { func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr string) error {
@@ -277,7 +277,7 @@ func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr s
return nil return nil
} }
func sortTree(nodes []*types.TreasureChest) []*types.TreasureChest { func sortTreasureChests(nodes []*types.TreasureChest) []*types.TreasureChest {
var ( var (
roots []*types.TreasureChest roots []*types.TreasureChest
) )

View File

@@ -66,7 +66,9 @@ templ EditAccount(account *types.Account) {
hx-swap="outerHTML" hx-swap="outerHTML"
class="button button-neglect px-1 flex items-center gap-2" class="button button-neglect px-1 flex items-center gap-2"
> >
<span class="h-4 w-4">
@svg.Cancel() @svg.Cancel()
</span>
<span> <span>
Cancel Cancel
</span> </span>

View File

@@ -71,10 +71,10 @@ if isSignIn {
Don't have an account? Don't have an account?
Sign Up Sign Up
</a> </a>
<button class="button button-primary font-pirata text-gray-600 text-2xl px-1">Sign In</button> <button class="button button-primary text-gray-600 text-2xl px-1">Sign In</button>
} else { } else {
<a href="/auth/signin" class="text-gray-500 text-sm px-1 button button-neglect">Already have an account? Sign In</a> <a href="/auth/signin" class="text-gray-500 text-sm px-1 button button-neglect">Already have an account? Sign In</a>
<button class="button button-primary font-pirata text-gray-600 text-2xl px-1"> <button class="button button-primary text-gray-600 text-2xl px-1">
Sign Up Sign Up
</button> </button>
} }

View File

@@ -1,7 +1,7 @@
package auth package auth
templ UserComp(user string) { templ UserComp(user string) {
<div id="user-info" class="flex gap-5 items-center"> <div id="user-info" class="flex items-center gap-2 text-nowrap">
if user != "" { if user != "" {
<div class="inline-block group relative"> <div class="inline-block group relative">
<button class="font-semibold py-2 px-4 inline-flex items-center"> <button class="font-semibold py-2 px-4 inline-flex items-center">
@@ -37,8 +37,8 @@ templ UserComp(user string) {
</div> </div>
</div> </div>
} else { } else {
<a href="/auth/signup" class="font-pirata text-xl button px-1 button-neglect">Sign Up</a> <a href="/auth/signup" class="text-xl button px-1 button-neglect">Sign Up</a>
<a href="/auth/signin" class="font-pirata text-xl button px-1 button-neglect">Sign In</a> <a href="/auth/signin" class="text-xl button px-1 button-neglect">Sign In</a>
} }
</div> </div>
} }

View File

@@ -1,7 +1,32 @@
package dashboard package dashboard
templ Dashboard() { import "spend-sparrow/internal/types"
templ Dashboard(treasureChests []*types.TreasureChest) {
<div class="mt-10 h-full"> <div class="mt-10 h-full">
<div id="graph" class="h-96"></div> <div id="main-chart" class="h-96 mt-10"></div>
<div id="treasure-chests" class="h-96 mt-10"></div>
<section>
<form class="flex items-center justify-end gap-4 mr-40">
<label for="treasure-chest">Treasure Chest:</label>
<select id="treasure-chest-id" name="treasure-chest-id" class="bg-white input">
<option value="">- Select Treasure Chest -</option>
for _, parent := range treasureChests {
if parent.ParentId == nil {
<optgroup label={ parent.Name }>
for _, child := range treasureChests {
if child.ParentId != nil && *child.ParentId == parent.Id {
<option
value={ child.Id.String() }
>{ child.Name }</option>
}
}
</optgroup>
}
}
</select>
</form>
<div id="treasure-chest" class="h-96 mt-10"></div>
</section>
</div> </div>
} }

View File

@@ -3,8 +3,7 @@ package template
templ Index() { templ Index() {
<div class="h-full flex flex-col items-center justify-center"> <div class="h-full flex flex-col items-center justify-center">
<h1 class="flex gap-2 w-full justify-center"> <h1 class="flex gap-2 w-full justify-center">
<img class="w-24" src="/static/favicon.svg" alt="SpendSparrow logo"/> <img width="600" src="/static/logo.svg" alt="SpendSparrow logo"/>
<span class="text-8xl tracking-tighter font-bold font-pirata">SpendSparrow</span>
</h1> </h1>
<h2 class="text-2xl mt-8 text-gray-800"> <h2 class="text-2xl mt-8 text-gray-800">
Spend your <span class="px-2 text-3xl text-yellow-800">treasure</span> on the important Spend your <span class="px-2 text-3xl text-yellow-800">treasure</span> on the important

View File

@@ -1,10 +1,14 @@
package template package template
import "spend-sparrow/internal/template/svg"
func layoutLinkClass(isActive bool) string { func layoutLinkClass(isActive bool) string {
common := "text-2xl p-2 text-gray-900 decoration-yellow-400 decoration-[0.25rem] hover:bg-gray-200 rounded-lg"
if isActive { if isActive {
return "text-xl hover:bg-gray-100 p-1 duration-100 rounded-xl transition-colors text-gray-900" return common + " " + "underline"
} }
return "text-xl hover:bg-gray-100 hover:text-gray-900 p-1 duration-200 rounded-xl transition-colors text-gray-400"
return common + " " + "hover:underline"
} }
templ Layout(slot templ.Component, user templ.Component, loggedIn bool, path string) { templ Layout(slot templ.Component, user templ.Component, loggedIn bool, path string) {
@@ -26,44 +30,66 @@ templ Layout(slot templ.Component, user templ.Component, loggedIn bool, path str
/> />
<script src="/static/js/htmx.min.js"></script> <script src="/static/js/htmx.min.js"></script>
<script src="/static/js/toast.js"></script> <script src="/static/js/toast.js"></script>
<script src="/static/js/layout.js"></script>
<script src="/static/js/transaction.js"></script>
<script src="/static/js/time.js"></script> <script src="/static/js/time.js"></script>
<script src="/static/js/echarts.min.js"></script> <script src="/static/js/echarts.min.js"></script>
<script src="/static/js/dashboard.js" defer></script> <script src="/static/js/dashboard.js" defer></script>
</head> </head>
<body class="h-screen flex flex-col" hx-headers='{"Csrf-Token": "CSRF_TOKEN"}'> <body hx-headers='{"Csrf-Token": "CSRF_TOKEN"}'>
// Header <div class="flex flex-col min-h-screen">
<nav class="flex bg-white items-center gap-2 py-1 px-2 h-12 md:gap-10 md:px-10 md:py-2"> <header class="sticky top-0 z-50 bg-white flex items-center gap-6 p-4 border-b-1 border-gray-200">
<a href="/" class="flex gap-2 mr-20"> <button id="menuButton" class="w-10 h-10 block xl:hidden">
<img class="w-6" src="/static/favicon.svg" alt="SpendSparrow logo"/> @svg.Menu()
<span class="text-4xl font-bold font-pirata">SpendSparrow</span> </button>
<a href="/" class="flex gap-2 -mt-2">
<img width="150" src="/static/logo.svg" alt="SpendSparrow logo"/>
</a> </a>
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> </header>
<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 flex-1">
if loggedIn {
<aside class="shrink-0 h-[calc(100vh-4rem)] xl:block hidden sticky top-18 border-r-1 border-gray-200 overflow-y-auto p-4">
@navigation(path)
</aside>
}
<main class="flex-1 p-6">
if slot != nil { if slot != nil {
@slot @slot
} }
</main> </main>
// Footer </div>
<!-- </div> --> </div>
<dialog id="menu" class="max-h-none w-64 h-screen">
<header class="sticky top-0 z-50 bg-white flex items-center justify-between p-4 border-b-1 border-gray-200">
<a href="/" class="flex gap-2 -mt-2">
<img width="150" src="/static/logo.svg" alt="SpendSparrow logo"/>
</a>
<button id="menuButtonClose" class="h-6 w-6">
@svg.Cancel()
</button>
</header>
@navigation(path)
</dialog>
<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"
class="transition-all duration-300 opacity-0 px-4 py-2 text-lg hidden text-bold rounded bg-amber-900 text-white" class="transition-all duration-300
> opacity-0 px-4 py-2 text-lg hidden text-bold rounded bg-amber-900 text-white"
M ></div>
</div>
</div> </div>
</body> </body>
</html> </html>
} }
templ navigation(path string) {
<nav class="w-64 text-nowrap flex gap-2 flex-col text-lg mt-5 px-5 pt-2">
<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>
</nav>
}

View File

@@ -31,7 +31,7 @@ templ Save() {
} }
templ Cancel() { templ Cancel() {
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" class="h-4 w-4 text-gray-500"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" class="text-gray-500">
<path fill="currentColor" d="m654 501l346 346l-154 154l-346-346l-346 346L0 847l346-346L0 155L154 1l346 346L846 1l154 154z"></path> <path fill="currentColor" d="m654 501l346 346l-154 154l-346-346l-346 346L0 847l346-346L0 155L154 1l346 346L846 1l154 154z"></path>
</svg> </svg>
} }
@@ -47,3 +47,13 @@ templ Info() {
<path fill="currentColor" d="M0 0h48v48H0z" mask="url(#ipSInfo0)"></path> <path fill="currentColor" d="M0 0h48v48H0z" mask="url(#ipSInfo0)"></path>
</svg> </svg>
} }
templ Menu() {
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" class="text-gray-500">
<g data-name="1" id="_1">
<path d="M441.13,166.52h-372a15,15,0,1,1,0-30h372a15,15,0,0,1,0,30Z"></path>
<path d="M441.13,279.72h-372a15,15,0,1,1,0-30h372a15,15,0,0,1,0,30Z"></path>
<path d="M441.13,392.92h-372a15,15,0,1,1,0-30h372a15,15,0,0,1,0,30Z"></path>
</g>
</svg>
}

View File

@@ -1,74 +1,97 @@
package transaction package transaction
import "fmt" import "fmt"
import "time"
import "spend-sparrow/internal/template/svg"
import "spend-sparrow/internal/types" import "spend-sparrow/internal/types"
import "github.com/google/uuid" import "github.com/google/uuid"
templ Transaction(items templ.Component, filter types.TransactionItemsFilter, accounts []*types.Account, treasureChests []*types.TreasureChest) { templ Transaction(
<div class="max-w-6xl mt-10 mx-auto"> items templ.Component,
<div class="flex items-center gap-4"> filter types.TransactionItemsFilter,
<form accounts []*types.Account,
hx-get="/transaction" treasureChests []*types.TreasureChest) {
hx-target="#transaction-items" <div class="">
hx-push-url="true" <div class="">
hx-trigger="change" <!-- <form -->
> <!-- id="transactionFilterForm" -->
<select name="account-id" class="bg-white input"> <!-- hx-get="/transaction" -->
<option value="">- Filter Acount -</option> <!-- hx-target="#transaction-items" -->
for _, account := range accounts { <!-- hx-push-url="true" -->
<option <!-- hx-trigger="change" -->
value={ account.Id.String() } <!-- > -->
selected?={ filter.AccountId == account.Id.String() } <!-- <select name="account-id" class=""> -->
>{ account.Name }</option> <!-- <option value="">- Filter Acount -</option> -->
} <!-- for _, account := range accounts { -->
</select> <!-- <option -->
<select name="treasure-chest-id" class="bg-white input"> <!-- value={ account.Id.String() } -->
<option value="">- Filter Treasure Chest -</option> <!-- selected?={ filter.AccountId == account.Id.String() } -->
for _, parent := range treasureChests { <!-- >{ account.Name }</option> -->
if parent.ParentId == nil { <!-- } -->
<optgroup label={ parent.Name }> <!-- </select> -->
for _, child := range treasureChests { <!-- <select name="treasure-chest-id" class=""> -->
if child.ParentId != nil && *child.ParentId == parent.Id { <!-- <option value="">- Filter Treasure Chest -</option> -->
<option <!-- for _, parent := range treasureChests { -->
value={ child.Id.String() } <!-- if parent.ParentId == nil { -->
selected?={ filter.TreasureChestId == child.Id.String() } <!-- <optgroup label={ parent.Name }> -->
>{ child.Name }</option> <!-- for _, child := range treasureChests { -->
} <!-- if child.ParentId != nil && *child.ParentId == parent.Id { -->
} <!-- <option -->
</optgroup> <!-- value={ child.Id.String() } -->
} <!-- selected?={ filter.TreasureChestId == child.Id.String() } -->
} <!-- >{ child.Name }</option> -->
</select> <!-- } -->
<select name="error" class="bg-white input"> <!-- } -->
<option value="">- Filter Error -</option> <!-- </optgroup> -->
<option <!-- } -->
value="true" <!-- } -->
selected?={ filter.Error == "true" } <!-- </select> -->
>Has Errors</option> <!-- <select name="error" class=""> -->
<option <!-- <option value="">- Filter Error -</option> -->
value="false" <!-- <option -->
selected?={ filter.Error == "false" } <!-- value="true" -->
>Has no Errors</option> <!-- selected?={ filter.Error == "true" } -->
</select> <!-- >Has Errors</option> -->
</form> <!-- <option -->
<button <!-- value="false" -->
hx-get="/transaction/new" <!-- selected?={ filter.Error == "false" } -->
hx-target="#transaction-items" <!-- >Has no Errors</option> -->
hx-swap="afterbegin" <!-- </select> -->
class="button button-primary ml-auto px-2 flex items-center gap-2 justify-center" <!-- <input id="page" name="page" type="hidden" value={ filter.Page }/> -->
> <!-- </form> -->
@svg.Plus() <!-- <button -->
<p>New Transaction</p> <!-- hx-get="/transaction/new" -->
</button> <!-- hx-target="#transaction-items" -->
<!-- hx-swap="afterbegin" -->
<!-- class="" -->
<!-- > -->
<!-- @svg.Plus() -->
<!-- <p>New Transaction</p> -->
<!-- </button> -->
</div> </div>
<!-- <div class=""> -->
<!-- <button id="pagePrev1" class=""> -->
<!-- &lt; -->
<!-- </button> -->
<!-- <span class="">Page: <span class="" id="page1">{ getPageNumber(filter.Page) }</span></span> -->
<!-- <button id="pageNext1" class=""> -->
<!-- &gt; -->
<!-- </button> -->
<!-- </div> -->
@items @items
<!-- <div class=""> -->
<!-- <button id="pagePrev2" class=""> -->
<!-- &lt; -->
<!-- </button> -->
<!-- <span class="">Page: <span class="" id="page2">{ getPageNumber(filter.Page) }</span></span> -->
<!-- <button id="pageNext2" class=""> -->
<!-- &gt; -->
<!-- </button> -->
<!-- </div> -->
</div> </div>
} }
templ TransactionItems(transactions []*types.Transaction, accounts, treasureChests map[uuid.UUID]string) { templ TransactionItems(transactions []*types.Transaction, accounts, treasureChests map[uuid.UUID]string) {
<div id="transaction-items" class="my-6"> <div id="transaction-items" class="flex flex-col gap-8">
for _, transaction := range transactions { for _, transaction := range transactions {
@TransactionItem(transaction, accounts, treasureChests) @TransactionItem(transaction, accounts, treasureChests)
} }
@@ -76,207 +99,186 @@ templ TransactionItems(transactions []*types.Transaction, accounts, treasureChes
} }
templ EditTransaction(transaction *types.Transaction, accounts []*types.Account, treasureChests []*types.TreasureChest) { templ EditTransaction(transaction *types.Transaction, accounts []*types.Account, treasureChests []*types.TreasureChest) {
{{ // {{
var ( // var (
timestamp time.Time // timestamp time.Time
//
id string // id string
cancelUrl string // cancelUrl string
) // )
party := "" // party := ""
description := "" // description := ""
accountId := "" // accountId := ""
value := "0.00" // value := "0.00"
treasureChestId := "" // treasureChestId := ""
if transaction == nil { // if transaction == nil {
timestamp = time.Now().UTC().Truncate(time.Minute) // timestamp = time.Now().UTC().Truncate(time.Minute)
//
id = "new" // id = "new"
cancelUrl = "/empty" // cancelUrl = "/empty"
} else { // } else {
timestamp = transaction.Timestamp.UTC().Truncate(time.Minute) // timestamp = transaction.Timestamp.UTC().Truncate(time.Minute)
party = transaction.Party // party = transaction.Party
description = transaction.Description // description = transaction.Description
if transaction.AccountId != nil { // if transaction.AccountId != nil {
accountId = transaction.AccountId.String() // accountId = transaction.AccountId.String()
} // }
if transaction.TreasureChestId != nil { // if transaction.TreasureChestId != nil {
treasureChestId = transaction.TreasureChestId.String() // treasureChestId = transaction.TreasureChestId.String()
} // }
value = formatFloat(transaction.Value) // value = formatFloat(transaction.Value)
//
id = transaction.Id.String() // id = transaction.Id.String()
cancelUrl = "/transaction/" + id // cancelUrl = "/transaction/" + id
} // }
}} // }}
<div id="transaction" class="border-1 border-gray-300 w-full my-4 p-4 bg-gray-50 rounded-lg"> // <div id="transaction" class="">
<form // <form
hx-post={ "/transaction/" + id } // hx-post={ "/transaction/" + id }
hx-target="closest #transaction" // hx-target="closest #transaction"
hx-swap="outerHTML" // hx-swap="outerHTML"
class="text-xl flex justify-end gap-4 items-center" // class=""
> // >
<div class="grid grid-cols-[auto_auto] items-center gap-4 mr-auto"> // <div class="">
<label for="timestamp" class="text-sm text-gray-500">Transaction Date</label> // <label for="timestamp" class="">Transaction Date</label>
<input // <input
autofocus // autofocus
name="timestamp" // name="timestamp"
type="date" // type="date"
value={ timestamp.String() } // value={ timestamp.String() }
class="bg-white input datetime" // class=""
/> // />
<label for="party" class="text-sm text-gray-500">Party</label> // <label for="party" class="">Party</label>
<input // <input
name="party" // name="party"
type="text" // type="text"
value={ party } // value={ party }
class="mr-auto bg-white input" // class=""
/> // />
<label for="description" class="text-sm text-gray-500">Description</label> // <label for="description" class="">Description</label>
<input // <input
name="description" // name="description"
type="text" // type="text"
value={ description } // value={ description }
class="mr-auto bg-white input" // class=""
/> // />
<label for="value" class="text-sm text-gray-500">Value (€)</label> // <label for="value" class="">Value (€)</label>
<input // <input
name="value" // name="value"
step="0.01" // step="0.01"
type="number" // type="number"
value={ value } // value={ value }
class="bg-white input" // class=""
/> // />
<label for="account-id" class="text-sm text-gray-500">Account</label> // <label for="account-id" class="">Account</label>
<select // <select
name="account-id" // name="account-id"
class="bg-white input" // class=""
> // >
<option value="">-</option> // <option value="">-</option>
for _, account := range accounts { // for _, account := range accounts {
<option selected?={ account.Id.String() == accountId } value={ account.Id.String() }>{ account.Name }</option> // <option selected?={ account.Id.String() == accountId } value={ account.Id.String() }>{ account.Name }</option>
} // }
</select> // </select>
<label for="treasure-chest-id" class="text-sm text-gray-500">Treasure Chest</label> // <label for="treasure-chest-id" class="">Treasure Chest</label>
<select name="treasure-chest-id" class="bg-white input"> // <select name="treasure-chest-id" class="">
<option value="">- Filter Treasure Chest -</option> // <option value="">- Filter Treasure Chest -</option>
for _, parent := range treasureChests { // for _, parent := range treasureChests {
if parent.ParentId == nil { // if parent.ParentId == nil {
<optgroup label={ parent.Name }> // <optgroup label={ parent.Name }>
for _, child := range treasureChests { // for _, child := range treasureChests {
if child.ParentId != nil && *child.ParentId == parent.Id { // if child.ParentId != nil && *child.ParentId == parent.Id {
<option // <option
value={ child.Id.String() } // value={ child.Id.String() }
selected?={ treasureChestId == child.Id.String() } // selected?={ treasureChestId == child.Id.String() }
>{ child.Name }</option> // >{ child.Name }</option>
} // }
} // }
</optgroup> // </optgroup>
} // }
} // }
</select> // </select>
</div> // </div>
<button type="submit" class="button button-neglect px-1 flex items-center gap-2"> // <button type="submit" class="">
@svg.Save() // @svg.Save()
<span> // <span>
Save // Save
</span> // </span>
</button> // </button>
<button // <button
hx-get={ cancelUrl } // hx-get={ cancelUrl }
hx-target="closest #transaction" // hx-target="closest #transaction"
hx-swap="outerHTML" // hx-swap="outerHTML"
class="button button-neglect px-1 flex items-center gap-2" // class=""
> // >
@svg.Cancel() // <span class="">
<span> // @svg.Cancel()
Cancel // </span>
</span> // <span>
</button> // Cancel
</form> // </span>
</div> // </button>
// </form>
// </div>
} }
templ TransactionItem(transaction *types.Transaction, accounts, treasureChests map[uuid.UUID]string) { templ TransactionItem(transaction *types.Transaction, accounts, treasureChests map[uuid.UUID]string) {
{{
background := "bg-gray-50"
if transaction.Error != nil {
background = "bg-yellow-50"
}
}}
<div <div
id="transaction" id="transaction"
class={ "mt-4 border-1 grid grid-cols-[auto_auto_1fr_1fr_auto_auto_auto_auto] gap-4 items-center text-xl border-gray-300 w-full p-4 rounded-lg " + background } class="grid grid-cols-[1fr_auto]"
if transaction.Error != nil { if transaction.Error != nil {
title={ *transaction.Error } title={ *transaction.Error }
} }
> >
<p class="mr-auto datetime">{ transaction.Timestamp.String() }</p> <p class="datetime">{ transaction.Timestamp.String() }</p>
<div class="w-6"> <p class="text-2xl flex items-center col-start-2 row-start-1 row-end-3 align-center">
if transaction.Error != nil {
@svg.Info()
}
</div>
<div>
<p class="text-sm text-gray-500">
if transaction.AccountId != nil {
{ accounts[*transaction.AccountId] }
} else {
&nbsp;
}
</p>
<p class="text-sm text-gray-500">
if transaction.TreasureChestId != nil {
{ treasureChests[*transaction.TreasureChestId] }
} else {
&nbsp;
}
</p>
</div>
<div>
<p class="text-sm text-gray-500">
if transaction.Party != "" {
{ transaction.Party }
} else {
&nbsp;
}
</p>
<p class="text-sm text-gray-500">
if transaction.Description != "" {
{ transaction.Description }
} else {
&nbsp;
}
</p>
</div>
if transaction.Value < 0 { if transaction.Value < 0 {
<p class="mr-8 min-w-22 text-right text-red-700">{ types.FormatEuros(transaction.Value) }</p> <span class="text-red-700">- { types.FormatEuros(transaction.Value) }</span>
} else { } else {
<p class="mr-8 w-22 text-right text-green-700">{ types.FormatEuros(transaction.Value) }</p> <span class="text-green-700">+ { types.FormatEuros(transaction.Value) }</span>
} }
<button </p>
hx-get={ "/transaction/" + transaction.Id.String() + "?edit=true" } <!-- if transaction.AccountId != nil { -->
hx-target="closest #transaction" <!-- <p class="col-start-1"> -->
hx-swap="outerHTML" <!-- { accounts[*transaction.AccountId] } -->
class="button button-neglect px-1 flex items-center gap-2" <!-- </p> -->
> <!-- } -->
@svg.Edit() if transaction.TreasureChestId != nil {
<span> <p class="col-start-1">
Edit { treasureChests[*transaction.TreasureChestId] }
</span> </p>
</button> }
<button <!-- if transaction.Party != "" { -->
hx-delete={ "/transaction/" + transaction.Id.String() } <!-- <p class="col-start-1"> -->
hx-target="closest #transaction" <!-- { transaction.Party } -->
hx-swap="outerHTML" <!-- </p> -->
hx-confirm="Are you sure you want to delete this transaction?" <!-- } -->
class="button button-neglect px-1 flex items-center gap-2" <!-- if transaction.Description != "" { -->
> <!-- <p class="col-start-1"> -->
@svg.Delete() <!-- { transaction.Description } -->
<span> <!-- </p> -->
Delete <!-- } -->
</span> <!-- <div class="col-start-2 col-end-3 flex gap-10 justify-end"> -->
</button> <!-- <button -->
<!-- hx-get={ "/transaction/" + transaction.Id.String() + "?edit=true" } -->
<!-- hx-target="closest #transaction" -->
<!-- hx-swap="outerHTML" -->
<!-- class="flex items-center gap-2" -->
<!-- > -->
<!-- @svg.Edit() -->
<!-- Edit -->
<!-- </button> -->
<!-- <button -->
<!-- hx-delete={ "/transaction/" + transaction.Id.String() } -->
<!-- hx-target="closest #transaction" -->
<!-- hx-swap="outerHTML" -->
<!-- hx-confirm="Are you sure you want to delete this transaction?" -->
<!-- class="flex items-center gap-2" -->
<!-- > -->
<!-- @svg.Delete() -->
<!-- Delete -->
<!-- </button> -->
<!-- </div> -->
</div> </div>
} }
@@ -285,3 +287,11 @@ func formatFloat(balance int64) string {
euros := float64(balance) / 100 euros := float64(balance) / 100
return fmt.Sprintf("%.2f", euros) return fmt.Sprintf("%.2f", euros)
} }
func getPageNumber(page string) string {
if page == "" {
return "1"
} else {
return page
}
}

View File

@@ -193,7 +193,9 @@ templ EditTransactionRecurring(transactionRecurring *types.TransactionRecurring,
hx-swap="outerHTML" hx-swap="outerHTML"
class="button button-neglect px-1 flex items-center gap-2" class="button button-neglect px-1 flex items-center gap-2"
> >
<span class="h-4 w-4">
@svg.Cancel() @svg.Cancel()
</span>
<span> <span>
Cancel Cancel
</span> </span>

View File

@@ -88,7 +88,9 @@ templ EditTreasureChest(treasureChest *types.TreasureChest, parents []*types.Tre
hx-swap="outerHTML" hx-swap="outerHTML"
class="button button-neglect px-1 flex items-center gap-2" class="button button-neglect px-1 flex items-center gap-2"
> >
<span class="h-4 w-4">
@svg.Cancel() @svg.Cancel()
</span>
<span> <span>
Cancel Cancel
</span> </span>

View File

@@ -22,3 +22,9 @@ type DashboardMainChartEntry struct {
Value int64 Value int64
Savings int64 Savings int64
} }
type DashboardTreasureChest struct {
Name string
Value int64
Children []DashboardTreasureChest
}

View File

@@ -51,4 +51,5 @@ type TransactionItemsFilter struct {
AccountId string AccountId string
TreasureChestId string TreasureChestId string
Error string Error string
Page string
} }

View File

@@ -22,7 +22,7 @@ func main() {
return return
} }
db, err := otelsqlx.Open("sqlite3", "./data/spend-sparrow.db", db, err := otelsqlx.Open("sqlite3", "./data/spend-sparrow.db?_journal_mode=WAL",
otelsql.WithAttributes(semconv.DBSystemSqlite)) otelsql.WithAttributes(semconv.DBSystemSqlite))
if err != nil { if err != nil {
slog.ErrorContext(ctx, "Could not open Database data.db", "err", err) slog.ErrorContext(ctx, "Could not open Database data.db", "err", err)

262
package-lock.json generated
View File

@@ -8,27 +8,11 @@
"name": "spend-sparrow", "name": "spend-sparrow",
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": {
"echarts": "^5.6.0"
},
"devDependencies": { "devDependencies": {
"@tailwindcss/cli": "4.1.10", "@tailwindcss/cli": "4.1.12",
"htmx.org": "2.0.4", "echarts": "6.0.0",
"tailwindcss": "4.1.10" "htmx.org": "2.0.6",
} "tailwindcss": "4.1.12"
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
} }
}, },
"node_modules/@isaacs/fs-minipass": { "node_modules/@isaacs/fs-minipass": {
@@ -45,18 +29,25 @@
} }
}, },
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.24" "@jridgewell/trace-mapping": "^0.3.24"
}
}, },
"engines": { "node_modules/@jridgewell/remapping": {
"node": ">=6.0.0" "version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
} }
}, },
"node_modules/@jridgewell/resolve-uri": { "node_modules/@jridgewell/resolve-uri": {
@@ -69,16 +60,6 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
@@ -87,9 +68,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25", "version": "0.3.30",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -407,44 +388,44 @@
} }
}, },
"node_modules/@tailwindcss/cli": { "node_modules/@tailwindcss/cli": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.12.tgz",
"integrity": "sha512-TuO7IOUpTG1JeqtMQbQXjR4RIhfZ43mor/vpCp3S5X9h0WxUom5NYgxfNO0PiFoLMJ6/eYCelC7KGvUOmqqK6A==", "integrity": "sha512-2PyJ5MGh/6JPS+cEaAq6MGDx3UemkX/mJt+/phm7/VOpycpecwNnHuFZbbgx6TNK/aIjvFOhhTVlappM7tmqvQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@parcel/watcher": "^2.5.1", "@parcel/watcher": "^2.5.1",
"@tailwindcss/node": "4.1.10", "@tailwindcss/node": "4.1.12",
"@tailwindcss/oxide": "4.1.10", "@tailwindcss/oxide": "4.1.12",
"enhanced-resolve": "^5.18.1", "enhanced-resolve": "^5.18.3",
"mri": "^1.2.0", "mri": "^1.2.0",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"tailwindcss": "4.1.10" "tailwindcss": "4.1.12"
}, },
"bin": { "bin": {
"tailwindcss": "dist/index.mjs" "tailwindcss": "dist/index.mjs"
} }
}, },
"node_modules/@tailwindcss/node": { "node_modules/@tailwindcss/node": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.12.tgz",
"integrity": "sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==", "integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.3.0", "@jridgewell/remapping": "^2.3.4",
"enhanced-resolve": "^5.18.1", "enhanced-resolve": "^5.18.3",
"jiti": "^2.4.2", "jiti": "^2.5.1",
"lightningcss": "1.30.1", "lightningcss": "1.30.1",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"source-map-js": "^1.2.1", "source-map-js": "^1.2.1",
"tailwindcss": "4.1.10" "tailwindcss": "4.1.12"
} }
}, },
"node_modules/@tailwindcss/oxide": { "node_modules/@tailwindcss/oxide": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.12.tgz",
"integrity": "sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q==", "integrity": "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
@@ -456,24 +437,24 @@
"node": ">= 10" "node": ">= 10"
}, },
"optionalDependencies": { "optionalDependencies": {
"@tailwindcss/oxide-android-arm64": "4.1.10", "@tailwindcss/oxide-android-arm64": "4.1.12",
"@tailwindcss/oxide-darwin-arm64": "4.1.10", "@tailwindcss/oxide-darwin-arm64": "4.1.12",
"@tailwindcss/oxide-darwin-x64": "4.1.10", "@tailwindcss/oxide-darwin-x64": "4.1.12",
"@tailwindcss/oxide-freebsd-x64": "4.1.10", "@tailwindcss/oxide-freebsd-x64": "4.1.12",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.10", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12",
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.10", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12",
"@tailwindcss/oxide-linux-arm64-musl": "4.1.10", "@tailwindcss/oxide-linux-arm64-musl": "4.1.12",
"@tailwindcss/oxide-linux-x64-gnu": "4.1.10", "@tailwindcss/oxide-linux-x64-gnu": "4.1.12",
"@tailwindcss/oxide-linux-x64-musl": "4.1.10", "@tailwindcss/oxide-linux-x64-musl": "4.1.12",
"@tailwindcss/oxide-wasm32-wasi": "4.1.10", "@tailwindcss/oxide-wasm32-wasi": "4.1.12",
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.10", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12",
"@tailwindcss/oxide-win32-x64-msvc": "4.1.10" "@tailwindcss/oxide-win32-x64-msvc": "4.1.12"
} }
}, },
"node_modules/@tailwindcss/oxide-android-arm64": { "node_modules/@tailwindcss/oxide-android-arm64": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.12.tgz",
"integrity": "sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==", "integrity": "sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -488,9 +469,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-darwin-arm64": { "node_modules/@tailwindcss/oxide-darwin-arm64": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.12.tgz",
"integrity": "sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ==", "integrity": "sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -505,9 +486,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-darwin-x64": { "node_modules/@tailwindcss/oxide-darwin-x64": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz",
"integrity": "sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==", "integrity": "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -522,9 +503,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-freebsd-x64": { "node_modules/@tailwindcss/oxide-freebsd-x64": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.12.tgz",
"integrity": "sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==", "integrity": "sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -539,9 +520,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.12.tgz",
"integrity": "sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==", "integrity": "sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -556,9 +537,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": { "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.12.tgz",
"integrity": "sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==", "integrity": "sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -573,9 +554,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-linux-arm64-musl": { "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.12.tgz",
"integrity": "sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==", "integrity": "sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -590,9 +571,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-linux-x64-gnu": { "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.12.tgz",
"integrity": "sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==", "integrity": "sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -607,9 +588,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-linux-x64-musl": { "node_modules/@tailwindcss/oxide-linux-x64-musl": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.12.tgz",
"integrity": "sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==", "integrity": "sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -624,9 +605,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-wasm32-wasi": { "node_modules/@tailwindcss/oxide-wasm32-wasi": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.12.tgz",
"integrity": "sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==", "integrity": "sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==",
"bundleDependencies": [ "bundleDependencies": [
"@napi-rs/wasm-runtime", "@napi-rs/wasm-runtime",
"@emnapi/core", "@emnapi/core",
@@ -642,11 +623,11 @@
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@emnapi/core": "^1.4.3", "@emnapi/core": "^1.4.5",
"@emnapi/runtime": "^1.4.3", "@emnapi/runtime": "^1.4.5",
"@emnapi/wasi-threads": "^1.0.2", "@emnapi/wasi-threads": "^1.0.4",
"@napi-rs/wasm-runtime": "^0.2.10", "@napi-rs/wasm-runtime": "^0.2.12",
"@tybys/wasm-util": "^0.9.0", "@tybys/wasm-util": "^0.10.0",
"tslib": "^2.8.0" "tslib": "^2.8.0"
}, },
"engines": { "engines": {
@@ -654,18 +635,18 @@
} }
}, },
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.4.3", "version": "1.4.5",
"dev": true, "dev": true,
"inBundle": true, "inBundle": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@emnapi/wasi-threads": "1.0.2", "@emnapi/wasi-threads": "1.0.4",
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.4.3", "version": "1.4.5",
"dev": true, "dev": true,
"inBundle": true, "inBundle": true,
"license": "MIT", "license": "MIT",
@@ -675,7 +656,7 @@
} }
}, },
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.0.2", "version": "1.0.4",
"dev": true, "dev": true,
"inBundle": true, "inBundle": true,
"license": "MIT", "license": "MIT",
@@ -685,7 +666,7 @@
} }
}, },
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.10", "version": "0.2.12",
"dev": true, "dev": true,
"inBundle": true, "inBundle": true,
"license": "MIT", "license": "MIT",
@@ -693,11 +674,11 @@
"dependencies": { "dependencies": {
"@emnapi/core": "^1.4.3", "@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3", "@emnapi/runtime": "^1.4.3",
"@tybys/wasm-util": "^0.9.0" "@tybys/wasm-util": "^0.10.0"
} }
}, },
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.9.0", "version": "0.10.0",
"dev": true, "dev": true,
"inBundle": true, "inBundle": true,
"license": "MIT", "license": "MIT",
@@ -714,9 +695,9 @@
"optional": true "optional": true
}, },
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.12.tgz",
"integrity": "sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==", "integrity": "sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -731,9 +712,9 @@
} }
}, },
"node_modules/@tailwindcss/oxide-win32-x64-msvc": { "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.12.tgz",
"integrity": "sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==", "integrity": "sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -794,19 +775,20 @@
} }
}, },
"node_modules/echarts": { "node_modules/echarts": {
"version": "5.6.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz", "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"tslib": "2.3.0", "tslib": "2.3.0",
"zrender": "5.6.1" "zrender": "6.0.0"
} }
}, },
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.18.1", "version": "5.18.3",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -838,9 +820,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/htmx.org": { "node_modules/htmx.org": {
"version": "2.0.4", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.4.tgz", "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.6.tgz",
"integrity": "sha512-HLxMCdfXDOJirs3vBZl/ZLoY+c7PfM4Ahr2Ad4YXh6d22T5ltbTXFFkpx9Tgb2vvmWFMbIc3LqN2ToNkZJvyYQ==", "integrity": "sha512-7ythjYneGSk3yCHgtCnQeaoF+D+o7U2LF37WU3O0JYv3gTZSicdEFiI/Ai/NJyC5ZpYJWMpUb11OC5Lr6AfAqA==",
"dev": true, "dev": true,
"license": "0BSD" "license": "0BSD"
}, },
@@ -878,9 +860,9 @@
} }
}, },
"node_modules/jiti": { "node_modules/jiti": {
"version": "2.4.2", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@@ -1247,16 +1229,16 @@
} }
}, },
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "4.1.10", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz",
"integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==", "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/tapable": { "node_modules/tapable": {
"version": "2.2.1", "version": "2.2.2",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1298,6 +1280,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"dev": true,
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/yallist": { "node_modules/yallist": {
@@ -1311,9 +1294,10 @@
} }
}, },
"node_modules/zrender": { "node_modules/zrender": {
"version": "5.6.1", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz", "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz",
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==",
"dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"tslib": "2.3.0" "tslib": "2.3.0"

View File

@@ -11,9 +11,9 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@tailwindcss/cli": "4.1.10", "@tailwindcss/cli": "4.1.12",
"htmx.org": "2.0.4", "htmx.org": "2.0.6",
"tailwindcss": "4.1.10", "tailwindcss": "4.1.12",
"echarts": "5.6.0" "echarts": "6.0.0"
} }
} }

View File

@@ -1 +1,53 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 128 128"><path d="M93.46 39.45c6.71-1.49 15.45-8.15 16.78-11.43.78-1.92-3.11-4.92-4.15-6.13-2.38-2.76-1.42-4.12-.5-7.41 1.05-3.74-1.44-7.87-4.97-9.49s-7.75-1.11-11.3.47-6.58 4.12-9.55 6.62c-2.17-1.37-5.63-7.42-11.23-3.49-3.87 2.71-4.22 8.61-3.72 13.32 1.17 10.87 3.85 16.51 8.9 18.03 6.38 1.92 13.44.91 19.74-.49" style="fill:#ffca28"/><path d="M104.36 8.18c-.85 14.65-15.14 24.37-21.92 28.65l4.4 3.78s2.79.06 6.61-1.16c6.55-2.08 16.12-7.96 16.78-11.43.97-5.05-4.21-3.95-5.38-7.94-.61-2.11 2.97-6.1-.49-11.9M79.78 12.09s-2.55-2.61-4.44-3.8c-.94 1.77-1.61 3.69-1.94 5.67-.59 3.48 0 8.42 1.39 12.1.22.57 1.04.48 1.13-.12 1.2-7.91 3.86-13.85 3.86-13.85" style="fill:#e2a610"/><path d="M61.96 38.16S30.77 41.53 16.7 68.61s-2.11 43.5 10.55 49.48 44.56 8.09 65.31 3.17 25.94-15.12 24.97-24.97c-1.41-14.38-14.77-23.22-14.77-23.22s.53-17.76-13.25-29.29c-12.23-10.24-27.55-5.62-27.55-5.62" style="fill:#ffca28"/><path d="M74.76 83.73c-6.69-8.44-14.59-9.57-17.12-12.6-1.38-1.65-2.19-3.32-1.88-5.39.33-2.2 2.88-3.72 4.86-4.09 2.31-.44 7.82-.21 12.45 4.2 1.1 1.04.7 2.66.67 4.11-.08 3.11 4.37 6.13 7.97 3.53 3.61-2.61.84-8.42-1.49-11.24-1.76-2.13-8.14-6.82-16.07-7.56-2.23-.21-11.2-1.54-16.38 8.31-1.49 2.83-2.04 9.67 5.76 15.45 1.63 1.21 10.09 5.51 12.44 8.3 4.07 4.83 1.28 9.08-1.9 9.64-8.67 1.52-13.58-3.17-14.49-5.74-.65-1.83.03-3.81-.81-5.53-.86-1.77-2.62-2.47-4.48-1.88-6.1 1.94-4.16 8.61-1.46 12.28 2.89 3.93 6.44 6.3 10.43 7.6 14.89 4.85 22.05-2.81 23.3-8.42.92-4.11.82-7.67-1.8-10.97" style="fill:#6b4b46"/><path d="M71.16 48.99c-12.67 27.06-14.85 61.23-14.85 61.23" style="fill:none;stroke:#6b4b46;stroke-width:5;stroke-miterlimit:10"/><path d="M81.67 31.96c8.44 2.75 10.31 10.38 9.7 12.46-.73 2.44-10.08-7.06-23.98-6.49-4.86.2-3.45-2.78-1.2-4.5 2.97-2.27 7.96-3.91 15.48-1.47" style="fill:#6d4c41"/><path d="M81.67 31.96c8.44 2.75 10.31 10.38 9.7 12.46-.73 2.44-10.08-7.06-23.98-6.49-4.86.2-3.45-2.78-1.2-4.5 2.97-2.27 7.96-3.91 15.48-1.47" style="fill:#6b4b46"/><path d="M96.49 58.86c1.06-.73 4.62.53 5.62 7.5.49 3.41.64 6.71.64 6.71s-4.2-3.77-5.59-6.42c-1.75-3.35-2.43-6.59-.67-7.79" style="fill:#e2a610"/></svg> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="28.384802mm"
height="31.749905mm"
viewBox="0 0 28.384802 31.749905"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1"
transform="translate(-37.253301,-88.598061)">
<path
d="m 59.240389,97.978247 c 1.775354,-0.394229 4.087813,-2.156354 4.439709,-3.024187 0.206375,-0.508 -0.822855,-1.30175 -1.098021,-1.621896 -0.629709,-0.73025 -0.375709,-1.090083 -0.132292,-1.960562 0.277813,-0.989542 -0.381,-2.082271 -1.314979,-2.510896 -0.933979,-0.428625 -2.050521,-0.293688 -2.989792,0.124354 -0.939271,0.418042 -1.740958,1.090083 -2.526771,1.751542 -0.574145,-0.36248 -1.489604,-1.963209 -2.97127,-0.923396 -1.023938,0.717021 -1.116542,2.278062 -0.98425,3.52425 0.309562,2.876021 1.018645,4.368271 2.354791,4.770437 1.688042,0.508 3.556,0.240771 5.222875,-0.129646"
style="fill:#ffca28;stroke-width:0.264583"
id="path1-3" />
<path
d="m 62.124348,89.704727 c -0.224896,3.876145 -4.005792,6.447895 -5.799667,7.580312 l 1.164167,1.000125 c 0,0 0.738187,0.01588 1.748895,-0.306917 1.733021,-0.550333 4.265084,-2.106083 4.439709,-3.024187 0.256646,-1.336146 -1.113896,-1.045104 -1.423459,-2.100792 -0.161395,-0.558271 0.785813,-1.613958 -0.129645,-3.148541 m -6.503459,1.03452 c 0,0 -0.674687,-0.690562 -1.17475,-1.005416 -0.248708,0.468312 -0.425979,0.976312 -0.513291,1.500187 -0.156105,0.92075 0,2.227792 0.36777,3.201459 0.05821,0.150812 0.275167,0.127 0.29898,-0.03175 0.3175,-2.092855 1.021291,-3.66448 1.021291,-3.66448"
style="fill:#e2a610;stroke-width:0.264583"
id="path2-6" />
<path
d="m 50.906014,97.636935 c 0,0 -8.252354,0.891646 -11.975042,8.056565 -3.722687,7.16492 -0.558271,11.50937 2.791354,13.09158 3.349626,1.58221 11.789834,2.14048 17.279938,0.83873 5.490104,-1.30175 6.863292,-4.0005 6.606646,-6.60664 -0.373062,-3.80471 -3.907896,-6.14363 -3.907896,-6.14363 0,0 0.140229,-4.699 -3.505729,-7.749647 -3.235854,-2.709333 -7.289271,-1.486958 -7.289271,-1.486958"
style="fill:#ffca28;stroke-width:0.264583"
id="path3-0" />
<path
d="m 56.120952,95.996518 c 2.233083,0.727604 2.727854,2.746375 2.566458,3.296709 -0.193146,0.645583 -2.667,-1.867959 -6.344708,-1.717146 -1.285875,0.05292 -0.912813,-0.735542 -0.3175,-1.190625 0.785812,-0.600604 2.106083,-1.034521 4.09575,-0.388938"
style="fill:#6d4c41;stroke-width:0.264583"
id="path6-6" />
<path
d="m 56.120952,95.996518 c 2.233083,0.727604 2.727854,2.746375 2.566458,3.296709 -0.193146,0.645583 -2.667,-1.867959 -6.344708,-1.717146 -1.285875,0.05292 -0.912813,-0.735542 -0.3175,-1.190625 0.785812,-0.600604 2.106083,-1.034521 4.09575,-0.388938"
style="fill:#6b4b46;stroke-width:0.264583"
id="path7-2" />
<path
d="m 60.042077,103.11381 c 0.280458,-0.19314 1.222375,0.14023 1.486958,1.98438 0.129646,0.90223 0.169333,1.77535 0.169333,1.77535 0,0 -1.11125,-0.99748 -1.47902,-1.69862 -0.463021,-0.88636 -0.642938,-1.74361 -0.177271,-2.06111"
style="fill:#e2a610;stroke-width:0.264583"
id="path8-6" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:19.7556px;font-family:'Pirata One';-inkscape-font-specification:'Pirata One, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;letter-spacing:-0.529167px;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000000;stroke-width:0.264583"
x="82.355011"
y="90.66716"
id="text4-9"
transform="rotate(20.578693)"><tspan
id="tspan4-2"
style="font-size:19.7556px;fill:#4d4d4d;stroke-width:0.264583"
x="82.355011"
y="90.66716">$</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

View File

@@ -1,15 +1,18 @@
// Initialize the echarts instance based on the prepared dom // Initialize the echarts instance based on the prepared dom
async function init() { async function initMainChart() {
const element = document.getElementById('graph') const element = document.getElementById('main-chart')
if (element === null) { if (element === null) {
return; return;
} }
var myChart = echarts.init(element); var myChart = echarts.init(element);
window.addEventListener('resize', function() {
myChart.resize();
});
try { try {
const response = await fetch("/dashboard/dataset"); const response = await fetch("/dashboard/main-chart");
if (!response.ok) { if (!response.ok) {
throw new Error(`Response status: ${response.status}`); throw new Error(`Response status: ${response.status}`);
} }
@@ -22,17 +25,76 @@ async function init() {
'Sum of Savings: <span class="font-bold">' + params[1].data[1] + '</span> €' 'Sum of Savings: <span class="font-bold">' + params[1].data[1] + '</span> €'
}; };
const chart = myChart.setOption(option); myChart.setOption(option);
console.log("initialized main-chart");
} catch (error) {
console.error(error.message);
}
}
async function initTreasureChests() {
const element = document.getElementById('treasure-chests')
if (element === null) {
return;
}
var myChart = echarts.init(element);
window.addEventListener('resize', function() { window.addEventListener('resize', function() {
myChart.resize(); myChart.resize();
}); });
console.log("initialized charts"); try {
const response = await fetch("/dashboard/treasure-chests");
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
const option = await response.json();
myChart.setOption(option);
console.log("initialized treasure-chests");
} catch (error) { } catch (error) {
console.error(error.message); console.error(error.message);
} }
// Display the chart using the configuration items and data just specified.
} }
init(); async function initTreasureChest() {
const element = document.getElementById('treasure-chest')
if (element === null) {
return;
}
var myChart = echarts.init(element);
window.addEventListener('resize', function() {
myChart.resize();
});
const treasureChestSelect = document.getElementById('treasure-chest-id')
treasureChestSelect.addEventListener("change", async (e) => {
try {
const response = await fetch("/dashboard/treasure-chest?id="+e.target.value);
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
const option = await response.json();
option.tooltip.formatter = function (params) {
return new Date(params[0].data[0]).toLocaleString([], { day: 'numeric', month: 'short', year: 'numeric' }) +
'<br />' +
'Sum of Accounts: <span class="font-bold">' + params[0].data[1] + '</span> €'
};
myChart.setOption(option);
} catch (error) {
console.error(error.message);
}
});
console.log("initialized treasure-chest");
}
initMainChart();
initTreasureChests();
initTreasureChest();

11
static/js/layout.js Normal file
View File

@@ -0,0 +1,11 @@
document.addEventListener("DOMContentLoaded", () => {
menuButton.addEventListener("click", function (e) {
menu.showModal();
});
menuButtonClose.addEventListener("click", function (e) {
menu.close();
});
})

43
static/js/transaction.js Normal file
View File

@@ -0,0 +1,43 @@
// document.addEventListener("DOMContentLoaded", () => {
// if (!page || !page1 || !pagePrev1 || !pageNext1 || !page2 || !pagePrev2 || !pageNext2 || !transactionFilterForm) {
// return;
// }
//
//
// const scrollToTop = function() {
// window.scrollTo(0, 0);
// };
// const incPage = function() {
// const currPage = Number(page.value);
// var nextPage = currPage
// if (currPage > 1) {
// nextPage -= 1;
// page.value = nextPage;
// transactionFilterForm.dispatchEvent(new Event('change'));
// }
// page1.textContent = nextPage;
// page2.textContent = nextPage;
// scrollToTop();
// };
// const decPage = function() {
// const currPage = Number(page.value);
// var nextPage = currPage + 1;
// page.value = nextPage;
// transactionFilterForm.dispatchEvent(new Event('change'));
// page1.textContent = nextPage;
// page2.textContent = nextPage;
// scrollToTop();
// };
//
//
//
// pagePrev1.addEventListener("click", incPage);
// pagePrev2.addEventListener("click", incPage);
//
// pageNext1.addEventListener("click", decPage);
// pageNext2.addEventListener("click", decPage);
//
// console.log("initialized pagination");
// })

71
static/logo.svg Normal file
View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="96.820343mm"
height="31.749899mm"
viewBox="0 0 96.820343 31.749899"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="g7"
transform="translate(-38.090175,-77.467441)">
<g
id="g8"
transform="translate(-38.797122,-28.178962)">
<path
d="m 98.874384,115.02659 c 1.775356,-0.39423 4.087816,-2.15635 4.439706,-3.02419 0.20638,-0.508 -0.82285,-1.30175 -1.09802,-1.62189 -0.62971,-0.73025 -0.37571,-1.09009 -0.13229,-1.96057 0.27781,-0.98954 -0.381,-2.08227 -1.31498,-2.51089 -0.933978,-0.42863 -2.05052,-0.29369 -2.989791,0.12435 -0.939271,0.41804 -1.740958,1.09009 -2.526771,1.75154 -0.574145,-0.36248 -1.489604,-1.96321 -2.97127,-0.92339 -1.023938,0.71702 -1.116542,2.27806 -0.98425,3.52425 0.309562,2.87602 1.018645,4.36827 2.354791,4.77043 1.688042,0.508 3.556,0.24078 5.222875,-0.12964"
style="fill:#ffca28;stroke-width:0.264583"
id="path1-3-3" />
<path
d="m 101.75834,106.75307 c -0.22489,3.87614 -4.005789,6.44789 -5.799664,7.58031 l 1.164167,1.00013 c 0,0 0.738187,0.0159 1.748895,-0.30692 1.733022,-0.55033 4.265082,-2.10608 4.439712,-3.02419 0.25664,-1.33614 -1.1139,-1.0451 -1.42346,-2.10079 -0.1614,-0.55827 0.78581,-1.61396 -0.12965,-3.14854 m -6.503456,1.03452 c 0,0 -0.674687,-0.69056 -1.17475,-1.00542 -0.248708,0.46832 -0.425979,0.97632 -0.513291,1.50019 -0.156105,0.92075 0,2.22779 0.36777,3.20146 0.05821,0.15081 0.275167,0.127 0.29898,-0.0317 0.3175,-2.09286 1.021291,-3.66448 1.021291,-3.66448"
style="fill:#e2a610;stroke-width:0.264583"
id="path2-6-6" />
<path
d="m 90.540009,114.68528 c 0,0 -8.252354,0.89164 -11.975042,8.05656 -3.722687,7.16492 -0.558271,11.50937 2.791354,13.09158 3.349626,1.58221 11.789834,2.14048 17.279938,0.83873 5.490101,-1.30175 6.863291,-4.0005 6.606641,-6.60664 -0.37306,-3.80471 -3.90789,-6.14363 -3.90789,-6.14363 0,0 0.14023,-4.699 -3.50573,-7.74964 -3.235854,-2.70934 -7.289271,-1.48696 -7.289271,-1.48696"
style="fill:#ffca28;stroke-width:0.264583"
id="path3-0-1" />
<path
d="m 95.754947,113.04486 c 2.233083,0.7276 2.727854,2.74638 2.566458,3.29671 -0.193146,0.64558 -2.667,-1.86796 -6.344708,-1.71715 -1.285875,0.0529 -0.912813,-0.73554 -0.3175,-1.19062 0.785812,-0.60061 2.106083,-1.03452 4.09575,-0.38894"
style="fill:#6d4c41;stroke-width:0.264583"
id="path6-6-2" />
<path
d="m 95.754947,113.04486 c 2.233083,0.7276 2.727854,2.74638 2.566458,3.29671 -0.193146,0.64558 -2.667,-1.86796 -6.344708,-1.71715 -1.285875,0.0529 -0.912813,-0.73554 -0.3175,-1.19062 0.785812,-0.60061 2.106083,-1.03452 4.09575,-0.38894"
style="fill:#6b4b46;stroke-width:0.264583"
id="path7-2-9" />
<path
d="m 99.676072,120.16215 c 0.280458,-0.19314 1.222378,0.14023 1.486958,1.98438 0.12965,0.90223 0.16933,1.77535 0.16933,1.77535 0,0 -1.11125,-0.99748 -1.479017,-1.69862 -0.463021,-0.88636 -0.642938,-1.74361 -0.177271,-2.06111"
style="fill:#e2a610;stroke-width:0.264583"
id="path8-6-3" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:19.7556px;font-family:'Pirata One';-inkscape-font-specification:'Pirata One, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;letter-spacing:-0.529167px;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000000;stroke-width:0.264583"
x="125.45235"
y="92.696564"
id="text4-9-1"
transform="rotate(20.578693)"><tspan
id="tspan4-2-9"
style="font-size:19.7556px;fill:#4d4d4d;stroke-width:0.264583"
x="125.45235"
y="92.696564">$</tspan></text>
</g>
<g
id="layer2"
transform="translate(-1.4293676,48.496402)">
<text
xml:space="preserve"
style="font-size:17.6389px;text-align:start;letter-spacing:-0.529167px;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#000000;stroke-width:0.264583"
x="57.635151"
y="55.655094"
id="text1"><tspan
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17.6389px;font-family:'Pirata One';-inkscape-font-specification:'Pirata One, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:-0.529167px;fill:#4d4d4d;stroke:none;stroke-width:0.264583"
x="57.635151"
y="55.655094">pendSparrow</tspan></text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -756,7 +756,7 @@ func TestIntegrationAuth(t *testing.T) {
VALUES (?, ?, datetime(), datetime("now", "+1 day"))`, sessionId, userId) VALUES (?, ?, datetime(), datetime("now", "+1 day"))`, sessionId, userId)
require.NoError(t, err) require.NoError(t, err)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, basePath+"/", nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, basePath+"/dashboard", nil)
require.NoError(t, err) require.NoError(t, err)
req.Header.Set("Cookie", "id="+sessionId) req.Header.Set("Cookie", "id="+sessionId)
resp, err := httpClient.Do(req) resp, err := httpClient.Do(req)
@@ -1862,7 +1862,6 @@ func TestIntegrationAccount(t *testing.T) {
">": 400, ">": 400,
"/": 400, "/": 400,
"\\": 400, "\\": 400,
"?": 400,
":": 400, ":": 400,
"*": 400, "*": 400,
"|": 400, "|": 400,