Compare commits

...

208 Commits

Author SHA1 Message Date
Yurii Motov 11fef0e167 Merge remote-tracking branch 'upstream/master' into migrate-to-@hey-api/openapi-ts-0.97.3 2026-07-31 17:19:16 +02:00
github-actions[bot] 546f18469c 📝 Update release notes
[skip ci]
2026-07-30 19:46:08 +00:00
Sebastián Ramírez daae6e1434 👷 Improve workflows for private repos (#2394) 2026-07-30 19:45:35 +00:00
github-actions[bot] 78c699079e 📝 Update release notes
[skip ci]
2026-07-30 19:41:33 +00:00
Sebastián Ramírez e80d92452e ♻️ Serve frontend from FastAPI (#2393)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-30 21:41:08 +02:00
github-actions[bot] 588dfd46e5 📝 Update release notes
[skip ci]
2026-07-30 14:44:59 +00:00
Sebastián Ramírez eb9275a2f8 🔧 Refactor PostgreSQL env var config (#2392) 2026-07-30 16:44:24 +02:00
github-actions[bot] c9e70d65c7 📝 Update release notes
[skip ci]
2026-07-27 17:26:48 +00:00
Sebastián Ramírez 7d0d2a890e ⬆️ Upgrade latest-changes to 0.7.1 (#2389) 2026-07-27 19:25:20 +02:00
Yurii Motov 48f0329b95 Merge remote-tracking branch 'upstream/master' into migrate-to-@hey-api/openapi-ts-0.97.3 2026-07-24 14:05:25 +02:00
Yurii Motov 3107696ad9 Migrate Playwright privateApi helper to new client API 2026-07-24 11:34:08 +02:00
Yurii Motov 2565fef91e Migrate Items and UserSettings components to new client API 2026-07-24 11:34:08 +02:00
Yurii Motov 3bdfa168b6 Migrate Admin user components to new client API 2026-07-24 11:32:01 +02:00
Yurii Motov 865a31954f Migrate route loaders and password flows to new client API 2026-07-24 11:27:44 +02:00
Yurii Motov 266a7a37c8 Migrate useAuth to new client API 2026-07-24 10:54:17 +02:00
Yurii Motov 5fd1470db0 Migrate client config and error handling to client-axios 2026-07-24 10:54:17 +02:00
Yurii Motov fcbc012e57 Generate client (types.gen.ts step 4) 2026-07-24 10:20:50 +02:00
Yurii Motov 0c77fac116 Generate client (types.gen.ts step 3) 2026-07-24 10:20:50 +02:00
Yurii Motov c544e44b6a Generate client (types.gen.ts step 2) 2026-07-24 10:20:50 +02:00
Yurii Motov bccfb48a6b Generate client (types.gen.ts step 1) 2026-07-24 10:20:50 +02:00
Yurii Motov e5324f5ade Generate client (except types.gen.ts) 2026-07-24 09:37:21 +02:00
Yurii Motov 6a7c22461b Update openapi-ts.config.ts to apply migration steps 2026-07-24 09:37:21 +02:00
Yurii Motov 2375da34f2 Update bun.lock 2026-07-23 15:56:07 +02:00
github-actions[bot] 5b358ea6f4 📝 Update release notes
[skip ci]
2026-07-23 09:45:02 +00:00
dependabot[bot] 1c82b25096 Bump axios from 1.16.0 to 1.18.0 in /frontend (#2386)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
2026-07-23 11:44:27 +02:00
github-actions[bot] 4d3d5e92c1 📝 Update release notes
[skip ci]
2026-07-14 09:10:28 +00:00
Sebastián Ramírez 402ca985bc ⬆ Bump pre-commit hooks (#2382)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-14 11:10:00 +02:00
github-actions[bot] 7d80b8534e 📝 Update release notes
[skip ci]
2026-07-13 14:19:34 +00:00
dependabot[bot] 183bcf5189 ⬆ Bump the python-packages group across 1 directory with 2 updates (#2380)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 16:18:45 +02:00
github-actions[bot] 4e5284b884 📝 Update release notes
[skip ci]
2026-07-13 14:18:25 +00:00
dependabot[bot] 7feaeb309e ⬆ Update sentry-sdk[fastapi] requirement from <3.0.0,>=2.0.0 to >=2.63.0,<3.0.0 (#2373)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 14:17:56 +00:00
github-actions[bot] 95580c0191 📝 Update release notes
[skip ci]
2026-07-13 12:57:13 +00:00
dependabot[bot] 886d05d597 ⬆ Bump the github-actions group across 1 directory with 5 updates (#2379)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 14:56:49 +02:00
github-actions[bot] 536b73011f 📝 Update release notes
[skip ci]
2026-07-13 09:22:43 +00:00
Yurii Motov 349a7537dc 👷 Add GH workflow to bump pre-commit hook versions (#2363) 2026-07-13 11:17:39 +02:00
github-actions[bot] 4cd0d9e51a 📝 Update release notes
[skip ci]
2026-07-04 20:52:27 +00:00
dependabot[bot] 04a8c0c8a7 Bump @hey-api/openapi-ts from 0.73.0 to 0.97.3 in /frontend
Bumps [@hey-api/openapi-ts](https://github.com/hey-api/hey-api/tree/HEAD/packages/openapi-ts) from 0.73.0 to 0.97.3.
- [Release notes](https://github.com/hey-api/hey-api/releases)
- [Changelog](https://github.com/hey-api/hey-api/blob/main/packages/openapi-ts/CHANGELOG.md)
- [Commits](https://github.com/hey-api/hey-api/commits/@hey-api/openapi-ts@0.97.3/packages/openapi-ts)

---
updated-dependencies:
- dependency-name: "@hey-api/openapi-ts"
  dependency-version: 0.97.3
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-04 20:52:04 +00:00
Yurii Motov 0def368739 🔧 Set Dependabot schedule interval to "monthly" (#2364) 2026-07-04 22:52:04 +02:00
github-actions[bot] d85ceb5025 📝 Update release notes
[skip ci]
2026-07-04 20:50:13 +00:00
dependabot[bot] f900e07cc3 ⬆ Bump the npm-packages group across 1 directory with 37 updates (#2333)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
Co-authored-by: Yurii Motov <109919500+YuriiMotov@users.noreply.github.com>
Co-authored-by: Alejandra <alejsdev@gmail.com>
2026-07-04 22:49:46 +02:00
github-actions[bot] 9e9b3a786b 📝 Update release notes
[skip ci]
2026-07-04 19:43:58 +00:00
github-actions[bot] b5a2b458cc 📝 Update release notes
[skip ci]
2026-07-04 19:40:25 +00:00
Sebastián Ramírez abecc7782e 👷 Update latest-changes (#2375) 2026-07-04 19:40:02 +00:00
github-actions[bot] 9eaf8185f3 📝 Update release notes
[skip ci]
2026-07-04 18:55:20 +00:00
dependabot[bot] 34d14a4e76 ⬆ Bump playwright from v1.58.2-noble to v1.61.1-noble in /frontend in the docker group across 1 directory (#2361)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alejandra <90076947+alejsdev@users.noreply.github.com>
Co-authored-by: Alejandra <alejsdev@gmail.com>
2026-07-04 20:54:56 +02:00
Bryan Mowrey 95e83b1352 📝 Improve README screenshot alt text (#2359) 2026-07-04 14:13:15 +02:00
github-actions[bot] a75858514a 📝 Update release notes
[skip ci]
2026-07-03 19:13:30 +00:00
Sebastián Ramírez 4214d8b1a8 ⬆️ Update issue-manager to 0.8.1 (#2368) 2026-07-03 21:08:43 +02:00
github-actions[bot] dbfd760dcc 📝 Update release notes
[skip ci]
2026-07-03 15:56:04 +00:00
dependabot[bot] 9a807a12c5 ⬆ Bump emails from 0.6 to 1.1.2 in the python-packages group across 1 directory (#2369)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
2026-07-03 17:55:40 +02:00
github-actions[bot] 0bc5df81a9 📝 Update release notes
[skip ci]
2026-07-03 15:32:52 +00:00
dependabot[bot] 7aecfb098b ⬆ Bump https://github.com/crate-ci/typos from v1.46.0 to 1.47.2 in the pre-commit group across 1 directory (#2343)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-03 17:32:24 +02:00
github-actions[bot] e4153f7cbf 📝 Update release notes
[skip ci]
2026-07-03 15:29:27 +00:00
dependabot[bot] 18a28cdb1e ⬆ Bump actions/checkout from 6.0.3 to 7.0.0 in the github-actions group (#2362)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-03 17:29:01 +02:00
github-actions[bot] 779323df09 📝 Update release notes
[skip ci]
2026-07-03 14:43:43 +00:00
Sebastián Ramírez 7cfe46bfdf ⬆️ Update latest-changes to 0.6.1 (#2367) 2026-07-03 16:41:54 +02:00
github-actions[bot] 3685fb6625 📝 Update release notes
[skip ci]
2026-06-26 15:57:23 +00:00
Sebastián Ramírez 119e31fba2 🔧 Use FastAPI entrypoint (#2360) 2026-06-26 15:57:03 +00:00
github-actions[bot] 6bc7fa47e3 📝 Update release notes
[skip ci]
2026-06-26 13:35:10 +00:00
Sebastián Ramírez 33c75e4cab ⬆️ Upgrade FastAPI version (#2357) 2026-06-26 13:34:50 +00:00
github-actions[bot] 6d63f81979 📝 Update release notes
[skip ci]
2026-06-26 13:30:51 +00:00
Sebastián Ramírez 77be72439c ♻️ Refactor models to improve types (#2356) 2026-06-26 13:30:32 +00:00
github-actions[bot] a586c1c05c 📝 Update release notes
[skip ci]
2026-06-26 13:19:31 +00:00
Sebastián Ramírez 8c6e31a8f7 ♻️ Add library-skills for FastAPI and SQLModel (#2354) 2026-06-26 15:19:12 +02:00
github-actions[bot] 8e4fd7c722 📝 Update release notes
[skip ci]
2026-06-25 16:26:19 +00:00
Sebastián Ramírez 54de75638b ➕ Move prek to top level dependencies (#2353) 2026-06-25 16:26:00 +00:00
github-actions[bot] 49a94eaab4 📝 Update release notes
[skip ci]
2026-06-25 16:19:39 +00:00
Sebastián Ramírez 14728b636c ⬆️ Upgrade Python to 3.14 (#2352) 2026-06-25 16:19:18 +00:00
github-actions[bot] a727535488 📝 Update release notes
[skip ci]
2026-06-25 16:04:26 +00:00
Sebastián Ramírez 95a7a61c8b 🔧 Sort keys in pyproject.toml (#2350) 2026-06-25 16:04:06 +00:00
github-actions[bot] 70461bb937 📝 Update release notes
[skip ci]
2026-06-24 22:40:06 +00:00
Sebastián Ramírez 4179f15185 👷 Simplify pull request workflow triggers (#2349) 2026-06-25 00:35:47 +02:00
github-actions[bot] 787e79a463 📝 Update release notes
[skip ci]
2026-06-24 12:28:09 +00:00
Sebastián Ramírez 67250999a5 👷 Update issue-manager to 0.7.1 (#2348) 2026-06-24 14:24:11 +02:00
github-actions[bot] 2a6eeda629 📝 Update release notes
[skip ci]
2026-06-23 23:16:41 +00:00
Sebastián Ramírez 6bb47f9c9c ⬆️ Update issue-manager to 0.7.0 (#2347) 2026-06-24 01:15:19 +02:00
github-actions[bot] 248d7d11e7 📝 Update release notes
[skip ci]
2026-06-21 20:51:02 +00:00
Sebastián Ramírez 98d93cfee8 🔒️ Update zizmor workflow security checks (#2345) 2026-06-21 22:50:46 +02:00
github-actions[bot] 2a56db28b2 📝 Update release notes
[skip ci]
2026-06-19 20:44:06 +00:00
Yurii Motov 13c4678515 👷 Add pre-commit hook to catch typos (#2317) 2026-06-19 22:43:47 +02:00
github-actions[bot] cd83fc10ca 📝 Update release notes
[skip ci]
2026-06-16 10:22:19 +00:00
dependabot[bot] 3beccfe716 Bump form-data from 4.0.5 to 4.0.6 in /frontend (#2337)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
2026-06-16 12:21:57 +02:00
github-actions[bot] 61da1d1f8d 📝 Update release notes
[skip ci]
2026-06-16 09:51:30 +00:00
dependabot[bot] 9ec63eae4a ⬆ Bump starlette from 1.0.1 to 1.3.1 (#2338)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <109919500+YuriiMotov@users.noreply.github.com>
2026-06-16 11:51:09 +02:00
github-actions[bot] 7975107f31 📝 Update release notes
[skip ci]
2026-06-16 09:37:40 +00:00
dependabot[bot] f459c20a17 ⬆ Bump the python-packages group across 1 directory with 16 updates (#2340)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
2026-06-16 11:37:12 +02:00
github-actions[bot] ee684d67db 📝 Update release notes
[skip ci]
2026-06-16 08:39:44 +00:00
dependabot[bot] 53f0cf4488 ⬆ Bump the github-actions group with 2 updates (#2332)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
2026-06-16 08:39:25 +00:00
github-actions[bot] a54720d4a1 📝 Update release notes
[skip ci]
2026-06-16 08:25:38 +00:00
dependabot[bot] 20731272ca ⬆ Bump pyjwt from 2.12.0 to 2.13.0 (#2336)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
2026-06-16 10:25:07 +02:00
github-actions[bot] 1c1175eb50 📝 Update release notes
[skip ci]
2026-06-11 19:44:57 +00:00
dependabot[bot] 3814f249b2 ⬆ Bump pytest from 7.4.4 to 9.0.3 (#2330)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
2026-06-11 21:44:31 +02:00
github-actions[bot] 31e9f272f4 📝 Update release notes
[skip ci]
2026-06-11 19:24:41 +00:00
dependabot[bot] e6d4aead1c ⬆ Bump axios from 1.13.5 to 1.16.0 in /frontend (#2323)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
Co-authored-by: Yurii Motov <109919500+YuriiMotov@users.noreply.github.com>
2026-06-11 21:24:23 +02:00
github-actions[bot] 9af21b878e 📝 Update release notes
[skip ci]
2026-06-11 14:35:17 +00:00
dependabot[bot] 49e9c5216f ⬆ Bump lxml from 6.0.2 to 6.1.0 (#2266)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <109919500+YuriiMotov@users.noreply.github.com>
2026-06-11 16:34:49 +02:00
github-actions[bot] 5027f2effb 📝 Update release notes
[skip ci]
2026-06-11 14:27:51 +00:00
dependabot[bot] e39f0630e0 ⬆ Bump mako from 1.3.10 to 1.3.12 (#2278)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-11 16:27:26 +02:00
github-actions[bot] 5ee2fe25b2 📝 Update release notes
[skip ci]
2026-06-11 13:49:23 +00:00
dependabot[bot] 469273290f ⬆ Bump pygments from 2.19.2 to 2.20.0 (#2248)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-11 15:49:04 +02:00
github-actions[bot] a6c8ec01e5 📝 Update release notes
[skip ci]
2026-06-11 13:48:38 +00:00
dependabot[bot] 085686b31f ⬆ Bump requests from 2.32.5 to 2.33.0 (#2245)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-11 15:48:10 +02:00
github-actions[bot] 2097350645 📝 Update release notes
[skip ci]
2026-06-11 13:44:35 +00:00
dependabot[bot] 5fcaab8bab ⬆ Bump python-dotenv from 1.2.1 to 1.2.2 (#2265)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-11 15:44:09 +02:00
github-actions[bot] 6a2b002a8e 📝 Update release notes
[skip ci]
2026-06-11 13:43:32 +00:00
dependabot[bot] 4f22958c3f ⬆ Bump starlette from 0.46.2 to 1.0.1 (#2322)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-11 15:43:05 +02:00
github-actions[bot] 800669075f 📝 Update release notes
[skip ci]
2026-06-11 13:34:05 +00:00
dependabot[bot] 495b8dbbd5 ⬆ Bump the github-actions group across 1 directory with 6 updates (#2326)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-11 15:33:43 +02:00
github-actions[bot] 38302d7492 📝 Update release notes
[skip ci]
2026-05-29 09:40:17 +00:00
serhiiur 8fefa3f3f2 ✏️ Fix typo in the comment for DOMAIN in the .env file (#2305)
Co-authored-by: Alejandra <90076947+alejsdev@users.noreply.github.com>
2026-05-29 09:39:57 +00:00
github-actions[bot] 34c8f8b78e 📝 Update release notes
[skip ci]
2026-05-25 20:06:32 +00:00
Yurii Motov c1b7479e7a 👷 Configure Dependabot to group updates and update weekly (#2293) 2026-05-25 22:06:02 +02:00
github-actions[bot] 33fa827e7e 📝 Update release notes
[skip ci]
2026-05-22 17:17:38 +00:00
Sebastián Ramírez b6b79b424d 🔥 Remove config files now in central GitHub repo (#2300) 2026-05-22 19:15:20 +02:00
github-actions[bot] baa12f641c 📝 Update release notes
[skip ci]
2026-05-21 20:29:36 +00:00
dependabot[bot] 7776e56880 ⬆ Bump actions/add-to-project from 1.0.2 to 2.0.0 (#2273)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-21 22:29:12 +02:00
github-actions[bot] d92bb18923 📝 Update release notes
[skip ci]
2026-05-21 08:02:14 +00:00
dependabot[bot] c81925a009 ⬆ Bump python-multipart from 0.0.21 to 0.0.27 (#2277)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Yurii Motov <yurii.motov.monte@gmail.com>
2026-05-21 10:01:50 +02:00
github-actions[bot] a2de9241ed 📝 Update release notes
[skip ci]
2026-05-21 06:58:19 +00:00
dependabot[bot] 94597a5021 ⬆ Bump idna from 3.11 to 3.15 (#2294)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-21 08:58:02 +02:00
github-actions[bot] 9b1819a478 📝 Update release notes
[skip ci]
2026-05-20 16:43:34 +00:00
Sebastián Ramírez 69e384859e 📝 Update security policy (#2297) 2026-05-20 16:42:02 +00:00
github-actions[bot] 32ebacfb42 📝 Update release notes
[skip ci]
2026-05-18 11:17:26 +00:00
Sofie Van Landeghem f8516cd73b 🔒️ Only allow team members to modify dependencies (#2292) 2026-05-18 13:17:04 +02:00
github-actions[bot] a2d50e26dd 📝 Update release notes
[skip ci]
2026-05-18 09:02:42 +00:00
dependabot[bot] 41b6dcedae ⬆ Bump urllib3 from 2.6.3 to 2.7.0 (#2282)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-18 11:02:23 +02:00
github-actions[bot] 39ecf9a093 📝 Update release notes
[skip ci]
2026-05-17 10:36:36 +00:00
Yurii Motov baa742be6b 🔒️ Add zizmor and fix audit findings (#2260)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-17 12:36:03 +02:00
github-actions[bot] 13652b51ea 📝 Update release notes
[skip ci]
2026-04-16 12:36:09 +00:00
Motov Yurii 03e021f30a 🔒 Pin GitHub actions by commit SHA (#2246) 2026-04-16 14:34:14 +02:00
github-actions[bot] bba8d07c0c 📝 Update release notes
[skip ci]
2026-04-03 12:05:22 +00:00
Motov Yurii 37287deafe 🔨 Add pre-commit hook to ensure latest release header has date (#2205)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2026-04-03 12:05:03 +00:00
github-actions[bot] 041377eb6d 📝 Update release notes
[skip ci]
2026-04-03 11:45:57 +00:00
Sofie Van Landeghem 2fdd62ce0c 👷 Add ty to precommit (#2227)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
2026-04-03 13:45:40 +02:00
github-actions[bot] 8bf0025039 📝 Update release notes
[skip ci]
2026-03-16 21:33:53 +00:00
github-actions[bot] d784c02d36 📝 Update release notes
[skip ci]
2026-03-16 21:33:37 +00:00
dependabot[bot] ae3f6e3038 ⬆ Bump dorny/paths-filter from 3 to 4 (#2230)
Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from 3 to 4.
- [Release notes](https://github.com/dorny/paths-filter/releases)
- [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md)
- [Commits](https://github.com/dorny/paths-filter/compare/v3...v4)

---
updated-dependencies:
- dependency-name: dorny/paths-filter
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 22:33:25 +01:00
github-actions[bot] 15e055f471 📝 Update release notes
[skip ci]
2026-03-16 21:33:14 +00:00
dependabot[bot] b42b147e98 ⬆ Bump pyjwt from 2.10.1 to 2.12.0 (#2231)
Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.10.1 to 2.12.0.
- [Release notes](https://github.com/jpadilla/pyjwt/releases)
- [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/jpadilla/pyjwt/compare/2.10.1...2.12.0)

---
updated-dependencies:
- dependency-name: pyjwt
  dependency-version: 2.12.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 22:33:13 +01:00
github-actions[bot] 6335787dc8 📝 Update release notes
[skip ci]
2026-03-16 21:33:10 +00:00
dependabot[bot] b16f3b4156 ⬆ Bump @types/node from 25.3.2 to 25.5.0 (#2233)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.3.2 to 25.5.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 22:32:57 +01:00
dependabot[bot] 2d7d4e7768 ⬆ Bump @tanstack/router-devtools from 1.159.10 to 1.166.7 (#2234)
Bumps [@tanstack/router-devtools](https://github.com/TanStack/router/tree/HEAD/packages/router-devtools) from 1.159.10 to 1.166.7.
- [Release notes](https://github.com/TanStack/router/releases)
- [Commits](https://github.com/TanStack/router/commits/v1.166.7/packages/router-devtools)

---
updated-dependencies:
- dependency-name: "@tanstack/router-devtools"
  dependency-version: 1.166.7
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-16 22:32:50 +01:00
github-actions[bot] fbaf2dbe9d 📝 Update release notes
[skip ci]
2026-03-09 20:11:04 +00:00
dependabot[bot] 1b2d94a887 ⬆ Bump tailwindcss from 4.2.0 to 4.2.1 (#2226)
Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) from 4.2.0 to 4.2.1.
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.1/packages/tailwindcss)

---
updated-dependencies:
- dependency-name: tailwindcss
  dependency-version: 4.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-09 21:10:44 +01:00
github-actions[bot] 4c63a663ac 📝 Update release notes
[skip ci]
2026-03-01 20:35:28 +00:00
github-actions[bot] 7005892795 📝 Update release notes
[skip ci]
2026-03-01 20:35:08 +00:00
dependabot[bot] 64455e5c7b ⬆ Bump actions/download-artifact from 7 to 8 (#2208)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-01 21:34:47 +01:00
dependabot[bot] 2f5ceec867 ⬆ Bump actions/upload-artifact from 6 to 7 (#2207)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-01 21:34:42 +01:00
github-actions[bot] 16afa0d363 📝 Update release notes
[skip ci]
2026-03-01 20:34:34 +00:00
dependabot[bot] 40384c9deb ⬆ Bump @tanstack/react-router from 1.157.3 to 1.163.3 (#2215)
Bumps [@tanstack/react-router](https://github.com/TanStack/router/tree/HEAD/packages/react-router) from 1.157.3 to 1.163.3.
- [Release notes](https://github.com/TanStack/router/releases)
- [Commits](https://github.com/TanStack/router/commits/v1.163.3/packages/react-router)

---
updated-dependencies:
- dependency-name: "@tanstack/react-router"
  dependency-version: 1.163.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-01 21:34:16 +01:00
github-actions[bot] aafa8ebfc9 📝 Update release notes
[skip ci]
2026-03-01 12:48:50 +00:00
github-actions[bot] 5e2e8a9e6a 📝 Update release notes
[skip ci]
2026-03-01 12:48:28 +00:00
github-actions[bot] 917c7c898c 📝 Update release notes
[skip ci]
2026-03-01 12:47:18 +00:00
dependabot[bot] b9ee37b8b4 ⬆ Bump @tanstack/react-router-devtools from 1.159.10 to 1.163.3 (#2212)
Bumps [@tanstack/react-router-devtools](https://github.com/TanStack/router/tree/HEAD/packages/react-router-devtools) from 1.159.10 to 1.163.3.
- [Release notes](https://github.com/TanStack/router/releases)
- [Commits](https://github.com/TanStack/router/commits/v1.163.3/packages/react-router-devtools)

---
updated-dependencies:
- dependency-name: "@tanstack/react-router-devtools"
  dependency-version: 1.163.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-01 13:46:47 +01:00
dependabot[bot] 465a5672d3 ⬆ Bump @tanstack/react-query from 5.90.20 to 5.90.21 (#2213)
Bumps [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) from 5.90.20 to 5.90.21.
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.90.21/packages/react-query)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.90.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-01 13:46:40 +01:00
dependabot[bot] cf956e2b0b ⬆ Bump @types/node from 25.1.0 to 25.3.2 (#2214)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.1.0 to 25.3.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.3.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-01 13:46:32 +01:00
github-actions[bot] 706bd53aa3 📝 Update release notes
[skip ci]
2026-02-26 20:21:47 +00:00
Sebastián Ramírez 960beee281 🔧 Add FastAPI VS Code extension to recommended extensions (#2206) 2026-02-26 20:21:26 +00:00
github-actions[bot] 9059faa6b0 📝 Update release notes
[skip ci]
2026-02-22 18:46:54 +00:00
github-actions[bot] aec52069da 📝 Update release notes
[skip ci]
2026-02-22 18:46:35 +00:00
dependabot[bot] 4a14d47647 ⬆ Bump tailwindcss from 4.1.18 to 4.2.0 (#2198)
Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) from 4.1.18 to 4.2.0.
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.0/packages/tailwindcss)

---
updated-dependencies:
- dependency-name: tailwindcss
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-22 19:46:30 +01:00
dependabot[bot] 56684e7714 ⬆ Bump axios from 1.13.4 to 1.13.5 (#2199)
Bumps [axios](https://github.com/axios/axios) from 1.13.4 to 1.13.5.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.13.4...v1.13.5)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.13.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-22 19:46:14 +01:00
github-actions[bot] 3f6cda7bf4 📝 Update release notes
[skip ci]
2026-02-22 18:46:13 +00:00
dependabot[bot] 97b8debf74 ⬆ Bump @vitejs/plugin-react-swc from 4.2.2 to 4.2.3 (#2200)
Bumps [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react-swc) from 4.2.2 to 4.2.3.
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react-swc@4.2.3/packages/plugin-react-swc)

---
updated-dependencies:
- dependency-name: "@vitejs/plugin-react-swc"
  dependency-version: 4.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-22 19:45:55 +01:00
github-actions[bot] 366eb587c1 📝 Update release notes
[skip ci]
2026-02-16 21:19:52 +00:00
dependabot[bot] a6c5dbe89d ⬆ Bump dotenv from 17.2.3 to 17.3.1 (#2185)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 17.2.3 to 17.3.1.
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v17.2.3...v17.3.1)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 17.3.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-16 22:19:34 +01:00
github-actions[bot] 4628375e8c 📝 Update release notes
[skip ci]
2026-02-16 18:48:46 +00:00
github-actions[bot] 012a7d02b9 📝 Update release notes
[skip ci]
2026-02-16 18:47:43 +00:00
dependabot[bot] 04e1a55f68 ⬆ Bump @tanstack/router-devtools from 1.157.17 to 1.159.10 (#2186)
Bumps [@tanstack/router-devtools](https://github.com/TanStack/router/tree/HEAD/packages/router-devtools) from 1.157.17 to 1.159.10.
- [Release notes](https://github.com/TanStack/router/releases)
- [Commits](https://github.com/TanStack/router/commits/v1.159.10/packages/router-devtools)

---
updated-dependencies:
- dependency-name: "@tanstack/router-devtools"
  dependency-version: 1.159.10
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-16 19:46:26 +01:00
dependabot[bot] 752c40cf5a ⬆ Bump @tanstack/react-router-devtools from 1.157.17 to 1.159.10 (#2188)
Bumps [@tanstack/react-router-devtools](https://github.com/TanStack/router/tree/HEAD/packages/react-router-devtools) from 1.157.17 to 1.159.10.
- [Release notes](https://github.com/TanStack/router/releases)
- [Commits](https://github.com/TanStack/router/commits/v1.159.10/packages/react-router-devtools)

---
updated-dependencies:
- dependency-name: "@tanstack/react-router-devtools"
  dependency-version: 1.159.10
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-16 19:46:20 +01:00
github-actions[bot] f6c2e534c3 📝 Update release notes
[skip ci]
2026-02-12 11:42:38 +00:00
Patrick Arminio 51295d09b7 ⬆️ Upgrade Sentry and FastAPI (#2181)
* ⬆️ Update Sentry and FastAPI

* 🎨 Auto format and update with pre-commit

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-12 11:42:15 +00:00
github-actions[bot] db4532678c 📝 Update release notes
[skip ci]
2026-02-09 19:59:22 +00:00
Alejandra b5ac33fe0b ♻️ Update meta titles (#2179) 2026-02-09 20:59:05 +01:00
github-actions[bot] 083d3e5c3d 📝 Update release notes
[skip ci]
2026-02-09 15:28:37 +00:00
Alejandra f21b241e25 ⬆️ Bump biome schema version from 2.3.12 to 2.3.14 (#2178) 2026-02-09 16:28:14 +01:00
github-actions[bot] 70bf8ba988 📝 Update release notes
[skip ci]
2026-02-09 15:20:28 +00:00
github-actions[bot] f3abf45ccd 📝 Update release notes
[skip ci]
2026-02-09 15:20:04 +00:00
dependabot[bot] 12788c2707 ⬆ Bump @biomejs/biome from 2.3.12 to 2.3.14 (#2177)
Bumps [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) from 2.3.12 to 2.3.14.
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.3.14/packages/@biomejs/biome)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.3.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-09 16:20:02 +01:00
dependabot[bot] 6f32450121 ⬆ Bump lucide-react from 0.562.0 to 0.563.0 (#2176)
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.562.0 to 0.563.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.563.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 0.563.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alejandra <90076947+alejsdev@users.noreply.github.com>
2026-02-09 16:19:38 +01:00
github-actions[bot] 7003bf07c4 📝 Update release notes
[skip ci]
2026-02-09 13:13:45 +00:00
dependabot[bot] 03dea29b79 ⬆ Bump @tanstack/react-query from 5.90.19 to 5.90.20 (#2174)
Bumps [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) from 5.90.19 to 5.90.20.
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.90.20/packages/react-query)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.90.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-09 14:13:28 +01:00
github-actions[bot] 42bf7e7cfe 📝 Update release notes
[skip ci]
2026-02-08 10:48:27 +00:00
dependabot[bot] fa39625d80 ⬆ Bump playwright from v1.58.0-noble to v1.58.2-noble in /frontend (#2175)
* ⬆ Bump playwright from v1.58.0-noble to v1.58.2-noble in /frontend

Bumps playwright from v1.58.0-noble to v1.58.2-noble.

---
updated-dependencies:
- dependency-name: playwright
  dependency-version: v1.58.2-noble
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* ⬆ Bump @playwright/test from 1.58.0 to 1.58.2

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alejandra <alejsdev@gmail.com>
2026-02-08 11:48:07 +01:00
github-actions[bot] 0f289cf840 📝 Update release notes
[skip ci]
2026-02-03 18:29:07 +00:00
Motov Yurii 8b35efe999 👷 Run mypy by pre-commit (#2169) 2026-02-03 19:28:50 +01:00
github-actions[bot] 7af1d80593 📝 Update release notes
[skip ci]
2026-02-02 10:58:18 +00:00
dependabot[bot] 1b523bbc0d ⬆ Bump @tanstack/router-devtools from 1.153.2 to 1.157.17 (#2166)
Bumps [@tanstack/router-devtools](https://github.com/TanStack/router/tree/HEAD/packages/router-devtools) from 1.153.2 to 1.157.17.
- [Release notes](https://github.com/TanStack/router/releases)
- [Commits](https://github.com/TanStack/router/commits/v1.157.17/packages/router-devtools)

---
updated-dependencies:
- dependency-name: "@tanstack/router-devtools"
  dependency-version: 1.157.17
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 11:58:00 +01:00
github-actions[bot] b40b0e0806 📝 Update release notes
[skip ci]
2026-02-02 10:57:50 +00:00
dependabot[bot] dcd8f1e641 ⬆ Bump @types/node from 25.0.10 to 25.1.0 (#2168)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.0.10 to 25.1.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 11:57:34 +01:00
github-actions[bot] 13b2dac791 📝 Update release notes
[skip ci]
2026-02-02 10:56:51 +00:00
dependabot[bot] 0d3bc003f1 ⬆ Bump axios from 1.13.2 to 1.13.4 (#2164)
Bumps [axios](https://github.com/axios/axios) from 1.13.2 to 1.13.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.13.2...v1.13.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.13.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 11:56:30 +01:00
github-actions[bot] 1c6d656482 📝 Update release notes
[skip ci]
2026-01-29 11:12:08 +00:00
Alejandra a252d2e678 📝 Add CONTRIBUTING.md (#2159) 2026-01-29 12:11:49 +01:00
github-actions[bot] fa8795141f 📝 Update release notes
[skip ci]
2026-01-26 13:53:13 +00:00
Alejandra 2fd5ba65da ⬆️ Bump biome schema version to 2.3.12 in biome.json (#2154) 2026-01-26 14:52:53 +01:00
github-actions[bot] 5e1fa15e65 📝 Update release notes
[skip ci]
2026-01-26 13:35:26 +00:00
dependabot[bot] 0162c1db74 ⬆ Bump @biomejs/biome from 2.3.11 to 2.3.12 (#2153)
Bumps [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) from 2.3.11 to 2.3.12.
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.3.12/packages/@biomejs/biome)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.3.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-26 14:35:05 +01:00
github-actions[bot] 3e6449ce0d 📝 Update release notes
[skip ci]
2026-01-26 13:08:05 +00:00
dependabot[bot] dedaa07be0 ⬆ Bump playwright from v1.57.0-noble to v1.58.0-noble in /frontend (#2150)
* ⬆ Bump playwright from v1.57.0-noble to v1.58.0-noble in /frontend

Bumps playwright from v1.57.0-noble to v1.58.0-noble.

---
updated-dependencies:
- dependency-name: playwright
  dependency-version: v1.58.0-noble
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* ⬆️ Update playwright in package.json

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alejandra <alejsdev@gmail.com>
2026-01-26 14:07:46 +01:00
github-actions[bot] a00382f3ca 📝 Update release notes
[skip ci]
2026-01-26 11:24:45 +00:00
dependabot[bot] 7fa809754c ⬆ Bump @tanstack/react-router from 1.153.2 to 1.156.0 (#2152)
Bumps [@tanstack/react-router](https://github.com/TanStack/router/tree/HEAD/packages/react-router) from 1.153.2 to 1.156.0.
- [Release notes](https://github.com/TanStack/router/releases)
- [Commits](https://github.com/TanStack/router/commits/v1.156.0/packages/react-router)

---
updated-dependencies:
- dependency-name: "@tanstack/react-router"
  dependency-version: 1.156.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-26 12:24:28 +01:00
github-actions[bot] cd3d744186 📝 Update release notes
[skip ci]
2026-01-24 17:28:39 +00:00
github-actions[bot] 72967929eb 📝 Update release notes
[skip ci]
2026-01-24 17:28:23 +00:00
dependabot[bot] a5ad271f7c ⬆ Bump zod from 4.3.5 to 4.3.6 (#2151)
Bumps [zod](https://github.com/colinhacks/zod) from 4.3.5 to 4.3.6.
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.3.5...v4.3.6)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 4.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-24 18:28:20 +01:00
github-actions[bot] 71d0e20af8 📝 Update release notes
[skip ci]
2026-01-24 17:28:15 +00:00
dependabot[bot] 4e58251f76 ⬆ Bump @types/node from 25.0.9 to 25.0.10 (#2149)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.0.9 to 25.0.10.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.0.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-24 18:28:06 +01:00
dependabot[bot] e127163403 ⬆ Bump @tanstack/react-router-devtools from 1.153.2 to 1.156.0 (#2147)
Bumps [@tanstack/react-router-devtools](https://github.com/TanStack/router/tree/HEAD/packages/react-router-devtools) from 1.153.2 to 1.156.0.
- [Release notes](https://github.com/TanStack/router/releases)
- [Commits](https://github.com/TanStack/router/commits/v1.156.0/packages/react-router-devtools)

---
updated-dependencies:
- dependency-name: "@tanstack/react-router-devtools"
  dependency-version: 1.156.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-24 18:27:55 +01:00
119 changed files with 4673 additions and 3962 deletions
+1
View File
@@ -0,0 +1 @@
../../.venv/lib/python3.14/site-packages/fastapi/.agents/skills/fastapi
@@ -0,0 +1,4 @@
{
"kind": "tool-skill",
"version": "0.0.19"
}
+37
View File
@@ -0,0 +1,37 @@
---
name: library-skills
description: Use Library Skills to discover, install, refresh, repair, check, and manage agent skills from installed packages.
---
# Library Skills
Use this skill when a project might benefit from agent skills bundled by its installed packages, or when existing Library Skills-managed symlinks are stale, broken, orphaned, or need to be checked.
Run commands from the project root.
Agents bundle their own skills by including an `.agents/skills` directory. More details in [Library Skills](https://library-skills.io).
## First-Time Setup
- Make sure project dependencies are installed first, for example with `uv sync` for Python projects or `npm install` / `bun install` for Node.js projects.
- Run `uvx library-skills` or `npx library-skills` to discover skills bundled by the installed packages and install selected skills interactively.
- Use `uvx library-skills --all` or `npx library-skills --all` only when all newly discovered skills should be installed without selecting individual skills.
- Use `uvx library-skills --tool-skill` or `npx library-skills --tool-skill` to copy this Library Skills tool skill into the project so future agents know how to discover, install, update, repair, and check skills.
## Commands
- Run `uvx library-skills` or `npx library-skills` to discover package-provided skills, install selected new skills, and reconcile existing managed symlinks.
- Run `uvx library-skills list` or `npx library-skills list` to inspect discovered and installed skills.
- Run `uvx library-skills list --json` or `npx library-skills list --json` for machine-readable installed status.
- Run `uvx library-skills scan --json` or `npx library-skills scan --json` for discovery-only automation.
- Run `uvx library-skills --check` or `npx library-skills --check` to validate managed skill symlink state without changing files.
- Run `uvx library-skills --yes` or `npx library-skills --yes` to repair stale managed symlinks and remove orphaned managed symlinks non-interactively.
- Add `--claude` when `.claude/skills` should also be managed.
- Add `--skill NAME` to install a specific discovered skill by name.
## Safety
- Prefer rerunning `library-skills` over editing managed symlinks manually.
- If installed skill symlinks are broken, dependencies may not be installed yet. Try the project's normal install command first, such as `uv sync`, `npm install`, or `bun install`, then rerun `library-skills`.
- Do not delete or overwrite hand-authored skill directories.
- Library Skills only removes managed symlinks. It should not remove copied or hand-authored skill directories.
+1
View File
@@ -0,0 +1 @@
../../.venv/lib/python3.14/site-packages/sqlmodel/.agents/skills/sqlmodel
+1
View File
@@ -0,0 +1 @@
../../.venv/lib/python3.14/site-packages/fastapi/.agents/skills/fastapi
@@ -0,0 +1,4 @@
{
"kind": "tool-skill",
"version": "0.0.19"
}
+37
View File
@@ -0,0 +1,37 @@
---
name: library-skills
description: Use Library Skills to discover, install, refresh, repair, check, and manage agent skills from installed packages.
---
# Library Skills
Use this skill when a project might benefit from agent skills bundled by its installed packages, or when existing Library Skills-managed symlinks are stale, broken, orphaned, or need to be checked.
Run commands from the project root.
Agents bundle their own skills by including an `.agents/skills` directory. More details in [Library Skills](https://library-skills.io).
## First-Time Setup
- Make sure project dependencies are installed first, for example with `uv sync` for Python projects or `npm install` / `bun install` for Node.js projects.
- Run `uvx library-skills` or `npx library-skills` to discover skills bundled by the installed packages and install selected skills interactively.
- Use `uvx library-skills --all` or `npx library-skills --all` only when all newly discovered skills should be installed without selecting individual skills.
- Use `uvx library-skills --tool-skill` or `npx library-skills --tool-skill` to copy this Library Skills tool skill into the project so future agents know how to discover, install, update, repair, and check skills.
## Commands
- Run `uvx library-skills` or `npx library-skills` to discover package-provided skills, install selected new skills, and reconcile existing managed symlinks.
- Run `uvx library-skills list` or `npx library-skills list` to inspect discovered and installed skills.
- Run `uvx library-skills list --json` or `npx library-skills list --json` for machine-readable installed status.
- Run `uvx library-skills scan --json` or `npx library-skills scan --json` for discovery-only automation.
- Run `uvx library-skills --check` or `npx library-skills --check` to validate managed skill symlink state without changing files.
- Run `uvx library-skills --yes` or `npx library-skills --yes` to repair stale managed symlinks and remove orphaned managed symlinks non-interactively.
- Add `--claude` when `.claude/skills` should also be managed.
- Add `--skill NAME` to install a specific discovered skill by name.
## Safety
- Prefer rerunning `library-skills` over editing managed symlinks manually.
- If installed skill symlinks are broken, dependencies may not be installed yet. Try the project's normal install command first, such as `uv sync`, `npm install`, or `bun install`, then rerun `library-skills`.
- Do not delete or overwrite hand-authored skill directories.
- Library Skills only removes managed symlinks. It should not remove copied or hand-authored skill directories.
+1
View File
@@ -0,0 +1 @@
../../.venv/lib/python3.14/site-packages/sqlmodel/.agents/skills/sqlmodel
+11
View File
@@ -0,0 +1,11 @@
.git
**/__pycache__
**/.venv
backend/app/frontend
backend/htmlcov
frontend/blob-report
frontend/dist
frontend/node_modules
frontend/playwright-report
frontend/test-results
node_modules
+5 -5
View File
@@ -1,14 +1,15 @@
# Domain
# This would be set to the production domain with an env var on deployment
# used by Traefik to transmit traffic and aqcuire TLS certificates
# used by Traefik to transmit traffic and acquire TLS certificates
DOMAIN=localhost
# To test the local Traefik config
# DOMAIN=localhost.tiangolo.com
# Used by the backend to generate links in emails to the frontend
# Public frontend URL, used by the backend to generate links in emails.
# Keep the Vite URL for local development; deployments use the application URL.
FRONTEND_HOST=http://localhost:5173
# In staging and production, set this env var to the frontend host, e.g.
# FRONTEND_HOST=https://dashboard.example.com
# FRONTEND_HOST=https://example.com
# Environment: local, staging, production
ENVIRONMENT=local
@@ -17,7 +18,7 @@ PROJECT_NAME="Full Stack FastAPI Project"
STACK_NAME=full-stack-fastapi-project
# Backend
BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://localhost.tiangolo.com"
BACKEND_CORS_ORIGINS="http://localhost,https://localhost,http://localhost.tiangolo.com"
SECRET_KEY=changethis
FIRST_SUPERUSER=admin@example.com
FIRST_SUPERUSER_PASSWORD=changethis
@@ -42,4 +43,3 @@ SENTRY_DSN=
# Configure these with your own Docker registry images
DOCKER_IMAGE_BACKEND=backend
DOCKER_IMAGE_FRONTEND=frontend
-118
View File
@@ -1,118 +0,0 @@
labels: [question]
body:
- type: markdown
attributes:
value: |
Thanks for your interest in this project! 🚀
Please follow these instructions, fill every question, and do every step. 🙏
I'm asking this because answering questions and solving problems in GitHub is what consumes most of the time.
I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling questions.
All that, on top of all the incredible help provided by a bunch of community members, that give a lot of their time to come here and help others.
That's a lot of work, but if more users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅).
By asking questions in a structured way (following this) it will be much easier to help you.
And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎
As there are too many questions, I'll have to discard and close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓
- type: checkboxes
id: checks
attributes:
label: First Check
description: Please confirm and check all the following options.
options:
- label: I added a very descriptive title here.
required: true
- label: I used the GitHub search to find a similar question and didn't find it.
required: true
- label: I searched in the documentation/README.
required: true
- label: I already searched in Google "How to do X" and didn't find any information.
required: true
- label: I already read and followed all the tutorial in the docs/README and didn't find an answer.
required: true
- type: checkboxes
id: help
attributes:
label: Commit to Help
description: |
After submitting this, I commit to one of:
* Read open questions until I find 2 where I can help someone and add a comment to help there.
* I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
options:
- label: I commit to help with one of those options 👆
required: true
- type: textarea
id: example
attributes:
label: Example Code
description: |
Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case.
If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you.
placeholder: |
Write your example code here.
render: Text
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: |
What is the problem, question, or error?
Write a short description telling me what you are doing, what you expect to happen, and what is currently happening.
placeholder: |
* Open the browser and call the endpoint `/`.
* It returns a JSON with `{"message": "Hello World"}`.
* But I expected it to return `{"message": "Hello Morty"}`.
validations:
required: true
- type: dropdown
id: os
attributes:
label: Operating System
description: What operating system are you on?
multiple: true
options:
- Linux
- Windows
- macOS
- Other
validations:
required: true
- type: textarea
id: os-details
attributes:
label: Operating System Details
description: You can add more details about your operating system here, in particular if you chose "Other".
validations:
required: true
- type: input
id: python-version
attributes:
label: Python Version
description: |
What Python version are you using?
You can find the Python version with:
```bash
python --version
```
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context
description: Add any additional context information or screenshots you think are useful.
-1
View File
@@ -1 +0,0 @@
github: [tiangolo]
-10
View File
@@ -1,10 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Security Contact
about: Please report security vulnerabilities to security@tiangolo.com
- name: Question or Problem
about: Ask a question or ask about a problem in GitHub Discussions.
url: https://github.com/fastapi/full-stack-fastapi-template/discussions/categories/questions
- name: Feature Request
about: To suggest an idea or ask about a feature, please start with a question saying what you would like to achieve. There might be a way to do it already.
url: https://github.com/fastapi/full-stack-fastapi-template/discussions/categories/questions
-22
View File
@@ -1,22 +0,0 @@
name: Privileged
description: You are @tiangolo or he asked you directly to create an issue here. If not, check the other options. 👇
body:
- type: markdown
attributes:
value: |
Thanks for your interest in this project! 🚀
If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/tiangolo/full-stack-fastapi-template/discussions/categories/questions) instead.
- type: checkboxes
id: privileged
attributes:
label: Privileged issue
description: Confirm that you are allowed to create an issue here.
options:
- label: I'm @tiangolo or he asked me directly to create an issue here.
required: true
- type: textarea
id: content
attributes:
label: Issue Content
description: Add the content of the issue here.
+39 -8
View File
@@ -4,43 +4,74 @@ updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
interval: "monthly"
cooldown:
default-days: 7
commit-message:
prefix: ⬆
labels: [dependencies, internal]
labels:
- "internal"
- "dependencies"
- "github_actions"
groups:
github-actions:
patterns:
- "*"
# Python uv
- package-ecosystem: uv
directory: /
schedule:
interval: weekly
interval: "monthly"
cooldown:
default-days: 7
commit-message:
prefix: ⬆
labels: [dependencies, internal]
groups:
python-packages:
patterns:
- "*"
# bun
- package-ecosystem: bun
directory: /
schedule:
interval: weekly
interval: "monthly"
cooldown:
default-days: 7
commit-message:
prefix: ⬆
labels: [dependencies, internal]
ignore:
- dependency-name: "@hey-api/openapi-ts"
groups:
npm-packages:
patterns:
- "*"
# Docker
- package-ecosystem: docker
directories:
- /backend
- /frontend
schedule:
interval: weekly
interval: "monthly"
cooldown:
default-days: 7
commit-message:
prefix: ⬆
labels: [dependencies, internal]
groups:
docker:
patterns:
- "*"
# Docker Compose
- package-ecosystem: docker-compose
directory: /
schedule:
interval: weekly
interval: "monthly"
cooldown:
default-days: 7
commit-message:
prefix: ⬆
labels: [dependencies, internal]
groups:
docker-compose:
patterns:
- "*"
+7 -3
View File
@@ -1,18 +1,22 @@
name: Add to Project
on:
pull_request_target:
pull_request_target: # zizmor: ignore[dangerous-triggers]
issues:
types:
- opened
- reopened
permissions: {}
jobs:
add-to-project:
name: Add to project
if: github.repository_owner == 'fastapi'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/add-to-project@v1.0.2
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
with:
project-url: https://github.com/orgs/fastapi/projects/2
github-token: ${{ secrets.PROJECTS_TOKEN }}
github-token: ${{ secrets.PROJECTS_TOKEN }} # zizmor: ignore[secrets-outside-env]
@@ -0,0 +1,67 @@
name: Bump pre-commit hooks
on:
schedule:
- cron: "0 12 1 * *"
workflow_dispatch:
permissions: {}
jobs:
bump-pre-commit-hooks:
if: github.repository_owner == 'fastapi'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
token: ${{ secrets.LATEST_CHANGES }}
persist-credentials: true
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.18"
cache-dependency-glob: |
pyproject.toml
uv.lock
- name: Bump pre-commit hooks
run: uv run prek auto-update --freeze --cooldown-days 7
- name: Create pull request
env:
GH_TOKEN: ${{ secrets.LATEST_CHANGES }}
BASE_BRANCH: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
if git diff --quiet; then
echo "No pre-commit hook updates available"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
branch="bump-pre-commit-hooks"
git switch -C "$branch"
git add .pre-commit-config.yaml
git commit -m "⬆ Bump pre-commit hooks"
git push --force origin "$branch"
if [ -z "$(gh pr list --head "$branch" --state open --json number --jq '.[].number')" ]; then
gh pr create \
--base "$BASE_BRANCH" \
--head "$branch" \
--title "⬆ Bump pre-commit hooks" \
--body "Bump pre-commit hook versions via \`prek auto-update --freeze --cooldown-days 7\`." \
--label internal \
--label dependencies \
--label pre-commit
else
echo "PR for \"$branch\" already open; branch updated in place."
fi
+8 -1
View File
@@ -5,8 +5,12 @@ on:
types:
- published
permissions:
contents: read
jobs:
deploy:
environment: production
# Do not deploy in the main repository, only in user projects
if: github.repository_owner != 'fastapi'
runs-on:
@@ -15,6 +19,7 @@ jobs:
env:
ENVIRONMENT: production
DOMAIN: ${{ secrets.DOMAIN_PRODUCTION }}
FRONTEND_HOST: https://${{ secrets.DOMAIN_PRODUCTION }}
STACK_NAME: ${{ secrets.STACK_NAME_PRODUCTION }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
FIRST_SUPERUSER: ${{ secrets.FIRST_SUPERUSER }}
@@ -27,6 +32,8 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- run: docker compose -f compose.yml --project-name ${{ secrets.STACK_NAME_PRODUCTION }} build
- run: docker compose -f compose.yml --project-name ${{ secrets.STACK_NAME_PRODUCTION }} up -d
+8 -1
View File
@@ -5,8 +5,12 @@ on:
branches:
- master
permissions:
contents: read
jobs:
deploy:
environment: staging
# Do not deploy in the main repository, only in user projects
if: github.repository_owner != 'fastapi'
runs-on:
@@ -15,6 +19,7 @@ jobs:
env:
ENVIRONMENT: staging
DOMAIN: ${{ secrets.DOMAIN_STAGING }}
FRONTEND_HOST: https://${{ secrets.DOMAIN_STAGING }}
STACK_NAME: ${{ secrets.STACK_NAME_STAGING }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
FIRST_SUPERUSER: ${{ secrets.FIRST_SUPERUSER }}
@@ -27,6 +32,8 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- run: docker compose -f compose.yml --project-name ${{ secrets.STACK_NAME_STAGING }} build
- run: docker compose -f compose.yml --project-name ${{ secrets.STACK_NAME_STAGING }} up -d
+5 -2
View File
@@ -1,18 +1,21 @@
name: "Conflict detector"
on:
push:
pull_request_target:
pull_request_target: # zizmor: ignore[dangerous-triggers]
types: [synchronize]
permissions: {}
jobs:
main:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check if PRs have merge conflicts
uses: eps1lon/actions-label-merge-conflict@v3
uses: eps1lon/actions-label-merge-conflict@0273be72a0bbd58fcd71d0d6c02c209b50d1e5e1 # v3.1.0
with:
dirtyLabel: "conflicts"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
+52
View File
@@ -0,0 +1,52 @@
name: Guard Dependencies
on:
pull_request_target: # zizmor: ignore[dangerous-triggers] -- This workflow only reads context.payload metadata, never checks out PR code
branches: [master]
paths:
- pyproject.toml
- uv.lock
permissions:
contents: read
issues: write
pull-requests: write
jobs:
check-author:
runs-on: ubuntu-latest
steps:
- name: Check if author is org member or allowed bot
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const pr = context.payload.pull_request;
const author = pr.user.login;
const assoc = pr.author_association;
const botAllowlist = new Set(['dependabot[bot]']);
const orgAuthorAssociations = new Set(['MEMBER', 'OWNER']);
const allowed =
botAllowlist.has(author) ||
(assoc != null && orgAuthorAssociations.has(assoc));
if (!allowed) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: `This PR modifies dependency files (\`pyproject.toml\` or \`uv.lock\`), which is restricted to members of the **${context.repo.owner}** organization on GitHub.\n\nIf you need a dependency change, please [open a discussion](https://github.com/${context.repo.owner}/${context.repo.repo}/discussions/new) describing what you need and why.\n\nClosing this PR automatically.`
});
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
state: 'closed'
});
core.setFailed('Dependency changes are restricted to organization members.');
} else {
console.log(`Author ${author} (author_association=${assoc}) is allowed to make dependency changes.`);
}
+7 -28
View File
@@ -9,47 +9,26 @@ on:
issues:
types:
- labeled
pull_request_target:
pull_request_target: # zizmor: ignore[dangerous-triggers]
types:
- labeled
workflow_dispatch:
permissions:
issues: write
pull-requests: write
permissions: {}
jobs:
issue-manager:
if: github.repository_owner == 'fastapi'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
issues: write
pull-requests: write
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: tiangolo/issue-manager@0.6.0
- uses: tiangolo/issue-manager@dc846170c36eb62fb434b3d943b36399fe240fb5 # 0.8.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
config: >
{
"answered": {
"delay": 864000,
"message": "Assuming the original need was handled, this will be automatically closed now. But feel free to add more comments or create new issues or PRs."
},
"waiting": {
"delay": 2628000,
"message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR.",
"reminder": {
"before": "P3D",
"message": "Heads-up: this will be closed in 3 days unless there's new activity."
}
},
"invalid": {
"delay": 0,
"message": "This was marked as invalid and will be closed now. If this is an error, please provide additional details."
},
"maybe-ai": {
"delay": 0,
"message": "This was marked as potentially AI generated and will be closed now. If this is an error, please provide additional details, make sure to read the docs about contributing and AI."
}
}
+7 -3
View File
@@ -1,6 +1,6 @@
name: Labels
on:
pull_request_target:
pull_request_target: # zizmor: ignore[dangerous-triggers]
types:
- opened
- synchronize
@@ -9,14 +9,17 @@ on:
- labeled
- unlabeled
permissions: {}
jobs:
labeler:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/labeler@v6
- uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
if: ${{ github.event.action != 'labeled' && github.event.action != 'unlabeled' }}
- run: echo "Done adding labels"
# Run this after labeler applied labels
@@ -26,8 +29,9 @@ jobs:
permissions:
pull-requests: read
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: docker://agilepathway/pull-request-label-checker:latest
- uses: agilepathway/label-checker@c3d16ad512e7cea5961df85ff2486bb774caf3c5 # v1.6.65
with:
one_of: breaking,security,feature,bug,refactor,upgrade,docs,lang-all,internal
repo_token: ${{ secrets.GITHUB_TOKEN }}
+11 -9
View File
@@ -1,7 +1,7 @@
name: Latest Changes
on:
pull_request_target:
pull_request_target: # zizmor: ignore[dangerous-triggers]
branches:
- master
types:
@@ -16,25 +16,27 @@ on:
required: false
default: "false"
permissions: {}
jobs:
latest-changes:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
pull-requests: read
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# To allow latest-changes to commit to the main branch
token: ${{ secrets.LATEST_CHANGES }}
- uses: tiangolo/latest-changes@0.4.1
ref: ${{ github.event.repository.default_branch }}
# To allow latest-changes to commit to the default branch
token: ${{ secrets.LATEST_CHANGES }} # zizmor: ignore[secrets-outside-env]
persist-credentials: true # required by tiangolo/latest-changes
- uses: tiangolo/latest-changes@8a940392f4c65274539453a5d5a76d9550203ac1 # 0.7.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
latest_changes_file: ./release-notes.md
latest_changes_header: "## Latest Changes"
end_regex: "^## "
debug_logs: true
label_header_prefix: "### "
+36 -18
View File
@@ -5,9 +5,6 @@ on:
branches:
- master
pull_request:
types:
- opened
- synchronize
workflow_dispatch:
inputs:
debug_enabled:
@@ -15,16 +12,23 @@ on:
required: false
default: 'false'
permissions:
contents: read
jobs:
changes:
runs-on: ubuntu-latest
timeout-minutes: 5
# Set job outputs to values from filter step
outputs:
changed: ${{ steps.filter.outputs.changed }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 2
persist-credentials: false
# For pull requests it's not necessary to checkout the code but for the main branch it is
- uses: dorny/paths-filter@v3
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: filter
with:
filters: |
@@ -39,7 +43,7 @@ jobs:
needs:
- changes
if: ${{ needs.changes.outputs.changed == 'true' }}
timeout-minutes: 60
timeout-minutes: 15
runs-on: ubuntu-latest
strategy:
matrix:
@@ -47,18 +51,26 @@ jobs:
shardTotal: [4]
fail-fast: false
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-python@v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
python-version: '3.10'
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.3.12
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version-file: .python-version
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
uses: mxschmitt/action-tmate@35b54afac29c97fb54faba5b513f8fbd1882f113 # v3.24
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
- name: Install uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.18"
- run: uv sync
working-directory: backend
- run: bun ci
@@ -71,7 +83,7 @@ jobs:
- run: docker compose down -v --remove-orphans
- name: Upload blob report to GitHub Actions Artifacts
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: blob-report-${{ matrix.shardIndex }}
path: frontend/blob-report
@@ -85,13 +97,18 @@ jobs:
# Merge reports after playwright-tests, even if some shards have failed
if: ${{ !cancelled() && needs.changes.outputs.changed == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.3.12
- name: Install dependencies
run: bun ci
- name: Download blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: frontend/all-blob-reports
pattern: blob-report-*
@@ -100,7 +117,7 @@ jobs:
run: bunx playwright merge-reports --reporter html ./all-blob-reports
working-directory: frontend
- name: Upload HTML report
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: html-report--attempt-${{ github.run_attempt }}
path: frontend/playwright-report
@@ -113,9 +130,10 @@ jobs:
needs:
- test-playwright
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
allowed-skips: test-playwright
+21 -13
View File
@@ -2,9 +2,8 @@ name: pre-commit
on:
pull_request:
types:
- opened
- synchronize
permissions:
contents: read
env:
# Forks and Dependabot don't have access to secrets
@@ -13,12 +12,13 @@ env:
jobs:
pre-commit:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
name: Checkout PR for own repo
if: env.HAS_SECRETS == 'true'
with:
@@ -28,23 +28,30 @@ jobs:
# And it needs the full history to be able to compute diffs
fetch-depth: 0
# A token other than the default GITHUB_TOKEN is needed to be able to trigger CI
token: ${{ secrets.PRE_COMMIT }}
token: ${{ secrets.PRE_COMMIT }} # zizmor: ignore[secrets-outside-env]
persist-credentials: true # Required for `git push` command
# pre-commit lite ci needs the default checkout configs to work
- uses: actions/checkout@v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
name: Checkout PR for fork
if: env.HAS_SECRETS == 'false'
with:
# To be able to commit it needs the head branch of the PR, the remote one
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- uses: oven-sh/setup-bun@v2
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.3.12
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.11"
python-version-file: .python-version
- name: Setup uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.18"
cache-dependency-glob: |
requirements**.txt
pyproject.toml
@@ -55,7 +62,7 @@ jobs:
run: bun ci
- name: Run prek - pre-commit
id: precommit
run: uvx prek run --from-ref origin/${GITHUB_BASE_REF} --to-ref HEAD --show-diff-on-failure
run: uv run prek run --from-ref origin/${GITHUB_BASE_REF} --to-ref HEAD --show-diff-on-failure
continue-on-error: true
- name: Commit and push changes
if: env.HAS_SECRETS == 'true'
@@ -69,7 +76,7 @@ jobs:
git commit -m "🎨 Auto format and update with pre-commit"
git push
fi
- uses: pre-commit-ci/lite-action@v1.1.0
- uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0
if: env.HAS_SECRETS == 'false'
with:
msg: 🎨 Auto format and update with pre-commit
@@ -83,12 +90,13 @@ jobs:
needs:
- pre-commit
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
+23 -8
View File
@@ -1,34 +1,49 @@
name: Smokeshow
on:
workflow_run:
workflow_run: # zizmor: ignore[dangerous-triggers]
workflows: [Test Backend]
types: [completed]
permissions: {}
jobs:
smokeshow:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
actions: read
contents: read
statuses: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
python-version: "3.13"
- run: pip install smokeshow
- uses: actions/download-artifact@v7
persist-credentials: false
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version-file: .python-version
- name: Setup uv
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.18"
cache-dependency-glob: |
pyproject.toml
uv.lock
- run: uv sync --all-packages --no-dev --group github-actions
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: coverage-html
path: backend/htmlcov
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- run: smokeshow upload backend/htmlcov
- run: uv run smokeshow upload backend/htmlcov
env:
SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage}
SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 90
SMOKESHOW_GITHUB_CONTEXT: coverage
SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }}
SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }} # zizmor: ignore[secrets-outside-env]
+15 -9
View File
@@ -5,22 +5,28 @@ on:
branches:
- master
pull_request:
types:
- opened
- synchronize
permissions:
contents: read
jobs:
test-backend:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
python-version: "3.10"
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version-file: .python-version
- name: Install uv
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.18"
- run: docker compose down -v --remove-orphans
- run: docker compose up -d db mailcatcher
- name: Migrate DB
@@ -31,7 +37,7 @@ jobs:
working-directory: backend
- run: docker compose down -v --remove-orphans
- name: Store coverage files
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-html
path: backend/htmlcov
+8 -6
View File
@@ -5,22 +5,24 @@ on:
branches:
- master
pull_request:
types:
- opened
- synchronize
permissions:
contents: read
jobs:
test-docker-compose:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- run: docker compose build
- run: docker compose down -v --remove-orphans
- run: docker compose up -d --wait backend frontend adminer
- run: docker compose up -d --wait backend adminer
- name: Test backend is up
run: curl http://localhost:8000/api/v1/utils/health-check
- name: Test frontend is up
run: curl http://localhost:5173
run: curl http://localhost:8000
- run: docker compose down -v --remove-orphans
+30
View File
@@ -0,0 +1,30 @@
name: Zizmor
on:
push:
branches:
- master
pull_request:
workflow_dispatch:
permissions: {}
jobs:
zizmor:
name: Run zizmor
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
actions: read
contents: read
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
with:
advanced-security: ${{ github.repository_owner == 'fastapi' }}
annotations: ${{ github.repository_owner != 'fastapi' }}
+1
View File
@@ -1,6 +1,7 @@
.vscode/*
!.vscode/extensions.json
node_modules/
backend/app/frontend/
/test-results/
/playwright-report/
/blob-report/
+35 -1
View File
@@ -2,7 +2,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # v6.0.0
hooks:
- id: check-added-large-files
- id: check-toml
@@ -17,6 +17,11 @@ repos:
)$
- id: trailing-whitespace
exclude: ^frontend/src/client/.*
- repo: https://github.com/crate-ci/typos
rev: bee27e3a4fd1ea2111cf90ab89cd076c870fce14 # frozen: v1.48.0
hooks:
- id: typos
args: [--force-exclude]
- repo: local
hooks:
- id: local-biome-check
@@ -40,9 +45,38 @@ repos:
language: unsupported
types: [python]
- id: local-mypy
name: mypy check
entry: uv run mypy backend/app
require_serial: true
language: unsupported
pass_filenames: false
- id: local-ty
name: ty check
entry: uv run ty check backend/app
require_serial: true
language: unsupported
pass_filenames: false
- id: generate-frontend-sdk
name: Generate Frontend SDK
entry: bash ./scripts/generate-client.sh
pass_filenames: false
language: unsupported
files: ^backend/.*$|^scripts/generate-client\.sh$
- id: add-release-date
language: unsupported
name: add date to latest release header
entry: uv run python scripts/add_latest_release_date.py
files: ^release-notes\.md$
pass_filenames: false
- id: zizmor
name: zizmor
language: python
entry: uv run zizmor .
files: ^\.github\/workflows\/
require_serial: true
pass_filenames: false
+1
View File
@@ -0,0 +1 @@
3.14
+1
View File
@@ -1,5 +1,6 @@
{
"recommendations": [
"FastAPILabs.fastapi-vscode",
"astral-sh.ty",
"biomejs.biome",
"bradlc.vscode-tailwindcss",
+68
View File
@@ -0,0 +1,68 @@
# Contributing
Thank you for your interest in contributing to the Full Stack FastAPI Template! 🙇
## Discussions First
For **big changes** (new features, architectural changes, significant refactoring), please start by opening a [GitHub Discussion](https://github.com/fastapi/full-stack-fastapi-template/discussions) first. This allows the community and maintainers to provide feedback on the approach before you invest significant time in implementation.
For small, straightforward changes, you can go directly to a Pull Request without starting a discussion first. This includes:
- Typos and grammatical fixes
- Small reproducible bug fixes
- Fixing lint warnings or type errors
- Minor code improvements (e.g., removing unused code)
Note that PRs from non-team members are not allowed to modify `pyproject.toml` or `uv.lock`, to prevent supply chain risk.
If you would like to add a new dependency, create a new [Discussion](https://github.com/fastapi/full-stack-fastapi-template/discussions) to explain why.
## Developing
For detailed instructions on setting up your development environment, running the stack, linting, pre-commit hooks, and more, see the [Development Guide](development.md).
## Pull Requests
When submitting a pull request:
1. Make sure all tests pass before submitting.
2. Keep PRs focused on a single change.
3. Update tests if you're changing functionality.
4. Reference any related issues in your PR description.
## Automated Code and AI
You are encouraged to use all the tools you want to do your work and contribute as efficiently as possible, this includes AI (LLM) tools, etc. Nevertheless, contributions should have meaningful human intervention, judgement, context, etc.
If the **human effort** put in a PR, e.g. writing LLM prompts, is **less** than the **effort we would need to put** to **review it**, please **don't** submit the PR.
Think of it this way: we can already write LLM prompts or run automated tools ourselves, and that would be faster than reviewing external PRs.
### Closing Automated and AI PRs
If we see PRs that seem AI generated or automated in similar ways, we'll flag them and close them.
The same applies to comments and descriptions, please don't copy paste the content generated by an LLM.
### Human Effort Denial of Service
Using automated tools and AI to submit PRs or comments that we have to carefully review and handle would be the equivalent of a [Denial-of-service attack](https://en.wikipedia.org/wiki/Denial-of-service_attack) on our human effort.
It would be very little effort from the person submitting the PR (an LLM prompt) that generates a large amount of effort on our side (carefully reviewing code).
Please don't do that.
We'll need to block accounts that spam us with repeated automated PRs or comments.
### Use Tools Wisely
As Uncle Ben said:
> With great ~~power~~ **tools** comes great responsibility.
Avoid inadvertently doing harm.
You have amazing tools at hand, use them wisely to help effectively.
## Questions?
If you have questions about contributing, feel free to open a [GitHub Discussion](https://github.com/fastapi/full-stack-fastapi-template/discussions).
+6 -5
View File
@@ -11,6 +11,7 @@
- 🔍 [Pydantic](https://docs.pydantic.dev), used by FastAPI, for the data validation and settings management.
- 💾 [PostgreSQL](https://www.postgresql.org) as the SQL database.
- 🚀 [React](https://react.dev) for the frontend.
- 🧩 Built into the backend image and served by FastAPI on the same domain as the API.
- 💃 Using TypeScript, hooks, [Vite](https://vitejs.dev), and other parts of a modern frontend stack.
- 🎨 [Tailwind CSS](https://tailwindcss.com) and [shadcn/ui](https://ui.shadcn.com) for the frontend components.
- 🤖 An automatically generated frontend client.
@@ -23,24 +24,24 @@
- 📬 [Mailcatcher](https://mailcatcher.me) for local email testing during development.
- ✅ Tests with [Pytest](https://pytest.org).
- 📞 [Traefik](https://traefik.io) as a reverse proxy / load balancer.
- 🚢 Deployment instructions using Docker Compose, including how to set up a frontend Traefik proxy to handle automatic HTTPS certificates.
- 🚢 Deployment instructions using Docker Compose, including how to set up Traefik to handle automatic HTTPS certificates.
- 🏭 CI (continuous integration) and CD (continuous deployment) based on GitHub Actions.
### Dashboard Login
[![API docs](img/login.png)](https://github.com/fastapi/full-stack-fastapi-template)
[![Dashboard login screenshot](img/login.png)](https://github.com/fastapi/full-stack-fastapi-template)
### Dashboard - Admin
[![API docs](img/dashboard.png)](https://github.com/fastapi/full-stack-fastapi-template)
[![Admin dashboard screenshot](img/dashboard.png)](https://github.com/fastapi/full-stack-fastapi-template)
### Dashboard - Items
[![API docs](img/dashboard-items.png)](https://github.com/fastapi/full-stack-fastapi-template)
[![Items dashboard screenshot](img/dashboard-items.png)](https://github.com/fastapi/full-stack-fastapi-template)
### Dashboard - Dark Mode
[![API docs](img/dashboard-dark.png)](https://github.com/fastapi/full-stack-fastapi-template)
[![Dark mode dashboard screenshot](img/dashboard-dark.png)](https://github.com/fastapi/full-stack-fastapi-template)
### Interactive API Documentation
-29
View File
@@ -1,29 +0,0 @@
# Security Policy
Security is very important for this project and its community. 🔒
Learn more about it below. 👇
## Versions
The latest version or release is supported.
You are encouraged to write tests for your application and update your versions frequently after ensuring that your tests are passing. This way you will benefit from the latest features, bug fixes, and **security fixes**.
## Reporting a Vulnerability
If you think you found a vulnerability, and even if you are not sure about it, please report it right away by sending an email to: security@tiangolo.com. Please try to be as explicit as possible, describing all the steps and example code to reproduce the security issue.
I (the author, [@tiangolo](https://twitter.com/tiangolo)) will review it thoroughly and get back to you.
## Public Discussions
Please restrain from publicly discussing a potential security vulnerability. 🙊
It's better to discuss privately and try to find a solution first, to limit the potential impact as much as possible.
---
Thanks for your help!
The community and I thank you for that. 🙇
+22 -2
View File
@@ -1,4 +1,22 @@
FROM python:3.10
FROM oven/bun:1 AS frontend-build
WORKDIR /app
COPY package.json bun.lock /app/
COPY frontend/package.json /app/frontend/
WORKDIR /app/frontend
RUN bun install
COPY ./frontend /app/frontend
ARG VITE_API_URL=
RUN bun run build
FROM python:3.14
ENV PYTHONUNBUFFERED=1
@@ -33,6 +51,8 @@ COPY ./backend/pyproject.toml ./backend/alembic.ini /app/backend/
COPY ./backend/app /app/backend/app
COPY --from=frontend-build /app/backend/app/frontend /app/backend/app/frontend
# Sync the project
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers
RUN --mount=type=cache,target=/root/.cache/uv \
@@ -42,4 +62,4 @@ RUN --mount=type=cache,target=/root/.cache/uv \
WORKDIR /app/backend/
CMD ["fastapi", "run", "--workers", "4", "app/main.py"]
CMD ["fastapi", "run", "--workers", "4"]
+2
View File
@@ -10,6 +10,7 @@ config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
assert config.config_file_name is not None
fileConfig(config.config_file_name)
# add your model's MetaData object here
@@ -62,6 +63,7 @@ def run_migrations_online():
"""
configuration = config.get_section(config.config_ini_section)
assert configuration is not None
configuration["sqlalchemy.url"] = get_url()
connectable = engine_from_config(
configuration,
@@ -29,7 +29,7 @@ def upgrade():
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'item', type_='foreignkey')
op.drop_constraint('item_owner_id_fkey', 'item', type_='foreignkey')
op.create_foreign_key('item_owner_id_fkey', 'item', 'user', ['owner_id'], ['id'])
op.alter_column('item', 'owner_id',
existing_type=sa.UUID(),
+2 -2
View File
@@ -18,7 +18,7 @@ reusable_oauth2 = OAuth2PasswordBearer(
)
def get_db() -> Generator[Session, None, None]:
def get_db() -> Generator[Session]:
with Session(engine) as session:
yield session
@@ -33,7 +33,7 @@ def get_current_user(session: SessionDep, token: TokenDep) -> User:
token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
)
token_data = TokenPayload(**payload)
except (InvalidTokenError, ValidationError):
except InvalidTokenError, ValidationError:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Could not validate credentials",
+5 -4
View File
@@ -2,7 +2,7 @@ import uuid
from typing import Any
from fastapi import APIRouter, HTTPException
from sqlmodel import func, select
from sqlmodel import col, func, select
from app.api.deps import CurrentUser, SessionDep
from app.models import Item, ItemCreate, ItemPublic, ItemsPublic, ItemUpdate, Message
@@ -22,7 +22,7 @@ def read_items(
count_statement = select(func.count()).select_from(Item)
count = session.exec(count_statement).one()
statement = (
select(Item).order_by(Item.created_at.desc()).offset(skip).limit(limit)
select(Item).order_by(col(Item.created_at).desc()).offset(skip).limit(limit)
)
items = session.exec(statement).all()
else:
@@ -35,13 +35,14 @@ def read_items(
statement = (
select(Item)
.where(Item.owner_id == current_user.id)
.order_by(Item.created_at.desc())
.order_by(col(Item.created_at).desc())
.offset(skip)
.limit(limit)
)
items = session.exec(statement).all()
return ItemsPublic(data=items, count=count)
items_public = [ItemPublic.model_validate(item) for item in items]
return ItemsPublic(data=items_public, count=count)
@router.get("/{id}", response_model=ItemPublic)
+6 -3
View File
@@ -42,10 +42,13 @@ def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any:
count_statement = select(func.count()).select_from(User)
count = session.exec(count_statement).one()
statement = select(User).order_by(User.created_at.desc()).offset(skip).limit(limit)
statement = (
select(User).order_by(col(User.created_at).desc()).offset(skip).limit(limit)
)
users = session.exec(statement).all()
return UsersPublic(data=users, count=count)
users_public = [UserPublic.model_validate(user) for user in users]
return UsersPublic(data=users_public, count=count)
@router.post(
@@ -223,7 +226,7 @@ def delete_user(
status_code=403, detail="Super users are not allowed to delete themselves"
)
statement = delete(Item).where(col(Item.owner_id) == user_id)
session.exec(statement) # type: ignore
session.exec(statement)
session.delete(user)
session.commit()
return Message(message="User deleted successfully")
+1 -2
View File
@@ -1,6 +1,6 @@
import secrets
import warnings
from typing import Annotated, Any, Literal
from typing import Annotated, Any, Literal, Self
from pydantic import (
AnyUrl,
@@ -12,7 +12,6 @@ from pydantic import (
model_validator,
)
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing_extensions import Self
def parse_cors(v: Any) -> list[str] | str:
+2 -2
View File
@@ -1,4 +1,4 @@
from datetime import datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta
from typing import Any
import jwt
@@ -20,7 +20,7 @@ ALGORITHM = "HS256"
def create_access_token(subject: str | Any, expires_delta: timedelta) -> str:
expire = datetime.now(timezone.utc) + expires_delta
expire = datetime.now(UTC) + expires_delta
to_encode = {"exp": expire, "sub": str(subject)}
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
+5
View File
@@ -1,3 +1,5 @@
from pathlib import Path
import sentry_sdk
from fastapi import FastAPI
from fastapi.routing import APIRoute
@@ -6,6 +8,8 @@ from starlette.middleware.cors import CORSMiddleware
from app.api.main import api_router
from app.core.config import settings
FRONTEND_DIR = Path(__file__).parent / "frontend"
def custom_generate_unique_id(route: APIRoute) -> str:
return f"{route.tags[0]}-{route.name}"
@@ -31,3 +35,4 @@ if settings.all_cors_origins:
)
app.include_router(api_router, prefix=settings.API_V1_STR)
app.frontend("/", directory=FRONTEND_DIR)
+11 -7
View File
@@ -1,5 +1,5 @@
import uuid
from datetime import datetime, timezone
from datetime import UTC, datetime
from pydantic import EmailStr
from sqlalchemy import DateTime
@@ -7,7 +7,7 @@ from sqlmodel import Field, Relationship, SQLModel
def get_datetime_utc() -> datetime:
return datetime.now(timezone.utc)
return datetime.now(UTC)
# Shared properties
@@ -30,8 +30,11 @@ class UserRegister(SQLModel):
# Properties to receive via API on update, all are optional
class UserUpdate(UserBase):
email: EmailStr | None = Field(default=None, max_length=255) # type: ignore
class UserUpdate(SQLModel):
email: EmailStr | None = Field(default=None, max_length=255)
is_active: bool | None = None
is_superuser: bool | None = None
full_name: str | None = Field(default=None, max_length=255)
password: str | None = Field(default=None, min_length=8, max_length=128)
@@ -53,7 +56,7 @@ class User(UserBase, table=True):
default_factory=get_datetime_utc,
sa_type=DateTime(timezone=True), # type: ignore
)
items: list["Item"] = Relationship(back_populates="owner", cascade_delete=True)
items: list[Item] = Relationship(back_populates="owner", cascade_delete=True)
# Properties to return via API, id is always required
@@ -79,8 +82,9 @@ class ItemCreate(ItemBase):
# Properties to receive on item update
class ItemUpdate(ItemBase):
title: str | None = Field(default=None, min_length=1, max_length=255) # type: ignore
class ItemUpdate(SQLModel):
title: str | None = Field(default=None, min_length=1, max_length=255)
description: str | None = Field(default=None, max_length=255)
# Database model, database table inferred from class name
+5 -4
View File
@@ -1,10 +1,10 @@
import logging
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import Any
import emails # type: ignore
import emails
import jwt
from jinja2 import Template
from jwt.exceptions import InvalidTokenError
@@ -37,7 +37,8 @@ def send_email(
html_content: str = "",
) -> None:
assert settings.emails_enabled, "no provided configuration for email variables"
message = emails.Message(
assert settings.EMAILS_FROM_EMAIL # For type checker
message = emails.message.Message(
subject=subject,
html=html_content,
mail_from=(settings.EMAILS_FROM_NAME, settings.EMAILS_FROM_EMAIL),
@@ -102,7 +103,7 @@ def generate_new_account_email(
def generate_password_reset_token(email: str) -> str:
delta = timedelta(hours=settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS)
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
expires = now + delta
exp = expires.timestamp()
encoded_jwt = jwt.encode(
+18 -12
View File
@@ -2,31 +2,31 @@
name = "app"
version = "0.1.0"
description = ""
requires-python = ">=3.10,<4.0"
requires-python = ">=3.14,<4.0"
dependencies = [
"fastapi[standard]<1.0.0,>=0.114.2",
"python-multipart<1.0.0,>=0.0.7",
"fastapi[standard]>=0.139.0,<1.0.0",
"python-multipart<1.0.0,>=0.0.27",
"email-validator<3.0.0.0,>=2.1.0.post1",
"tenacity<9.0.0,>=8.2.3",
"tenacity<10.0.0,>=8.2.3",
"pydantic>2.0",
"emails<1.0,>=0.6",
"emails>=1.1.2,<2.0",
"jinja2<4.0.0,>=3.1.4",
"alembic<2.0.0,>=1.12.1",
"httpx<1.0.0,>=0.25.1",
"psycopg[binary]<4.0.0,>=3.1.13",
"sqlmodel<1.0.0,>=0.0.21",
"sqlmodel>=0.0.39,<1.0.0",
"pydantic-settings<3.0.0,>=2.2.1",
"sentry-sdk[fastapi]<2.0.0,>=1.40.6",
"pyjwt<3.0.0,>=2.8.0",
"sentry-sdk[fastapi]>=2.63.0,<3.0.0",
"pyjwt<3.0.0,>=2.13.0",
"pwdlib[argon2,bcrypt]>=0.3.0",
]
[dependency-groups]
dev = [
"pytest<8.0.0,>=7.4.3",
"mypy<2.0.0,>=1.8.0",
"pytest<10.0.0,>=7.4.3",
"mypy<3.0.0,>=1.8.0",
"ty>=0.0.25",
"ruff<1.0.0,>=0.2.2",
"prek>=0.2.24,<1.0.0",
"coverage<8.0.0,>=7.4.3",
]
@@ -39,7 +39,7 @@ strict = true
exclude = ["venv", ".venv", "alembic"]
[tool.ruff]
target-version = "py310"
target-version = "py314"
exclude = ["alembic"]
[tool.ruff.lint]
@@ -75,3 +75,9 @@ sort = "-Cover"
[tool.coverage.html]
show_contexts = true
[tool.ty.terminal]
error-on-warning = true
[tool.fastapi]
entrypoint = "app.main:app"
+1
View File
@@ -4,5 +4,6 @@ set -e
set -x
mypy app
ty check app
ruff check app
ruff format app --check
+1 -1
View File
@@ -3,6 +3,6 @@
set -e
set -x
coverage run -m pytest tests/
FASTAPI_ENV=development coverage run -m pytest tests/
coverage report
coverage html --title "${@-coverage}"
+2 -2
View File
@@ -13,7 +13,7 @@ from tests.utils.utils import get_superuser_token_headers
@pytest.fixture(scope="session", autouse=True)
def db() -> Generator[Session, None, None]:
def db() -> Generator[Session]:
with Session(engine) as session:
init_db(session)
yield session
@@ -25,7 +25,7 @@ def db() -> Generator[Session, None, None]:
@pytest.fixture(scope="module")
def client() -> Generator[TestClient, None, None]:
def client() -> Generator[TestClient]:
with TestClient(app) as c:
yield c
+260 -404
View File
File diff suppressed because it is too large Load Diff
+9 -14
View File
@@ -1,8 +1,7 @@
services:
# Local services are available on their ports, but also available on:
# http://api.localhost.tiangolo.com: backend
# http://dashboard.localhost.tiangolo.com: frontend
# http://localhost.tiangolo.com: application
# etc. To enable it, update .env, set:
# DOMAIN=localhost.tiangolo.com
proxy:
@@ -78,6 +77,13 @@ services:
- .venv
- path: ./backend/pyproject.toml
action: rebuild
- path: ./frontend
action: rebuild
ignore:
- ./frontend/node_modules
- ./frontend/dist
- ./frontend/blob-report
- ./frontend/test-results
# TODO: remove once coverage is done locally
volumes:
- ./backend/htmlcov:/app/backend/htmlcov
@@ -93,23 +99,11 @@ services:
- "1080:1080"
- "1025:1025"
frontend:
restart: "no"
ports:
- "5173:80"
build:
context: .
dockerfile: frontend/Dockerfile
args:
- VITE_API_URL=http://localhost:8000
- NODE_ENV=development
playwright:
build:
context: .
dockerfile: frontend/Dockerfile.playwright
args:
- VITE_API_URL=http://backend:8000
- NODE_ENV=production
ipc: host
depends_on:
@@ -118,6 +112,7 @@ services:
env_file:
- .env
environment:
- PLAYWRIGHT_BASE_URL=http://backend:8000
- VITE_API_URL=http://backend:8000
- MAILCATCHER_HOST=http://mailcatcher:1080
# For the reports when run locally
+3 -34
View File
@@ -10,11 +10,10 @@ services:
start_period: 30s
timeout: 10s
volumes:
- app-db-data:/var/lib/postgresql/data/pgdata
- app-db-data:/var/lib/postgresql
env_file:
- .env
environment:
- PGDATA=/var/lib/postgresql/data/pgdata
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set}
- POSTGRES_USER=${POSTGRES_USER?Variable not set}
- POSTGRES_DB=${POSTGRES_DB?Variable not set}
@@ -125,46 +124,16 @@ services:
- traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=8000
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=Host(`api.${DOMAIN?Variable not set}`)
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=Host(`${DOMAIN?Variable not set}`)
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.entrypoints=http
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=Host(`api.${DOMAIN?Variable not set}`)
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=Host(`${DOMAIN?Variable not set}`)
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.entrypoints=https
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls=true
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls.certresolver=le
# Enable redirection for HTTP and HTTPS
- traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.middlewares=https-redirect
frontend:
image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}'
restart: always
networks:
- traefik-public
- default
build:
context: .
dockerfile: frontend/Dockerfile
args:
- VITE_API_URL=https://api.${DOMAIN?Variable not set}
- NODE_ENV=production
labels:
- traefik.enable=true
- traefik.docker.network=traefik-public
- traefik.constraint-label=traefik-public
- traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`dashboard.${DOMAIN?Variable not set}`)
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.entrypoints=http
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.rule=Host(`dashboard.${DOMAIN?Variable not set}`)
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.entrypoints=https
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls=true
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls.certresolver=le
# Enable redirection for HTTP and HTTPS
- traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.middlewares=https-redirect
volumes:
app-db-data:
+18 -15
View File
@@ -11,8 +11,7 @@ But you have to configure a couple things first. 🤓
## Preparation
* Have a remote server ready and available.
* Configure the DNS records of your domain to point to the IP of the server you just created.
* Configure a wildcard subdomain for your domain, so that you can have multiple subdomains for different services, e.g. `*.fastapi-project.example.com`. This will be useful for accessing different components, like `dashboard.fastapi-project.example.com`, `api.fastapi-project.example.com`, `traefik.fastapi-project.example.com`, `adminer.fastapi-project.example.com`, etc. And also for `staging`, like `dashboard.staging.fastapi-project.example.com`, `adminer.staging.fastapi-project.example.com`, etc.
* Configure DNS records pointing to the server for the application domain and any supporting service subdomains you want to expose, e.g. `fastapi-project.example.com`, `traefik.fastapi-project.example.com`, and `adminer.fastapi-project.example.com`. Do the same for staging domains such as `staging.fastapi-project.example.com`.
* Install and configure [Docker](https://docs.docker.com/engine/install/) on the remote server (Docker Engine, not Docker Desktop).
## Public Traefik
@@ -112,7 +111,7 @@ Now that you have Traefik in place you can deploy your FastAPI project with Dock
## Copy the Code
```bash
rsync -av --filter=":- .gitignore" ./ root@your-server.example.com:/root/code/app/
rsync -av --exclude=".git/" --filter=":- .gitignore" ./ root@your-server.example.com:/root/code/app/
```
Note: `--filter=":- .gitignore"` tells `rsync` to use the same rules as git, ignore files ignored by git, like the Python virtual environment.
@@ -167,17 +166,17 @@ Set the `FIRST_SUPER_USER_PASSWORD` to something different than `changethis`:
export FIRST_SUPERUSER_PASSWORD="changethis"
```
Set the `BACKEND_CORS_ORIGINS` to include your domain:
Set the `FRONTEND_HOST` to the application URL. It is used to generate links in emails:
```bash
export BACKEND_CORS_ORIGINS="https://dashboard.${DOMAIN?Variable not set},https://api.${DOMAIN?Variable not set}"
export FRONTEND_HOST="https://${DOMAIN?Variable not set}"
```
You can set several other environment variables:
* `PROJECT_NAME`: The name of the project, used in the API for the docs and emails.
* `STACK_NAME`: The name of the stack used for Docker Compose labels and project name, this should be different for `staging`, `production`, etc. You could use the same domain replacing dots with dashes, e.g. `fastapi-project-example-com` and `staging-fastapi-project-example-com`.
* `BACKEND_CORS_ORIGINS`: A list of allowed CORS origins separated by commas.
* `BACKEND_CORS_ORIGINS`: A list of additional allowed CORS origins separated by commas. The frontend served by FastAPI uses the same origin and doesn't need to be added.
* `FIRST_SUPERUSER`: The email of the first superuser, this superuser will be the one that can create new users.
* `SMTP_HOST`: The SMTP server host to send emails, this would come from your email provider (E.g. Mailgun, Sparkpost, Sendgrid, etc).
* `SMTP_USER`: The SMTP server user to send emails.
@@ -288,9 +287,15 @@ cd /home/github/actions-runner
You can read more about it in the official guide: [Configuring the self-hosted runner application as a service](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service).
### Configure GitHub Environments
The deployment workflows use [GitHub Environments](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments) for `staging` and `production`. This enables environment-specific secrets, deployment protection rules (e.g. required reviewers, wait timers), and deployment status tracking.
To configure them, go to your repository's **Settings** > **Environments** and create the `staging` and `production` environments.
### Set Secrets
On your repository, configure secrets for the environment variables you need, the same ones described above, including `SECRET_KEY`, etc. Follow the [official GitHub guide for setting repository secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository).
For each GitHub Environment (`staging` and `production`), configure the required secrets as [environment secrets](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets#creating-secrets-for-an-environment). Environment secrets are preferred over [repository secrets](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets#creating-secrets-for-a-repository) because they are scoped to the specific environment, reducing exposure and aligning with any protection rules you configure.
The current Github Actions workflows expect these secrets:
@@ -313,6 +318,8 @@ There are GitHub Action workflows in the `.github/workflows` directory already c
* `staging`: after pushing (or merging) to the branch `master`.
* `production`: after publishing a release.
Both workflows are associated with their respective GitHub Environments, so deployments will be visible in the repository's **Environments** section and will respect any protection rules you configure.
If you need to add extra environments you could use those as a starting point.
## URLs
@@ -325,20 +332,16 @@ Traefik UI: `https://traefik.fastapi-project.example.com`
### Production
Frontend: `https://dashboard.fastapi-project.example.com`
Application (frontend and API): `https://fastapi-project.example.com`
Backend API docs: `https://api.fastapi-project.example.com/docs`
Backend API base URL: `https://api.fastapi-project.example.com`
Interactive API docs: `https://fastapi-project.example.com/docs`
Adminer: `https://adminer.fastapi-project.example.com`
### Staging
Frontend: `https://dashboard.staging.fastapi-project.example.com`
Application (frontend and API): `https://staging.fastapi-project.example.com`
Backend API docs: `https://api.staging.fastapi-project.example.com/docs`
Backend API base URL: `https://api.staging.fastapi-project.example.com`
Interactive API docs: `https://staging.fastapi-project.example.com/docs`
Adminer: `https://adminer.staging.fastapi-project.example.com`
+15 -27
View File
@@ -10,11 +10,9 @@ docker compose watch
* Now you can open your browser and interact with these URLs:
Frontend, built with Docker, with routes handled based on the path: <http://localhost:5173>
Application, with the frontend and API served by FastAPI: <http://localhost:8000>
Backend, JSON based web API based on OpenAPI: <http://localhost:8000>
Automatic interactive documentation with Swagger UI (from the OpenAPI backend): <http://localhost:8000/docs>
Automatic interactive API documentation with Swagger UI: <http://localhost:8000/docs>
Adminer, database web administration: <http://localhost:8080>
@@ -48,19 +46,13 @@ The backend is automatically configured to use Mailcatcher when running with Doc
## Local Development
The Docker Compose files are configured so that each of the services is available in a different port in `localhost`.
The Docker Compose files are configured so that each supporting service is available in a different port in `localhost`.
For the backend and frontend, they use the same port that would be used by their local development server, so, the backend is at `http://localhost:8000` and the frontend at `http://localhost:5173`.
FastAPI serves the built frontend and the API as one application at `http://localhost:8000`. The API routes live under `/api`.
This way, you could turn off a Docker Compose service and start its local development service, and everything would keep working, because it all uses the same ports.
For frontend development with live reload, you can still run the local Vite development server separately.
For example, you can stop that `frontend` service in the Docker Compose, in another terminal, run:
```bash
docker compose stop frontend
```
And then start the local frontend development server:
Start the local frontend development server with:
```bash
bun run dev
@@ -81,11 +73,11 @@ fastapi dev app/main.py
## Docker Compose in `localhost.tiangolo.com`
When you start the Docker Compose stack, it uses `localhost` by default, with different ports for each service (backend, frontend, adminer, etc).
When you start the Docker Compose stack, it uses `localhost` by default, with different ports for each service (backend, adminer, etc).
When you deploy it to production (or staging), it will deploy each service in a different subdomain, like `api.example.com` for the backend and `dashboard.example.com` for the frontend.
When you deploy it to production (or staging), the application uses one domain. The frontend is served at `/` and the API lives under `/api`.
In the guide about [deployment](deployment.md) you can read about Traefik, the configured proxy. That's the component in charge of transmitting traffic to each service based on the subdomain.
In the guide about [deployment](deployment.md) you can read about Traefik, the configured proxy. That's the component in charge of transmitting traffic to the application service based on the domain.
If you want to test that it's all working locally, you can edit the local `.env` file, and change:
@@ -95,7 +87,7 @@ DOMAIN=localhost.tiangolo.com
That will be used by the Docker Compose files to configure the base domain for the services.
Traefik will use this to transmit traffic at `api.localhost.tiangolo.com` to the backend, and traffic at `dashboard.localhost.tiangolo.com` to the frontend.
Traefik will transmit application traffic at `localhost.tiangolo.com` to FastAPI, which serves both the frontend and API.
The domain `localhost.tiangolo.com` is a special domain that is configured (with all its subdomains) to point to `127.0.0.1`. This way you can use that for your local development.
@@ -105,7 +97,7 @@ After you update it, run again:
docker compose watch
```
When deploying, for example in production, the main Traefik is configured outside of the Docker Compose files. For local development, there's an included Traefik in `compose.override.yml`, just to let you test that the domains work as expected, for example with `api.localhost.tiangolo.com` and `dashboard.localhost.tiangolo.com`.
When deploying, for example in production, the main Traefik is configured outside of the Docker Compose files. For local development, there's an included Traefik in `compose.override.yml`, just to let you test that the domain works as expected, for example with `localhost.tiangolo.com`.
## Docker Compose files and env vars
@@ -188,9 +180,7 @@ The production or staging URLs would use these same paths, but with your own dom
Development URLs, for local development.
Frontend: <http://localhost:5173>
Backend: <http://localhost:8000>
Application: <http://localhost:8000>
Automatic Interactive Docs (Swagger UI): <http://localhost:8000/docs>
@@ -206,13 +196,11 @@ MailCatcher: <http://localhost:1080>
Development URLs, for local development.
Frontend: <http://dashboard.localhost.tiangolo.com>
Application: <http://localhost.tiangolo.com>
Backend: <http://api.localhost.tiangolo.com>
Automatic Interactive Docs (Swagger UI): <http://localhost.tiangolo.com/docs>
Automatic Interactive Docs (Swagger UI): <http://api.localhost.tiangolo.com/docs>
Automatic Alternative Docs (ReDoc): <http://api.localhost.tiangolo.com/redoc>
Automatic Alternative Docs (ReDoc): <http://localhost.tiangolo.com/redoc>
Adminer: <http://localhost.tiangolo.com:8080>
-26
View File
@@ -1,26 +0,0 @@
# Stage 0, "build-stage", based on Bun, to build and compile the frontend
FROM oven/bun:1 AS build-stage
WORKDIR /app
COPY package.json bun.lock /app/
COPY frontend/package.json /app/frontend/
WORKDIR /app/frontend
RUN bun install
COPY ./frontend /app/frontend
ARG VITE_API_URL
RUN bun run build
# Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx
FROM nginx:1
COPY --from=build-stage /app/frontend/dist/ /usr/share/nginx/html
COPY ./frontend/nginx.conf /etc/nginx/conf.d/default.conf
COPY ./frontend/nginx-backend-not-found.conf /etc/nginx/extra-conf.d/backend-not-found.conf
+1 -3
View File
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/playwright:v1.57.0-noble
FROM mcr.microsoft.com/playwright:v1.61.1-noble
WORKDIR /app
@@ -17,5 +17,3 @@ WORKDIR /app/frontend
RUN bun install
COPY ./frontend /app/frontend
ARG VITE_API_URL
+8 -7
View File
@@ -15,7 +15,7 @@ bun run dev
* Then open your browser at http://localhost:5173/.
Notice that this live server is not running inside Docker, it's for local development, and that is the recommended workflow. Once you are happy with your frontend, you can build the frontend Docker image and start it, to test it in a production-like environment. But building the image at every change will not be as productive as running the local development server with live reload.
Notice that this live server is not running inside Docker, it's for local development, and that is the recommended workflow. Once you are happy with your frontend, you can build the backend Docker image and start it, to test the production-like setup where FastAPI serves the built frontend at `http://localhost:8000`.
Check the file `package.json` to see other available options.
@@ -25,18 +25,19 @@ If you are developing an API-only app and want to remove the frontend, you can d
* Remove the `./frontend` directory.
* In the `compose.yml` file, remove the whole service / section `frontend`.
* In the `backend/app/main.py` file, remove the `app.frontend()` call.
* In the `compose.override.yml` file, remove the whole service / section `frontend` and `playwright`.
* In the `backend/Dockerfile` file, remove the frontend build stage and the `COPY --from=frontend-build` instruction.
* In the `compose.override.yml` file, remove the `playwright` service.
Done, you have a frontend-less (api-only) app. 🤓
---
If you want, you can also remove the `FRONTEND` environment variables from:
If you want, you can also remove the `FRONTEND_HOST` environment variable from:
* `.env`
* `./scripts/*.sh`
But it would be only to clean them up, leaving them won't really have any effect either way.
@@ -71,10 +72,10 @@ Notice that everytime the backend changes (changing the OpenAPI schema), you sho
## Using a Remote API
If you want to use a remote API, you can set the environment variable `VITE_API_URL` to the URL of the remote API. For example, you can set it in the `frontend/.env` file:
By default, the built frontend uses the same origin as the FastAPI app. If you want to use a remote API while running the Vite development server, you can set the environment variable `VITE_API_URL` to the URL of the remote API. For example, you can set it in the `frontend/.env` file:
```env
VITE_API_URL=https://api.my-domain.example.com
VITE_API_URL=https://my-domain.example.com
```
Then, when you run the frontend, it will use that URL as the base URL for the API.
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"files": {
"includes": [
-9
View File
@@ -1,9 +0,0 @@
location /api {
return 404;
}
location /docs {
return 404;
}
location /redoc {
return 404;
}
-11
View File
@@ -1,11 +0,0 @@
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri /index.html =404;
}
include /etc/nginx/extra-conf.d/*.conf;
}
+8 -20
View File
@@ -5,29 +5,17 @@ export default defineConfig({
output: "./src/client",
plugins: [
"legacy/axios",
{ name: "@hey-api/client-axios", throwOnError: true },
{ name: "@hey-api/typescript", case: "preserve" },
{
name: "@hey-api/sdk",
// NOTE: this doesn't allow tree-shaking
asClass: true,
operationId: true,
classNameBuilder: "{{name}}Service",
methodNameBuilder: (operation) => {
// @ts-expect-error
let name: string = operation.name
// @ts-expect-error
const service: string = operation.service
if (service && name.toLowerCase().startsWith(service.toLowerCase())) {
name = name.slice(service.length)
}
return name.charAt(0).toLowerCase() + name.slice(1)
operations: {
// NOTE: this doesn't allow tree-shaking
strategy: "byTags",
methods: "static",
containerName: "{{name}}Service",
methodName: (name: string): string => name.replace(/^[^-]*-/, ""),
},
},
{
name: "@hey-api/schemas",
type: "json",
},
],
})
+38 -38
View File
@@ -13,54 +13,54 @@
"test:ui": "bunx playwright test --ui"
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@hookform/resolvers": "^5.4.0",
"@radix-ui/react-avatar": "^1.1.12",
"@radix-ui/react-checkbox": "^1.3.4",
"@radix-ui/react-dialog": "^1.1.16",
"@radix-ui/react-dropdown-menu": "^2.1.17",
"@radix-ui/react-label": "^2.1.9",
"@radix-ui/react-radio-group": "^1.4.0",
"@radix-ui/react-scroll-area": "^1.2.11",
"@radix-ui/react-select": "^2.3.0",
"@radix-ui/react-separator": "^1.1.9",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/react-query": "^5.90.12",
"@tanstack/react-query-devtools": "^5.91.1",
"@tanstack/react-router": "^1.142.11",
"@tanstack/react-router-devtools": "^1.142.8",
"@radix-ui/react-tabs": "^1.1.14",
"@radix-ui/react-tooltip": "^1.2.9",
"@tailwindcss/vite": "^4.3.0",
"@tanstack/react-query": "^5.101.0",
"@tanstack/react-query-devtools": "^5.101.0",
"@tanstack/react-router": "^1.170.15",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-table": "^8.21.3",
"axios": "1.13.2",
"axios": "1.18.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"form-data": "4.0.5",
"lucide-react": "^0.562.0",
"form-data": "4.0.6",
"lucide-react": "^1.17.0",
"next-themes": "^0.4.6",
"react": "^19.1.1",
"react-dom": "^19.2.3",
"react-error-boundary": "^6.0.0",
"react-hook-form": "^7.68.0",
"react-icons": "^5.5.0",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"react-error-boundary": "^6.1.2",
"react-hook-form": "^7.78.0",
"react-icons": "^5.6.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.17",
"zod": "^4.2.1"
"tailwind-merge": "^3.6.0",
"tailwindcss": "^4.2.1",
"zod": "^4.4.3"
},
"devDependencies": {
"@biomejs/biome": "^2.3.10",
"@hey-api/openapi-ts": "0.73.0",
"@playwright/test": "1.57.0",
"@tanstack/router-devtools": "^1.142.11",
"@tanstack/router-plugin": "^1.140.0",
"@types/node": "^25.0.2",
"@types/react": "^19.2.7",
"@biomejs/biome": "^2.4.16",
"@hey-api/openapi-ts": "0.97.3",
"@playwright/test": "1.61.1",
"@tanstack/router-devtools": "^1.167.0",
"@tanstack/router-plugin": "^1.168.18",
"@types/node": "^25.9.2",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react-swc": "^4.2.2",
"dotenv": "^17.2.3",
"@vitejs/plugin-react-swc": "^4.3.1",
"dotenv": "^17.4.2",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"vite": "^7.3.0"
"vite": "^8.0.16"
}
}
+10 -6
View File
@@ -1,6 +1,8 @@
import { defineConfig, devices } from '@playwright/test';
import 'dotenv/config'
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:5173'
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
@@ -24,7 +26,7 @@ export default defineConfig({
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:5173',
baseURL,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
@@ -83,9 +85,11 @@ export default defineConfig({
],
/* Run your local dev server before starting the tests */
webServer: {
command: 'bun run dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
},
webServer: process.env.PLAYWRIGHT_BASE_URL
? undefined
: {
command: 'bun run dev',
url: baseURL,
reuseExistingServer: !process.env.CI,
},
});
+16
View File
@@ -0,0 +1,16 @@
// This file is auto-generated by @hey-api/openapi-ts
import { type ClientOptions, type Config, createClient, createConfig } from './client';
import type { ClientOptions as ClientOptions2 } from './types.gen';
/**
* The `createClientConfig()` function will be called on client initialization
* and the returned object will become the client's initial configuration.
*
* You may want to initialize your client this way instead of calling
* `setConfig()`. This is useful for example if you're using Next.js
* to ensure your client always has the correct values.
*/
export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;
export const client = createClient(createConfig<ClientOptions2>({ throwOnError: true }));
+161
View File
@@ -0,0 +1,161 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { AxiosError, AxiosInstance, RawAxiosRequestHeaders } from 'axios';
import axios from 'axios';
import { createSseClient } from '../core/serverSentEvents.gen';
import type { HttpMethod } from '../core/types.gen';
import { getValidRequestBody } from '../core/utils.gen';
import type { Client, Config, RequestOptions } from './types.gen';
import { buildUrl, createConfig, mergeConfigs, mergeHeaders, setAuthParams } from './utils.gen';
export const createClient = (config: Config = {}): Client => {
let _config = mergeConfigs(createConfig(), config);
let instance: AxiosInstance;
if (_config.axios && !('Axios' in _config.axios)) {
instance = _config.axios;
} else {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { auth, ...configWithoutAuth } = _config;
instance = axios.create(configWithoutAuth);
}
const getConfig = (): Config => ({ ..._config });
const setConfig = (config: Config): Config => {
_config = mergeConfigs(_config, config);
instance.defaults = {
...instance.defaults,
..._config,
// @ts-expect-error
headers: mergeHeaders(instance.defaults.headers, _config.headers),
};
return getConfig();
};
const beforeRequest = async <
TData = unknown,
ThrowOnError extends boolean = boolean,
Url extends string = string,
>(
options: RequestOptions<TData, ThrowOnError, Url>,
) => {
const opts = {
..._config,
...options,
axios: options.axios ?? _config.axios ?? instance,
headers: mergeHeaders(_config.headers, options.headers),
};
if (opts.security) {
await setAuthParams(opts);
}
if (opts.requestValidator) {
await opts.requestValidator(opts);
}
if (opts.body !== undefined && opts.bodySerializer) {
opts.body = opts.bodySerializer(opts.body);
}
const url = buildUrl(opts);
return { opts, url };
};
// @ts-expect-error
const request: Client['request'] = async (options) => {
const { opts, url } = await beforeRequest(options);
try {
// assign Axios here for consistency with fetch
const _axios = opts.axios!;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { auth, ...optsWithoutAuth } = opts;
const response = await _axios({
...optsWithoutAuth,
baseURL: '', // the baseURL is already included in `url`
data: getValidRequestBody(opts),
headers: opts.headers as RawAxiosRequestHeaders,
// let `paramsSerializer()` handle query params if it exists
params: opts.paramsSerializer ? opts.query : undefined,
url,
});
let { data } = response;
if (opts.responseType === 'json') {
if (opts.responseValidator) {
await opts.responseValidator(data);
}
if (opts.responseTransformer) {
data = await opts.responseTransformer(data);
}
}
return {
...response,
data: data ?? {},
};
} catch (error) {
const e = error as AxiosError;
if (opts.throwOnError) {
throw e;
}
// @ts-expect-error
e.error = e.response?.data ?? {};
return e;
}
};
const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
request({ ...options, method });
const makeSseFn = (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
const { opts, url } = await beforeRequest(options);
return createSseClient({
...opts,
body: opts.body as BodyInit | null | undefined,
headers: opts.headers as Record<string, string>,
method,
serializedBody: getValidRequestBody(opts) as BodyInit | null | undefined,
// @ts-expect-error
signal: opts.signal,
url,
});
};
const _buildUrl: Client['buildUrl'] = (options) =>
buildUrl({ axios: instance, ..._config, ...options });
return {
buildUrl: _buildUrl,
connect: makeMethodFn('CONNECT'),
delete: makeMethodFn('DELETE'),
get: makeMethodFn('GET'),
getConfig,
head: makeMethodFn('HEAD'),
instance,
options: makeMethodFn('OPTIONS'),
patch: makeMethodFn('PATCH'),
post: makeMethodFn('POST'),
put: makeMethodFn('PUT'),
request,
setConfig,
sse: {
connect: makeSseFn('CONNECT'),
delete: makeSseFn('DELETE'),
get: makeSseFn('GET'),
head: makeSseFn('HEAD'),
options: makeSseFn('OPTIONS'),
patch: makeSseFn('PATCH'),
post: makeSseFn('POST'),
put: makeSseFn('PUT'),
trace: makeSseFn('TRACE'),
},
trace: makeMethodFn('TRACE'),
} as Client;
};
+23
View File
@@ -0,0 +1,23 @@
// This file is auto-generated by @hey-api/openapi-ts
export type { Auth } from '../core/auth.gen';
export type { QuerySerializerOptions } from '../core/bodySerializer.gen';
export {
formDataBodySerializer,
jsonBodySerializer,
urlSearchParamsBodySerializer,
} from '../core/bodySerializer.gen';
export { buildClientParams } from '../core/params.gen';
export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen';
export { createClient } from './client.gen';
export type {
Client,
ClientOptions,
Config,
CreateClientConfig,
Options,
RequestOptions,
RequestResult,
TDataShape,
} from './types.gen';
export { createConfig } from './utils.gen';
+169
View File
@@ -0,0 +1,169 @@
// This file is auto-generated by @hey-api/openapi-ts
import type {
AxiosError,
AxiosInstance,
AxiosRequestHeaders,
AxiosResponse,
AxiosStatic,
CreateAxiosDefaults,
} from 'axios';
import type { Auth } from '../core/auth.gen';
import type {
ServerSentEventsOptions,
ServerSentEventsResult,
} from '../core/serverSentEvents.gen';
import type { Client as CoreClient, Config as CoreConfig } from '../core/types.gen';
export interface Config<T extends ClientOptions = ClientOptions>
extends Omit<CreateAxiosDefaults, 'auth' | 'baseURL' | 'headers' | 'method'>, CoreConfig {
/**
* Axios implementation. You can use this option to provide either an
* `AxiosStatic` or an `AxiosInstance`.
*
* @default axios
*/
axios?: AxiosStatic | AxiosInstance;
/**
* Base URL for all requests made by this client.
*/
baseURL?: T['baseURL'];
/**
* An object containing any HTTP headers that you want to pre-populate your
* `Headers` object with.
*
* {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more}
*/
headers?:
| AxiosRequestHeaders
| Record<
string,
string | number | boolean | (string | number | boolean)[] | null | undefined | unknown
>;
/**
* Throw an error instead of returning it in the response?
*
* @default false
*/
throwOnError?: T['throwOnError'];
}
export interface RequestOptions<
TData = unknown,
ThrowOnError extends boolean = boolean,
Url extends string = string,
>
extends
Config<{
throwOnError: ThrowOnError;
}>,
Pick<
ServerSentEventsOptions<TData>,
| 'onRequest'
| 'onSseError'
| 'onSseEvent'
| 'sseDefaultRetryDelay'
| 'sseMaxRetryAttempts'
| 'sseMaxRetryDelay'
> {
/**
* Any body that you want to add to your request.
*
* {@link https://developer.mozilla.org/docs/Web/API/fetch#body}
*/
body?: unknown;
path?: Record<string, unknown>;
query?: Record<string, unknown>;
/**
* Security mechanism(s) to use for the request.
*/
security?: ReadonlyArray<Auth>;
url: Url;
}
export interface ClientOptions {
baseURL?: string;
throwOnError?: boolean;
}
export type RequestResult<
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = boolean,
> = ThrowOnError extends true
? Promise<AxiosResponse<TData extends Record<string, unknown> ? TData[keyof TData] : TData>>
: Promise<
| (AxiosResponse<TData extends Record<string, unknown> ? TData[keyof TData] : TData> & {
error: undefined;
})
| (AxiosError<TError extends Record<string, unknown> ? TError[keyof TError] : TError> & {
data: undefined;
error: TError extends Record<string, unknown> ? TError[keyof TError] : TError;
})
>;
type MethodFn = <TData = unknown, TError = unknown, ThrowOnError extends boolean = false>(
options: Omit<RequestOptions<TData, ThrowOnError>, 'method'>,
) => RequestResult<TData, TError, ThrowOnError>;
type SseFn = <
TData = unknown,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_TError = unknown,
ThrowOnError extends boolean = false,
>(
options: Omit<RequestOptions<never, ThrowOnError>, 'method'>,
) => Promise<ServerSentEventsResult<TData>>;
type RequestFn = <TData = unknown, TError = unknown, ThrowOnError extends boolean = false>(
options: Omit<RequestOptions<TData, ThrowOnError>, 'method'> &
Pick<Required<RequestOptions<TData, ThrowOnError>>, 'method'>,
) => RequestResult<TData, TError, ThrowOnError>;
type BuildUrlFn = <
TData extends {
path?: Record<string, unknown>;
query?: Record<string, unknown>;
url: string;
},
>(
options: TData &
Pick<
RequestOptions<unknown, boolean>,
'axios' | 'baseURL' | 'paramsSerializer' | 'querySerializer'
>,
) => string;
export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn, SseFn> & {
instance: AxiosInstance;
};
/**
* The `createClientConfig()` function will be called on client initialization
* and the returned object will become the client's initial configuration.
*
* You may want to initialize your client this way instead of calling
* `setConfig()`. This is useful for example if you're using Next.js
* to ensure your client always has the correct values.
*/
export type CreateClientConfig<T extends ClientOptions = ClientOptions> = (
override?: Config<ClientOptions & T>,
) => Config<Required<ClientOptions> & T>;
export interface TDataShape {
body?: unknown;
headers?: unknown;
path?: unknown;
query?: unknown;
url: string;
}
type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>;
export type Options<
TData extends TDataShape = TDataShape,
ThrowOnError extends boolean = boolean,
TResponse = unknown,
> = OmitKeys<RequestOptions<TResponse, ThrowOnError>, 'body' | 'path' | 'query' | 'url'> &
([TData] extends [never] ? unknown : Omit<TData, 'url'>);
+206
View File
@@ -0,0 +1,206 @@
// This file is auto-generated by @hey-api/openapi-ts
import { getAuthToken } from '../core/auth.gen';
import type { QuerySerializerOptions } from '../core/bodySerializer.gen';
import {
serializeArrayParam,
serializeObjectParam,
serializePrimitiveParam,
} from '../core/pathSerializer.gen';
import { getUrl } from '../core/utils.gen';
import type { Client, ClientOptions, Config, RequestOptions } from './types.gen';
export const createQuerySerializer = <T = unknown>({
parameters = {},
...args
}: QuerySerializerOptions = {}) => {
const querySerializer = (queryParams: T) => {
const search: string[] = [];
if (queryParams && typeof queryParams === 'object') {
for (const name in queryParams) {
const value = queryParams[name];
if (value === undefined || value === null) {
continue;
}
const options = parameters[name] || args;
if (Array.isArray(value)) {
const serializedArray = serializeArrayParam({
allowReserved: options.allowReserved,
explode: true,
name,
style: 'form',
value,
...options.array,
});
if (serializedArray) search.push(serializedArray);
} else if (typeof value === 'object') {
const serializedObject = serializeObjectParam({
allowReserved: options.allowReserved,
explode: true,
name,
style: 'deepObject',
value: value as Record<string, unknown>,
...options.object,
});
if (serializedObject) search.push(serializedObject);
} else {
const serializedPrimitive = serializePrimitiveParam({
allowReserved: options.allowReserved,
name,
value: value as string,
});
if (serializedPrimitive) search.push(serializedPrimitive);
}
}
}
return search.join('&');
};
return querySerializer;
};
const checkForExistence = (
options: Pick<RequestOptions, 'auth' | 'query'> & {
headers: Record<any, unknown>;
},
name?: string,
): boolean => {
if (!name) {
return false;
}
if (name in options.headers || options.query?.[name]) {
return true;
}
if (
'Cookie' in options.headers &&
options.headers['Cookie'] &&
typeof options.headers['Cookie'] === 'string'
) {
return options.headers['Cookie'].includes(`${name}=`);
}
return false;
};
export async function setAuthParams(
options: Pick<RequestOptions, 'auth' | 'query' | 'security'> & {
headers: Record<any, unknown>;
},
): Promise<void> {
for (const auth of options.security ?? []) {
if (checkForExistence(options, auth.name)) {
continue;
}
const token = await getAuthToken(auth, options.auth);
if (!token) {
continue;
}
const name = auth.name ?? 'Authorization';
switch (auth.in) {
case 'query':
if (!options.query) {
options.query = {};
}
options.query[name] = token;
break;
case 'cookie': {
const value = `${name}=${token}`;
if ('Cookie' in options.headers && options.headers['Cookie']) {
options.headers['Cookie'] = `${options.headers['Cookie']}; ${value}`;
} else {
options.headers['Cookie'] = value;
}
break;
}
case 'header':
default:
options.headers[name] = token;
break;
}
}
}
export const buildUrl: Client['buildUrl'] = (options) => {
const instanceBaseUrl = options.axios?.defaults?.baseURL;
const baseUrl =
options.baseURL && typeof options.baseURL === 'string' ? options.baseURL : instanceBaseUrl;
return getUrl({
baseUrl: baseUrl as string,
path: options.path,
// let `paramsSerializer()` handle query params if it exists
query: !options.paramsSerializer ? options.query : undefined,
querySerializer:
typeof options.querySerializer === 'function'
? options.querySerializer
: createQuerySerializer(options.querySerializer),
url: options.url,
});
};
export const mergeConfigs = (a: Config, b: Config): Config => {
const config = { ...a, ...b };
config.headers = mergeHeaders(a.headers, b.headers);
return config;
};
/**
* Special Axios headers keywords allowing to set headers by request method.
*/
export const axiosHeadersKeywords = [
'common',
'delete',
'get',
'head',
'patch',
'post',
'put',
] as const;
export const mergeHeaders = (
...headers: Array<Required<Config>['headers'] | undefined>
): Record<any, unknown> => {
const mergedHeaders: Record<any, unknown> = {};
for (const header of headers) {
if (!header || typeof header !== 'object') {
continue;
}
const iterator = Object.entries(header);
for (const [key, value] of iterator) {
if (
axiosHeadersKeywords.includes(key as (typeof axiosHeadersKeywords)[number]) &&
typeof value === 'object'
) {
mergedHeaders[key] = {
...(mergedHeaders[key] as Record<any, unknown>),
...value,
};
} else if (value === null) {
delete mergedHeaders[key];
} else if (Array.isArray(value)) {
for (const v of value) {
// @ts-expect-error
mergedHeaders[key] = [...(mergedHeaders[key] ?? []), v as string];
}
} else if (value !== undefined) {
// assume object headers are meant to be JSON stringified, i.e., their
// content value in OpenAPI specification is 'application/json'
mergedHeaders[key] = typeof value === 'object' ? JSON.stringify(value) : (value as string);
}
}
}
return mergedHeaders;
};
export const createConfig = <T extends ClientOptions = ClientOptions>(
override: Config<Omit<ClientOptions, keyof T> & T> = {},
): Config<Omit<ClientOptions, keyof T> & T> => ({
...override,
});
-21
View File
@@ -1,21 +0,0 @@
import type { ApiRequestOptions } from './ApiRequestOptions';
import type { ApiResult } from './ApiResult';
export class ApiError extends Error {
public readonly url: string;
public readonly status: number;
public readonly statusText: string;
public readonly body: unknown;
public readonly request: ApiRequestOptions;
constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
super(message);
this.name = 'ApiError';
this.url = response.url;
this.status = response.status;
this.statusText = response.statusText;
this.body = response.body;
this.request = request;
}
}
@@ -1,21 +0,0 @@
export type ApiRequestOptions<T = unknown> = {
readonly body?: any;
readonly cookies?: Record<string, unknown>;
readonly errors?: Record<number | string, string>;
readonly formData?: Record<string, unknown> | any[] | Blob | File;
readonly headers?: Record<string, unknown>;
readonly mediaType?: string;
readonly method:
| 'DELETE'
| 'GET'
| 'HEAD'
| 'OPTIONS'
| 'PATCH'
| 'POST'
| 'PUT';
readonly path?: Record<string, unknown>;
readonly query?: Record<string, unknown>;
readonly responseHeader?: string;
readonly responseTransformer?: (data: unknown) => Promise<T>;
readonly url: string;
};
-7
View File
@@ -1,7 +0,0 @@
export type ApiResult<TData = any> = {
readonly body: TData;
readonly ok: boolean;
readonly status: number;
readonly statusText: string;
readonly url: string;
};
@@ -1,126 +0,0 @@
export class CancelError extends Error {
constructor(message: string) {
super(message);
this.name = 'CancelError';
}
public get isCancelled(): boolean {
return true;
}
}
export interface OnCancel {
readonly isResolved: boolean;
readonly isRejected: boolean;
readonly isCancelled: boolean;
(cancelHandler: () => void): void;
}
export class CancelablePromise<T> implements Promise<T> {
private _isResolved: boolean;
private _isRejected: boolean;
private _isCancelled: boolean;
readonly cancelHandlers: (() => void)[];
readonly promise: Promise<T>;
private _resolve?: (value: T | PromiseLike<T>) => void;
private _reject?: (reason?: unknown) => void;
constructor(
executor: (
resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: unknown) => void,
onCancel: OnCancel
) => void
) {
this._isResolved = false;
this._isRejected = false;
this._isCancelled = false;
this.cancelHandlers = [];
this.promise = new Promise<T>((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
const onResolve = (value: T | PromiseLike<T>): void => {
if (this._isResolved || this._isRejected || this._isCancelled) {
return;
}
this._isResolved = true;
if (this._resolve) this._resolve(value);
};
const onReject = (reason?: unknown): void => {
if (this._isResolved || this._isRejected || this._isCancelled) {
return;
}
this._isRejected = true;
if (this._reject) this._reject(reason);
};
const onCancel = (cancelHandler: () => void): void => {
if (this._isResolved || this._isRejected || this._isCancelled) {
return;
}
this.cancelHandlers.push(cancelHandler);
};
Object.defineProperty(onCancel, 'isResolved', {
get: (): boolean => this._isResolved,
});
Object.defineProperty(onCancel, 'isRejected', {
get: (): boolean => this._isRejected,
});
Object.defineProperty(onCancel, 'isCancelled', {
get: (): boolean => this._isCancelled,
});
return executor(onResolve, onReject, onCancel as OnCancel);
});
}
get [Symbol.toStringTag]() {
return "Cancellable Promise";
}
public then<TResult1 = T, TResult2 = never>(
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
): Promise<TResult1 | TResult2> {
return this.promise.then(onFulfilled, onRejected);
}
public catch<TResult = never>(
onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null
): Promise<T | TResult> {
return this.promise.catch(onRejected);
}
public finally(onFinally?: (() => void) | null): Promise<T> {
return this.promise.finally(onFinally);
}
public cancel(): void {
if (this._isResolved || this._isRejected || this._isCancelled) {
return;
}
this._isCancelled = true;
if (this.cancelHandlers.length) {
try {
for (const cancelHandler of this.cancelHandlers) {
cancelHandler();
}
} catch (error) {
console.warn('Cancellation threw an error', error);
return;
}
}
this.cancelHandlers.length = 0;
if (this._reject) this._reject(new CancelError('Request aborted'));
}
public get isCancelled(): boolean {
return this._isCancelled;
}
}
-57
View File
@@ -1,57 +0,0 @@
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
import type { ApiRequestOptions } from './ApiRequestOptions';
type Headers = Record<string, string>;
type Middleware<T> = (value: T) => T | Promise<T>;
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>;
export class Interceptors<T> {
_fns: Middleware<T>[];
constructor() {
this._fns = [];
}
eject(fn: Middleware<T>): void {
const index = this._fns.indexOf(fn);
if (index !== -1) {
this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)];
}
}
use(fn: Middleware<T>): void {
this._fns = [...this._fns, fn];
}
}
export type OpenAPIConfig = {
BASE: string;
CREDENTIALS: 'include' | 'omit' | 'same-origin';
ENCODE_PATH?: ((path: string) => string) | undefined;
HEADERS?: Headers | Resolver<Headers> | undefined;
PASSWORD?: string | Resolver<string> | undefined;
TOKEN?: string | Resolver<string> | undefined;
USERNAME?: string | Resolver<string> | undefined;
VERSION: string;
WITH_CREDENTIALS: boolean;
interceptors: {
request: Interceptors<AxiosRequestConfig>;
response: Interceptors<AxiosResponse>;
};
};
export const OpenAPI: OpenAPIConfig = {
BASE: '',
CREDENTIALS: 'include',
ENCODE_PATH: undefined,
HEADERS: undefined,
PASSWORD: undefined,
TOKEN: undefined,
USERNAME: undefined,
VERSION: '0.1.0',
WITH_CREDENTIALS: false,
interceptors: {
request: new Interceptors(),
response: new Interceptors(),
},
};
+41
View File
@@ -0,0 +1,41 @@
// This file is auto-generated by @hey-api/openapi-ts
export type AuthToken = string | undefined;
export interface Auth {
/**
* Which part of the request do we use to send the auth?
*
* @default 'header'
*/
in?: 'header' | 'query' | 'cookie';
/**
* Header or query parameter name.
*
* @default 'Authorization'
*/
name?: string;
scheme?: 'basic' | 'bearer';
type: 'apiKey' | 'http';
}
export const getAuthToken = async (
auth: Auth,
callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,
): Promise<string | undefined> => {
const token = typeof callback === 'function' ? await callback(auth) : callback;
if (!token) {
return;
}
if (auth.scheme === 'bearer') {
return `Bearer ${token}`;
}
if (auth.scheme === 'basic') {
return `Basic ${btoa(token)}`;
}
return token;
};
@@ -0,0 +1,82 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { ArrayStyle, ObjectStyle, SerializerOptions } from './pathSerializer.gen';
export type QuerySerializer = (query: Record<string, unknown>) => string;
export type BodySerializer = (body: unknown) => unknown;
type QuerySerializerOptionsObject = {
allowReserved?: boolean;
array?: Partial<SerializerOptions<ArrayStyle>>;
object?: Partial<SerializerOptions<ObjectStyle>>;
};
export type QuerySerializerOptions = QuerySerializerOptionsObject & {
/**
* Per-parameter serialization overrides. When provided, these settings
* override the global array/object settings for specific parameter names.
*/
parameters?: Record<string, QuerySerializerOptionsObject>;
};
const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => {
if (typeof value === 'string' || value instanceof Blob) {
data.append(key, value);
} else if (value instanceof Date) {
data.append(key, value.toISOString());
} else {
data.append(key, JSON.stringify(value));
}
};
const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value: unknown): void => {
if (typeof value === 'string') {
data.append(key, value);
} else {
data.append(key, JSON.stringify(value));
}
};
export const formDataBodySerializer = {
bodySerializer: (body: unknown): FormData => {
const data = new FormData();
Object.entries(body as Record<string, unknown>).forEach(([key, value]) => {
if (value === undefined || value === null) {
return;
}
if (Array.isArray(value)) {
value.forEach((v) => serializeFormDataPair(data, key, v));
} else {
serializeFormDataPair(data, key, value);
}
});
return data;
},
};
export const jsonBodySerializer = {
bodySerializer: (body: unknown): string =>
JSON.stringify(body, (_key, value) => (typeof value === 'bigint' ? value.toString() : value)),
};
export const urlSearchParamsBodySerializer = {
bodySerializer: (body: unknown): string => {
const data = new URLSearchParams();
Object.entries(body as Record<string, unknown>).forEach(([key, value]) => {
if (value === undefined || value === null) {
return;
}
if (Array.isArray(value)) {
value.forEach((v) => serializeUrlSearchParamsPair(data, key, v));
} else {
serializeUrlSearchParamsPair(data, key, value);
}
});
return data.toString();
},
};
+169
View File
@@ -0,0 +1,169 @@
// This file is auto-generated by @hey-api/openapi-ts
type Slot = 'body' | 'headers' | 'path' | 'query';
export type Field =
| {
in: Exclude<Slot, 'body'>;
/**
* Field name. This is the name we want the user to see and use.
*/
key: string;
/**
* Field mapped name. This is the name we want to use in the request.
* If omitted, we use the same value as `key`.
*/
map?: string;
}
| {
in: Extract<Slot, 'body'>;
/**
* Key isn't required for bodies.
*/
key?: string;
map?: string;
}
| {
/**
* Field name. This is the name we want the user to see and use.
*/
key: string;
/**
* Field mapped name. This is the name we want to use in the request.
* If `in` is omitted, `map` aliases `key` to the transport layer.
*/
map: Slot;
};
export interface Fields {
allowExtra?: Partial<Record<Slot, boolean>>;
args?: ReadonlyArray<Field>;
}
export type FieldsConfig = ReadonlyArray<Field | Fields>;
const extraPrefixesMap: Record<string, Slot> = {
$body_: 'body',
$headers_: 'headers',
$path_: 'path',
$query_: 'query',
};
const extraPrefixes = Object.entries(extraPrefixesMap);
type KeyMap = Map<
string,
| {
in: Slot;
map?: string;
}
| {
in?: never;
map: Slot;
}
>;
const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
if (!map) {
map = new Map();
}
for (const config of fields) {
if ('in' in config) {
if (config.key) {
map.set(config.key, {
in: config.in,
map: config.map,
});
}
} else if ('key' in config) {
map.set(config.key, {
map: config.map,
});
} else if (config.args) {
buildKeyMap(config.args, map);
}
}
return map;
};
interface Params {
body: unknown;
headers: Record<string, unknown>;
path: Record<string, unknown>;
query: Record<string, unknown>;
}
const stripEmptySlots = (params: Params) => {
for (const [slot, value] of Object.entries(params)) {
if (value && typeof value === 'object' && !Array.isArray(value) && !Object.keys(value).length) {
delete params[slot as Slot];
}
}
};
export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsConfig) => {
const params: Params = {
body: Object.create(null),
headers: Object.create(null),
path: Object.create(null),
query: Object.create(null),
};
const map = buildKeyMap(fields);
let config: FieldsConfig[number] | undefined;
for (const [index, arg] of args.entries()) {
if (fields[index]) {
config = fields[index];
}
if (!config) {
continue;
}
if ('in' in config) {
if (config.key) {
const field = map.get(config.key)!;
const name = field.map || config.key;
if (field.in) {
(params[field.in] as Record<string, unknown>)[name] = arg;
}
} else {
params.body = arg;
}
} else {
for (const [key, value] of Object.entries(arg ?? {})) {
const field = map.get(key);
if (field) {
if (field.in) {
const name = field.map || key;
(params[field.in] as Record<string, unknown>)[name] = value;
} else {
params[field.map] = value;
}
} else {
const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix));
if (extra) {
const [prefix, slot] = extra;
(params[slot] as Record<string, unknown>)[key.slice(prefix.length)] = value;
} else if ('allowExtra' in config && config.allowExtra) {
for (const [slot, allowed] of Object.entries(config.allowExtra)) {
if (allowed) {
(params[slot as Slot] as Record<string, unknown>)[key] = value;
break;
}
}
}
}
}
}
}
stripEmptySlots(params);
return params;
};
@@ -0,0 +1,171 @@
// This file is auto-generated by @hey-api/openapi-ts
interface SerializeOptions<T> extends SerializePrimitiveOptions, SerializerOptions<T> {}
interface SerializePrimitiveOptions {
allowReserved?: boolean;
name: string;
}
export interface SerializerOptions<T> {
/**
* @default true
*/
explode: boolean;
style: T;
}
export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited';
export type ArraySeparatorStyle = ArrayStyle | MatrixStyle;
type MatrixStyle = 'label' | 'matrix' | 'simple';
export type ObjectStyle = 'form' | 'deepObject';
type ObjectSeparatorStyle = ObjectStyle | MatrixStyle;
interface SerializePrimitiveParam extends SerializePrimitiveOptions {
value: string;
}
export const separatorArrayExplode = (style: ArraySeparatorStyle) => {
switch (style) {
case 'label':
return '.';
case 'matrix':
return ';';
case 'simple':
return ',';
default:
return '&';
}
};
export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {
switch (style) {
case 'form':
return ',';
case 'pipeDelimited':
return '|';
case 'spaceDelimited':
return '%20';
default:
return ',';
}
};
export const separatorObjectExplode = (style: ObjectSeparatorStyle) => {
switch (style) {
case 'label':
return '.';
case 'matrix':
return ';';
case 'simple':
return ',';
default:
return '&';
}
};
export const serializeArrayParam = ({
allowReserved,
explode,
name,
style,
value,
}: SerializeOptions<ArraySeparatorStyle> & {
value: unknown[];
}) => {
if (!explode) {
const joinedValues = (
allowReserved ? value : value.map((v) => encodeURIComponent(v as string))
).join(separatorArrayNoExplode(style));
switch (style) {
case 'label':
return `.${joinedValues}`;
case 'matrix':
return `;${name}=${joinedValues}`;
case 'simple':
return joinedValues;
default:
return `${name}=${joinedValues}`;
}
}
const separator = separatorArrayExplode(style);
const joinedValues = value
.map((v) => {
if (style === 'label' || style === 'simple') {
return allowReserved ? v : encodeURIComponent(v as string);
}
return serializePrimitiveParam({
allowReserved,
name,
value: v as string,
});
})
.join(separator);
return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues;
};
export const serializePrimitiveParam = ({
allowReserved,
name,
value,
}: SerializePrimitiveParam) => {
if (value === undefined || value === null) {
return '';
}
if (typeof value === 'object') {
throw new Error(
'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.',
);
}
return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
};
export const serializeObjectParam = ({
allowReserved,
explode,
name,
style,
value,
valueOnly,
}: SerializeOptions<ObjectSeparatorStyle> & {
value: Record<string, unknown> | Date;
valueOnly?: boolean;
}) => {
if (value instanceof Date) {
return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
}
if (style !== 'deepObject' && !explode) {
let values: string[] = [];
Object.entries(value).forEach(([key, v]) => {
values = [...values, key, allowReserved ? (v as string) : encodeURIComponent(v as string)];
});
const joinedValues = values.join(',');
switch (style) {
case 'form':
return `${name}=${joinedValues}`;
case 'label':
return `.${joinedValues}`;
case 'matrix':
return `;${name}=${joinedValues}`;
default:
return joinedValues;
}
}
const separator = separatorObjectExplode(style);
const joinedValues = Object.entries(value)
.map(([key, v]) =>
serializePrimitiveParam({
allowReserved,
name: style === 'deepObject' ? `${name}[${key}]` : key,
value: v as string,
}),
)
.join(separator);
return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues;
};
@@ -0,0 +1,117 @@
// This file is auto-generated by @hey-api/openapi-ts
/**
* JSON-friendly union that mirrors what Pinia Colada can hash.
*/
export type JsonValue =
| null
| string
| number
| boolean
| JsonValue[]
| { [key: string]: JsonValue };
/**
* Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes.
*/
export const queryKeyJsonReplacer = (_key: string, value: unknown) => {
if (value === undefined || typeof value === 'function' || typeof value === 'symbol') {
return undefined;
}
if (typeof value === 'bigint') {
return value.toString();
}
if (value instanceof Date) {
return value.toISOString();
}
return value;
};
/**
* Safely stringifies a value and parses it back into a JsonValue.
*/
export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => {
try {
const json = JSON.stringify(input, queryKeyJsonReplacer);
if (json === undefined) {
return undefined;
}
return JSON.parse(json) as JsonValue;
} catch {
return undefined;
}
};
/**
* Detects plain objects (including objects with a null prototype).
*/
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
if (value === null || typeof value !== 'object') {
return false;
}
const prototype = Object.getPrototypeOf(value as object);
return prototype === Object.prototype || prototype === null;
};
/**
* Turns URLSearchParams into a sorted JSON object for deterministic keys.
*/
const serializeSearchParams = (params: URLSearchParams): JsonValue => {
const entries = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b));
const result: Record<string, JsonValue> = {};
for (const [key, value] of entries) {
const existing = result[key];
if (existing === undefined) {
result[key] = value;
continue;
}
if (Array.isArray(existing)) {
(existing as string[]).push(value);
} else {
result[key] = [existing, value];
}
}
return result;
};
/**
* Normalizes any accepted value into a JSON-friendly shape for query keys.
*/
export const serializeQueryKeyValue = (value: unknown): JsonValue | undefined => {
if (value === null) {
return null;
}
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
return value;
}
if (value === undefined || typeof value === 'function' || typeof value === 'symbol') {
return undefined;
}
if (typeof value === 'bigint') {
return value.toString();
}
if (value instanceof Date) {
return value.toISOString();
}
if (Array.isArray(value)) {
return stringifyToJsonValue(value);
}
if (typeof URLSearchParams !== 'undefined' && value instanceof URLSearchParams) {
return serializeSearchParams(value);
}
if (isPlainObject(value)) {
return stringifyToJsonValue(value);
}
return undefined;
};
-347
View File
@@ -1,347 +0,0 @@
import axios from 'axios';
import type { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios';
import { ApiError } from './ApiError';
import type { ApiRequestOptions } from './ApiRequestOptions';
import type { ApiResult } from './ApiResult';
import { CancelablePromise } from './CancelablePromise';
import type { OnCancel } from './CancelablePromise';
import type { OpenAPIConfig } from './OpenAPI';
export const isString = (value: unknown): value is string => {
return typeof value === 'string';
};
export const isStringWithValue = (value: unknown): value is string => {
return isString(value) && value !== '';
};
export const isBlob = (value: any): value is Blob => {
return value instanceof Blob;
};
export const isFormData = (value: unknown): value is FormData => {
return value instanceof FormData;
};
export const isSuccess = (status: number): boolean => {
return status >= 200 && status < 300;
};
export const base64 = (str: string): string => {
try {
return btoa(str);
} catch (err) {
// @ts-ignore
return Buffer.from(str).toString('base64');
}
};
export const getQueryString = (params: Record<string, unknown>): string => {
const qs: string[] = [];
const append = (key: string, value: unknown) => {
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
};
const encodePair = (key: string, value: unknown) => {
if (value === undefined || value === null) {
return;
}
if (value instanceof Date) {
append(key, value.toISOString());
} else if (Array.isArray(value)) {
value.forEach(v => encodePair(key, v));
} else if (typeof value === 'object') {
Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v));
} else {
append(key, value);
}
};
Object.entries(params).forEach(([key, value]) => encodePair(key, value));
return qs.length ? `?${qs.join('&')}` : '';
};
const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
const encoder = config.ENCODE_PATH || encodeURI;
const path = options.url
.replace('{api-version}', config.VERSION)
.replace(/{(.*?)}/g, (substring: string, group: string) => {
if (options.path?.hasOwnProperty(group)) {
return encoder(String(options.path[group]));
}
return substring;
});
const url = config.BASE + path;
return options.query ? url + getQueryString(options.query) : url;
};
export const getFormData = (options: ApiRequestOptions): FormData | undefined => {
if (options.formData) {
const formData = new FormData();
const process = (key: string, value: unknown) => {
if (isString(value) || isBlob(value)) {
formData.append(key, value);
} else {
formData.append(key, JSON.stringify(value));
}
};
Object.entries(options.formData)
.filter(([, value]) => value !== undefined && value !== null)
.forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach(v => process(key, v));
} else {
process(key, value);
}
});
return formData;
}
return undefined;
};
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>;
export const resolve = async <T>(options: ApiRequestOptions<T>, resolver?: T | Resolver<T>): Promise<T | undefined> => {
if (typeof resolver === 'function') {
return (resolver as Resolver<T>)(options);
}
return resolver;
};
export const getHeaders = async <T>(config: OpenAPIConfig, options: ApiRequestOptions<T>): Promise<Record<string, string>> => {
const [token, username, password, additionalHeaders] = await Promise.all([
// @ts-ignore
resolve(options, config.TOKEN),
// @ts-ignore
resolve(options, config.USERNAME),
// @ts-ignore
resolve(options, config.PASSWORD),
// @ts-ignore
resolve(options, config.HEADERS),
]);
const headers = Object.entries({
Accept: 'application/json',
...additionalHeaders,
...options.headers,
})
.filter(([, value]) => value !== undefined && value !== null)
.reduce((headers, [key, value]) => ({
...headers,
[key]: String(value),
}), {} as Record<string, string>);
if (isStringWithValue(token)) {
headers['Authorization'] = `Bearer ${token}`;
}
if (isStringWithValue(username) && isStringWithValue(password)) {
const credentials = base64(`${username}:${password}`);
headers['Authorization'] = `Basic ${credentials}`;
}
if (options.body !== undefined) {
if (options.mediaType) {
headers['Content-Type'] = options.mediaType;
} else if (isBlob(options.body)) {
headers['Content-Type'] = options.body.type || 'application/octet-stream';
} else if (isString(options.body)) {
headers['Content-Type'] = 'text/plain';
} else if (!isFormData(options.body)) {
headers['Content-Type'] = 'application/json';
}
} else if (options.formData !== undefined) {
if (options.mediaType) {
headers['Content-Type'] = options.mediaType;
}
}
return headers;
};
export const getRequestBody = (options: ApiRequestOptions): unknown => {
if (options.body) {
return options.body;
}
return undefined;
};
export const sendRequest = async <T>(
config: OpenAPIConfig,
options: ApiRequestOptions<T>,
url: string,
body: unknown,
formData: FormData | undefined,
headers: Record<string, string>,
onCancel: OnCancel,
axiosClient: AxiosInstance
): Promise<AxiosResponse<T>> => {
const controller = new AbortController();
let requestConfig: AxiosRequestConfig = {
data: body ?? formData,
headers,
method: options.method,
signal: controller.signal,
url,
withCredentials: config.WITH_CREDENTIALS,
};
onCancel(() => controller.abort());
for (const fn of config.interceptors.request._fns) {
requestConfig = await fn(requestConfig);
}
try {
return await axiosClient.request(requestConfig);
} catch (error) {
const axiosError = error as AxiosError<T>;
if (axiosError.response) {
return axiosError.response;
}
throw error;
}
};
export const getResponseHeader = (response: AxiosResponse<unknown>, responseHeader?: string): string | undefined => {
if (responseHeader) {
const content = response.headers[responseHeader];
if (isString(content)) {
return content;
}
}
return undefined;
};
export const getResponseBody = (response: AxiosResponse<unknown>): unknown => {
if (response.status !== 204) {
return response.data;
}
return undefined;
};
export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => {
const errors: Record<number, string> = {
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Timeout',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Payload Too Large',
414: 'URI Too Long',
415: 'Unsupported Media Type',
416: 'Range Not Satisfiable',
417: 'Expectation Failed',
418: 'Im a teapot',
421: 'Misdirected Request',
422: 'Unprocessable Content',
423: 'Locked',
424: 'Failed Dependency',
425: 'Too Early',
426: 'Upgrade Required',
428: 'Precondition Required',
429: 'Too Many Requests',
431: 'Request Header Fields Too Large',
451: 'Unavailable For Legal Reasons',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Timeout',
505: 'HTTP Version Not Supported',
506: 'Variant Also Negotiates',
507: 'Insufficient Storage',
508: 'Loop Detected',
510: 'Not Extended',
511: 'Network Authentication Required',
...options.errors,
}
const error = errors[result.status];
if (error) {
throw new ApiError(options, result, error);
}
if (!result.ok) {
const errorStatus = result.status ?? 'unknown';
const errorStatusText = result.statusText ?? 'unknown';
const errorBody = (() => {
try {
return JSON.stringify(result.body, null, 2);
} catch (e) {
return undefined;
}
})();
throw new ApiError(options, result,
`Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`
);
}
};
/**
* Request method
* @param config The OpenAPI configuration object
* @param options The request options from the service
* @param axiosClient The axios client instance to use
* @returns CancelablePromise<T>
* @throws ApiError
*/
export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions<T>, axiosClient: AxiosInstance = axios): CancelablePromise<T> => {
return new CancelablePromise(async (resolve, reject, onCancel) => {
try {
const url = getUrl(config, options);
const formData = getFormData(options);
const body = getRequestBody(options);
const headers = await getHeaders(config, options);
if (!onCancel.isCancelled) {
let response = await sendRequest<T>(config, options, url, body, formData, headers, onCancel, axiosClient);
for (const fn of config.interceptors.response._fns) {
response = await fn(response);
}
const responseBody = getResponseBody(response);
const responseHeader = getResponseHeader(response, options.responseHeader);
let transformedBody = responseBody;
if (options.responseTransformer && isSuccess(response.status)) {
transformedBody = await options.responseTransformer(responseBody)
}
const result: ApiResult = {
url,
ok: isSuccess(response.status),
status: response.status,
statusText: response.statusText,
body: responseHeader ?? transformedBody,
};
catchErrorCodes(options, result);
resolve(result.body);
}
} catch (error) {
reject(error);
}
});
};
@@ -0,0 +1,242 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { Config } from './types.gen';
export type ServerSentEventsOptions<TData = unknown> = Omit<RequestInit, 'method'> &
Pick<Config, 'method' | 'responseTransformer' | 'responseValidator'> & {
/**
* Fetch API implementation. You can use this option to provide a custom
* fetch instance.
*
* @default globalThis.fetch
*/
fetch?: typeof fetch;
/**
* Implementing clients can call request interceptors inside this hook.
*/
onRequest?: (url: string, init: RequestInit) => Promise<Request>;
/**
* Callback invoked when a network or parsing error occurs during streaming.
*
* This option applies only if the endpoint returns a stream of events.
*
* @param error The error that occurred.
*/
onSseError?: (error: unknown) => void;
/**
* Callback invoked when an event is streamed from the server.
*
* This option applies only if the endpoint returns a stream of events.
*
* @param event Event streamed from the server.
* @returns Nothing (void).
*/
onSseEvent?: (event: StreamEvent<TData>) => void;
serializedBody?: RequestInit['body'];
/**
* Default retry delay in milliseconds.
*
* This option applies only if the endpoint returns a stream of events.
*
* @default 3000
*/
sseDefaultRetryDelay?: number;
/**
* Maximum number of retry attempts before giving up.
*/
sseMaxRetryAttempts?: number;
/**
* Maximum retry delay in milliseconds.
*
* Applies only when exponential backoff is used.
*
* This option applies only if the endpoint returns a stream of events.
*
* @default 30000
*/
sseMaxRetryDelay?: number;
/**
* Optional sleep function for retry backoff.
*
* Defaults to using `setTimeout`.
*/
sseSleepFn?: (ms: number) => Promise<void>;
url: string;
};
export interface StreamEvent<TData = unknown> {
data: TData;
event?: string;
id?: string;
retry?: number;
}
export type ServerSentEventsResult<TData = unknown, TReturn = void, TNext = unknown> = {
stream: AsyncGenerator<
TData extends Record<string, unknown> ? TData[keyof TData] : TData,
TReturn,
TNext
>;
};
export function createSseClient<TData = unknown>({
onRequest,
onSseError,
onSseEvent,
responseTransformer,
responseValidator,
sseDefaultRetryDelay,
sseMaxRetryAttempts,
sseMaxRetryDelay,
sseSleepFn,
url,
...options
}: ServerSentEventsOptions): ServerSentEventsResult<TData> {
let lastEventId: string | undefined;
const sleep = sseSleepFn ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));
const createStream = async function* () {
let retryDelay: number = sseDefaultRetryDelay ?? 3000;
let attempt = 0;
const signal = options.signal ?? new AbortController().signal;
while (true) {
if (signal.aborted) break;
attempt++;
const headers =
options.headers instanceof Headers
? options.headers
: new Headers(options.headers as Record<string, string> | undefined);
if (lastEventId !== undefined) {
headers.set('Last-Event-ID', lastEventId);
}
try {
const requestInit: RequestInit = {
redirect: 'follow',
...options,
body: options.serializedBody,
headers,
signal,
};
let request = new Request(url, requestInit);
if (onRequest) {
request = await onRequest(url, requestInit);
}
// fetch must be assigned here, otherwise it would throw the error:
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
const _fetch = options.fetch ?? globalThis.fetch;
const response = await _fetch(request);
if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
if (!response.body) throw new Error('No body in SSE response');
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = '';
const abortHandler = () => {
try {
reader.cancel();
} catch {
// noop
}
};
signal.addEventListener('abort', abortHandler);
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += value;
buffer = buffer.replace(/\r\n?/g, '\n'); // normalize line endings
const chunks = buffer.split('\n\n');
buffer = chunks.pop() ?? '';
for (const chunk of chunks) {
const lines = chunk.split('\n');
const dataLines: Array<string> = [];
let eventName: string | undefined;
for (const line of lines) {
if (line.startsWith('data:')) {
dataLines.push(line.replace(/^data:\s*/, ''));
} else if (line.startsWith('event:')) {
eventName = line.replace(/^event:\s*/, '');
} else if (line.startsWith('id:')) {
lastEventId = line.replace(/^id:\s*/, '');
} else if (line.startsWith('retry:')) {
const parsed = Number.parseInt(line.replace(/^retry:\s*/, ''), 10);
if (!Number.isNaN(parsed)) {
retryDelay = parsed;
}
}
}
let data: unknown;
let parsedJson = false;
if (dataLines.length) {
const rawData = dataLines.join('\n');
try {
data = JSON.parse(rawData);
parsedJson = true;
} catch {
data = rawData;
}
}
if (parsedJson) {
if (responseValidator) {
await responseValidator(data);
}
if (responseTransformer) {
data = await responseTransformer(data);
}
}
onSseEvent?.({
data,
event: eventName,
id: lastEventId,
retry: retryDelay,
});
if (dataLines.length) {
yield data as any;
}
}
}
} finally {
signal.removeEventListener('abort', abortHandler);
reader.releaseLock();
}
break; // exit loop on normal completion
} catch (error) {
// connection failed or aborted; retry after delay
onSseError?.(error);
if (sseMaxRetryAttempts !== undefined && attempt >= sseMaxRetryAttempts) {
break; // stop after firing error
}
// exponential backoff: double retry each attempt, cap at 30s
const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 30000);
await sleep(backoff);
}
}
};
const stream = createStream();
return { stream };
}
+104
View File
@@ -0,0 +1,104 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { Auth, AuthToken } from './auth.gen';
import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from './bodySerializer.gen';
export type HttpMethod =
| 'connect'
| 'delete'
| 'get'
| 'head'
| 'options'
| 'patch'
| 'post'
| 'put'
| 'trace';
export type Client<
RequestFn = never,
Config = unknown,
MethodFn = never,
BuildUrlFn = never,
SseFn = never,
> = {
/**
* Returns the final request URL.
*/
buildUrl: BuildUrlFn;
getConfig: () => Config;
request: RequestFn;
setConfig: (config: Config) => Config;
} & {
[K in HttpMethod]: MethodFn;
} & ([SseFn] extends [never] ? { sse?: never } : { sse: { [K in HttpMethod]: SseFn } });
export interface Config {
/**
* Auth token or a function returning auth token. The resolved value will be
* added to the request payload as defined by its `security` array.
*/
auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken;
/**
* A function for serializing request body parameter. By default,
* {@link JSON.stringify()} will be used.
*/
bodySerializer?: BodySerializer | null;
/**
* An object containing any HTTP headers that you want to pre-populate your
* `Headers` object with.
*
* {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more}
*/
headers?:
| RequestInit['headers']
| Record<
string,
string | number | boolean | (string | number | boolean)[] | null | undefined | unknown
>;
/**
* The request method.
*
* {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
*/
method?: Uppercase<HttpMethod>;
/**
* A function for serializing request query parameters. By default, arrays
* will be exploded in form style, objects will be exploded in deepObject
* style, and reserved characters are percent-encoded.
*
* This method will have no effect if the native `paramsSerializer()` Axios
* API function is used.
*
* {@link https://swagger.io/docs/specification/serialization/#query View examples}
*/
querySerializer?: QuerySerializer | QuerySerializerOptions;
/**
* A function validating request data. This is useful if you want to ensure
* the request conforms to the desired shape, so it can be safely sent to
* the server.
*/
requestValidator?: (data: unknown) => Promise<unknown>;
/**
* A function transforming response data before it's returned. This is useful
* for post-processing data, e.g., converting ISO strings into Date objects.
*/
responseTransformer?: (data: unknown) => Promise<unknown>;
/**
* A function validating response data. This is useful if you want to ensure
* the response conforms to the desired shape, so it can be safely passed to
* the transformers and returned to the user.
*/
responseValidator?: (data: unknown) => Promise<unknown>;
}
type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
? true
: [T] extends [never | undefined]
? [undefined] extends [T]
? false
: true
: false;
export type OmitNever<T extends Record<string, unknown>> = {
[K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true ? never : K]: T[K];
};
+140
View File
@@ -0,0 +1,140 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { BodySerializer, QuerySerializer } from './bodySerializer.gen';
import {
type ArraySeparatorStyle,
serializeArrayParam,
serializeObjectParam,
serializePrimitiveParam,
} from './pathSerializer.gen';
export interface PathSerializer {
path: Record<string, unknown>;
url: string;
}
export const PATH_PARAM_RE = /\{[^{}]+\}/g;
export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
let url = _url;
const matches = _url.match(PATH_PARAM_RE);
if (matches) {
for (const match of matches) {
let explode = false;
let name = match.substring(1, match.length - 1);
let style: ArraySeparatorStyle = 'simple';
if (name.endsWith('*')) {
explode = true;
name = name.substring(0, name.length - 1);
}
if (name.startsWith('.')) {
name = name.substring(1);
style = 'label';
} else if (name.startsWith(';')) {
name = name.substring(1);
style = 'matrix';
}
const value = path[name];
if (value === undefined || value === null) {
continue;
}
if (Array.isArray(value)) {
url = url.replace(match, serializeArrayParam({ explode, name, style, value }));
continue;
}
if (typeof value === 'object') {
url = url.replace(
match,
serializeObjectParam({
explode,
name,
style,
value: value as Record<string, unknown>,
valueOnly: true,
}),
);
continue;
}
if (style === 'matrix') {
url = url.replace(
match,
`;${serializePrimitiveParam({
name,
value: value as string,
})}`,
);
continue;
}
const replaceValue = encodeURIComponent(
style === 'label' ? `.${value as string}` : (value as string),
);
url = url.replace(match, replaceValue);
}
}
return url;
};
export const getUrl = ({
baseUrl,
path,
query,
querySerializer,
url: _url,
}: {
baseUrl?: string;
path?: Record<string, unknown>;
query?: Record<string, unknown>;
querySerializer: QuerySerializer;
url: string;
}) => {
const pathUrl = _url.startsWith('/') ? _url : `/${_url}`;
let url = (baseUrl ?? '') + pathUrl;
if (path) {
url = defaultPathSerializer({ path, url });
}
let search = query ? querySerializer(query) : '';
if (search.startsWith('?')) {
search = search.substring(1);
}
if (search) {
url += `?${search}`;
}
return url;
};
export function getValidRequestBody(options: {
body?: unknown;
bodySerializer?: BodySerializer | null;
serializedBody?: unknown;
}) {
const hasBody = options.body !== undefined;
const isSerializedBody = hasBody && options.bodySerializer;
if (isSerializedBody) {
if ('serializedBody' in options) {
const hasSerializedBody =
options.serializedBody !== undefined && options.serializedBody !== '';
return hasSerializedBody ? options.serializedBody : null;
}
// not all clients implement a serializedBody property (i.e., client-axios)
return options.body !== '' ? options.body : null;
}
// plain/text body
if (hasBody) {
return options.body;
}
// no body was provided
return undefined;
}
+3 -5
View File
@@ -1,6 +1,4 @@
// This file is auto-generated by @hey-api/openapi-ts
export { ApiError } from './core/ApiError';
export { CancelablePromise, CancelError } from './core/CancelablePromise';
export { OpenAPI, type OpenAPIConfig } from './core/OpenAPI';
export * from './sdk.gen';
export * from './types.gen';
export { ItemsService, LoginService, type Options, PrivateService, UsersService, UtilsService } from './sdk.gen';
export type { Body_login_login_access_token, ClientOptions, HTTPValidationError, ItemCreate, ItemPublic, itemsCreateItemData, itemsCreateItemError, itemsCreateItemErrors, itemsCreateItemResponse, itemsCreateItemResponses, itemsDeleteItemData, itemsDeleteItemError, itemsDeleteItemErrors, itemsDeleteItemResponse, itemsDeleteItemResponses, ItemsPublic, itemsReadItemData, itemsReadItemError, itemsReadItemErrors, itemsReadItemResponse, itemsReadItemResponses, itemsReadItemsData, itemsReadItemsError, itemsReadItemsErrors, itemsReadItemsResponse, itemsReadItemsResponses, itemsUpdateItemData, itemsUpdateItemError, itemsUpdateItemErrors, itemsUpdateItemResponse, itemsUpdateItemResponses, ItemUpdate, loginLoginAccessTokenData, loginLoginAccessTokenError, loginLoginAccessTokenErrors, loginLoginAccessTokenResponse, loginLoginAccessTokenResponses, loginRecoverPasswordData, loginRecoverPasswordError, loginRecoverPasswordErrors, loginRecoverPasswordHtmlContentData, loginRecoverPasswordHtmlContentError, loginRecoverPasswordHtmlContentErrors, loginRecoverPasswordHtmlContentResponse, loginRecoverPasswordHtmlContentResponses, loginRecoverPasswordResponse, loginRecoverPasswordResponses, loginResetPasswordData, loginResetPasswordError, loginResetPasswordErrors, loginResetPasswordResponse, loginResetPasswordResponses, loginTestTokenData, loginTestTokenResponse, loginTestTokenResponses, Message, NewPassword, privateCreateUserData, privateCreateUserError, privateCreateUserErrors, privateCreateUserResponse, privateCreateUserResponses, PrivateUserCreate, Token, UpdatePassword, UserCreate, UserPublic, UserRegister, usersCreateUserData, usersCreateUserError, usersCreateUserErrors, usersCreateUserResponse, usersCreateUserResponses, usersDeleteUserData, usersDeleteUserError, usersDeleteUserErrors, usersDeleteUserMeData, usersDeleteUserMeResponse, usersDeleteUserMeResponses, usersDeleteUserResponse, usersDeleteUserResponses, UsersPublic, usersReadUserByIdData, usersReadUserByIdError, usersReadUserByIdErrors, usersReadUserByIdResponse, usersReadUserByIdResponses, usersReadUserMeData, usersReadUserMeResponse, usersReadUserMeResponses, usersReadUsersData, usersReadUsersError, usersReadUsersErrors, usersReadUsersResponse, usersReadUsersResponses, usersRegisterUserData, usersRegisterUserError, usersRegisterUserErrors, usersRegisterUserResponse, usersRegisterUserResponses, usersUpdatePasswordMeData, usersUpdatePasswordMeError, usersUpdatePasswordMeErrors, usersUpdatePasswordMeResponse, usersUpdatePasswordMeResponses, usersUpdateUserData, usersUpdateUserError, usersUpdateUserErrors, usersUpdateUserMeData, usersUpdateUserMeError, usersUpdateUserMeErrors, usersUpdateUserMeResponse, usersUpdateUserMeResponses, usersUpdateUserResponse, usersUpdateUserResponses, UserUpdate, UserUpdateMe, utilsHealthCheckData, utilsHealthCheckResponse, utilsHealthCheckResponses, utilsTestEmailData, utilsTestEmailError, utilsTestEmailErrors, utilsTestEmailResponse, utilsTestEmailResponses, ValidationError } from './types.gen';
-552
View File
@@ -1,552 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
export const Body_login_login_access_tokenSchema = {
properties: {
grant_type: {
anyOf: [
{
type: 'string',
pattern: '^password$'
},
{
type: 'null'
}
],
title: 'Grant Type'
},
username: {
type: 'string',
title: 'Username'
},
password: {
type: 'string',
format: 'password',
title: 'Password'
},
scope: {
type: 'string',
title: 'Scope',
default: ''
},
client_id: {
anyOf: [
{
type: 'string'
},
{
type: 'null'
}
],
title: 'Client Id'
},
client_secret: {
anyOf: [
{
type: 'string'
},
{
type: 'null'
}
],
format: 'password',
title: 'Client Secret'
}
},
type: 'object',
required: ['username', 'password'],
title: 'Body_login-login_access_token'
} as const;
export const HTTPValidationErrorSchema = {
properties: {
detail: {
items: {
'$ref': '#/components/schemas/ValidationError'
},
type: 'array',
title: 'Detail'
}
},
type: 'object',
title: 'HTTPValidationError'
} as const;
export const ItemCreateSchema = {
properties: {
title: {
type: 'string',
maxLength: 255,
minLength: 1,
title: 'Title'
},
description: {
anyOf: [
{
type: 'string',
maxLength: 255
},
{
type: 'null'
}
],
title: 'Description'
}
},
type: 'object',
required: ['title'],
title: 'ItemCreate'
} as const;
export const ItemPublicSchema = {
properties: {
title: {
type: 'string',
maxLength: 255,
minLength: 1,
title: 'Title'
},
description: {
anyOf: [
{
type: 'string',
maxLength: 255
},
{
type: 'null'
}
],
title: 'Description'
},
id: {
type: 'string',
format: 'uuid',
title: 'Id'
},
owner_id: {
type: 'string',
format: 'uuid',
title: 'Owner Id'
},
created_at: {
anyOf: [
{
type: 'string',
format: 'date-time'
},
{
type: 'null'
}
],
title: 'Created At'
}
},
type: 'object',
required: ['title', 'id', 'owner_id'],
title: 'ItemPublic'
} as const;
export const ItemUpdateSchema = {
properties: {
title: {
anyOf: [
{
type: 'string',
maxLength: 255,
minLength: 1
},
{
type: 'null'
}
],
title: 'Title'
},
description: {
anyOf: [
{
type: 'string',
maxLength: 255
},
{
type: 'null'
}
],
title: 'Description'
}
},
type: 'object',
title: 'ItemUpdate'
} as const;
export const ItemsPublicSchema = {
properties: {
data: {
items: {
'$ref': '#/components/schemas/ItemPublic'
},
type: 'array',
title: 'Data'
},
count: {
type: 'integer',
title: 'Count'
}
},
type: 'object',
required: ['data', 'count'],
title: 'ItemsPublic'
} as const;
export const MessageSchema = {
properties: {
message: {
type: 'string',
title: 'Message'
}
},
type: 'object',
required: ['message'],
title: 'Message'
} as const;
export const NewPasswordSchema = {
properties: {
token: {
type: 'string',
title: 'Token'
},
new_password: {
type: 'string',
maxLength: 128,
minLength: 8,
title: 'New Password'
}
},
type: 'object',
required: ['token', 'new_password'],
title: 'NewPassword'
} as const;
export const PrivateUserCreateSchema = {
properties: {
email: {
type: 'string',
title: 'Email'
},
password: {
type: 'string',
title: 'Password'
},
full_name: {
type: 'string',
title: 'Full Name'
},
is_verified: {
type: 'boolean',
title: 'Is Verified',
default: false
}
},
type: 'object',
required: ['email', 'password', 'full_name'],
title: 'PrivateUserCreate'
} as const;
export const TokenSchema = {
properties: {
access_token: {
type: 'string',
title: 'Access Token'
},
token_type: {
type: 'string',
title: 'Token Type',
default: 'bearer'
}
},
type: 'object',
required: ['access_token'],
title: 'Token'
} as const;
export const UpdatePasswordSchema = {
properties: {
current_password: {
type: 'string',
maxLength: 128,
minLength: 8,
title: 'Current Password'
},
new_password: {
type: 'string',
maxLength: 128,
minLength: 8,
title: 'New Password'
}
},
type: 'object',
required: ['current_password', 'new_password'],
title: 'UpdatePassword'
} as const;
export const UserCreateSchema = {
properties: {
email: {
type: 'string',
maxLength: 255,
format: 'email',
title: 'Email'
},
is_active: {
type: 'boolean',
title: 'Is Active',
default: true
},
is_superuser: {
type: 'boolean',
title: 'Is Superuser',
default: false
},
full_name: {
anyOf: [
{
type: 'string',
maxLength: 255
},
{
type: 'null'
}
],
title: 'Full Name'
},
password: {
type: 'string',
maxLength: 128,
minLength: 8,
title: 'Password'
}
},
type: 'object',
required: ['email', 'password'],
title: 'UserCreate'
} as const;
export const UserPublicSchema = {
properties: {
email: {
type: 'string',
maxLength: 255,
format: 'email',
title: 'Email'
},
is_active: {
type: 'boolean',
title: 'Is Active',
default: true
},
is_superuser: {
type: 'boolean',
title: 'Is Superuser',
default: false
},
full_name: {
anyOf: [
{
type: 'string',
maxLength: 255
},
{
type: 'null'
}
],
title: 'Full Name'
},
id: {
type: 'string',
format: 'uuid',
title: 'Id'
},
created_at: {
anyOf: [
{
type: 'string',
format: 'date-time'
},
{
type: 'null'
}
],
title: 'Created At'
}
},
type: 'object',
required: ['email', 'id'],
title: 'UserPublic'
} as const;
export const UserRegisterSchema = {
properties: {
email: {
type: 'string',
maxLength: 255,
format: 'email',
title: 'Email'
},
password: {
type: 'string',
maxLength: 128,
minLength: 8,
title: 'Password'
},
full_name: {
anyOf: [
{
type: 'string',
maxLength: 255
},
{
type: 'null'
}
],
title: 'Full Name'
}
},
type: 'object',
required: ['email', 'password'],
title: 'UserRegister'
} as const;
export const UserUpdateSchema = {
properties: {
email: {
anyOf: [
{
type: 'string',
maxLength: 255,
format: 'email'
},
{
type: 'null'
}
],
title: 'Email'
},
is_active: {
type: 'boolean',
title: 'Is Active',
default: true
},
is_superuser: {
type: 'boolean',
title: 'Is Superuser',
default: false
},
full_name: {
anyOf: [
{
type: 'string',
maxLength: 255
},
{
type: 'null'
}
],
title: 'Full Name'
},
password: {
anyOf: [
{
type: 'string',
maxLength: 128,
minLength: 8
},
{
type: 'null'
}
],
title: 'Password'
}
},
type: 'object',
title: 'UserUpdate'
} as const;
export const UserUpdateMeSchema = {
properties: {
full_name: {
anyOf: [
{
type: 'string',
maxLength: 255
},
{
type: 'null'
}
],
title: 'Full Name'
},
email: {
anyOf: [
{
type: 'string',
maxLength: 255,
format: 'email'
},
{
type: 'null'
}
],
title: 'Email'
}
},
type: 'object',
title: 'UserUpdateMe'
} as const;
export const UsersPublicSchema = {
properties: {
data: {
items: {
'$ref': '#/components/schemas/UserPublic'
},
type: 'array',
title: 'Data'
},
count: {
type: 'integer',
title: 'Count'
}
},
type: 'object',
required: ['data', 'count'],
title: 'UsersPublic'
} as const;
export const ValidationErrorSchema = {
properties: {
loc: {
items: {
anyOf: [
{
type: 'string'
},
{
type: 'integer'
}
]
},
type: 'array',
title: 'Location'
},
msg: {
type: 'string',
title: 'Message'
},
type: {
type: 'string',
title: 'Error Type'
}
},
type: 'object',
required: ['loc', 'msg', 'type'],
title: 'ValidationError'
} as const;
+254 -338
View File
@@ -1,236 +1,97 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { CancelablePromise } from './core/CancelablePromise';
import { OpenAPI } from './core/OpenAPI';
import { request as __request } from './core/request';
import type { ItemsReadItemsData, ItemsReadItemsResponse, ItemsCreateItemData, ItemsCreateItemResponse, ItemsReadItemData, ItemsReadItemResponse, ItemsUpdateItemData, ItemsUpdateItemResponse, ItemsDeleteItemData, ItemsDeleteItemResponse, LoginLoginAccessTokenData, LoginLoginAccessTokenResponse, LoginTestTokenResponse, LoginRecoverPasswordData, LoginRecoverPasswordResponse, LoginResetPasswordData, LoginResetPasswordResponse, LoginRecoverPasswordHtmlContentData, LoginRecoverPasswordHtmlContentResponse, PrivateCreateUserData, PrivateCreateUserResponse, UsersReadUsersData, UsersReadUsersResponse, UsersCreateUserData, UsersCreateUserResponse, UsersReadUserMeResponse, UsersDeleteUserMeResponse, UsersUpdateUserMeData, UsersUpdateUserMeResponse, UsersUpdatePasswordMeData, UsersUpdatePasswordMeResponse, UsersRegisterUserData, UsersRegisterUserResponse, UsersReadUserByIdData, UsersReadUserByIdResponse, UsersUpdateUserData, UsersUpdateUserResponse, UsersDeleteUserData, UsersDeleteUserResponse, UtilsTestEmailData, UtilsTestEmailResponse, UtilsHealthCheckResponse } from './types.gen';
import { type Client, type Options as Options2, type TDataShape, urlSearchParamsBodySerializer } from './client';
import { client } from './client.gen';
import type { itemsCreateItemData, itemsCreateItemErrors, itemsCreateItemResponses, itemsDeleteItemData, itemsDeleteItemErrors, itemsDeleteItemResponses, itemsReadItemData, itemsReadItemErrors, itemsReadItemResponses, itemsReadItemsData, itemsReadItemsErrors, itemsReadItemsResponses, itemsUpdateItemData, itemsUpdateItemErrors, itemsUpdateItemResponses, loginLoginAccessTokenData, loginLoginAccessTokenErrors, loginLoginAccessTokenResponses, loginRecoverPasswordData, loginRecoverPasswordErrors, loginRecoverPasswordHtmlContentData, loginRecoverPasswordHtmlContentErrors, loginRecoverPasswordHtmlContentResponses, loginRecoverPasswordResponses, loginResetPasswordData, loginResetPasswordErrors, loginResetPasswordResponses, loginTestTokenData, loginTestTokenResponses, privateCreateUserData, privateCreateUserErrors, privateCreateUserResponses, usersCreateUserData, usersCreateUserErrors, usersCreateUserResponses, usersDeleteUserData, usersDeleteUserErrors, usersDeleteUserMeData, usersDeleteUserMeResponses, usersDeleteUserResponses, usersReadUserByIdData, usersReadUserByIdErrors, usersReadUserByIdResponses, usersReadUserMeData, usersReadUserMeResponses, usersReadUsersData, usersReadUsersErrors, usersReadUsersResponses, usersRegisterUserData, usersRegisterUserErrors, usersRegisterUserResponses, usersUpdatePasswordMeData, usersUpdatePasswordMeErrors, usersUpdatePasswordMeResponses, usersUpdateUserData, usersUpdateUserErrors, usersUpdateUserMeData, usersUpdateUserMeErrors, usersUpdateUserMeResponses, usersUpdateUserResponses, utilsHealthCheckData, utilsHealthCheckResponses, utilsTestEmailData, utilsTestEmailErrors, utilsTestEmailResponses } from './types.gen';
export class ItemsService {
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean, TResponse = unknown> = Options2<TData, ThrowOnError, TResponse> & {
/**
* Read Items
* Retrieve items.
* @param data The data for the request.
* @param data.skip
* @param data.limit
* @returns ItemsPublic Successful Response
* @throws ApiError
* You can provide a client instance returned by `createClient()` instead of
* individual options. This might be also useful if you want to implement a
* custom client.
*/
public static readItems(data: ItemsReadItemsData = {}): CancelablePromise<ItemsReadItemsResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/items/',
query: {
skip: data.skip,
limit: data.limit
},
errors: {
422: 'Validation Error'
}
});
}
client?: Client;
/**
* Create Item
* Create new item.
* @param data The data for the request.
* @param data.requestBody
* @returns ItemPublic Successful Response
* @throws ApiError
* You can pass arbitrary values through the `meta` object. This can be
* used to access values that aren't defined as part of the SDK function.
*/
public static createItem(data: ItemsCreateItemData): CancelablePromise<ItemsCreateItemResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/items/',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
/**
* Read Item
* Get item by ID.
* @param data The data for the request.
* @param data.id
* @returns ItemPublic Successful Response
* @throws ApiError
*/
public static readItem(data: ItemsReadItemData): CancelablePromise<ItemsReadItemResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/items/{id}',
path: {
id: data.id
},
errors: {
422: 'Validation Error'
}
});
}
/**
* Update Item
* Update an item.
* @param data The data for the request.
* @param data.id
* @param data.requestBody
* @returns ItemPublic Successful Response
* @throws ApiError
*/
public static updateItem(data: ItemsUpdateItemData): CancelablePromise<ItemsUpdateItemResponse> {
return __request(OpenAPI, {
method: 'PUT',
url: '/api/v1/items/{id}',
path: {
id: data.id
},
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
/**
* Delete Item
* Delete an item.
* @param data The data for the request.
* @param data.id
* @returns Message Successful Response
* @throws ApiError
*/
public static deleteItem(data: ItemsDeleteItemData): CancelablePromise<ItemsDeleteItemResponse> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/api/v1/items/{id}',
path: {
id: data.id
},
errors: {
422: 'Validation Error'
}
});
}
}
meta?: Record<string, unknown>;
};
export class LoginService {
/**
* Login Access Token
*
* OAuth2 compatible token login, get an access token for future requests
* @param data The data for the request.
* @param data.formData
* @returns Token Successful Response
* @throws ApiError
*/
public static loginAccessToken(data: LoginLoginAccessTokenData): CancelablePromise<LoginLoginAccessTokenResponse> {
return __request(OpenAPI, {
method: 'POST',
public static loginAccessToken<ThrowOnError extends boolean = true>(options: Options<loginLoginAccessTokenData, ThrowOnError>) {
return (options.client ?? client).post<loginLoginAccessTokenResponses, loginLoginAccessTokenErrors, ThrowOnError>({
...urlSearchParamsBodySerializer,
responseType: 'json',
url: '/api/v1/login/access-token',
formData: data.formData,
mediaType: 'application/x-www-form-urlencoded',
errors: {
422: 'Validation Error'
...options,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
...options.headers
}
});
}
/**
* Test Token
*
* Test access token
* @returns UserPublic Successful Response
* @throws ApiError
*/
public static testToken(): CancelablePromise<LoginTestTokenResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/login/test-token'
public static testToken<ThrowOnError extends boolean = true>(options?: Options<loginTestTokenData, ThrowOnError>) {
return (options?.client ?? client).post<loginTestTokenResponses, unknown, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/login/test-token',
...options
});
}
/**
* Recover Password
*
* Password Recovery
* @param data The data for the request.
* @param data.email
* @returns Message Successful Response
* @throws ApiError
*/
public static recoverPassword(data: LoginRecoverPasswordData): CancelablePromise<LoginRecoverPasswordResponse> {
return __request(OpenAPI, {
method: 'POST',
public static recoverPassword<ThrowOnError extends boolean = true>(options: Options<loginRecoverPasswordData, ThrowOnError>) {
return (options.client ?? client).post<loginRecoverPasswordResponses, loginRecoverPasswordErrors, ThrowOnError>({
responseType: 'json',
url: '/api/v1/password-recovery/{email}',
path: {
email: data.email
},
errors: {
422: 'Validation Error'
}
...options
});
}
/**
* Reset Password
*
* Reset password
* @param data The data for the request.
* @param data.requestBody
* @returns Message Successful Response
* @throws ApiError
*/
public static resetPassword(data: LoginResetPasswordData): CancelablePromise<LoginResetPasswordResponse> {
return __request(OpenAPI, {
method: 'POST',
public static resetPassword<ThrowOnError extends boolean = true>(options: Options<loginResetPasswordData, ThrowOnError>) {
return (options.client ?? client).post<loginResetPasswordResponses, loginResetPasswordErrors, ThrowOnError>({
responseType: 'json',
url: '/api/v1/reset-password/',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
}
/**
* Recover Password Html Content
*
* HTML Content for Password Recovery
* @param data The data for the request.
* @param data.email
* @returns string Successful Response
* @throws ApiError
*/
public static recoverPasswordHtmlContent(data: LoginRecoverPasswordHtmlContentData): CancelablePromise<LoginRecoverPasswordHtmlContentResponse> {
return __request(OpenAPI, {
method: 'POST',
public static recoverPasswordHtmlContent<ThrowOnError extends boolean = true>(options: Options<loginRecoverPasswordHtmlContentData, ThrowOnError>) {
return (options.client ?? client).post<loginRecoverPasswordHtmlContentResponses, loginRecoverPasswordHtmlContentErrors, ThrowOnError>({
responseType: 'text',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/password-recovery-html-content/{email}',
path: {
email: data.email
},
errors: {
422: 'Validation Error'
}
});
}
}
export class PrivateService {
/**
* Create User
* Create a new user.
* @param data The data for the request.
* @param data.requestBody
* @returns UserPublic Successful Response
* @throws ApiError
*/
public static createUser(data: PrivateCreateUserData): CancelablePromise<PrivateCreateUserResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/private/users/',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
...options
});
}
}
@@ -238,195 +99,159 @@ export class PrivateService {
export class UsersService {
/**
* Read Users
*
* Retrieve users.
* @param data The data for the request.
* @param data.skip
* @param data.limit
* @returns UsersPublic Successful Response
* @throws ApiError
*/
public static readUsers(data: UsersReadUsersData = {}): CancelablePromise<UsersReadUsersResponse> {
return __request(OpenAPI, {
method: 'GET',
public static readUsers<ThrowOnError extends boolean = true>(options?: Options<usersReadUsersData, ThrowOnError>) {
return (options?.client ?? client).get<usersReadUsersResponses, usersReadUsersErrors, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/users/',
query: {
skip: data.skip,
limit: data.limit
},
errors: {
422: 'Validation Error'
}
...options
});
}
/**
* Create User
*
* Create new user.
* @param data The data for the request.
* @param data.requestBody
* @returns UserPublic Successful Response
* @throws ApiError
*/
public static createUser(data: UsersCreateUserData): CancelablePromise<UsersCreateUserResponse> {
return __request(OpenAPI, {
method: 'POST',
public static createUser<ThrowOnError extends boolean = true>(options: Options<usersCreateUserData, ThrowOnError>) {
return (options.client ?? client).post<usersCreateUserResponses, usersCreateUserErrors, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/users/',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
}
/**
* Read User Me
* Get current user.
* @returns UserPublic Successful Response
* @throws ApiError
* Delete User Me
*
* Delete own user.
*/
public static readUserMe(): CancelablePromise<UsersReadUserMeResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/users/me'
public static deleteUserMe<ThrowOnError extends boolean = true>(options?: Options<usersDeleteUserMeData, ThrowOnError>) {
return (options?.client ?? client).delete<usersDeleteUserMeResponses, unknown, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/users/me',
...options
});
}
/**
* Delete User Me
* Delete own user.
* @returns Message Successful Response
* @throws ApiError
* Read User Me
*
* Get current user.
*/
public static deleteUserMe(): CancelablePromise<UsersDeleteUserMeResponse> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/api/v1/users/me'
public static readUserMe<ThrowOnError extends boolean = true>(options?: Options<usersReadUserMeData, ThrowOnError>) {
return (options?.client ?? client).get<usersReadUserMeResponses, unknown, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/users/me',
...options
});
}
/**
* Update User Me
*
* Update own user.
* @param data The data for the request.
* @param data.requestBody
* @returns UserPublic Successful Response
* @throws ApiError
*/
public static updateUserMe(data: UsersUpdateUserMeData): CancelablePromise<UsersUpdateUserMeResponse> {
return __request(OpenAPI, {
method: 'PATCH',
public static updateUserMe<ThrowOnError extends boolean = true>(options: Options<usersUpdateUserMeData, ThrowOnError>) {
return (options.client ?? client).patch<usersUpdateUserMeResponses, usersUpdateUserMeErrors, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/users/me',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
}
/**
* Update Password Me
*
* Update own password.
* @param data The data for the request.
* @param data.requestBody
* @returns Message Successful Response
* @throws ApiError
*/
public static updatePasswordMe(data: UsersUpdatePasswordMeData): CancelablePromise<UsersUpdatePasswordMeResponse> {
return __request(OpenAPI, {
method: 'PATCH',
public static updatePasswordMe<ThrowOnError extends boolean = true>(options: Options<usersUpdatePasswordMeData, ThrowOnError>) {
return (options.client ?? client).patch<usersUpdatePasswordMeResponses, usersUpdatePasswordMeErrors, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/users/me/password',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
}
/**
* Register User
*
* Create new user without the need to be logged in.
* @param data The data for the request.
* @param data.requestBody
* @returns UserPublic Successful Response
* @throws ApiError
*/
public static registerUser(data: UsersRegisterUserData): CancelablePromise<UsersRegisterUserResponse> {
return __request(OpenAPI, {
method: 'POST',
public static registerUser<ThrowOnError extends boolean = true>(options: Options<usersRegisterUserData, ThrowOnError>) {
return (options.client ?? client).post<usersRegisterUserResponses, usersRegisterUserErrors, ThrowOnError>({
responseType: 'json',
url: '/api/v1/users/signup',
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}
/**
* Read User By Id
* Get a specific user by id.
* @param data The data for the request.
* @param data.userId
* @returns UserPublic Successful Response
* @throws ApiError
*/
public static readUserById(data: UsersReadUserByIdData): CancelablePromise<UsersReadUserByIdResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/users/{user_id}',
path: {
user_id: data.userId
},
errors: {
422: 'Validation Error'
}
});
}
/**
* Update User
* Update a user.
* @param data The data for the request.
* @param data.userId
* @param data.requestBody
* @returns UserPublic Successful Response
* @throws ApiError
*/
public static updateUser(data: UsersUpdateUserData): CancelablePromise<UsersUpdateUserResponse> {
return __request(OpenAPI, {
method: 'PATCH',
url: '/api/v1/users/{user_id}',
path: {
user_id: data.userId
},
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
}
/**
* Delete User
*
* Delete a user.
* @param data The data for the request.
* @param data.userId
* @returns Message Successful Response
* @throws ApiError
*/
public static deleteUser(data: UsersDeleteUserData): CancelablePromise<UsersDeleteUserResponse> {
return __request(OpenAPI, {
method: 'DELETE',
public static deleteUser<ThrowOnError extends boolean = true>(options: Options<usersDeleteUserData, ThrowOnError>) {
return (options.client ?? client).delete<usersDeleteUserResponses, usersDeleteUserErrors, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/users/{user_id}',
path: {
user_id: data.userId
},
errors: {
422: 'Validation Error'
...options
});
}
/**
* Read User By Id
*
* Get a specific user by id.
*/
public static readUserById<ThrowOnError extends boolean = true>(options: Options<usersReadUserByIdData, ThrowOnError>) {
return (options.client ?? client).get<usersReadUserByIdResponses, usersReadUserByIdErrors, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/users/{user_id}',
...options
});
}
/**
* Update User
*
* Update a user.
*/
public static updateUser<ThrowOnError extends boolean = true>(options: Options<usersUpdateUserData, ThrowOnError>) {
return (options.client ?? client).patch<usersUpdateUserResponses, usersUpdateUserErrors, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/users/{user_id}',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
}
@@ -435,34 +260,125 @@ export class UsersService {
export class UtilsService {
/**
* Test Email
*
* Test emails.
* @param data The data for the request.
* @param data.emailTo
* @returns Message Successful Response
* @throws ApiError
*/
public static testEmail(data: UtilsTestEmailData): CancelablePromise<UtilsTestEmailResponse> {
return __request(OpenAPI, {
method: 'POST',
public static testEmail<ThrowOnError extends boolean = true>(options: Options<utilsTestEmailData, ThrowOnError>) {
return (options.client ?? client).post<utilsTestEmailResponses, utilsTestEmailErrors, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/utils/test-email/',
query: {
email_to: data.emailTo
},
errors: {
422: 'Validation Error'
}
...options
});
}
/**
* Health Check
* @returns boolean Successful Response
* @throws ApiError
*/
public static healthCheck(): CancelablePromise<UtilsHealthCheckResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/utils/health-check/'
public static healthCheck<ThrowOnError extends boolean = true>(options?: Options<utilsHealthCheckData, ThrowOnError>) {
return (options?.client ?? client).get<utilsHealthCheckResponses, unknown, ThrowOnError>({
responseType: 'json',
url: '/api/v1/utils/health-check/',
...options
});
}
}
}
export class ItemsService {
/**
* Read Items
*
* Retrieve items.
*/
public static readItems<ThrowOnError extends boolean = true>(options?: Options<itemsReadItemsData, ThrowOnError>) {
return (options?.client ?? client).get<itemsReadItemsResponses, itemsReadItemsErrors, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/items/',
...options
});
}
/**
* Create Item
*
* Create new item.
*/
public static createItem<ThrowOnError extends boolean = true>(options: Options<itemsCreateItemData, ThrowOnError>) {
return (options.client ?? client).post<itemsCreateItemResponses, itemsCreateItemErrors, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/items/',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
}
/**
* Delete Item
*
* Delete an item.
*/
public static deleteItem<ThrowOnError extends boolean = true>(options: Options<itemsDeleteItemData, ThrowOnError>) {
return (options.client ?? client).delete<itemsDeleteItemResponses, itemsDeleteItemErrors, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/items/{id}',
...options
});
}
/**
* Read Item
*
* Get item by ID.
*/
public static readItem<ThrowOnError extends boolean = true>(options: Options<itemsReadItemData, ThrowOnError>) {
return (options.client ?? client).get<itemsReadItemResponses, itemsReadItemErrors, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/items/{id}',
...options
});
}
/**
* Update Item
*
* Update an item.
*/
public static updateItem<ThrowOnError extends boolean = true>(options: Options<itemsUpdateItemData, ThrowOnError>) {
return (options.client ?? client).put<itemsUpdateItemResponses, itemsUpdateItemErrors, ThrowOnError>({
responseType: 'json',
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/items/{id}',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
}
}
export class PrivateService {
/**
* Create User
*
* Create a new user.
*/
public static createUser<ThrowOnError extends boolean = true>(options: Options<privateCreateUserData, ThrowOnError>) {
return (options.client ?? client).post<privateCreateUserResponses, privateCreateUserErrors, ThrowOnError>({
responseType: 'json',
url: '/api/v1/private/users/',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
}
}
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -72,8 +72,7 @@ const AddUser = () => {
})
const mutation = useMutation({
mutationFn: (data: UserCreate) =>
UsersService.createUser({ requestBody: data }),
mutationFn: (data: UserCreate) => UsersService.createUser({ body: data }),
onSuccess: () => {
showSuccessToast("User created successfully")
form.reset()
+1 -1
View File
@@ -31,7 +31,7 @@ const DeleteUser = ({ id, onSuccess }: DeleteUserProps) => {
const { handleSubmit } = useForm()
const deleteUser = async (id: string) => {
await UsersService.deleteUser({ userId: id })
await UsersService.deleteUser({ path: { user_id: id } })
}
const mutation = useMutation({
+1 -1
View File
@@ -75,7 +75,7 @@ const EditUser = ({ user, onSuccess }: EditUserProps) => {
const mutation = useMutation({
mutationFn: (data: FormData) =>
UsersService.updateUser({ userId: user.id, requestBody: data }),
UsersService.updateUser({ path: { user_id: user.id }, body: data }),
onSuccess: () => {
showSuccessToast("User updated successfully")
setIsOpen(false)
+1 -2
View File
@@ -53,8 +53,7 @@ const AddItem = () => {
})
const mutation = useMutation({
mutationFn: (data: ItemCreate) =>
ItemsService.createItem({ requestBody: data }),
mutationFn: (data: ItemCreate) => ItemsService.createItem({ body: data }),
onSuccess: () => {
showSuccessToast("Item created successfully")
form.reset()
+1 -1
View File
@@ -31,7 +31,7 @@ const DeleteItem = ({ id, onSuccess }: DeleteItemProps) => {
const { handleSubmit } = useForm()
const deleteItem = async (id: string) => {
await ItemsService.deleteItem({ id: id })
await ItemsService.deleteItem({ path: { id } })
}
const mutation = useMutation({
+1 -1
View File
@@ -59,7 +59,7 @@ const EditItem = ({ item, onSuccess }: EditItemProps) => {
const mutation = useMutation({
mutationFn: (data: FormData) =>
ItemsService.updateItem({ id: item.id, requestBody: data }),
ItemsService.updateItem({ path: { id: item.id }, body: data }),
onSuccess: () => {
showSuccessToast("Item updated successfully")
setIsOpen(false)
@@ -53,7 +53,7 @@ const ChangePassword = () => {
const mutation = useMutation({
mutationFn: (data: UpdatePassword) =>
UsersService.updatePasswordMe({ requestBody: data }),
UsersService.updatePasswordMe({ body: data }),
onSuccess: () => {
showSuccessToast("Password updated successfully")
form.reset()
@@ -50,7 +50,7 @@ const UserInformation = () => {
const mutation = useMutation({
mutationFn: (data: UserUpdateMe) =>
UsersService.updateUserMe({ requestBody: data }),
UsersService.updateUserMe({ body: data }),
onSuccess: () => {
showSuccessToast("User updated successfully")
toggleEditMode()
+4 -4
View File
@@ -22,13 +22,13 @@ const useAuth = () => {
const { data: user } = useQuery<UserPublic | null, Error>({
queryKey: ["currentUser"],
queryFn: UsersService.readUserMe,
queryFn: async () => (await UsersService.readUserMe()).data,
enabled: isLoggedIn(),
})
const signUpMutation = useMutation({
mutationFn: (data: UserRegister) =>
UsersService.registerUser({ requestBody: data }),
UsersService.registerUser({ body: data }),
onSuccess: () => {
navigate({ to: "/login" })
},
@@ -40,9 +40,9 @@ const useAuth = () => {
const login = async (data: AccessToken) => {
const response = await LoginService.loginAccessToken({
formData: data,
body: data,
})
localStorage.setItem("access_token", response.access_token)
localStorage.setItem("access_token", response.data.access_token)
}
const loginMutation = useMutation({
+10 -6
View File
@@ -5,21 +5,25 @@ import {
QueryClientProvider,
} from "@tanstack/react-query"
import { createRouter, RouterProvider } from "@tanstack/react-router"
import { AxiosError } from "axios"
import { StrictMode } from "react"
import ReactDOM from "react-dom/client"
import { ApiError, OpenAPI } from "./client"
import { client } from "./client/client.gen"
import { ThemeProvider } from "./components/theme-provider"
import { Toaster } from "./components/ui/sonner"
import "./index.css"
import { routeTree } from "./routeTree.gen"
OpenAPI.BASE = import.meta.env.VITE_API_URL
OpenAPI.TOKEN = async () => {
return localStorage.getItem("access_token") || ""
}
client.setConfig({
baseURL: import.meta.env.VITE_API_URL ?? "",
auth: () => localStorage.getItem("access_token") || "",
})
const handleApiError = (error: Error) => {
if (error instanceof ApiError && [401, 403].includes(error.status)) {
if (
error instanceof AxiosError &&
[401, 403].includes(error.response?.status ?? 0)
) {
localStorage.removeItem("access_token")
window.location.href = "/login"
}
+3 -3
View File
@@ -65,6 +65,7 @@ const LayoutAdminRoute = LayoutAdminRouteImport.update({
} as any)
export interface FileRoutesByFullPath {
'/': typeof LayoutIndexRoute
'/login': typeof LoginRoute
'/recover-password': typeof RecoverPasswordRoute
'/reset-password': typeof ResetPasswordRoute
@@ -72,7 +73,6 @@ export interface FileRoutesByFullPath {
'/admin': typeof LayoutAdminRoute
'/items': typeof LayoutItemsRoute
'/settings': typeof LayoutSettingsRoute
'/': typeof LayoutIndexRoute
}
export interface FileRoutesByTo {
'/login': typeof LoginRoute
@@ -99,6 +99,7 @@ export interface FileRoutesById {
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/login'
| '/recover-password'
| '/reset-password'
@@ -106,7 +107,6 @@ export interface FileRouteTypes {
| '/admin'
| '/items'
| '/settings'
| '/'
fileRoutesByTo: FileRoutesByTo
to:
| '/login'
@@ -171,7 +171,7 @@ declare module '@tanstack/react-router' {
'/_layout': {
id: '/_layout'
path: ''
fullPath: ''
fullPath: '/'
preLoaderRoute: typeof LayoutRouteImport
parentRoute: typeof rootRouteImport
}

Some files were not shown because too many files have changed in this diff Show More