├── .github
├── dependabot.yml
└── workflows
│ └── release.yaml
├── .gitignore
├── .releaserc.json
├── LICENSE
├── README.md
├── api
├── Dockerfile
├── docs
│ ├── docs.go
│ ├── swagger.json
│ └── swagger.yaml
├── go.mod
├── go.sum
└── main.go
├── doc
├── default-icon.svg
├── screenshot_1.png
├── screenshot_2.png
├── screenshot_3.png
└── screenshot_4.png
├── docker-compose-build.yaml
├── docker-compose.yaml
└── ui
├── .gitignore
├── .npmrc
├── 01-sub.sh
├── Dockerfile
├── package.json
├── pnpm-lock.yaml
├── src
├── app.css
├── app.d.ts
├── app.html
├── icons
│ └── fox.svg
├── lib
│ ├── stores.ts
│ └── types.ts
└── routes
│ ├── +layout.svelte
│ ├── +layout.ts
│ ├── +page.svelte
│ └── layout.svelte.ts
├── static
├── favicon.png
├── icons
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ └── apple-touch-icon.png
└── manifest.webmanifest
├── svelte.config.js
├── tsconfig.json
└── vite.config.ts
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "gomod"
4 | directory: "/api"
5 | commit-message:
6 | prefix: "build: "
7 | schedule:
8 | interval: "weekly"
9 | - package-ecosystem: "docker"
10 | directory: "/api"
11 | commit-message:
12 | prefix: "build: "
13 | schedule:
14 | interval: "weekly"
15 | - package-ecosystem: "npm"
16 | directory: "/ui"
17 | commit-message:
18 | prefix: "build: "
19 | schedule:
20 | interval: "weekly"
21 | - package-ecosystem: "docker"
22 | directory: "/ui"
23 | commit-message:
24 | prefix: "build: "
25 | schedule:
26 | interval: "weekly"
27 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | push:
4 | branches:
5 | - main
6 |
7 | jobs:
8 | release:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | contents: write
12 | issues: write
13 | pull-requests: write
14 | id-token: write
15 | outputs:
16 | VERSION_NUMBER: ${{ steps.output.outputs.VERSION_NUMBER }}
17 | CI_CREATE_IMAGE: ${{ steps.output.outputs.CI_CREATE_IMAGE }}
18 | steps:
19 | - uses: actions/checkout@v4
20 | - name: Install pnpm
21 | uses: pnpm/action-setup@v4
22 | with:
23 | version: 10
24 | - name: Use Node.js 20
25 | uses: actions/setup-node@v4
26 | with:
27 | node-version: 20
28 | - name: Run semantic-release
29 | env:
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 | run: pnpm --package semantic-release@23 --package @semantic-release/exec --package @semantic-release/changelog --package conventional-changelog-conventionalcommits@7.0.2 dlx semantic-release
32 | - name: Generate output
33 | id: output
34 | run: test -f "release.env" && cat release.env >> "$GITHUB_OUTPUT" || exit 0
35 | docker:
36 | runs-on: ubuntu-latest
37 | needs: release
38 | if: ${{ needs.release.outputs.CI_CREATE_IMAGE == 'true' }}
39 | steps:
40 | - name: Set QEMU
41 | uses: docker/setup-qemu-action@v3
42 | - name: Set Buildx
43 | uses: docker/setup-buildx-action@v3
44 | - name: Login to Docker Hub
45 | uses: docker/login-action@v3
46 | with:
47 | username: ${{ vars.DOCKERHUB_USERNAME }}
48 | password: ${{ secrets.DOCKERHUB_TOKEN }}
49 | - uses: actions/checkout@v4
50 | - name: Build and push UI
51 | uses: docker/build-push-action@v6
52 | with:
53 | context: ./ui
54 | push: true
55 | platforms: linux/amd64,linux/arm64
56 | tags: "${{ vars.DOCKERHUB_USERNAME }}/thrifty-ui:latest, ${{ vars.DOCKERHUB_USERNAME }}/thrifty-ui:${{ needs.release.outputs.VERSION_NUMBER }}"
57 | - name: Build and push API
58 | uses: docker/build-push-action@v6
59 | with:
60 | context: ./api
61 | push: true
62 | platforms: linux/amd64,linux/arm64
63 | tags: "${{ vars.DOCKERHUB_USERNAME }}/thrifty-api:latest, ${{ vars.DOCKERHUB_USERNAME }}/thrifty-api:${{ needs.release.outputs.VERSION_NUMBER }}"
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .pnpm-store
3 | .scannerwork
4 | sonar-project.properties
5 | data
6 | **/.dockerignore
7 | **.sqlite
8 | **/.DS_Store
9 |
--------------------------------------------------------------------------------
/.releaserc.json:
--------------------------------------------------------------------------------
1 | {
2 | "branches": [
3 | "main"
4 | ],
5 | "plugins": [
6 | [
7 | "@semantic-release/commit-analyzer",
8 | {
9 | "preset": "conventionalcommits"
10 | }
11 | ],
12 | [
13 | "@semantic-release/release-notes-generator",
14 | {
15 | "preset": "conventionalcommits",
16 | "presetConfig": {
17 | "types": [
18 | {
19 | "type": "feat",
20 | "section": "✨ Features ✨",
21 | "hidden": false
22 | },
23 | {
24 | "type": "fix",
25 | "section": "🐞 Bug fixes 🐞",
26 | "hidden": false
27 | },
28 | {
29 | "type": "perf",
30 | "section": "\uD83D\uDE80 Performance improvements \uD83D\uDE80",
31 | "hidden": false
32 | },
33 | {
34 | "type": "ci",
35 | "section": "\uD83D\uDEA7 CI changes \uD83D\uDEA7",
36 | "hidden": false
37 | },
38 | {
39 | "type": "build",
40 | "section": "\uD83C\uDFD7️ Build tool changes \uD83C\uDFD7️",
41 | "hidden": false
42 | },
43 | {
44 | "type": "docs",
45 | "section": "\uD83D\uDDCE Documentation \uD83D\uDDCE",
46 | "hidden": false
47 | },
48 | {
49 | "type": "refactor",
50 | "section": "\uD83D\uDD27 Refactoring \uD83D\uDD27",
51 | "hidden": false
52 | },
53 | {
54 | "type": "test",
55 | "section": "\uD83D\uDC4C Test cases \uD83D\uDC4C",
56 | "hidden": false
57 | },
58 | {
59 | "type": "chore",
60 | "section": "ℹ\uFE0F Uncategorized changes ℹ\uFE0F",
61 | "hidden": false
62 | },
63 | {
64 | "type": "revert",
65 | "section": "\uD83D\uDCA3 Reverts \uD83D\uDCA3",
66 | "hidden": false
67 | }
68 | ]
69 | }
70 | }
71 | ],
72 | "@semantic-release/changelog",
73 | [
74 | "@semantic-release/github",
75 | {
76 | "assets": [
77 | {
78 | "path": "CHANGELOG.md",
79 | "label": "Changes"
80 | }
81 | ]
82 | }
83 | ],
84 | [
85 | "@semantic-release/exec",
86 | {
87 | "analyzeCommitsCmd": "echo \"VERSION_NUMBER=${lastRelease.version}\" > release.env; echo \"CI_CREATE_IMAGE=false\" >> release.env",
88 | "generateNotesCmd": "echo \"VERSION_NUMBER=${nextRelease.version}\" > release.env; echo \"CI_CREATE_IMAGE=true\" >> release.env"
89 | }
90 | ]
91 | ]
92 | }
93 |
94 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 2-Clause License
2 |
3 | Copyright (c) 2025, tiehfood
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | # Thrifty
20 |
21 | Please be gracious with me, this is my first ever app 🙈. Thrifty is a simple web application that helps you manage your income and expenses.
22 | It's focused on simplicity and is not aimed to track every single penny you spend.
23 | The idea is to get a rough overview of your monthly cash flow and what's left to spend.
24 |
25 | Features:
26 | - Add income and expenses
27 | - Edit existing entries
28 | - Delete entries
29 | - Support for SVG icons (default:
)
30 | - Two rows for income and expenses (collapse into single one on smaller devices)
31 | - API documentation at `/swagger/index.html`
32 | - Currency configurable via environment variable
33 |
34 | Frameworks used:
35 |
36 | - __UI__: SvelteKit and Flowbite
37 | - __API__: golang
38 |
39 | ## Screenshots
40 | |  |  |
41 | |:------------------------------------:|:------------------------------------:|
42 | |  |  |
43 |
44 | ## Running the app
45 |
46 | Every release since 1.2.0 should create a docker image on DockerHub.
47 | Use [docker-compose.yaml](docker-compose.yaml) to run the app with the latest version.
48 |
49 | ```bash
50 | # run the app
51 | docker compose -f docker-compose.yaml up -d
52 | ```
53 | The app should now be available at `http://localhost:9090`.
54 |
55 | If you want to build the image yourself, you can use the following commands.
56 | ```bash
57 | # run the app
58 | docker compose -f docker-compose-build.yaml build
59 |
60 | # run the app
61 | docker compose -f docker-compose-build.yaml up -d
62 | ```
63 |
64 | ### Tips
65 | #### Want to use `$` as the currency symbol?
66 | >Set `CURRENCY_ISO=USD` for the UI in the docker compose file and restart the UI container.
67 | #### How about other currencies?
68 | > The step above should apply for any other currency ISO-code in this list: [ISO 4217](https://de.wikipedia.org/wiki/ISO_4217)
69 | #### Where is the data stored?
70 | > The data is stored in a SQLite database in a docker volume. You can also use a custom path in the [docker-compose.yaml](docker-compose.yaml) to expose the database.
71 | #### I need a docker image for a different architecture.
72 | > Currently amd64 and arm64 builds are created. For other architectures you can build the image yourself like described above.
73 | #### Want to use the app without a reverse proxy?
74 | > The default internal port for both containers is port 8080.
75 | > If you want to use docker compose and just want to change the external port of the API container, you could add the environment variable `LOCAL_API_PORT=8080` to the UI container.
76 | > Change it to the desired external port of your docker compose file.
77 |
78 | > If you want to use the containers for example in host network mode, you could also change the port the API is listening on.
79 | > Set the variable `PORT=8081` on the API container to change the internal port to 8081.
80 | > Depending on your setup you might need to set `LOCAL_API_PORT` to the same value.
81 |
82 | > You can also set `LOACL_API_HOSTNAME` and `LOCAL_API_PROTOCOL` if needed.
83 | #### Want to have a single column all the time?
84 | > Set the variable `USE_SINGLE_COLUMN=true` in the UI container to always use a single column layout.
85 |
86 | ## Developing
87 |
88 | ### Frontend
89 | Install node and node modules.
90 | Running locally requires you to change the API URL in [+page.svelte](ui/src/routes/+page.svelte).
91 | Change `currentProtocol`, `currentHostname` and `currentPort` in the _onMount_ method to where your API development is running on.
92 | ```bash
93 | cd ui
94 |
95 | # install dependencies
96 | pnpm i
97 |
98 | # run ui
99 | pnpm dev
100 | ```
101 | You could also use regular npm instead of pnpm.
102 | ### Backend
103 | Install golang and run the following commands.
104 | ```bash
105 | cd api
106 |
107 | # install dependencies
108 | go get .
109 |
110 | # install swag (optional)
111 | go install github.com/swaggo/swag/cmd/swag@latest
112 |
113 | # generate swagger documentation (optional)
114 | swag init
115 |
116 | # run api
117 | go run .
118 | ```
119 |
120 | ## Credits
121 | - [NumberFlow](https://number-flow.barvian.me/svelte)
122 | - [Swag](https://github.com/swaggo/swag)
--------------------------------------------------------------------------------
/api/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.23.6-alpine AS builder
2 |
3 | COPY . /app
4 | WORKDIR /app
5 |
6 | ENV GOOS=linux
7 | RUN go get .; \
8 | go install github.com/swaggo/swag/cmd/swag@latest; \
9 | swag init; \
10 | go build -o thrifty .; \
11 | chmod +x thrifty;
12 |
13 | FROM scratch
14 | COPY --from=builder /app/thrifty /thrifty
15 | ENTRYPOINT ["/thrifty"]
16 |
--------------------------------------------------------------------------------
/api/docs/docs.go:
--------------------------------------------------------------------------------
1 | // Package docs Code generated by swaggo/swag. DO NOT EDIT
2 | package docs
3 |
4 | import "github.com/swaggo/swag"
5 |
6 | const docTemplate = `{
7 | "schemes": {{ marshal .Schemes }},
8 | "swagger": "2.0",
9 | "info": {
10 | "description": "{{escape .Description}}",
11 | "title": "{{.Title}}",
12 | "contact": {},
13 | "version": "{{.Version}}"
14 | },
15 | "host": "{{.Host}}",
16 | "basePath": "{{.BasePath}}",
17 | "paths": {
18 | "/flows": {
19 | "get": {
20 | "description": "Get all flows in a JSON object",
21 | "produces": [
22 | "application/json"
23 | ],
24 | "tags": [
25 | "Flows"
26 | ],
27 | "summary": "Get all flows",
28 | "responses": {
29 | "200": {
30 | "description": "OK",
31 | "schema": {
32 | "type": "array",
33 | "items": {
34 | "$ref": "#/definitions/main.Flow"
35 | }
36 | }
37 | },
38 | "500": {
39 | "description": "Internal Server Error",
40 | "schema": {
41 | "$ref": "#/definitions/main.HTTPError"
42 | }
43 | }
44 | }
45 | },
46 | "post": {
47 | "description": "Get all flows in a JSON object",
48 | "produces": [
49 | "application/json"
50 | ],
51 | "tags": [
52 | "Flows"
53 | ],
54 | "summary": "Add new flow",
55 | "parameters": [
56 | {
57 | "description": "Flow object, id is set by the server and could be omitted",
58 | "name": "request",
59 | "in": "body",
60 | "required": true,
61 | "schema": {
62 | "$ref": "#/definitions/main.Flow"
63 | }
64 | }
65 | ],
66 | "responses": {
67 | "200": {
68 | "description": "OK",
69 | "schema": {
70 | "$ref": "#/definitions/main.Flow"
71 | }
72 | },
73 | "500": {
74 | "description": "Internal Server Error",
75 | "schema": {
76 | "$ref": "#/definitions/main.HTTPError"
77 | }
78 | }
79 | }
80 | }
81 | },
82 | "/flows/{id}": {
83 | "delete": {
84 | "description": "Delete an existing flow",
85 | "produces": [
86 | "application/json"
87 | ],
88 | "tags": [
89 | "Flows"
90 | ],
91 | "summary": "Delete a flow",
92 | "parameters": [
93 | {
94 | "type": "integer",
95 | "description": "Flow id",
96 | "name": "id",
97 | "in": "path",
98 | "required": true
99 | }
100 | ],
101 | "responses": {
102 | "200": {
103 | "description": "OK",
104 | "schema": {
105 | "$ref": "#/definitions/main.HTTPResponse"
106 | }
107 | },
108 | "500": {
109 | "description": "Internal Server Error",
110 | "schema": {
111 | "$ref": "#/definitions/main.HTTPError"
112 | }
113 | }
114 | }
115 | },
116 | "patch": {
117 | "description": "Update an existing flow with new data",
118 | "produces": [
119 | "application/json"
120 | ],
121 | "tags": [
122 | "Flows"
123 | ],
124 | "summary": "Update existing flow",
125 | "parameters": [
126 | {
127 | "type": "integer",
128 | "description": "Flow id",
129 | "name": "id",
130 | "in": "path",
131 | "required": true
132 | },
133 | {
134 | "description": "Flow object, id is ignored and could be omitted",
135 | "name": "request",
136 | "in": "body",
137 | "required": true,
138 | "schema": {
139 | "$ref": "#/definitions/main.Flow"
140 | }
141 | }
142 | ],
143 | "responses": {
144 | "200": {
145 | "description": "OK",
146 | "schema": {
147 | "$ref": "#/definitions/main.Flow"
148 | }
149 | },
150 | "500": {
151 | "description": "Internal Server Error",
152 | "schema": {
153 | "$ref": "#/definitions/main.HTTPError"
154 | }
155 | }
156 | }
157 | }
158 | }
159 | },
160 | "definitions": {
161 | "main.Flow": {
162 | "type": "object",
163 | "properties": {
164 | "amount": {
165 | "type": "number"
166 | },
167 | "description": {
168 | "type": "string"
169 | },
170 | "icon": {
171 | "type": "string"
172 | },
173 | "id": {
174 | "type": "string"
175 | },
176 | "name": {
177 | "type": "string"
178 | }
179 | }
180 | },
181 | "main.HTTPError": {
182 | "type": "object",
183 | "properties": {
184 | "error": {
185 | "type": "string"
186 | }
187 | }
188 | },
189 | "main.HTTPResponse": {
190 | "type": "object",
191 | "properties": {
192 | "ok": {
193 | "type": "string"
194 | }
195 | }
196 | }
197 | }
198 | }`
199 |
200 | // SwaggerInfo holds exported Swagger Info so clients can modify it
201 | var SwaggerInfo = &swag.Spec{
202 | Version: "",
203 | Host: "",
204 | BasePath: "",
205 | Schemes: []string{},
206 | Title: "",
207 | Description: "",
208 | InfoInstanceName: "swagger",
209 | SwaggerTemplate: docTemplate,
210 | LeftDelim: "{{",
211 | RightDelim: "}}",
212 | }
213 |
214 | func init() {
215 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
216 | }
217 |
--------------------------------------------------------------------------------
/api/docs/swagger.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "info": {
4 | "contact": {}
5 | },
6 | "paths": {
7 | "/flows": {
8 | "get": {
9 | "description": "Get all flows in a JSON object",
10 | "produces": [
11 | "application/json"
12 | ],
13 | "tags": [
14 | "Flows"
15 | ],
16 | "summary": "Get all flows",
17 | "responses": {
18 | "200": {
19 | "description": "OK",
20 | "schema": {
21 | "type": "array",
22 | "items": {
23 | "$ref": "#/definitions/main.Flow"
24 | }
25 | }
26 | },
27 | "500": {
28 | "description": "Internal Server Error",
29 | "schema": {
30 | "$ref": "#/definitions/main.HTTPError"
31 | }
32 | }
33 | }
34 | },
35 | "post": {
36 | "description": "Get all flows in a JSON object",
37 | "produces": [
38 | "application/json"
39 | ],
40 | "tags": [
41 | "Flows"
42 | ],
43 | "summary": "Add new flow",
44 | "parameters": [
45 | {
46 | "description": "Flow object, id is set by the server and could be omitted",
47 | "name": "request",
48 | "in": "body",
49 | "required": true,
50 | "schema": {
51 | "$ref": "#/definitions/main.Flow"
52 | }
53 | }
54 | ],
55 | "responses": {
56 | "200": {
57 | "description": "OK",
58 | "schema": {
59 | "$ref": "#/definitions/main.Flow"
60 | }
61 | },
62 | "500": {
63 | "description": "Internal Server Error",
64 | "schema": {
65 | "$ref": "#/definitions/main.HTTPError"
66 | }
67 | }
68 | }
69 | }
70 | },
71 | "/flows/{id}": {
72 | "delete": {
73 | "description": "Delete an existing flow",
74 | "produces": [
75 | "application/json"
76 | ],
77 | "tags": [
78 | "Flows"
79 | ],
80 | "summary": "Delete a flow",
81 | "parameters": [
82 | {
83 | "type": "integer",
84 | "description": "Flow id",
85 | "name": "id",
86 | "in": "path",
87 | "required": true
88 | }
89 | ],
90 | "responses": {
91 | "200": {
92 | "description": "OK",
93 | "schema": {
94 | "$ref": "#/definitions/main.HTTPResponse"
95 | }
96 | },
97 | "500": {
98 | "description": "Internal Server Error",
99 | "schema": {
100 | "$ref": "#/definitions/main.HTTPError"
101 | }
102 | }
103 | }
104 | },
105 | "patch": {
106 | "description": "Update an existing flow with new data",
107 | "produces": [
108 | "application/json"
109 | ],
110 | "tags": [
111 | "Flows"
112 | ],
113 | "summary": "Update existing flow",
114 | "parameters": [
115 | {
116 | "type": "integer",
117 | "description": "Flow id",
118 | "name": "id",
119 | "in": "path",
120 | "required": true
121 | },
122 | {
123 | "description": "Flow object, id is ignored and could be omitted",
124 | "name": "request",
125 | "in": "body",
126 | "required": true,
127 | "schema": {
128 | "$ref": "#/definitions/main.Flow"
129 | }
130 | }
131 | ],
132 | "responses": {
133 | "200": {
134 | "description": "OK",
135 | "schema": {
136 | "$ref": "#/definitions/main.Flow"
137 | }
138 | },
139 | "500": {
140 | "description": "Internal Server Error",
141 | "schema": {
142 | "$ref": "#/definitions/main.HTTPError"
143 | }
144 | }
145 | }
146 | }
147 | }
148 | },
149 | "definitions": {
150 | "main.Flow": {
151 | "type": "object",
152 | "properties": {
153 | "amount": {
154 | "type": "number"
155 | },
156 | "description": {
157 | "type": "string"
158 | },
159 | "icon": {
160 | "type": "string"
161 | },
162 | "id": {
163 | "type": "string"
164 | },
165 | "name": {
166 | "type": "string"
167 | }
168 | }
169 | },
170 | "main.HTTPError": {
171 | "type": "object",
172 | "properties": {
173 | "error": {
174 | "type": "string"
175 | }
176 | }
177 | },
178 | "main.HTTPResponse": {
179 | "type": "object",
180 | "properties": {
181 | "ok": {
182 | "type": "string"
183 | }
184 | }
185 | }
186 | }
187 | }
--------------------------------------------------------------------------------
/api/docs/swagger.yaml:
--------------------------------------------------------------------------------
1 | definitions:
2 | main.Flow:
3 | properties:
4 | amount:
5 | type: number
6 | description:
7 | type: string
8 | icon:
9 | type: string
10 | id:
11 | type: string
12 | name:
13 | type: string
14 | type: object
15 | main.HTTPError:
16 | properties:
17 | error:
18 | type: string
19 | type: object
20 | main.HTTPResponse:
21 | properties:
22 | ok:
23 | type: string
24 | type: object
25 | info:
26 | contact: {}
27 | paths:
28 | /flows:
29 | get:
30 | description: Get all flows in a JSON object
31 | produces:
32 | - application/json
33 | responses:
34 | "200":
35 | description: OK
36 | schema:
37 | items:
38 | $ref: '#/definitions/main.Flow'
39 | type: array
40 | "500":
41 | description: Internal Server Error
42 | schema:
43 | $ref: '#/definitions/main.HTTPError'
44 | summary: Get all flows
45 | tags:
46 | - Flows
47 | post:
48 | description: Get all flows in a JSON object
49 | parameters:
50 | - description: Flow object, id is set by the server and could be omitted
51 | in: body
52 | name: request
53 | required: true
54 | schema:
55 | $ref: '#/definitions/main.Flow'
56 | produces:
57 | - application/json
58 | responses:
59 | "200":
60 | description: OK
61 | schema:
62 | $ref: '#/definitions/main.Flow'
63 | "500":
64 | description: Internal Server Error
65 | schema:
66 | $ref: '#/definitions/main.HTTPError'
67 | summary: Add new flow
68 | tags:
69 | - Flows
70 | /flows/{id}:
71 | delete:
72 | description: Delete an existing flow
73 | parameters:
74 | - description: Flow id
75 | in: path
76 | name: id
77 | required: true
78 | type: integer
79 | produces:
80 | - application/json
81 | responses:
82 | "200":
83 | description: OK
84 | schema:
85 | $ref: '#/definitions/main.HTTPResponse'
86 | "500":
87 | description: Internal Server Error
88 | schema:
89 | $ref: '#/definitions/main.HTTPError'
90 | summary: Delete a flow
91 | tags:
92 | - Flows
93 | patch:
94 | description: Update an existing flow with new data
95 | parameters:
96 | - description: Flow id
97 | in: path
98 | name: id
99 | required: true
100 | type: integer
101 | - description: Flow object, id is ignored and could be omitted
102 | in: body
103 | name: request
104 | required: true
105 | schema:
106 | $ref: '#/definitions/main.Flow'
107 | produces:
108 | - application/json
109 | responses:
110 | "200":
111 | description: OK
112 | schema:
113 | $ref: '#/definitions/main.Flow'
114 | "500":
115 | description: Internal Server Error
116 | schema:
117 | $ref: '#/definitions/main.HTTPError'
118 | summary: Update existing flow
119 | tags:
120 | - Flows
121 | swagger: "2.0"
122 |
--------------------------------------------------------------------------------
/api/go.mod:
--------------------------------------------------------------------------------
1 | module tiehfood/thrifty
2 |
3 | go 1.23.6
4 |
5 | require (
6 | github.com/gin-contrib/cors v1.7.3
7 | github.com/gin-gonic/gin v1.10.0
8 | github.com/google/uuid v1.6.0
9 | github.com/swaggo/files v1.0.1
10 | github.com/swaggo/gin-swagger v1.6.0
11 | github.com/swaggo/swag v1.16.4
12 | modernc.org/sqlite v1.35.0
13 | )
14 |
15 | require (
16 | github.com/KyleBanks/depth v1.2.1 // indirect
17 | github.com/PuerkitoBio/purell v1.1.1 // indirect
18 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
19 | github.com/bytedance/sonic v1.12.6 // indirect
20 | github.com/bytedance/sonic/loader v0.2.1 // indirect
21 | github.com/cloudwego/base64x v0.1.4 // indirect
22 | github.com/cloudwego/iasm v0.2.0 // indirect
23 | github.com/dustin/go-humanize v1.0.1 // indirect
24 | github.com/gabriel-vasile/mimetype v1.4.7 // indirect
25 | github.com/gin-contrib/sse v0.1.0 // indirect
26 | github.com/go-openapi/jsonpointer v0.19.5 // indirect
27 | github.com/go-openapi/jsonreference v0.19.6 // indirect
28 | github.com/go-openapi/spec v0.20.4 // indirect
29 | github.com/go-openapi/swag v0.19.15 // indirect
30 | github.com/go-playground/locales v0.14.1 // indirect
31 | github.com/go-playground/universal-translator v0.18.1 // indirect
32 | github.com/go-playground/validator/v10 v10.23.0 // indirect
33 | github.com/goccy/go-json v0.10.4 // indirect
34 | github.com/josharian/intern v1.0.0 // indirect
35 | github.com/json-iterator/go v1.1.12 // indirect
36 | github.com/klauspost/cpuid/v2 v2.2.9 // indirect
37 | github.com/leodido/go-urn v1.4.0 // indirect
38 | github.com/mailru/easyjson v0.7.6 // indirect
39 | github.com/mattn/go-isatty v0.0.20 // indirect
40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
41 | github.com/modern-go/reflect2 v1.0.2 // indirect
42 | github.com/ncruces/go-strftime v0.1.9 // indirect
43 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect
44 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
45 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
46 | github.com/ugorji/go/codec v1.2.12 // indirect
47 | golang.org/x/arch v0.12.0 // indirect
48 | golang.org/x/crypto v0.31.0 // indirect
49 | golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect
50 | golang.org/x/net v0.33.0 // indirect
51 | golang.org/x/sys v0.28.0 // indirect
52 | golang.org/x/text v0.21.0 // indirect
53 | golang.org/x/tools v0.23.0 // indirect
54 | google.golang.org/protobuf v1.36.1 // indirect
55 | gopkg.in/yaml.v2 v2.4.0 // indirect
56 | gopkg.in/yaml.v3 v3.0.1 // indirect
57 | modernc.org/libc v1.61.13 // indirect
58 | modernc.org/mathutil v1.7.1 // indirect
59 | modernc.org/memory v1.8.2 // indirect
60 | )
61 |
--------------------------------------------------------------------------------
/api/go.sum:
--------------------------------------------------------------------------------
1 | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
2 | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
3 | github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
4 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
5 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
6 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
7 | github.com/bytedance/sonic v1.12.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk=
8 | github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
9 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
10 | github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
11 | github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
12 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
13 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
14 | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
15 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
16 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
20 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
21 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
22 | github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
23 | github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
24 | github.com/gin-contrib/cors v1.7.3 h1:hV+a5xp8hwJoTw7OY+a70FsL8JkVVFTXw9EcfrYUdns=
25 | github.com/gin-contrib/cors v1.7.3/go.mod h1:M3bcKZhxzsvI+rlRSkkxHyljJt1ESd93COUvemZ79j4=
26 | github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
27 | github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
28 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
29 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
30 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
31 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
32 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
33 | github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
34 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
35 | github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
36 | github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
37 | github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
38 | github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
39 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
40 | github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
41 | github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
42 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
43 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
44 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
45 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
46 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
47 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
48 | github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
49 | github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
50 | github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
51 | github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
52 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
53 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
54 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
55 | github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
56 | github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
57 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
58 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
59 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
60 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
61 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
62 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
63 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
64 | github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
65 | github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
66 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
67 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
68 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
69 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
70 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
71 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
72 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
73 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
74 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
75 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
76 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
77 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
78 | github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
79 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
80 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
81 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
82 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
83 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
84 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
85 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
86 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
87 | github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
88 | github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
89 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
90 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
91 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
92 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
93 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
94 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
95 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
96 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
97 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
98 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
99 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
100 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
101 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
102 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
103 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
104 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
105 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
106 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
107 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
108 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
109 | github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
110 | github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
111 | github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
112 | github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
113 | github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
114 | github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
115 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
116 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
117 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
118 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
119 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
120 | golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
121 | golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
122 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
123 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
124 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
125 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
126 | golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
127 | golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
128 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
129 | golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
130 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
131 | golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
132 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
133 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
134 | golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
135 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
136 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
137 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
138 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
139 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
140 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
141 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
142 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
143 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
144 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
145 | golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
146 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
147 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
148 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
149 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
150 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
151 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
152 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
153 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
154 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
155 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
156 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
157 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
158 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
159 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
160 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
161 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
162 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
163 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
164 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
165 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
166 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
167 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
168 | golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
169 | golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
170 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
171 | google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
172 | google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
173 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
174 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
175 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
176 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
177 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
178 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
179 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
180 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
181 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
182 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
183 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
184 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
185 | modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
186 | modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
187 | modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
188 | modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
189 | modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
190 | modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
191 | modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
192 | modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
193 | modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
194 | modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
195 | modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=
196 | modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
197 | modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
198 | modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
199 | modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
200 | modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
201 | modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
202 | modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
203 | modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
204 | modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
205 | modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
206 | modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
207 | modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
208 | modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
209 | modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
210 | modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
211 | modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
212 | modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
213 | modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
214 | modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g=
215 | modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE=
216 | modernc.org/sqlite v1.35.0 h1:yQps4fegMnZFdphtzlfQTCNBWtS0CZv48pRpW3RFHRw=
217 | modernc.org/sqlite v1.35.0/go.mod h1:9cr2sicr7jIaWTBKQmAxQLfBv9LL0su4ZTEV+utt3ic=
218 | modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
219 | modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
220 | modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
221 | modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
222 | modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
223 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
224 |
--------------------------------------------------------------------------------
/api/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/md5"
5 | "database/sql"
6 | "encoding/json"
7 | "fmt"
8 | "github.com/gin-contrib/cors"
9 | "github.com/gin-gonic/gin"
10 | "github.com/google/uuid"
11 | swaggerFiles "github.com/swaggo/files"
12 | ginSwagger "github.com/swaggo/gin-swagger"
13 | _ "modernc.org/sqlite"
14 | "net/http"
15 | "os"
16 | "strconv"
17 | "tiehfood/thrifty/docs"
18 | )
19 |
20 | type Flow struct {
21 | ID string `json:"id"`
22 | Name string `json:"name"`
23 | Description string `json:"description"`
24 | Amount float64 `json:"amount"`
25 | Icon string `json:"icon"`
26 | }
27 |
28 | type HTTPResponse struct {
29 | Ok string `json:"ok"`
30 | }
31 |
32 | type HTTPError struct {
33 | Error string `json:"error"`
34 | }
35 |
36 | var defaultIconId = "00000000-0000-0000-0000-000000000000"
37 | var errorPrefix = "Error: "
38 |
39 | func main() {
40 | dbCon, err := initAndOpenDb()
41 | if err != nil {
42 | fmt.Println(errorPrefix, err)
43 | }
44 |
45 | docs.SwaggerInfo.Title = "Thrifty API"
46 | docs.SwaggerInfo.Description = "This is the documentation for the Thrifty API"
47 | docs.SwaggerInfo.BasePath = "/api"
48 | docs.SwaggerInfo.Version = "1.0"
49 |
50 | gin.SetMode(gin.ReleaseMode)
51 | router := gin.Default()
52 |
53 | router.Use(cors.New(cors.Config{
54 | AllowOrigins: []string{"*"},
55 | AllowMethods: []string{"GET", "POST", "PATCH", "OPTIONS", "DELETE"},
56 | AllowHeaders: []string{"Content-Type"},
57 | }))
58 |
59 | router.Use(func(context *gin.Context) {
60 | context.Set("dbCon", dbCon)
61 | context.Next()
62 | })
63 |
64 | v1 := router.Group("/api")
65 | {
66 | flows := v1.Group("/flows")
67 | {
68 | flows.GET("", getFlows)
69 | flows.POST("", addFlow)
70 | flows.PATCH(":id", updateFlow)
71 | flows.DELETE(":id", deleteFlow)
72 | }
73 | }
74 |
75 | router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
76 | port, err := getAndValidatePort()
77 | if err != nil {
78 | fmt.Println(errorPrefix, err)
79 | }
80 | fmt.Printf("Running on port: %d\n", port)
81 | err = router.Run(fmt.Sprintf(":%d", port))
82 | if err != nil {
83 | fmt.Println(errorPrefix, err)
84 | }
85 | }
86 |
87 | func getAndValidatePort() (int, error) {
88 | portStr := os.Getenv("PORT")
89 | if portStr == "" {
90 | return 8080, nil
91 | }
92 |
93 | port, err := strconv.Atoi(portStr)
94 | if err != nil {
95 | return 8080, fmt.Errorf("invalid PORT value: %v", err)
96 | }
97 |
98 | if port < 1 || port > 65535 {
99 | return 0, fmt.Errorf("PORT value out of range (1-65535)")
100 | }
101 |
102 | return port, nil
103 | }
104 |
105 | func isInvalidFlow(flow Flow) bool {
106 | if flow.Amount != 0.0 {
107 | if len(flow.Name) > 0 {
108 | return false
109 | }
110 | }
111 | return true
112 | }
113 |
114 | func initAndOpenDb() (*sql.DB, error) {
115 | dataSourceName := os.Getenv("SQLITE_DB_PATH")
116 | defaultIconFlow := Flow{
117 | Icon: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyNTQuMiAyNTQuMiI+CiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC5zdDAgewogICAgICAgIGZpbGw6ICM0NDliMzg7CiAgICAgIH0KCiAgICAgIC5zdDEgewogICAgICAgIGZpbGw6ICNmZmMzMzY7CiAgICAgIH0KCiAgICAgIC5zdDIgewogICAgICAgIGZpbGw6ICNjODkzMTg7CiAgICAgIH0KCiAgICAgIC5zdDMgewogICAgICAgIGZpbGw6ICM2MmM3NTE7CiAgICAgIH0KCiAgICAgIC5zdDQgewogICAgICAgIGZpbGw6ICNlYWIwMjg7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIDxyZWN0IGNsYXNzPSJzdDAiIHk9IjU0LjYiIHdpZHRoPSIyNTQuMiIgaGVpZ2h0PSIxMzAuNyIvPgogIDxwYXRoIGNsYXNzPSJzdDMiIGQ9Ik0yMTMuNSw3My41SDQwLjhjMCwxMi4xLTkuOCwyMS45LTIxLjksMjEuOXY0OS4yYzEyLjEsMCwyMS45LDkuOCwyMS45LDIxLjloMTcyLjdjMC0uMiwwLS40LDAtLjcsMC0xMi4xLDkuOC0yMS45LDIxLjktMjEuOXYtNDkuMmMtMTEuOCwwLTIxLjUtOS40LTIxLjgtMjEuMloiLz4KICA8Y2lyY2xlIGNsYXNzPSJzdDAiIGN4PSIxMjcuMSIgY3k9IjEyMCIgcj0iMzguOSIvPgogIDxjaXJjbGUgY2xhc3M9InN0MCIgY3g9IjY3IiBjeT0iMTIwIiByPSI3LjgiLz4KICA8Y2lyY2xlIGNsYXNzPSJzdDAiIGN4PSIxODcuMiIgY3k9IjEyMCIgcj0iNy44IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzOC44IDI4NS41KSByb3RhdGUoLTgwLjgpIi8+CiAgPHBhdGggY2xhc3M9InN0MyIgZD0iTTEyNi44LDE0MC40Yy01LjksMC0xMS4yLTMuMS0xMy4zLThsNy43LTMuNWMuOCwxLjgsMy4xLDMsNS42LDNzNC44LTEuMiw1LjYtM2MuMy0uNi4zLTEuMi4yLTEuNS0uMi0uNS0xLjQtMS45LTYuNy0zLjUtNC40LTEuMy05LjctMy40LTExLjktNy45LS45LTEuOC0xLjUtNC43LDAtOC41LDIuMi01LDcuNS04LjEsMTMuNC04LjFzMTEuMiwzLjEsMTMuMyw4bC03LjcsMy41Yy0uOC0xLjgtMy4xLTMtNS42LTNzLTQuOCwxLjItNS42LDNjLS4zLjYtLjMsMS4yLS4yLDEuNS4yLjUsMS40LDEuOSw2LjcsMy41LDQuNCwxLjMsOS43LDMuNCwxMS45LDcuOS45LDEuOCwxLjUsNC43LDAsOC41LTIuMiw1LTcuNSw4LjEtMTMuNCw4LjFaIi8+CiAgPHJlY3QgY2xhc3M9InN0MyIgeD0iMTIyLjYiIHk9IjEzNi4yIiB3aWR0aD0iOC41IiBoZWlnaHQ9IjkuOCIvPgogIDxyZWN0IGNsYXNzPSJzdDMiIHg9IjEyMi42IiB5PSI5My45IiB3aWR0aD0iOC41IiBoZWlnaHQ9IjkuOCIvPgogIDxjaXJjbGUgY2xhc3M9InN0MyIgY3g9IjI0LjQiIGN5PSI3OC44IiByPSI1LjUiLz4KICA8Y2lyY2xlIGNsYXNzPSJzdDMiIGN4PSIyNC40IiBjeT0iMTYxIiByPSI1LjUiLz4KICA8Y2lyY2xlIGNsYXNzPSJzdDMiIGN4PSIyMjkuOCIgY3k9Ijc4LjkiIHI9IjUuNSIvPgogIDxjaXJjbGUgY2xhc3M9InN0MyIgY3g9IjIyOS44IiBjeT0iMTYxLjEiIHI9IjUuNSIvPgogIDxjaXJjbGUgY2xhc3M9InN0NCIgY3g9IjIxNy43IiBjeT0iMTY2LjYiIHI9IjI5LjUiLz4KICA8Y2lyY2xlIGNsYXNzPSJzdDEiIGN4PSIyMTcuNyIgY3k9IjE2Ni42IiByPSIyMy45Ii8+CiAgPHBhdGggY2xhc3M9InN0MiIgZD0iTTIxNy41LDE3OS4xYy0zLjYsMC02LjktMS45LTguMi00LjlsNC44LTIuMWMuNSwxLjEsMS45LDEuOCwzLjQsMS44czMtLjgsMy40LTEuOGMuMi0uNC4yLS43LjEtLjktLjEtLjMtLjktMS4xLTQuMS0yLjEtMi43LS44LTYtMi4xLTcuMy00LjktLjUtMS4xLS45LTIuOSwwLTUuMiwxLjQtMy4xLDQuNi01LDguMi01czYuOSwxLjksOC4yLDQuOWwtNC44LDIuMWMtLjUtMS4xLTEuOS0xLjgtMy40LTEuOHMtMywuOC0zLjQsMS44Yy0uMi40LS4yLjctLjEuOS4xLjMuOSwxLjEsNC4xLDIuMSwyLjcuOCw2LDIuMSw3LjMsNC45LjUsMS4xLjksMi45LDAsNS4yLTEuNCwzLjEtNC42LDUtOC4yLDVaIi8+CiAgPHJlY3QgY2xhc3M9InN0MiIgeD0iMjE0LjkiIHk9IjE3Ni41IiB3aWR0aD0iNS4yIiBoZWlnaHQ9IjYiLz4KICA8cmVjdCBjbGFzcz0ic3QyIiB4PSIyMTQuOSIgeT0iMTUwLjYiIHdpZHRoPSI1LjIiIGhlaWdodD0iNiIvPgogIDxjaXJjbGUgY2xhc3M9InN0NCIgY3g9IjE3MC42IiBjeT0iMTgzLjEiIHI9IjI5LjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zNy41IDMyMi4xKSByb3RhdGUoLTgwLjgpIi8+CiAgPGNpcmNsZSBjbGFzcz0ic3QxIiBjeD0iMTcwLjUiIGN5PSIxODMuMSIgcj0iMjMuOSIvPgogIDxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xNzAuMywxOTUuN2MtMy42LDAtNi45LTEuOS04LjItNC45bDQuOC0yLjFjLjUsMS4xLDEuOSwxLjgsMy40LDEuOHMzLS44LDMuNC0xLjhjLjItLjQuMi0uNy4xLS45LS4xLS4zLS45LTEuMS00LjEtMi4xLTIuNy0uOC02LTIuMS03LjMtNC45LS41LTEuMS0uOS0yLjksMC01LjIsMS40LTMuMSw0LjYtNSw4LjItNXM2LjksMS45LDguMiw0LjlsLTQuOCwyLjFjLS41LTEuMS0xLjktMS44LTMuNC0xLjhzLTMsLjgtMy40LDEuOGMtLjIuNC0uMi43LS4xLjkuMS4zLjksMS4xLDQuMSwyLjEsMi43LjgsNiwyLjEsNy4zLDQuOS41LDEuMS45LDIuOSwwLDUuMi0xLjQsMy4xLTQuNiw1LTguMiw1WiIvPgogIDxyZWN0IGNsYXNzPSJzdDIiIHg9IjE2Ny43IiB5PSIxOTMuMSIgd2lkdGg9IjUuMiIgaGVpZ2h0PSI2Ii8+CiAgPHJlY3QgY2xhc3M9InN0MiIgeD0iMTY3LjciIHk9IjE2Ny4xIiB3aWR0aD0iNS4yIiBoZWlnaHQ9IjYiLz4KICA8Y2lyY2xlIGNsYXNzPSJzdDQiIGN4PSIyMTQuOCIgY3k9IjIwNS42IiByPSIyOS41Ii8+CiAgPGNpcmNsZSBjbGFzcz0ic3QxIiBjeD0iMjE0LjgiIGN5PSIyMDUuNiIgcj0iMjMuOSIvPgogIDxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0yMTQuNiwyMTguMmMtMy42LDAtNi45LTEuOS04LjItNC45bDQuOC0yLjFjLjUsMS4xLDEuOSwxLjgsMy40LDEuOHMzLS44LDMuNC0xLjhjLjItLjQuMi0uNy4xLS45LS4xLS4zLS45LTEuMS00LjEtMi4xLTIuNy0uOC02LTIuMS03LjMtNC45LS41LTEuMS0uOS0yLjksMC01LjIsMS40LTMuMSw0LjYtNSw4LjItNXM2LjksMS45LDguMiw0LjlsLTQuOCwyLjFjLS41LTEuMS0xLjktMS44LTMuNC0xLjhzLTMsLjgtMy40LDEuOGMtLjIuNC0uMi43LS4xLjkuMS4zLjksMS4xLDQuMSwyLjEsMi43LjgsNiwyLjEsNy4zLDQuOS41LDEuMS45LDIuOSwwLDUuMi0xLjQsMy4xLTQuNiw1LTguMiw1WiIvPgogIDxyZWN0IGNsYXNzPSJzdDIiIHg9IjIxMiIgeT0iMjE1LjYiIHdpZHRoPSI1LjIiIGhlaWdodD0iNiIvPgogIDxyZWN0IGNsYXNzPSJzdDIiIHg9IjIxMiIgeT0iMTg5LjYiIHdpZHRoPSI1LjIiIGhlaWdodD0iNiIvPgo8L3N2Zz4=",
118 | }
119 |
120 | if len(dataSourceName) == 0 {
121 | dataSourceName = "thrifty.sqlite"
122 | }
123 | dbCon, err := sql.Open("sqlite", dataSourceName)
124 | if err != nil {
125 | return nil, err
126 | }
127 |
128 | queryFlows := fmt.Sprintf(`
129 | CREATE TABLE IF NOT EXISTS flows (
130 | id TEXT NOT NULL PRIMARY KEY,
131 | name TEXT,
132 | description TEXT,
133 | amount float,
134 | iconId TEXT DEFAULT '%s',
135 | FOREIGN KEY (iconId) REFERENCES icons(id));
136 | `, defaultIconId)
137 |
138 | queryIcons := `
139 | CREATE TABLE IF NOT EXISTS icons (
140 | id TEXT NOT NULL PRIMARY KEY,
141 | data TEXT,
142 | hash TEXT);
143 | `
144 | printSqlVersion(dbCon)
145 | execSql(dbCon, queryFlows)
146 | execSql(dbCon, queryIcons)
147 | insertIcon(dbCon, defaultIconFlow, &defaultIconId)
148 | return dbCon, nil
149 | }
150 |
151 | func printSqlVersion(dbCon *sql.DB) {
152 | query := "SELECT sqlite_version();"
153 | row, err := getSqlRow(dbCon, query, nil)
154 | if err != nil {
155 | fmt.Println("Error: ", err)
156 | }
157 | fmt.Println("SQLite version: ", row[0])
158 | }
159 |
160 | func execSql(dbCon *sql.DB, query string, args ...interface{}) {
161 | result, err := dbCon.Exec(query, args...)
162 | if err != nil {
163 | fmt.Println(errorPrefix, err)
164 | }
165 |
166 | id, _ := result.LastInsertId()
167 | numRows, _ := result.RowsAffected()
168 | fmt.Printf("LastInsertId %d, RowsAffected: %d\n", id, numRows)
169 | }
170 |
171 | func getSqlRow(dbCon *sql.DB, query string, args ...interface{}) ([]interface{}, error) {
172 | rows, err := dbCon.Query(query, args...)
173 | if err != nil {
174 | return nil, err
175 | }
176 | defer rows.Close()
177 |
178 | columns, err := rows.Columns()
179 | if err != nil {
180 | return nil, err
181 | }
182 |
183 | row := make([]interface{}, len(columns))
184 | scanArgs := make([]interface{}, len(columns))
185 | for i := range row {
186 | scanArgs[i] = &row[i]
187 | }
188 |
189 | // get first row
190 | if rows.Next() {
191 | err = rows.Scan(scanArgs...)
192 | if err != nil {
193 | return nil, err
194 | }
195 | }
196 | return row, rows.Err()
197 | }
198 |
199 | func getMD5(input string) string {
200 | hash := md5.Sum([]byte(input))
201 | return fmt.Sprintf("%x", hash)
202 | }
203 |
204 | func getDbConnection(c *gin.Context) *sql.DB {
205 | dbConVar, exists := c.Get("dbCon")
206 | if !exists {
207 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Database connection not found"})
208 | return nil
209 | }
210 | dbCon, ok := dbConVar.(*sql.DB)
211 | if !ok {
212 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Invalid database connection"})
213 | return nil
214 | }
215 | return dbCon
216 | }
217 |
218 | func insertIcon(dbCon *sql.DB, flow Flow, iconId *string) string {
219 | hash := getMD5(flow.Icon)
220 |
221 | if iconId == nil {
222 | var id = uuid.New().String()
223 | iconId = &id
224 | }
225 |
226 | query := "SELECT id, data, hash FROM icons WHERE hash = ?"
227 | row, err := getSqlRow(dbCon, query, []interface{}{hash}...)
228 |
229 | if err != nil {
230 | fmt.Println(errorPrefix, err)
231 | }
232 |
233 | if row[0] == nil {
234 | query = "INSERT OR REPLACE INTO icons (id, data, hash) VALUES (?, ?, ?)"
235 | queryArgs := []interface{}{*iconId, flow.Icon, hash}
236 | execSql(dbCon, query, queryArgs...)
237 | } else {
238 | var value = row[0].(string)
239 | iconId = &value
240 | }
241 | return *iconId
242 | }
243 |
244 | // getFlows godoc
245 | // @Summary Get all flows
246 | // @Description Get all flows in a JSON object
247 | // @Tags Flows
248 | // @Produce json
249 | // @Success 200 {array} main.Flow
250 | // @Failure 500 {object} main.HTTPError
251 | // @Router /flows [get]
252 | func getFlows(c *gin.Context) {
253 | query := `
254 | SELECT
255 | json_group_array(json_object('id', id, 'name', name, 'description', description, 'amount', amount, 'icon', data)) AS json_result
256 | FROM
257 | (
258 | SELECT
259 | f.id, f.name as name, f.description, f.amount, icons.data
260 | FROM
261 | flows f
262 | LEFT JOIN icons ON f.iconId = icons.id
263 | ORDER BY
264 | amount DESC, name
265 | );
266 | `
267 | dbCon := getDbConnection(c)
268 | if dbCon == nil {
269 | return
270 | }
271 |
272 | var jsonResult []byte
273 | err := dbCon.QueryRow(query).Scan(&jsonResult)
274 | if err != nil {
275 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Failed to query database"})
276 | return
277 | }
278 |
279 | if !json.Valid(jsonResult) {
280 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Invalid data returned from database"})
281 | }
282 | c.Header("Content-Type", "application/json")
283 | c.Data(http.StatusOK, "application/json", jsonResult)
284 | }
285 |
286 | // addFlow godoc
287 | // @Summary Add new flow
288 | // @Description Get all flows in a JSON object
289 | // @Tags Flows
290 | // @Param request body main.Flow true "Flow object, id is set by the server and could be omitted"
291 | // @Produce json
292 | // @Success 200 {object} main.Flow
293 | // @Failure 500 {object} main.HTTPError
294 | // @Router /flows [post]
295 | func addFlow(c *gin.Context) {
296 | var newFlow Flow
297 |
298 | if err := c.BindJSON(&newFlow); err != nil {
299 | fmt.Println(errorPrefix, err)
300 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Wrong data format"})
301 | return
302 | }
303 |
304 | if isInvalidFlow(newFlow) {
305 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Please provide name and amount"})
306 | return
307 | }
308 |
309 | newFlow.ID = uuid.New().String()
310 |
311 | dbCon := getDbConnection(c)
312 | if dbCon == nil {
313 | return
314 | }
315 |
316 | query := "INSERT OR REPLACE INTO flows (id, name, description, amount) VALUES (?, ?, ?, ?)"
317 | queryArgs := []interface{}{newFlow.ID, newFlow.Name, newFlow.Description, newFlow.Amount}
318 | if newFlow.Icon != "" {
319 | iconId := insertIcon(dbCon, newFlow, nil)
320 | query = "INSERT OR REPLACE INTO flows (id, name, description, amount, iconId) VALUES (?, ?, ?, ?, ?)"
321 | queryArgs = []interface{}{newFlow.ID, newFlow.Name, newFlow.Description, newFlow.Amount, iconId}
322 | }
323 |
324 | execSql(dbCon, query, queryArgs...)
325 |
326 | c.IndentedJSON(http.StatusCreated, newFlow)
327 | }
328 |
329 | // updateFlow godoc
330 | // @Summary Update existing flow
331 | // @Description Update an existing flow with new data
332 | // @Tags Flows
333 | // @Param id path int true "Flow id"
334 | // @Param request body main.Flow true "Flow object, id is ignored and could be omitted"
335 | // @Produce json
336 | // @Success 200 {object} main.Flow
337 | // @Failure 500 {object} main.HTTPError
338 | // @Router /flows/{id} [patch]
339 | func updateFlow(c *gin.Context) {
340 | var newFlow Flow
341 |
342 | if err := c.BindJSON(&newFlow); err != nil {
343 | fmt.Println(errorPrefix, err)
344 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Wrong data format"})
345 | return
346 | }
347 |
348 | if isInvalidFlow(newFlow) {
349 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Please provide name and amount"})
350 | return
351 | }
352 |
353 | newFlow.ID = c.Param("id")
354 |
355 | dbCon := getDbConnection(c)
356 | if dbCon == nil {
357 | return
358 | }
359 |
360 | query := "SELECT flows.id, iconId, hash FROM flows JOIN icons ON flows.iconId = icons.id WHERE flows.id = ?"
361 | row, err := getSqlRow(dbCon, query, []interface{}{newFlow.ID}...)
362 |
363 | if err != nil || row[0] == nil {
364 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Flow with given ID does not exist"})
365 | return
366 | }
367 |
368 | iconId, ok := row[1].(string)
369 | if !ok {
370 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Database error"})
371 | return
372 | }
373 |
374 | hash, ok := row[2].(string)
375 | if !ok {
376 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Database error"})
377 | return
378 | }
379 |
380 | if iconId != defaultIconId && hash != getMD5(newFlow.Icon) {
381 | query = "SELECT count(id) FROM flows WHERE iconId = ?"
382 | row, err = getSqlRow(dbCon, query, []interface{}{iconId}...)
383 | count, ok := row[0].(int64)
384 |
385 | if err != nil || !ok {
386 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Database error"})
387 | return
388 | }
389 |
390 | if count == 1 {
391 | query = "DELETE FROM icons WHERE id = ?"
392 | execSql(dbCon, query, []interface{}{iconId}...)
393 | }
394 | }
395 |
396 | query = "UPDATE flows SET name = ?, description = ?, amount = ? WHERE id = ?"
397 | queryArgs := []interface{}{newFlow.Name, newFlow.Description, newFlow.Amount, newFlow.ID}
398 | if newFlow.Icon != "" {
399 | iconId := insertIcon(dbCon, newFlow, nil)
400 | query = "UPDATE flows SET name = ?, description = ?, amount = ?, iconId = ? WHERE id = ?"
401 | queryArgs = []interface{}{newFlow.Name, newFlow.Description, newFlow.Amount, iconId, newFlow.ID}
402 | }
403 |
404 | execSql(dbCon, query, queryArgs...)
405 | c.JSON(http.StatusOK, newFlow)
406 | }
407 |
408 | // deleteFlow godoc
409 | // @Summary Delete a flow
410 | // @Description Delete an existing flow
411 | // @Tags Flows
412 | // @Param id path int true "Flow id"
413 | // @Produce json
414 | // @Success 200 {object} main.HTTPResponse
415 | // @Failure 500 {object} main.HTTPError
416 | // @Router /flows/{id} [delete]
417 | func deleteFlow(c *gin.Context) {
418 | flowId := c.Param("id")
419 | dbCon := getDbConnection(c)
420 | if dbCon == nil {
421 | return
422 | }
423 |
424 | query := "SELECT id, iconId FROM flows WHERE id = ?"
425 | row, err := getSqlRow(dbCon, query, []interface{}{flowId}...)
426 |
427 | if err != nil || row[0] == nil {
428 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Flow with given ID does not exist"})
429 | return
430 | }
431 |
432 | iconId, ok := row[1].(string)
433 | if !ok {
434 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Database error"})
435 | return
436 | }
437 |
438 | if iconId != defaultIconId {
439 | query = "SELECT count(id) FROM flows WHERE iconId = ?"
440 | row, err = getSqlRow(dbCon, query, []interface{}{iconId}...)
441 | count, ok := row[0].(int64)
442 |
443 | if err != nil || !ok {
444 | c.JSON(http.StatusInternalServerError, HTTPError{Error: "Database error"})
445 | return
446 | }
447 |
448 | if count == 1 {
449 | query = "DELETE FROM icons WHERE id = ?"
450 | execSql(dbCon, query, []interface{}{iconId}...)
451 | }
452 | }
453 | query = "DELETE FROM flows WHERE id = ?"
454 | execSql(dbCon, query, []interface{}{flowId}...)
455 | c.JSON(http.StatusOK, HTTPResponse{Ok: "Flow deleted"})
456 | }
457 |
--------------------------------------------------------------------------------
/doc/default-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/doc/screenshot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tiehfood/thrifty/03c6e67a2db40890b3cae3bfad27c480d1cdaa10/doc/screenshot_1.png
--------------------------------------------------------------------------------
/doc/screenshot_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tiehfood/thrifty/03c6e67a2db40890b3cae3bfad27c480d1cdaa10/doc/screenshot_2.png
--------------------------------------------------------------------------------
/doc/screenshot_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tiehfood/thrifty/03c6e67a2db40890b3cae3bfad27c480d1cdaa10/doc/screenshot_3.png
--------------------------------------------------------------------------------
/doc/screenshot_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tiehfood/thrifty/03c6e67a2db40890b3cae3bfad27c480d1cdaa10/doc/screenshot_4.png
--------------------------------------------------------------------------------
/docker-compose-build.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | traefik:
3 | container_name: traefik
4 | image: traefik:3.3
5 | ports:
6 | - "9090:80"
7 | environment:
8 | - TRAEFIK_GLOBAL_SENDANONYMOUSUSAGE=false
9 | - TRAEFIK_PROVIDERS_DOCKER=true
10 | - TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT=true
11 | - TRAEFIK_ENTRYPOINTS_main=true
12 | - TRAEFIK_ENTRYPOINTS_main_ADDRESS=:80
13 | - TRAEFIK_ENTRYPOINTS_main_HTTP_MIDDLEWARES=gzip@docker
14 | volumes:
15 | - /var/run/docker.sock:/var/run/docker.sock:ro
16 | labels:
17 | - "traefik.enable=true"
18 | - "traefik.http.middlewares.gzip.compress=true"
19 |
20 | ui:
21 | container_name: ui
22 | build:
23 | context: ui
24 | dockerfile: Dockerfile
25 | environment:
26 | - CURRENCY_ISO=EUR
27 | labels:
28 | - "traefik.http.routers.ui.entrypoints=main"
29 | - "traefik.http.routers.ui.rule=PathPrefix(`/`)"
30 | - "traefik.http.routers.ui.service=ui"
31 | - "traefik.http.services.ui.loadbalancer.server.port=8080"
32 |
33 | api:
34 | container_name: api
35 | build:
36 | context: api
37 | dockerfile: Dockerfile
38 | environment:
39 | - SQLITE_DB_PATH=/data/thrifty.sqlite
40 | volumes:
41 | - database:/data
42 | labels:
43 | - "traefik.http.routers.api.entrypoints=main"
44 | - "traefik.http.routers.api.rule=PathRegexp(`^/(api|swagger)/`)"
45 | - "traefik.http.routers.api.service=api"
46 | - "traefik.http.services.api.loadbalancer.server.port=8080"
47 |
48 | volumes:
49 | database:
50 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | traefik:
3 | container_name: traefik
4 | image: traefik:3.3
5 | ports:
6 | - "9090:80"
7 | environment:
8 | - TRAEFIK_GLOBAL_SENDANONYMOUSUSAGE=false
9 | - TRAEFIK_PROVIDERS_DOCKER=true
10 | - TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT=true
11 | - TRAEFIK_ENTRYPOINTS_main=true
12 | - TRAEFIK_ENTRYPOINTS_main_ADDRESS=:80
13 | - TRAEFIK_ENTRYPOINTS_main_HTTP_MIDDLEWARES=gzip@docker
14 | volumes:
15 | - /var/run/docker.sock:/var/run/docker.sock:ro
16 | labels:
17 | - "traefik.enable=true"
18 | - "traefik.http.middlewares.gzip.compress=true"
19 |
20 | ui:
21 | container_name: ui
22 | image: tiehfood/thrifty-ui:latest
23 | environment:
24 | - CURRENCY_ISO=EUR
25 | labels:
26 | - "traefik.http.routers.ui.entrypoints=main"
27 | - "traefik.http.routers.ui.rule=PathPrefix(`/`)"
28 | - "traefik.http.routers.ui.service=ui"
29 | - "traefik.http.services.ui.loadbalancer.server.port=8080"
30 |
31 | api:
32 | container_name: api
33 | image: tiehfood/thrifty-api:latest
34 | environment:
35 | - SQLITE_DB_PATH=/data/thrifty.sqlite
36 | volumes:
37 | - database:/data
38 | labels:
39 | - "traefik.http.routers.api.entrypoints=main"
40 | - "traefik.http.routers.api.rule=PathRegexp(`^/(api|swagger)/`)"
41 | - "traefik.http.routers.api.service=api"
42 | - "traefik.http.services.api.loadbalancer.server.port=8080"
43 |
44 | volumes:
45 | database:
46 |
--------------------------------------------------------------------------------
/ui/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | # Output
4 | .output
5 | .vercel
6 | .netlify
7 | .wrangler
8 | /.svelte-kit
9 | /build
10 |
11 | # OS
12 | .DS_Store
13 | Thumbs.db
14 |
15 | # Env
16 | .env
17 | .env.*
18 | !.env.example
19 | !.env.test
20 |
21 | # Vite
22 | vite.config.js.timestamp-*
23 | vite.config.ts.timestamp-*
24 |
--------------------------------------------------------------------------------
/ui/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/ui/01-sub.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ash
2 |
3 | CONFIG_FILE=/etc/nginx/conf.d/01-sub_filter.conf
4 |
5 | cat << EOF > "$CONFIG_FILE"
6 | sub_filter_types application/javascript;
7 | sub_filter_once off;
8 | EOF
9 |
10 | if [ -n "$CURRENCY_ISO" ]; then
11 | cat << EOF >> "$CONFIG_FILE"
12 | sub_filter 'currency:"EUR"' 'currency:"${CURRENCY_ISO}"';
13 | EOF
14 | fi
15 |
16 | if [ -n "$LOCAL_API_PROTOCOL" ]; then
17 | cat << EOF >> "$CONFIG_FILE"
18 | sub_filter 'window.location.protocol.replace(":","")' '"${LOCAL_API_PROTOCOL}"';
19 | EOF
20 | fi
21 |
22 | if [ -n "$LOCAL_API_HOSTNAME" ]; then
23 | cat << EOF >> "$CONFIG_FILE"
24 | sub_filter 'window.location.hostname.toLowerCase()' '"${LOCAL_API_HOSTNAME}"';
25 | EOF
26 | fi
27 |
28 | if [ -n "$LOCAL_API_PORT" ]; then
29 | cat << EOF >> "$CONFIG_FILE"
30 | sub_filter 'window.location.port.trim()' '"${LOCAL_API_PORT}"';
31 | EOF
32 | fi
33 |
34 | if [ "$USE_SINGLE_COLUMN" = true ]; then
35 | cat << EOF >> "$CONFIG_FILE"
36 | sub_filter 'md:grid-cols-2 ' '';
37 | EOF
38 | fi
39 |
--------------------------------------------------------------------------------
/ui/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:alpine AS builder
2 |
3 | COPY . /app
4 | WORKDIR /app
5 |
6 | RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" sh -; \
7 | source /root/.shrc; \
8 | pnpm install; \
9 | pnpm build;
10 |
11 | FROM nginxinc/nginx-unprivileged:alpine3.20
12 |
13 | COPY --from=builder /app/build /usr/share/nginx/html
14 | COPY 01-sub.sh /docker-entrypoint.d/
15 | USER nginx
--------------------------------------------------------------------------------
/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ui",
3 | "private": true,
4 | "version": "0.0.1",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite dev --host",
8 | "build": "vite build",
9 | "preview": "vite preview --host",
10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
12 | },
13 | "devDependencies": {
14 | "@number-flow/svelte": "^0.3.3",
15 | "@poppanator/sveltekit-svg": "^5.0.0",
16 | "@sveltejs/adapter-auto": "^4.0.0",
17 | "@sveltejs/adapter-static": "^3.0.8",
18 | "@sveltejs/kit": "^2.17.1",
19 | "@sveltejs/vite-plugin-svelte": "^5.0.3",
20 | "@tailwindcss/vite": "^4.0.8",
21 | "autoprefixer": "^10.4.20",
22 | "debounce": "^2.2.0",
23 | "flowbite": "^3.1.2",
24 | "flowbite-svelte": "^0.47.4",
25 | "svelte": "^5.20.2",
26 | "svelte-check": "^4.1.4",
27 | "tailwindcss": "^4.0.6",
28 | "typescript": "^5.7.3",
29 | "vite": "^6.1.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/ui/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '9.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | importers:
8 |
9 | .:
10 | devDependencies:
11 | '@number-flow/svelte':
12 | specifier: ^0.3.3
13 | version: 0.3.3(svelte@5.20.2)
14 | '@poppanator/sveltekit-svg':
15 | specifier: ^5.0.0
16 | version: 5.0.0(rollup@4.34.6)(svelte@5.20.2)(svgo@3.3.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))
17 | '@sveltejs/adapter-auto':
18 | specifier: ^4.0.0
19 | version: 4.0.0(@sveltejs/kit@2.17.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))
20 | '@sveltejs/adapter-static':
21 | specifier: ^3.0.8
22 | version: 3.0.8(@sveltejs/kit@2.17.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))
23 | '@sveltejs/kit':
24 | specifier: ^2.17.1
25 | version: 2.17.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))
26 | '@sveltejs/vite-plugin-svelte':
27 | specifier: ^5.0.3
28 | version: 5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))
29 | '@tailwindcss/vite':
30 | specifier: ^4.0.8
31 | version: 4.0.8(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))
32 | autoprefixer:
33 | specifier: ^10.4.20
34 | version: 10.4.20(postcss@8.5.2)
35 | debounce:
36 | specifier: ^2.2.0
37 | version: 2.2.0
38 | flowbite:
39 | specifier: ^3.1.2
40 | version: 3.1.2(rollup@4.34.6)
41 | flowbite-svelte:
42 | specifier: ^0.47.4
43 | version: 0.47.4(rollup@4.34.6)(svelte@5.20.2)
44 | svelte:
45 | specifier: ^5.20.2
46 | version: 5.20.2
47 | svelte-check:
48 | specifier: ^4.1.4
49 | version: 4.1.4(picomatch@4.0.2)(svelte@5.20.2)(typescript@5.7.3)
50 | tailwindcss:
51 | specifier: ^4.0.6
52 | version: 4.0.6
53 | typescript:
54 | specifier: ^5.7.3
55 | version: 5.7.3
56 | vite:
57 | specifier: ^6.1.0
58 | version: 6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)
59 |
60 | packages:
61 |
62 | '@ampproject/remapping@2.3.0':
63 | resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
64 | engines: {node: '>=6.0.0'}
65 |
66 | '@esbuild/aix-ppc64@0.24.2':
67 | resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==}
68 | engines: {node: '>=18'}
69 | cpu: [ppc64]
70 | os: [aix]
71 |
72 | '@esbuild/android-arm64@0.24.2':
73 | resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==}
74 | engines: {node: '>=18'}
75 | cpu: [arm64]
76 | os: [android]
77 |
78 | '@esbuild/android-arm@0.24.2':
79 | resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==}
80 | engines: {node: '>=18'}
81 | cpu: [arm]
82 | os: [android]
83 |
84 | '@esbuild/android-x64@0.24.2':
85 | resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==}
86 | engines: {node: '>=18'}
87 | cpu: [x64]
88 | os: [android]
89 |
90 | '@esbuild/darwin-arm64@0.24.2':
91 | resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==}
92 | engines: {node: '>=18'}
93 | cpu: [arm64]
94 | os: [darwin]
95 |
96 | '@esbuild/darwin-x64@0.24.2':
97 | resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==}
98 | engines: {node: '>=18'}
99 | cpu: [x64]
100 | os: [darwin]
101 |
102 | '@esbuild/freebsd-arm64@0.24.2':
103 | resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==}
104 | engines: {node: '>=18'}
105 | cpu: [arm64]
106 | os: [freebsd]
107 |
108 | '@esbuild/freebsd-x64@0.24.2':
109 | resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==}
110 | engines: {node: '>=18'}
111 | cpu: [x64]
112 | os: [freebsd]
113 |
114 | '@esbuild/linux-arm64@0.24.2':
115 | resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==}
116 | engines: {node: '>=18'}
117 | cpu: [arm64]
118 | os: [linux]
119 |
120 | '@esbuild/linux-arm@0.24.2':
121 | resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==}
122 | engines: {node: '>=18'}
123 | cpu: [arm]
124 | os: [linux]
125 |
126 | '@esbuild/linux-ia32@0.24.2':
127 | resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==}
128 | engines: {node: '>=18'}
129 | cpu: [ia32]
130 | os: [linux]
131 |
132 | '@esbuild/linux-loong64@0.24.2':
133 | resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==}
134 | engines: {node: '>=18'}
135 | cpu: [loong64]
136 | os: [linux]
137 |
138 | '@esbuild/linux-mips64el@0.24.2':
139 | resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==}
140 | engines: {node: '>=18'}
141 | cpu: [mips64el]
142 | os: [linux]
143 |
144 | '@esbuild/linux-ppc64@0.24.2':
145 | resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==}
146 | engines: {node: '>=18'}
147 | cpu: [ppc64]
148 | os: [linux]
149 |
150 | '@esbuild/linux-riscv64@0.24.2':
151 | resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==}
152 | engines: {node: '>=18'}
153 | cpu: [riscv64]
154 | os: [linux]
155 |
156 | '@esbuild/linux-s390x@0.24.2':
157 | resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==}
158 | engines: {node: '>=18'}
159 | cpu: [s390x]
160 | os: [linux]
161 |
162 | '@esbuild/linux-x64@0.24.2':
163 | resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==}
164 | engines: {node: '>=18'}
165 | cpu: [x64]
166 | os: [linux]
167 |
168 | '@esbuild/netbsd-arm64@0.24.2':
169 | resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==}
170 | engines: {node: '>=18'}
171 | cpu: [arm64]
172 | os: [netbsd]
173 |
174 | '@esbuild/netbsd-x64@0.24.2':
175 | resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==}
176 | engines: {node: '>=18'}
177 | cpu: [x64]
178 | os: [netbsd]
179 |
180 | '@esbuild/openbsd-arm64@0.24.2':
181 | resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==}
182 | engines: {node: '>=18'}
183 | cpu: [arm64]
184 | os: [openbsd]
185 |
186 | '@esbuild/openbsd-x64@0.24.2':
187 | resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==}
188 | engines: {node: '>=18'}
189 | cpu: [x64]
190 | os: [openbsd]
191 |
192 | '@esbuild/sunos-x64@0.24.2':
193 | resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==}
194 | engines: {node: '>=18'}
195 | cpu: [x64]
196 | os: [sunos]
197 |
198 | '@esbuild/win32-arm64@0.24.2':
199 | resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==}
200 | engines: {node: '>=18'}
201 | cpu: [arm64]
202 | os: [win32]
203 |
204 | '@esbuild/win32-ia32@0.24.2':
205 | resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==}
206 | engines: {node: '>=18'}
207 | cpu: [ia32]
208 | os: [win32]
209 |
210 | '@esbuild/win32-x64@0.24.2':
211 | resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==}
212 | engines: {node: '>=18'}
213 | cpu: [x64]
214 | os: [win32]
215 |
216 | '@floating-ui/core@1.6.9':
217 | resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==}
218 |
219 | '@floating-ui/dom@1.6.13':
220 | resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==}
221 |
222 | '@floating-ui/utils@0.2.9':
223 | resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
224 |
225 | '@jridgewell/gen-mapping@0.3.8':
226 | resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
227 | engines: {node: '>=6.0.0'}
228 |
229 | '@jridgewell/resolve-uri@3.1.2':
230 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
231 | engines: {node: '>=6.0.0'}
232 |
233 | '@jridgewell/set-array@1.2.1':
234 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
235 | engines: {node: '>=6.0.0'}
236 |
237 | '@jridgewell/sourcemap-codec@1.5.0':
238 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
239 |
240 | '@jridgewell/trace-mapping@0.3.25':
241 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
242 |
243 | '@number-flow/svelte@0.3.3':
244 | resolution: {integrity: sha512-wo5tiU1qKc74k61sNx8GwvnmZ7yKdbfj1onjzaAa67HRXFClRcrfLlXXatBSQ/uK2XTz9156ILs3VmpuSG1sFA==}
245 | peerDependencies:
246 | svelte: ^4 || ^5
247 |
248 | '@polka/url@1.0.0-next.28':
249 | resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==}
250 |
251 | '@poppanator/sveltekit-svg@5.0.0':
252 | resolution: {integrity: sha512-b7hk55SF0HjTS+xFgMG20hy6W0F/m+yRA/ZWcjnsa391rB3Ys3desCiUyIKQYcfvcyuRiQCPedUJMYgu00VdCA==}
253 | peerDependencies:
254 | svelte: '>=5.x'
255 | svgo: '>=3.x'
256 | vite: '>=5.x'
257 |
258 | '@popperjs/core@2.11.8':
259 | resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
260 |
261 | '@rollup/plugin-node-resolve@15.3.1':
262 | resolution: {integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==}
263 | engines: {node: '>=14.0.0'}
264 | peerDependencies:
265 | rollup: ^2.78.0||^3.0.0||^4.0.0
266 | peerDependenciesMeta:
267 | rollup:
268 | optional: true
269 |
270 | '@rollup/pluginutils@5.1.4':
271 | resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==}
272 | engines: {node: '>=14.0.0'}
273 | peerDependencies:
274 | rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
275 | peerDependenciesMeta:
276 | rollup:
277 | optional: true
278 |
279 | '@rollup/rollup-android-arm-eabi@4.34.6':
280 | resolution: {integrity: sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==}
281 | cpu: [arm]
282 | os: [android]
283 |
284 | '@rollup/rollup-android-arm64@4.34.6':
285 | resolution: {integrity: sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==}
286 | cpu: [arm64]
287 | os: [android]
288 |
289 | '@rollup/rollup-darwin-arm64@4.34.6':
290 | resolution: {integrity: sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==}
291 | cpu: [arm64]
292 | os: [darwin]
293 |
294 | '@rollup/rollup-darwin-x64@4.34.6':
295 | resolution: {integrity: sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==}
296 | cpu: [x64]
297 | os: [darwin]
298 |
299 | '@rollup/rollup-freebsd-arm64@4.34.6':
300 | resolution: {integrity: sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==}
301 | cpu: [arm64]
302 | os: [freebsd]
303 |
304 | '@rollup/rollup-freebsd-x64@4.34.6':
305 | resolution: {integrity: sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==}
306 | cpu: [x64]
307 | os: [freebsd]
308 |
309 | '@rollup/rollup-linux-arm-gnueabihf@4.34.6':
310 | resolution: {integrity: sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==}
311 | cpu: [arm]
312 | os: [linux]
313 |
314 | '@rollup/rollup-linux-arm-musleabihf@4.34.6':
315 | resolution: {integrity: sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==}
316 | cpu: [arm]
317 | os: [linux]
318 |
319 | '@rollup/rollup-linux-arm64-gnu@4.34.6':
320 | resolution: {integrity: sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==}
321 | cpu: [arm64]
322 | os: [linux]
323 |
324 | '@rollup/rollup-linux-arm64-musl@4.34.6':
325 | resolution: {integrity: sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==}
326 | cpu: [arm64]
327 | os: [linux]
328 |
329 | '@rollup/rollup-linux-loongarch64-gnu@4.34.6':
330 | resolution: {integrity: sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==}
331 | cpu: [loong64]
332 | os: [linux]
333 |
334 | '@rollup/rollup-linux-powerpc64le-gnu@4.34.6':
335 | resolution: {integrity: sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==}
336 | cpu: [ppc64]
337 | os: [linux]
338 |
339 | '@rollup/rollup-linux-riscv64-gnu@4.34.6':
340 | resolution: {integrity: sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==}
341 | cpu: [riscv64]
342 | os: [linux]
343 |
344 | '@rollup/rollup-linux-s390x-gnu@4.34.6':
345 | resolution: {integrity: sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==}
346 | cpu: [s390x]
347 | os: [linux]
348 |
349 | '@rollup/rollup-linux-x64-gnu@4.34.6':
350 | resolution: {integrity: sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==}
351 | cpu: [x64]
352 | os: [linux]
353 |
354 | '@rollup/rollup-linux-x64-musl@4.34.6':
355 | resolution: {integrity: sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==}
356 | cpu: [x64]
357 | os: [linux]
358 |
359 | '@rollup/rollup-win32-arm64-msvc@4.34.6':
360 | resolution: {integrity: sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==}
361 | cpu: [arm64]
362 | os: [win32]
363 |
364 | '@rollup/rollup-win32-ia32-msvc@4.34.6':
365 | resolution: {integrity: sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==}
366 | cpu: [ia32]
367 | os: [win32]
368 |
369 | '@rollup/rollup-win32-x64-msvc@4.34.6':
370 | resolution: {integrity: sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==}
371 | cpu: [x64]
372 | os: [win32]
373 |
374 | '@sveltejs/adapter-auto@4.0.0':
375 | resolution: {integrity: sha512-kmuYSQdD2AwThymQF0haQhM8rE5rhutQXG4LNbnbShwhMO4qQGnKaaTy+88DuNSuoQDi58+thpq8XpHc1+oEKQ==}
376 | peerDependencies:
377 | '@sveltejs/kit': ^2.0.0
378 |
379 | '@sveltejs/adapter-static@3.0.8':
380 | resolution: {integrity: sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==}
381 | peerDependencies:
382 | '@sveltejs/kit': ^2.0.0
383 |
384 | '@sveltejs/kit@2.17.1':
385 | resolution: {integrity: sha512-CpoGSLqE2MCmcQwA2CWJvOsZ9vW+p/1H3itrFykdgajUNAEyQPbsaSn7fZb6PLHQwe+07njxje9ss0fjZoCAyw==}
386 | engines: {node: '>=18.13'}
387 | hasBin: true
388 | peerDependencies:
389 | '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0
390 | svelte: ^4.0.0 || ^5.0.0-next.0
391 | vite: ^5.0.3 || ^6.0.0
392 |
393 | '@sveltejs/vite-plugin-svelte-inspector@4.0.1':
394 | resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==}
395 | engines: {node: ^18.0.0 || ^20.0.0 || >=22}
396 | peerDependencies:
397 | '@sveltejs/vite-plugin-svelte': ^5.0.0
398 | svelte: ^5.0.0
399 | vite: ^6.0.0
400 |
401 | '@sveltejs/vite-plugin-svelte@5.0.3':
402 | resolution: {integrity: sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==}
403 | engines: {node: ^18.0.0 || ^20.0.0 || >=22}
404 | peerDependencies:
405 | svelte: ^5.0.0
406 | vite: ^6.0.0
407 |
408 | '@tailwindcss/node@4.0.8':
409 | resolution: {integrity: sha512-FKArQpbrbwv08TNT0k7ejYXpF+R8knZFAatNc0acOxbgeqLzwb86r+P3LGOjIeI3Idqe9CVkZrh4GlsJLJKkkw==}
410 |
411 | '@tailwindcss/oxide-android-arm64@4.0.8':
412 | resolution: {integrity: sha512-We7K79+Sm4mwJHk26Yzu/GAj7C7myemm7PeXvpgMxyxO70SSFSL3uCcqFbz9JA5M5UPkrl7N9fkBe/Y0iazqpA==}
413 | engines: {node: '>= 10'}
414 | cpu: [arm64]
415 | os: [android]
416 |
417 | '@tailwindcss/oxide-darwin-arm64@4.0.8':
418 | resolution: {integrity: sha512-Lv9Isi2EwkCTG1sRHNDi0uRNN1UGFdEThUAGFrydRmQZnraGLMjN8gahzg2FFnOizDl7LB2TykLUuiw833DSNg==}
419 | engines: {node: '>= 10'}
420 | cpu: [arm64]
421 | os: [darwin]
422 |
423 | '@tailwindcss/oxide-darwin-x64@4.0.8':
424 | resolution: {integrity: sha512-fWfywfYIlSWtKoqWTjukTHLWV3ARaBRjXCC2Eo0l6KVpaqGY4c2y8snUjp1xpxUtpqwMvCvFWFaleMoz1Vhzlw==}
425 | engines: {node: '>= 10'}
426 | cpu: [x64]
427 | os: [darwin]
428 |
429 | '@tailwindcss/oxide-freebsd-x64@4.0.8':
430 | resolution: {integrity: sha512-SO+dyvjJV9G94bnmq2288Ke0BIdvrbSbvtPLaQdqjqHR83v5L2fWADyFO+1oecHo9Owsk8MxcXh1agGVPIKIqw==}
431 | engines: {node: '>= 10'}
432 | cpu: [x64]
433 | os: [freebsd]
434 |
435 | '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.8':
436 | resolution: {integrity: sha512-ZSHggWiEblQNV69V0qUK5vuAtHP+I+S2eGrKGJ5lPgwgJeAd6GjLsVBN+Mqn2SPVfYM3BOpS9jX/zVg9RWQVDQ==}
437 | engines: {node: '>= 10'}
438 | cpu: [arm]
439 | os: [linux]
440 |
441 | '@tailwindcss/oxide-linux-arm64-gnu@4.0.8':
442 | resolution: {integrity: sha512-xWpr6M0OZLDNsr7+bQz+3X7zcnDJZJ1N9gtBWCtfhkEtDjjxYEp+Lr5L5nc/yXlL4MyCHnn0uonGVXy3fhxaVA==}
443 | engines: {node: '>= 10'}
444 | cpu: [arm64]
445 | os: [linux]
446 |
447 | '@tailwindcss/oxide-linux-arm64-musl@4.0.8':
448 | resolution: {integrity: sha512-5tz2IL7LN58ssGEq7h/staD7pu/izF/KeMWdlJ86WDe2Ah46LF3ET6ZGKTr5eZMrnEA0M9cVFuSPprKRHNgjeg==}
449 | engines: {node: '>= 10'}
450 | cpu: [arm64]
451 | os: [linux]
452 |
453 | '@tailwindcss/oxide-linux-x64-gnu@4.0.8':
454 | resolution: {integrity: sha512-KSzMkhyrxAQyY2o194NKVKU9j/c+NFSoMvnHWFaNHKi3P1lb+Vq1UC19tLHrmxSkKapcMMu69D7+G1+FVGNDXQ==}
455 | engines: {node: '>= 10'}
456 | cpu: [x64]
457 | os: [linux]
458 |
459 | '@tailwindcss/oxide-linux-x64-musl@4.0.8':
460 | resolution: {integrity: sha512-yFYKG5UtHTRimjtqxUWXBgI4Tc6NJe3USjRIVdlTczpLRxq/SFwgzGl5JbatCxgSRDPBFwRrNPxq+ukfQFGdrw==}
461 | engines: {node: '>= 10'}
462 | cpu: [x64]
463 | os: [linux]
464 |
465 | '@tailwindcss/oxide-win32-arm64-msvc@4.0.8':
466 | resolution: {integrity: sha512-tndGujmCSba85cRCnQzXgpA2jx5gXimyspsUYae5jlPyLRG0RjXbDshFKOheVXU4TLflo7FSG8EHCBJ0EHTKdQ==}
467 | engines: {node: '>= 10'}
468 | cpu: [arm64]
469 | os: [win32]
470 |
471 | '@tailwindcss/oxide-win32-x64-msvc@4.0.8':
472 | resolution: {integrity: sha512-T77jroAc0p4EHVVgTUiNeFn6Nj3jtD3IeNId2X+0k+N1XxfNipy81BEkYErpKLiOkNhpNFjPee8/ZVas29b2OQ==}
473 | engines: {node: '>= 10'}
474 | cpu: [x64]
475 | os: [win32]
476 |
477 | '@tailwindcss/oxide@4.0.8':
478 | resolution: {integrity: sha512-KfMcuAu/Iw+DcV1e8twrFyr2yN8/ZDC/odIGta4wuuJOGkrkHZbvJvRNIbQNhGh7erZTYV6Ie0IeD6WC9Y8Hcw==}
479 | engines: {node: '>= 10'}
480 |
481 | '@tailwindcss/vite@4.0.8':
482 | resolution: {integrity: sha512-+SAq44yLzYlzyrb7QTcFCdU8Xa7FOA0jp+Xby7fPMUie+MY9HhJysM7Vp+vL8qIp8ceQJfLD+FjgJuJ4lL6nyg==}
483 | peerDependencies:
484 | vite: ^5.2.0 || ^6
485 |
486 | '@trysound/sax@0.2.0':
487 | resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
488 | engines: {node: '>=10.13.0'}
489 |
490 | '@types/cookie@0.6.0':
491 | resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
492 |
493 | '@types/estree@1.0.6':
494 | resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
495 |
496 | '@types/resolve@1.20.2':
497 | resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
498 |
499 | '@yr/monotone-cubic-spline@1.0.3':
500 | resolution: {integrity: sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==}
501 |
502 | acorn-typescript@1.4.13:
503 | resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==}
504 | peerDependencies:
505 | acorn: '>=8.9.0'
506 |
507 | acorn@8.14.0:
508 | resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
509 | engines: {node: '>=0.4.0'}
510 | hasBin: true
511 |
512 | apexcharts@3.54.1:
513 | resolution: {integrity: sha512-E4et0h/J1U3r3EwS/WlqJCQIbepKbp6wGUmaAwJOMjHUP4Ci0gxanLa7FR3okx6p9coi4st6J853/Cb1NP0vpA==}
514 |
515 | aria-query@5.3.2:
516 | resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
517 | engines: {node: '>= 0.4'}
518 |
519 | autoprefixer@10.4.20:
520 | resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
521 | engines: {node: ^10 || ^12 || >=14}
522 | hasBin: true
523 | peerDependencies:
524 | postcss: ^8.1.0
525 |
526 | axobject-query@4.1.0:
527 | resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
528 | engines: {node: '>= 0.4'}
529 |
530 | boolbase@1.0.0:
531 | resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
532 |
533 | browserslist@4.24.4:
534 | resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==}
535 | engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
536 | hasBin: true
537 |
538 | caniuse-lite@1.0.30001695:
539 | resolution: {integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==}
540 |
541 | chokidar@4.0.3:
542 | resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
543 | engines: {node: '>= 14.16.0'}
544 |
545 | clsx@2.1.1:
546 | resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
547 | engines: {node: '>=6'}
548 |
549 | commander@7.2.0:
550 | resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
551 | engines: {node: '>= 10'}
552 |
553 | cookie@0.6.0:
554 | resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
555 | engines: {node: '>= 0.6'}
556 |
557 | css-select@5.1.0:
558 | resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
559 |
560 | css-tree@2.2.1:
561 | resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==}
562 | engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
563 |
564 | css-tree@2.3.1:
565 | resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
566 | engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
567 |
568 | css-what@6.1.0:
569 | resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
570 | engines: {node: '>= 6'}
571 |
572 | csso@5.0.5:
573 | resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
574 | engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
575 |
576 | debounce@2.2.0:
577 | resolution: {integrity: sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==}
578 | engines: {node: '>=18'}
579 |
580 | debug@4.4.0:
581 | resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
582 | engines: {node: '>=6.0'}
583 | peerDependencies:
584 | supports-color: '*'
585 | peerDependenciesMeta:
586 | supports-color:
587 | optional: true
588 |
589 | deepmerge@4.3.1:
590 | resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
591 | engines: {node: '>=0.10.0'}
592 |
593 | detect-libc@1.0.3:
594 | resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
595 | engines: {node: '>=0.10'}
596 | hasBin: true
597 |
598 | devalue@5.1.1:
599 | resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==}
600 |
601 | dom-serializer@2.0.0:
602 | resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
603 |
604 | domelementtype@2.3.0:
605 | resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
606 |
607 | domhandler@5.0.3:
608 | resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
609 | engines: {node: '>= 4'}
610 |
611 | domutils@3.2.2:
612 | resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
613 |
614 | electron-to-chromium@1.5.84:
615 | resolution: {integrity: sha512-I+DQ8xgafao9Ha6y0qjHHvpZ9OfyA1qKlkHkjywxzniORU2awxyz7f/iVJcULmrF2yrM3nHQf+iDjJtbbexd/g==}
616 |
617 | enhanced-resolve@5.18.1:
618 | resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
619 | engines: {node: '>=10.13.0'}
620 |
621 | entities@4.5.0:
622 | resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
623 | engines: {node: '>=0.12'}
624 |
625 | esbuild@0.24.2:
626 | resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==}
627 | engines: {node: '>=18'}
628 | hasBin: true
629 |
630 | escalade@3.2.0:
631 | resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
632 | engines: {node: '>=6'}
633 |
634 | esm-env@1.2.2:
635 | resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
636 |
637 | esrap@1.4.5:
638 | resolution: {integrity: sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==}
639 |
640 | estree-walker@2.0.2:
641 | resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
642 |
643 | fdir@6.4.3:
644 | resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==}
645 | peerDependencies:
646 | picomatch: ^3 || ^4
647 | peerDependenciesMeta:
648 | picomatch:
649 | optional: true
650 |
651 | flowbite-datepicker@1.3.2:
652 | resolution: {integrity: sha512-6Nfm0MCVX3mpaR7YSCjmEO2GO8CDt6CX8ZpQnGdeu03WUCWtEPQ/uy0PUiNtIJjJZWnX0Cm3H55MOhbD1g+E/g==}
653 |
654 | flowbite-svelte@0.47.4:
655 | resolution: {integrity: sha512-8oiY/oeWA7fgkDF91MZKEBo5VmjL8El3wuqTDWAFO1j7p45BHIL6G1VGnnidgCEYlbADDQN9BIGCvyPq4J3g+w==}
656 | peerDependencies:
657 | svelte: ^3.55.1 || ^4.0.0 || ^5.0.0
658 |
659 | flowbite@2.5.2:
660 | resolution: {integrity: sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA==}
661 |
662 | flowbite@3.1.2:
663 | resolution: {integrity: sha512-MkwSgbbybCYgMC+go6Da5idEKUFfMqc/AmSjm/2ZbdmvoKf5frLPq/eIhXc9P+rC8t9boZtUXzHDgt5whZ6A/Q==}
664 |
665 | fraction.js@4.3.7:
666 | resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
667 |
668 | fsevents@2.3.3:
669 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
670 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
671 | os: [darwin]
672 |
673 | function-bind@1.1.2:
674 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
675 |
676 | graceful-fs@4.2.11:
677 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
678 |
679 | hasown@2.0.2:
680 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
681 | engines: {node: '>= 0.4'}
682 |
683 | import-meta-resolve@4.1.0:
684 | resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==}
685 |
686 | is-core-module@2.16.1:
687 | resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
688 | engines: {node: '>= 0.4'}
689 |
690 | is-module@1.0.0:
691 | resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
692 |
693 | is-reference@3.0.3:
694 | resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
695 |
696 | jiti@2.4.2:
697 | resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
698 | hasBin: true
699 |
700 | kleur@4.1.5:
701 | resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
702 | engines: {node: '>=6'}
703 |
704 | lightningcss-darwin-arm64@1.29.1:
705 | resolution: {integrity: sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw==}
706 | engines: {node: '>= 12.0.0'}
707 | cpu: [arm64]
708 | os: [darwin]
709 |
710 | lightningcss-darwin-x64@1.29.1:
711 | resolution: {integrity: sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA==}
712 | engines: {node: '>= 12.0.0'}
713 | cpu: [x64]
714 | os: [darwin]
715 |
716 | lightningcss-freebsd-x64@1.29.1:
717 | resolution: {integrity: sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ==}
718 | engines: {node: '>= 12.0.0'}
719 | cpu: [x64]
720 | os: [freebsd]
721 |
722 | lightningcss-linux-arm-gnueabihf@1.29.1:
723 | resolution: {integrity: sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg==}
724 | engines: {node: '>= 12.0.0'}
725 | cpu: [arm]
726 | os: [linux]
727 |
728 | lightningcss-linux-arm64-gnu@1.29.1:
729 | resolution: {integrity: sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ==}
730 | engines: {node: '>= 12.0.0'}
731 | cpu: [arm64]
732 | os: [linux]
733 |
734 | lightningcss-linux-arm64-musl@1.29.1:
735 | resolution: {integrity: sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw==}
736 | engines: {node: '>= 12.0.0'}
737 | cpu: [arm64]
738 | os: [linux]
739 |
740 | lightningcss-linux-x64-gnu@1.29.1:
741 | resolution: {integrity: sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw==}
742 | engines: {node: '>= 12.0.0'}
743 | cpu: [x64]
744 | os: [linux]
745 |
746 | lightningcss-linux-x64-musl@1.29.1:
747 | resolution: {integrity: sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw==}
748 | engines: {node: '>= 12.0.0'}
749 | cpu: [x64]
750 | os: [linux]
751 |
752 | lightningcss-win32-arm64-msvc@1.29.1:
753 | resolution: {integrity: sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog==}
754 | engines: {node: '>= 12.0.0'}
755 | cpu: [arm64]
756 | os: [win32]
757 |
758 | lightningcss-win32-x64-msvc@1.29.1:
759 | resolution: {integrity: sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q==}
760 | engines: {node: '>= 12.0.0'}
761 | cpu: [x64]
762 | os: [win32]
763 |
764 | lightningcss@1.29.1:
765 | resolution: {integrity: sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==}
766 | engines: {node: '>= 12.0.0'}
767 |
768 | locate-character@3.0.0:
769 | resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
770 |
771 | magic-string@0.30.17:
772 | resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
773 |
774 | mdn-data@2.0.28:
775 | resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
776 |
777 | mdn-data@2.0.30:
778 | resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
779 |
780 | mini-svg-data-uri@1.4.4:
781 | resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
782 | hasBin: true
783 |
784 | mri@1.2.0:
785 | resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
786 | engines: {node: '>=4'}
787 |
788 | mrmime@2.0.0:
789 | resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==}
790 | engines: {node: '>=10'}
791 |
792 | ms@2.1.3:
793 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
794 |
795 | nanoid@3.3.8:
796 | resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
797 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
798 | hasBin: true
799 |
800 | node-releases@2.0.19:
801 | resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
802 |
803 | normalize-range@0.1.2:
804 | resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
805 | engines: {node: '>=0.10.0'}
806 |
807 | nth-check@2.1.1:
808 | resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
809 |
810 | number-flow@0.5.3:
811 | resolution: {integrity: sha512-iLKyssImNWQmJ41rza9K7P5lHRZTyishi/9FarWPLQHYY2Ydtl6eiXINEjZ1fa8dHeY0O7+YOD+Py3ZsJddYkg==}
812 |
813 | path-parse@1.0.7:
814 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
815 |
816 | picocolors@1.1.1:
817 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
818 |
819 | picomatch@4.0.2:
820 | resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
821 | engines: {node: '>=12'}
822 |
823 | postcss-value-parser@4.2.0:
824 | resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
825 |
826 | postcss@8.5.1:
827 | resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==}
828 | engines: {node: ^10 || ^12 || >=14}
829 |
830 | postcss@8.5.2:
831 | resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==}
832 | engines: {node: ^10 || ^12 || >=14}
833 |
834 | readdirp@4.1.1:
835 | resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==}
836 | engines: {node: '>= 14.18.0'}
837 |
838 | resolve@1.22.10:
839 | resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
840 | engines: {node: '>= 0.4'}
841 | hasBin: true
842 |
843 | rollup@4.34.6:
844 | resolution: {integrity: sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==}
845 | engines: {node: '>=18.0.0', npm: '>=8.0.0'}
846 | hasBin: true
847 |
848 | sade@1.8.1:
849 | resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
850 | engines: {node: '>=6'}
851 |
852 | set-cookie-parser@2.7.1:
853 | resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
854 |
855 | sirv@3.0.0:
856 | resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==}
857 | engines: {node: '>=18'}
858 |
859 | source-map-js@1.2.1:
860 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
861 | engines: {node: '>=0.10.0'}
862 |
863 | supports-preserve-symlinks-flag@1.0.0:
864 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
865 | engines: {node: '>= 0.4'}
866 |
867 | svelte-check@4.1.4:
868 | resolution: {integrity: sha512-v0j7yLbT29MezzaQJPEDwksybTE2Ups9rUxEXy92T06TiA0cbqcO8wAOwNUVkFW6B0hsYHA+oAX3BS8b/2oHtw==}
869 | engines: {node: '>= 18.0.0'}
870 | hasBin: true
871 | peerDependencies:
872 | svelte: ^4.0.0 || ^5.0.0-next.0
873 | typescript: '>=5.0.0'
874 |
875 | svelte@5.20.2:
876 | resolution: {integrity: sha512-aYXJreNUiyTob0QOzRZeBXZMGeFZDch6SrSRV8QTncZb6zj0O3BEdUzPpojuHQ1pTvk+KX7I6rZCXPUf8pTPxA==}
877 | engines: {node: '>=18'}
878 |
879 | svg.draggable.js@2.2.2:
880 | resolution: {integrity: sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==}
881 | engines: {node: '>= 0.8.0'}
882 |
883 | svg.easing.js@2.0.0:
884 | resolution: {integrity: sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==}
885 | engines: {node: '>= 0.8.0'}
886 |
887 | svg.filter.js@2.0.2:
888 | resolution: {integrity: sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==}
889 | engines: {node: '>= 0.8.0'}
890 |
891 | svg.js@2.7.1:
892 | resolution: {integrity: sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==}
893 |
894 | svg.pathmorphing.js@0.1.3:
895 | resolution: {integrity: sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==}
896 | engines: {node: '>= 0.8.0'}
897 |
898 | svg.resize.js@1.4.3:
899 | resolution: {integrity: sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==}
900 | engines: {node: '>= 0.8.0'}
901 |
902 | svg.select.js@2.1.2:
903 | resolution: {integrity: sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==}
904 | engines: {node: '>= 0.8.0'}
905 |
906 | svg.select.js@3.0.1:
907 | resolution: {integrity: sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==}
908 | engines: {node: '>= 0.8.0'}
909 |
910 | svgo@3.3.2:
911 | resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==}
912 | engines: {node: '>=14.0.0'}
913 | hasBin: true
914 |
915 | tailwind-merge@2.6.0:
916 | resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
917 |
918 | tailwindcss@4.0.6:
919 | resolution: {integrity: sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw==}
920 |
921 | tailwindcss@4.0.8:
922 | resolution: {integrity: sha512-Me7N5CKR+D2A1xdWA5t5+kjjT7bwnxZOE6/yDI/ixJdJokszsn2n++mdU5yJwrsTpqFX2B9ZNMBJDwcqk9C9lw==}
923 |
924 | tapable@2.2.1:
925 | resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
926 | engines: {node: '>=6'}
927 |
928 | totalist@3.0.1:
929 | resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
930 | engines: {node: '>=6'}
931 |
932 | typescript@5.7.3:
933 | resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
934 | engines: {node: '>=14.17'}
935 | hasBin: true
936 |
937 | update-browserslist-db@1.1.2:
938 | resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==}
939 | hasBin: true
940 | peerDependencies:
941 | browserslist: '>= 4.21.0'
942 |
943 | vite@6.1.0:
944 | resolution: {integrity: sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==}
945 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
946 | hasBin: true
947 | peerDependencies:
948 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
949 | jiti: '>=1.21.0'
950 | less: '*'
951 | lightningcss: ^1.21.0
952 | sass: '*'
953 | sass-embedded: '*'
954 | stylus: '*'
955 | sugarss: '*'
956 | terser: ^5.16.0
957 | tsx: ^4.8.1
958 | yaml: ^2.4.2
959 | peerDependenciesMeta:
960 | '@types/node':
961 | optional: true
962 | jiti:
963 | optional: true
964 | less:
965 | optional: true
966 | lightningcss:
967 | optional: true
968 | sass:
969 | optional: true
970 | sass-embedded:
971 | optional: true
972 | stylus:
973 | optional: true
974 | sugarss:
975 | optional: true
976 | terser:
977 | optional: true
978 | tsx:
979 | optional: true
980 | yaml:
981 | optional: true
982 |
983 | vitefu@1.0.5:
984 | resolution: {integrity: sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==}
985 | peerDependencies:
986 | vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
987 | peerDependenciesMeta:
988 | vite:
989 | optional: true
990 |
991 | yaml@2.7.0:
992 | resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==}
993 | engines: {node: '>= 14'}
994 | hasBin: true
995 |
996 | zimmerframe@1.1.2:
997 | resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==}
998 |
999 | snapshots:
1000 |
1001 | '@ampproject/remapping@2.3.0':
1002 | dependencies:
1003 | '@jridgewell/gen-mapping': 0.3.8
1004 | '@jridgewell/trace-mapping': 0.3.25
1005 |
1006 | '@esbuild/aix-ppc64@0.24.2':
1007 | optional: true
1008 |
1009 | '@esbuild/android-arm64@0.24.2':
1010 | optional: true
1011 |
1012 | '@esbuild/android-arm@0.24.2':
1013 | optional: true
1014 |
1015 | '@esbuild/android-x64@0.24.2':
1016 | optional: true
1017 |
1018 | '@esbuild/darwin-arm64@0.24.2':
1019 | optional: true
1020 |
1021 | '@esbuild/darwin-x64@0.24.2':
1022 | optional: true
1023 |
1024 | '@esbuild/freebsd-arm64@0.24.2':
1025 | optional: true
1026 |
1027 | '@esbuild/freebsd-x64@0.24.2':
1028 | optional: true
1029 |
1030 | '@esbuild/linux-arm64@0.24.2':
1031 | optional: true
1032 |
1033 | '@esbuild/linux-arm@0.24.2':
1034 | optional: true
1035 |
1036 | '@esbuild/linux-ia32@0.24.2':
1037 | optional: true
1038 |
1039 | '@esbuild/linux-loong64@0.24.2':
1040 | optional: true
1041 |
1042 | '@esbuild/linux-mips64el@0.24.2':
1043 | optional: true
1044 |
1045 | '@esbuild/linux-ppc64@0.24.2':
1046 | optional: true
1047 |
1048 | '@esbuild/linux-riscv64@0.24.2':
1049 | optional: true
1050 |
1051 | '@esbuild/linux-s390x@0.24.2':
1052 | optional: true
1053 |
1054 | '@esbuild/linux-x64@0.24.2':
1055 | optional: true
1056 |
1057 | '@esbuild/netbsd-arm64@0.24.2':
1058 | optional: true
1059 |
1060 | '@esbuild/netbsd-x64@0.24.2':
1061 | optional: true
1062 |
1063 | '@esbuild/openbsd-arm64@0.24.2':
1064 | optional: true
1065 |
1066 | '@esbuild/openbsd-x64@0.24.2':
1067 | optional: true
1068 |
1069 | '@esbuild/sunos-x64@0.24.2':
1070 | optional: true
1071 |
1072 | '@esbuild/win32-arm64@0.24.2':
1073 | optional: true
1074 |
1075 | '@esbuild/win32-ia32@0.24.2':
1076 | optional: true
1077 |
1078 | '@esbuild/win32-x64@0.24.2':
1079 | optional: true
1080 |
1081 | '@floating-ui/core@1.6.9':
1082 | dependencies:
1083 | '@floating-ui/utils': 0.2.9
1084 |
1085 | '@floating-ui/dom@1.6.13':
1086 | dependencies:
1087 | '@floating-ui/core': 1.6.9
1088 | '@floating-ui/utils': 0.2.9
1089 |
1090 | '@floating-ui/utils@0.2.9': {}
1091 |
1092 | '@jridgewell/gen-mapping@0.3.8':
1093 | dependencies:
1094 | '@jridgewell/set-array': 1.2.1
1095 | '@jridgewell/sourcemap-codec': 1.5.0
1096 | '@jridgewell/trace-mapping': 0.3.25
1097 |
1098 | '@jridgewell/resolve-uri@3.1.2': {}
1099 |
1100 | '@jridgewell/set-array@1.2.1': {}
1101 |
1102 | '@jridgewell/sourcemap-codec@1.5.0': {}
1103 |
1104 | '@jridgewell/trace-mapping@0.3.25':
1105 | dependencies:
1106 | '@jridgewell/resolve-uri': 3.1.2
1107 | '@jridgewell/sourcemap-codec': 1.5.0
1108 |
1109 | '@number-flow/svelte@0.3.3(svelte@5.20.2)':
1110 | dependencies:
1111 | esm-env: 1.2.2
1112 | number-flow: 0.5.3
1113 | svelte: 5.20.2
1114 |
1115 | '@polka/url@1.0.0-next.28': {}
1116 |
1117 | '@poppanator/sveltekit-svg@5.0.0(rollup@4.34.6)(svelte@5.20.2)(svgo@3.3.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))':
1118 | dependencies:
1119 | '@rollup/pluginutils': 5.1.4(rollup@4.34.6)
1120 | svelte: 5.20.2
1121 | svgo: 3.3.2
1122 | vite: 6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)
1123 | transitivePeerDependencies:
1124 | - rollup
1125 |
1126 | '@popperjs/core@2.11.8': {}
1127 |
1128 | '@rollup/plugin-node-resolve@15.3.1(rollup@4.34.6)':
1129 | dependencies:
1130 | '@rollup/pluginutils': 5.1.4(rollup@4.34.6)
1131 | '@types/resolve': 1.20.2
1132 | deepmerge: 4.3.1
1133 | is-module: 1.0.0
1134 | resolve: 1.22.10
1135 | optionalDependencies:
1136 | rollup: 4.34.6
1137 |
1138 | '@rollup/pluginutils@5.1.4(rollup@4.34.6)':
1139 | dependencies:
1140 | '@types/estree': 1.0.6
1141 | estree-walker: 2.0.2
1142 | picomatch: 4.0.2
1143 | optionalDependencies:
1144 | rollup: 4.34.6
1145 |
1146 | '@rollup/rollup-android-arm-eabi@4.34.6':
1147 | optional: true
1148 |
1149 | '@rollup/rollup-android-arm64@4.34.6':
1150 | optional: true
1151 |
1152 | '@rollup/rollup-darwin-arm64@4.34.6':
1153 | optional: true
1154 |
1155 | '@rollup/rollup-darwin-x64@4.34.6':
1156 | optional: true
1157 |
1158 | '@rollup/rollup-freebsd-arm64@4.34.6':
1159 | optional: true
1160 |
1161 | '@rollup/rollup-freebsd-x64@4.34.6':
1162 | optional: true
1163 |
1164 | '@rollup/rollup-linux-arm-gnueabihf@4.34.6':
1165 | optional: true
1166 |
1167 | '@rollup/rollup-linux-arm-musleabihf@4.34.6':
1168 | optional: true
1169 |
1170 | '@rollup/rollup-linux-arm64-gnu@4.34.6':
1171 | optional: true
1172 |
1173 | '@rollup/rollup-linux-arm64-musl@4.34.6':
1174 | optional: true
1175 |
1176 | '@rollup/rollup-linux-loongarch64-gnu@4.34.6':
1177 | optional: true
1178 |
1179 | '@rollup/rollup-linux-powerpc64le-gnu@4.34.6':
1180 | optional: true
1181 |
1182 | '@rollup/rollup-linux-riscv64-gnu@4.34.6':
1183 | optional: true
1184 |
1185 | '@rollup/rollup-linux-s390x-gnu@4.34.6':
1186 | optional: true
1187 |
1188 | '@rollup/rollup-linux-x64-gnu@4.34.6':
1189 | optional: true
1190 |
1191 | '@rollup/rollup-linux-x64-musl@4.34.6':
1192 | optional: true
1193 |
1194 | '@rollup/rollup-win32-arm64-msvc@4.34.6':
1195 | optional: true
1196 |
1197 | '@rollup/rollup-win32-ia32-msvc@4.34.6':
1198 | optional: true
1199 |
1200 | '@rollup/rollup-win32-x64-msvc@4.34.6':
1201 | optional: true
1202 |
1203 | '@sveltejs/adapter-auto@4.0.0(@sveltejs/kit@2.17.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))':
1204 | dependencies:
1205 | '@sveltejs/kit': 2.17.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))
1206 | import-meta-resolve: 4.1.0
1207 |
1208 | '@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.17.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))':
1209 | dependencies:
1210 | '@sveltejs/kit': 2.17.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))
1211 |
1212 | '@sveltejs/kit@2.17.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))':
1213 | dependencies:
1214 | '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))
1215 | '@types/cookie': 0.6.0
1216 | cookie: 0.6.0
1217 | devalue: 5.1.1
1218 | esm-env: 1.2.2
1219 | import-meta-resolve: 4.1.0
1220 | kleur: 4.1.5
1221 | magic-string: 0.30.17
1222 | mrmime: 2.0.0
1223 | sade: 1.8.1
1224 | set-cookie-parser: 2.7.1
1225 | sirv: 3.0.0
1226 | svelte: 5.20.2
1227 | vite: 6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)
1228 |
1229 | '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))':
1230 | dependencies:
1231 | '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))
1232 | debug: 4.4.0
1233 | svelte: 5.20.2
1234 | vite: 6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)
1235 | transitivePeerDependencies:
1236 | - supports-color
1237 |
1238 | '@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))':
1239 | dependencies:
1240 | '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)))(svelte@5.20.2)(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))
1241 | debug: 4.4.0
1242 | deepmerge: 4.3.1
1243 | kleur: 4.1.5
1244 | magic-string: 0.30.17
1245 | svelte: 5.20.2
1246 | vite: 6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)
1247 | vitefu: 1.0.5(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))
1248 | transitivePeerDependencies:
1249 | - supports-color
1250 |
1251 | '@tailwindcss/node@4.0.8':
1252 | dependencies:
1253 | enhanced-resolve: 5.18.1
1254 | jiti: 2.4.2
1255 | tailwindcss: 4.0.8
1256 |
1257 | '@tailwindcss/oxide-android-arm64@4.0.8':
1258 | optional: true
1259 |
1260 | '@tailwindcss/oxide-darwin-arm64@4.0.8':
1261 | optional: true
1262 |
1263 | '@tailwindcss/oxide-darwin-x64@4.0.8':
1264 | optional: true
1265 |
1266 | '@tailwindcss/oxide-freebsd-x64@4.0.8':
1267 | optional: true
1268 |
1269 | '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.8':
1270 | optional: true
1271 |
1272 | '@tailwindcss/oxide-linux-arm64-gnu@4.0.8':
1273 | optional: true
1274 |
1275 | '@tailwindcss/oxide-linux-arm64-musl@4.0.8':
1276 | optional: true
1277 |
1278 | '@tailwindcss/oxide-linux-x64-gnu@4.0.8':
1279 | optional: true
1280 |
1281 | '@tailwindcss/oxide-linux-x64-musl@4.0.8':
1282 | optional: true
1283 |
1284 | '@tailwindcss/oxide-win32-arm64-msvc@4.0.8':
1285 | optional: true
1286 |
1287 | '@tailwindcss/oxide-win32-x64-msvc@4.0.8':
1288 | optional: true
1289 |
1290 | '@tailwindcss/oxide@4.0.8':
1291 | optionalDependencies:
1292 | '@tailwindcss/oxide-android-arm64': 4.0.8
1293 | '@tailwindcss/oxide-darwin-arm64': 4.0.8
1294 | '@tailwindcss/oxide-darwin-x64': 4.0.8
1295 | '@tailwindcss/oxide-freebsd-x64': 4.0.8
1296 | '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.8
1297 | '@tailwindcss/oxide-linux-arm64-gnu': 4.0.8
1298 | '@tailwindcss/oxide-linux-arm64-musl': 4.0.8
1299 | '@tailwindcss/oxide-linux-x64-gnu': 4.0.8
1300 | '@tailwindcss/oxide-linux-x64-musl': 4.0.8
1301 | '@tailwindcss/oxide-win32-arm64-msvc': 4.0.8
1302 | '@tailwindcss/oxide-win32-x64-msvc': 4.0.8
1303 |
1304 | '@tailwindcss/vite@4.0.8(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0))':
1305 | dependencies:
1306 | '@tailwindcss/node': 4.0.8
1307 | '@tailwindcss/oxide': 4.0.8
1308 | lightningcss: 1.29.1
1309 | tailwindcss: 4.0.8
1310 | vite: 6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)
1311 |
1312 | '@trysound/sax@0.2.0': {}
1313 |
1314 | '@types/cookie@0.6.0': {}
1315 |
1316 | '@types/estree@1.0.6': {}
1317 |
1318 | '@types/resolve@1.20.2': {}
1319 |
1320 | '@yr/monotone-cubic-spline@1.0.3': {}
1321 |
1322 | acorn-typescript@1.4.13(acorn@8.14.0):
1323 | dependencies:
1324 | acorn: 8.14.0
1325 |
1326 | acorn@8.14.0: {}
1327 |
1328 | apexcharts@3.54.1:
1329 | dependencies:
1330 | '@yr/monotone-cubic-spline': 1.0.3
1331 | svg.draggable.js: 2.2.2
1332 | svg.easing.js: 2.0.0
1333 | svg.filter.js: 2.0.2
1334 | svg.pathmorphing.js: 0.1.3
1335 | svg.resize.js: 1.4.3
1336 | svg.select.js: 3.0.1
1337 |
1338 | aria-query@5.3.2: {}
1339 |
1340 | autoprefixer@10.4.20(postcss@8.5.2):
1341 | dependencies:
1342 | browserslist: 4.24.4
1343 | caniuse-lite: 1.0.30001695
1344 | fraction.js: 4.3.7
1345 | normalize-range: 0.1.2
1346 | picocolors: 1.1.1
1347 | postcss: 8.5.2
1348 | postcss-value-parser: 4.2.0
1349 |
1350 | axobject-query@4.1.0: {}
1351 |
1352 | boolbase@1.0.0: {}
1353 |
1354 | browserslist@4.24.4:
1355 | dependencies:
1356 | caniuse-lite: 1.0.30001695
1357 | electron-to-chromium: 1.5.84
1358 | node-releases: 2.0.19
1359 | update-browserslist-db: 1.1.2(browserslist@4.24.4)
1360 |
1361 | caniuse-lite@1.0.30001695: {}
1362 |
1363 | chokidar@4.0.3:
1364 | dependencies:
1365 | readdirp: 4.1.1
1366 |
1367 | clsx@2.1.1: {}
1368 |
1369 | commander@7.2.0: {}
1370 |
1371 | cookie@0.6.0: {}
1372 |
1373 | css-select@5.1.0:
1374 | dependencies:
1375 | boolbase: 1.0.0
1376 | css-what: 6.1.0
1377 | domhandler: 5.0.3
1378 | domutils: 3.2.2
1379 | nth-check: 2.1.1
1380 |
1381 | css-tree@2.2.1:
1382 | dependencies:
1383 | mdn-data: 2.0.28
1384 | source-map-js: 1.2.1
1385 |
1386 | css-tree@2.3.1:
1387 | dependencies:
1388 | mdn-data: 2.0.30
1389 | source-map-js: 1.2.1
1390 |
1391 | css-what@6.1.0: {}
1392 |
1393 | csso@5.0.5:
1394 | dependencies:
1395 | css-tree: 2.2.1
1396 |
1397 | debounce@2.2.0: {}
1398 |
1399 | debug@4.4.0:
1400 | dependencies:
1401 | ms: 2.1.3
1402 |
1403 | deepmerge@4.3.1: {}
1404 |
1405 | detect-libc@1.0.3: {}
1406 |
1407 | devalue@5.1.1: {}
1408 |
1409 | dom-serializer@2.0.0:
1410 | dependencies:
1411 | domelementtype: 2.3.0
1412 | domhandler: 5.0.3
1413 | entities: 4.5.0
1414 |
1415 | domelementtype@2.3.0: {}
1416 |
1417 | domhandler@5.0.3:
1418 | dependencies:
1419 | domelementtype: 2.3.0
1420 |
1421 | domutils@3.2.2:
1422 | dependencies:
1423 | dom-serializer: 2.0.0
1424 | domelementtype: 2.3.0
1425 | domhandler: 5.0.3
1426 |
1427 | electron-to-chromium@1.5.84: {}
1428 |
1429 | enhanced-resolve@5.18.1:
1430 | dependencies:
1431 | graceful-fs: 4.2.11
1432 | tapable: 2.2.1
1433 |
1434 | entities@4.5.0: {}
1435 |
1436 | esbuild@0.24.2:
1437 | optionalDependencies:
1438 | '@esbuild/aix-ppc64': 0.24.2
1439 | '@esbuild/android-arm': 0.24.2
1440 | '@esbuild/android-arm64': 0.24.2
1441 | '@esbuild/android-x64': 0.24.2
1442 | '@esbuild/darwin-arm64': 0.24.2
1443 | '@esbuild/darwin-x64': 0.24.2
1444 | '@esbuild/freebsd-arm64': 0.24.2
1445 | '@esbuild/freebsd-x64': 0.24.2
1446 | '@esbuild/linux-arm': 0.24.2
1447 | '@esbuild/linux-arm64': 0.24.2
1448 | '@esbuild/linux-ia32': 0.24.2
1449 | '@esbuild/linux-loong64': 0.24.2
1450 | '@esbuild/linux-mips64el': 0.24.2
1451 | '@esbuild/linux-ppc64': 0.24.2
1452 | '@esbuild/linux-riscv64': 0.24.2
1453 | '@esbuild/linux-s390x': 0.24.2
1454 | '@esbuild/linux-x64': 0.24.2
1455 | '@esbuild/netbsd-arm64': 0.24.2
1456 | '@esbuild/netbsd-x64': 0.24.2
1457 | '@esbuild/openbsd-arm64': 0.24.2
1458 | '@esbuild/openbsd-x64': 0.24.2
1459 | '@esbuild/sunos-x64': 0.24.2
1460 | '@esbuild/win32-arm64': 0.24.2
1461 | '@esbuild/win32-ia32': 0.24.2
1462 | '@esbuild/win32-x64': 0.24.2
1463 |
1464 | escalade@3.2.0: {}
1465 |
1466 | esm-env@1.2.2: {}
1467 |
1468 | esrap@1.4.5:
1469 | dependencies:
1470 | '@jridgewell/sourcemap-codec': 1.5.0
1471 |
1472 | estree-walker@2.0.2: {}
1473 |
1474 | fdir@6.4.3(picomatch@4.0.2):
1475 | optionalDependencies:
1476 | picomatch: 4.0.2
1477 |
1478 | flowbite-datepicker@1.3.2(rollup@4.34.6):
1479 | dependencies:
1480 | '@rollup/plugin-node-resolve': 15.3.1(rollup@4.34.6)
1481 | flowbite: 2.5.2(rollup@4.34.6)
1482 | transitivePeerDependencies:
1483 | - rollup
1484 |
1485 | flowbite-svelte@0.47.4(rollup@4.34.6)(svelte@5.20.2):
1486 | dependencies:
1487 | '@floating-ui/dom': 1.6.13
1488 | apexcharts: 3.54.1
1489 | flowbite: 2.5.2(rollup@4.34.6)
1490 | svelte: 5.20.2
1491 | tailwind-merge: 2.6.0
1492 | transitivePeerDependencies:
1493 | - rollup
1494 |
1495 | flowbite@2.5.2(rollup@4.34.6):
1496 | dependencies:
1497 | '@popperjs/core': 2.11.8
1498 | flowbite-datepicker: 1.3.2(rollup@4.34.6)
1499 | mini-svg-data-uri: 1.4.4
1500 | transitivePeerDependencies:
1501 | - rollup
1502 |
1503 | flowbite@3.1.2(rollup@4.34.6):
1504 | dependencies:
1505 | '@popperjs/core': 2.11.8
1506 | flowbite-datepicker: 1.3.2(rollup@4.34.6)
1507 | mini-svg-data-uri: 1.4.4
1508 | postcss: 8.5.2
1509 | transitivePeerDependencies:
1510 | - rollup
1511 |
1512 | fraction.js@4.3.7: {}
1513 |
1514 | fsevents@2.3.3:
1515 | optional: true
1516 |
1517 | function-bind@1.1.2: {}
1518 |
1519 | graceful-fs@4.2.11: {}
1520 |
1521 | hasown@2.0.2:
1522 | dependencies:
1523 | function-bind: 1.1.2
1524 |
1525 | import-meta-resolve@4.1.0: {}
1526 |
1527 | is-core-module@2.16.1:
1528 | dependencies:
1529 | hasown: 2.0.2
1530 |
1531 | is-module@1.0.0: {}
1532 |
1533 | is-reference@3.0.3:
1534 | dependencies:
1535 | '@types/estree': 1.0.6
1536 |
1537 | jiti@2.4.2: {}
1538 |
1539 | kleur@4.1.5: {}
1540 |
1541 | lightningcss-darwin-arm64@1.29.1:
1542 | optional: true
1543 |
1544 | lightningcss-darwin-x64@1.29.1:
1545 | optional: true
1546 |
1547 | lightningcss-freebsd-x64@1.29.1:
1548 | optional: true
1549 |
1550 | lightningcss-linux-arm-gnueabihf@1.29.1:
1551 | optional: true
1552 |
1553 | lightningcss-linux-arm64-gnu@1.29.1:
1554 | optional: true
1555 |
1556 | lightningcss-linux-arm64-musl@1.29.1:
1557 | optional: true
1558 |
1559 | lightningcss-linux-x64-gnu@1.29.1:
1560 | optional: true
1561 |
1562 | lightningcss-linux-x64-musl@1.29.1:
1563 | optional: true
1564 |
1565 | lightningcss-win32-arm64-msvc@1.29.1:
1566 | optional: true
1567 |
1568 | lightningcss-win32-x64-msvc@1.29.1:
1569 | optional: true
1570 |
1571 | lightningcss@1.29.1:
1572 | dependencies:
1573 | detect-libc: 1.0.3
1574 | optionalDependencies:
1575 | lightningcss-darwin-arm64: 1.29.1
1576 | lightningcss-darwin-x64: 1.29.1
1577 | lightningcss-freebsd-x64: 1.29.1
1578 | lightningcss-linux-arm-gnueabihf: 1.29.1
1579 | lightningcss-linux-arm64-gnu: 1.29.1
1580 | lightningcss-linux-arm64-musl: 1.29.1
1581 | lightningcss-linux-x64-gnu: 1.29.1
1582 | lightningcss-linux-x64-musl: 1.29.1
1583 | lightningcss-win32-arm64-msvc: 1.29.1
1584 | lightningcss-win32-x64-msvc: 1.29.1
1585 |
1586 | locate-character@3.0.0: {}
1587 |
1588 | magic-string@0.30.17:
1589 | dependencies:
1590 | '@jridgewell/sourcemap-codec': 1.5.0
1591 |
1592 | mdn-data@2.0.28: {}
1593 |
1594 | mdn-data@2.0.30: {}
1595 |
1596 | mini-svg-data-uri@1.4.4: {}
1597 |
1598 | mri@1.2.0: {}
1599 |
1600 | mrmime@2.0.0: {}
1601 |
1602 | ms@2.1.3: {}
1603 |
1604 | nanoid@3.3.8: {}
1605 |
1606 | node-releases@2.0.19: {}
1607 |
1608 | normalize-range@0.1.2: {}
1609 |
1610 | nth-check@2.1.1:
1611 | dependencies:
1612 | boolbase: 1.0.0
1613 |
1614 | number-flow@0.5.3:
1615 | dependencies:
1616 | esm-env: 1.2.2
1617 |
1618 | path-parse@1.0.7: {}
1619 |
1620 | picocolors@1.1.1: {}
1621 |
1622 | picomatch@4.0.2: {}
1623 |
1624 | postcss-value-parser@4.2.0: {}
1625 |
1626 | postcss@8.5.1:
1627 | dependencies:
1628 | nanoid: 3.3.8
1629 | picocolors: 1.1.1
1630 | source-map-js: 1.2.1
1631 |
1632 | postcss@8.5.2:
1633 | dependencies:
1634 | nanoid: 3.3.8
1635 | picocolors: 1.1.1
1636 | source-map-js: 1.2.1
1637 |
1638 | readdirp@4.1.1: {}
1639 |
1640 | resolve@1.22.10:
1641 | dependencies:
1642 | is-core-module: 2.16.1
1643 | path-parse: 1.0.7
1644 | supports-preserve-symlinks-flag: 1.0.0
1645 |
1646 | rollup@4.34.6:
1647 | dependencies:
1648 | '@types/estree': 1.0.6
1649 | optionalDependencies:
1650 | '@rollup/rollup-android-arm-eabi': 4.34.6
1651 | '@rollup/rollup-android-arm64': 4.34.6
1652 | '@rollup/rollup-darwin-arm64': 4.34.6
1653 | '@rollup/rollup-darwin-x64': 4.34.6
1654 | '@rollup/rollup-freebsd-arm64': 4.34.6
1655 | '@rollup/rollup-freebsd-x64': 4.34.6
1656 | '@rollup/rollup-linux-arm-gnueabihf': 4.34.6
1657 | '@rollup/rollup-linux-arm-musleabihf': 4.34.6
1658 | '@rollup/rollup-linux-arm64-gnu': 4.34.6
1659 | '@rollup/rollup-linux-arm64-musl': 4.34.6
1660 | '@rollup/rollup-linux-loongarch64-gnu': 4.34.6
1661 | '@rollup/rollup-linux-powerpc64le-gnu': 4.34.6
1662 | '@rollup/rollup-linux-riscv64-gnu': 4.34.6
1663 | '@rollup/rollup-linux-s390x-gnu': 4.34.6
1664 | '@rollup/rollup-linux-x64-gnu': 4.34.6
1665 | '@rollup/rollup-linux-x64-musl': 4.34.6
1666 | '@rollup/rollup-win32-arm64-msvc': 4.34.6
1667 | '@rollup/rollup-win32-ia32-msvc': 4.34.6
1668 | '@rollup/rollup-win32-x64-msvc': 4.34.6
1669 | fsevents: 2.3.3
1670 |
1671 | sade@1.8.1:
1672 | dependencies:
1673 | mri: 1.2.0
1674 |
1675 | set-cookie-parser@2.7.1: {}
1676 |
1677 | sirv@3.0.0:
1678 | dependencies:
1679 | '@polka/url': 1.0.0-next.28
1680 | mrmime: 2.0.0
1681 | totalist: 3.0.1
1682 |
1683 | source-map-js@1.2.1: {}
1684 |
1685 | supports-preserve-symlinks-flag@1.0.0: {}
1686 |
1687 | svelte-check@4.1.4(picomatch@4.0.2)(svelte@5.20.2)(typescript@5.7.3):
1688 | dependencies:
1689 | '@jridgewell/trace-mapping': 0.3.25
1690 | chokidar: 4.0.3
1691 | fdir: 6.4.3(picomatch@4.0.2)
1692 | picocolors: 1.1.1
1693 | sade: 1.8.1
1694 | svelte: 5.20.2
1695 | typescript: 5.7.3
1696 | transitivePeerDependencies:
1697 | - picomatch
1698 |
1699 | svelte@5.20.2:
1700 | dependencies:
1701 | '@ampproject/remapping': 2.3.0
1702 | '@jridgewell/sourcemap-codec': 1.5.0
1703 | '@types/estree': 1.0.6
1704 | acorn: 8.14.0
1705 | acorn-typescript: 1.4.13(acorn@8.14.0)
1706 | aria-query: 5.3.2
1707 | axobject-query: 4.1.0
1708 | clsx: 2.1.1
1709 | esm-env: 1.2.2
1710 | esrap: 1.4.5
1711 | is-reference: 3.0.3
1712 | locate-character: 3.0.0
1713 | magic-string: 0.30.17
1714 | zimmerframe: 1.1.2
1715 |
1716 | svg.draggable.js@2.2.2:
1717 | dependencies:
1718 | svg.js: 2.7.1
1719 |
1720 | svg.easing.js@2.0.0:
1721 | dependencies:
1722 | svg.js: 2.7.1
1723 |
1724 | svg.filter.js@2.0.2:
1725 | dependencies:
1726 | svg.js: 2.7.1
1727 |
1728 | svg.js@2.7.1: {}
1729 |
1730 | svg.pathmorphing.js@0.1.3:
1731 | dependencies:
1732 | svg.js: 2.7.1
1733 |
1734 | svg.resize.js@1.4.3:
1735 | dependencies:
1736 | svg.js: 2.7.1
1737 | svg.select.js: 2.1.2
1738 |
1739 | svg.select.js@2.1.2:
1740 | dependencies:
1741 | svg.js: 2.7.1
1742 |
1743 | svg.select.js@3.0.1:
1744 | dependencies:
1745 | svg.js: 2.7.1
1746 |
1747 | svgo@3.3.2:
1748 | dependencies:
1749 | '@trysound/sax': 0.2.0
1750 | commander: 7.2.0
1751 | css-select: 5.1.0
1752 | css-tree: 2.3.1
1753 | css-what: 6.1.0
1754 | csso: 5.0.5
1755 | picocolors: 1.1.1
1756 |
1757 | tailwind-merge@2.6.0: {}
1758 |
1759 | tailwindcss@4.0.6: {}
1760 |
1761 | tailwindcss@4.0.8: {}
1762 |
1763 | tapable@2.2.1: {}
1764 |
1765 | totalist@3.0.1: {}
1766 |
1767 | typescript@5.7.3: {}
1768 |
1769 | update-browserslist-db@1.1.2(browserslist@4.24.4):
1770 | dependencies:
1771 | browserslist: 4.24.4
1772 | escalade: 3.2.0
1773 | picocolors: 1.1.1
1774 |
1775 | vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0):
1776 | dependencies:
1777 | esbuild: 0.24.2
1778 | postcss: 8.5.1
1779 | rollup: 4.34.6
1780 | optionalDependencies:
1781 | fsevents: 2.3.3
1782 | jiti: 2.4.2
1783 | lightningcss: 1.29.1
1784 | yaml: 2.7.0
1785 |
1786 | vitefu@1.0.5(vite@6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)):
1787 | optionalDependencies:
1788 | vite: 6.1.0(jiti@2.4.2)(lightningcss@1.29.1)(yaml@2.7.0)
1789 |
1790 | yaml@2.7.0:
1791 | optional: true
1792 |
1793 | zimmerframe@1.1.2: {}
1794 |
--------------------------------------------------------------------------------
/ui/src/app.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 |
3 | @plugin "flowbite/plugin";
4 | @source "../node_modules/flowbite";
5 |
6 | @custom-variant dark (&:where(.dark, .dark *));
7 |
8 | @theme {
9 | --color-primary-50: #FFF5F2;
10 | --color-primary-100: #FFF1EE;
11 | --color-primary-200: #FFE4DE;
12 | --color-primary-300: #FFD5CC;
13 | --color-primary-400: #FFBCAD;
14 | --color-primary-500: #FE795D;
15 | --color-primary-600: #EF562F;
16 | --color-primary-700: #EB4F27;
17 | --color-primary-800: #CC4522;
18 | --color-primary-900: #A5371B;
19 |
20 | --font-sans: 'Inter', 'ui-sans-serif', 'system-ui', '-apple-system', 'system-ui', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans', 'sans-serif', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
21 | --font-body: 'Inter', 'ui-sans-serif', 'system-ui', '-apple-system', 'system-ui', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans', 'sans-serif', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
22 | --font-mono: 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', 'monospace';
23 | }
--------------------------------------------------------------------------------
/ui/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://svelte.dev/docs/kit/types#app.d.ts
2 | // for information about these interfaces
3 | import '@poppanator/sveltekit-svg/dist/svg'
4 |
5 | declare global {
6 | namespace App {
7 | // interface Error {}
8 | // interface Locals {}
9 | // interface PageData {}
10 | // interface PageState {}
11 | // interface Platform {}
12 | }
13 | }
14 |
15 | export {};
16 |
--------------------------------------------------------------------------------
/ui/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %sveltekit.head%
7 |
8 |
9 | %sveltekit.body%
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ui/src/icons/fox.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ui/src/lib/stores.ts:
--------------------------------------------------------------------------------
1 | import { writable } from "svelte/store";
2 | import type { Writable } from "svelte/store";
3 | import type { Flow } from "$lib/types";
4 |
5 | export const newFlowHandlerStore: Writable<(flow: Flow) => void> = writable();
6 | export const editFlowHandlerStore: Writable<(flow: Flow) => void> = writable();
7 |
--------------------------------------------------------------------------------
/ui/src/lib/types.ts:
--------------------------------------------------------------------------------
1 | import type { MouseEventHandler } from "svelte/elements";
2 |
3 | export interface Flow {
4 | id: string | undefined;
5 | name: string;
6 | description: string;
7 | amount: number;
8 | icon?: string;
9 | }
10 |
11 | export interface PageButton {
12 | name: string;
13 | clickHandle?: MouseEventHandler;
14 | color?: "alternative" | "none" | "red" | "yellow" | "green" | "purple" | "blue" | "light" | "dark" | "primary";
15 | hidden?: boolean;
16 | }
17 |
--------------------------------------------------------------------------------
/ui/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
123 |
124 |
129 |
130 |
131 | Thrifty
132 |
133 |
134 |
135 |
136 |
137 | {#snippet addButton(button: PageButton, type?: "submit" | "reset" | "button" | null | undefined)}
138 |
142 | {/snippet}
143 |
144 |
145 |
146 |
147 | Thrifty
148 |
149 |
150 | {#each buttons as button}
151 |
156 | {/each}
157 |
158 |
159 |
160 | {@render children()}
161 |
162 |
197 |
198 |
199 |
203 |
--------------------------------------------------------------------------------
/ui/src/routes/+layout.ts:
--------------------------------------------------------------------------------
1 | export const prerender = true;
--------------------------------------------------------------------------------
/ui/src/routes/+page.svelte:
--------------------------------------------------------------------------------
1 |
114 |
115 | {#snippet flowCard(flows: Flow[])}
116 |
117 | {#each flows as flow}
118 |
119 |
120 |
121 |

122 |
123 |
124 | {flow.name}
125 |
126 |
127 | {flow.description}
128 |
129 |
130 |
131 |
132 |
133 |
134 | {#if (sharedState.isEditMode)}
135 |
141 | {/if}
142 |
143 |
144 | {/each}
145 |
146 | {/snippet}
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | Total
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | {#if (flows.length > 0)}
164 |
165 | {/if}
166 |
167 | {@render flowCard(getIncome(flows))}
168 | {@render flowCard(getExpenses(flows))}
169 |
170 |
171 |
--------------------------------------------------------------------------------
/ui/src/routes/layout.svelte.ts:
--------------------------------------------------------------------------------
1 | export const sharedState = $state({"isEditMode": false});
2 |
--------------------------------------------------------------------------------
/ui/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tiehfood/thrifty/03c6e67a2db40890b3cae3bfad27c480d1cdaa10/ui/static/favicon.png
--------------------------------------------------------------------------------
/ui/static/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tiehfood/thrifty/03c6e67a2db40890b3cae3bfad27c480d1cdaa10/ui/static/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/ui/static/icons/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tiehfood/thrifty/03c6e67a2db40890b3cae3bfad27c480d1cdaa10/ui/static/icons/android-chrome-512x512.png
--------------------------------------------------------------------------------
/ui/static/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tiehfood/thrifty/03c6e67a2db40890b3cae3bfad27c480d1cdaa10/ui/static/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/ui/static/manifest.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "icons": [
3 | {
4 | "src": "./icons/android-chrome-192x192.png",
5 | "sizes": "192x192",
6 | "type": "image/png"
7 | },
8 | {
9 | "src": "./icons/android-chrome-512x512.png",
10 | "sizes": "512x512",
11 | "type": "image/png"
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/ui/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-static';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | preprocess: vitePreprocess(),
7 | kit: {
8 | adapter: adapter()
9 | }
10 | };
11 |
12 | export default config;
13 |
--------------------------------------------------------------------------------
/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "moduleResolution": "bundler"
13 | }
14 | // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
15 | // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
16 | //
17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
18 | // from the referenced tsconfig.json - TypeScript does not merge them in
19 | }
20 |
--------------------------------------------------------------------------------
/ui/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { defineConfig } from 'vite';
3 | import tailwindcss from '@tailwindcss/vite'
4 | import svg from '@poppanator/sveltekit-svg'
5 |
6 | export default defineConfig({
7 | plugins: [
8 | tailwindcss(),
9 | sveltekit(),
10 | svg({
11 | includePaths: ['./src/icons/']
12 | })
13 | ]
14 | });
15 |
--------------------------------------------------------------------------------