fastapi-fullstack 0.2.2__py3-none-any.whl → 0.2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {fastapi_fullstack-0.2.2.dist-info → fastapi_fullstack-0.2.4.dist-info}/METADATA +2 -2
  2. {fastapi_fullstack-0.2.2.dist-info → fastapi_fullstack-0.2.4.dist-info}/RECORD +29 -17
  3. fastapi_gen/template/hooks/post_gen_project.py +2 -0
  4. fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/commands/add-endpoint.md +40 -0
  5. fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/commands/fix-issue.md +16 -0
  6. fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/commands/review.md +31 -0
  7. fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/api-conventions.md +90 -0
  8. fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/architecture.md +109 -0
  9. fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/code-style.md +67 -0
  10. fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/exceptions-security.md +54 -0
  11. fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/frontend.md +27 -0
  12. fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/schemas-models.md +90 -0
  13. fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/testing.md +85 -0
  14. fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/settings.json +17 -0
  15. fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +2 -2
  16. fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +2 -4
  17. fastapi_gen/template/{{cookiecutter.project_slug}}/.gitlab-ci.yml +2 -2
  18. fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +110 -57
  19. fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +2 -2
  20. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +3 -7
  21. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +1 -1
  22. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +151 -0
  23. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +69 -3
  24. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +86 -62
  25. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_ssrf.py +207 -0
  26. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/commands.md +1 -1
  27. {fastapi_fullstack-0.2.2.dist-info → fastapi_fullstack-0.2.4.dist-info}/WHEEL +0 -0
  28. {fastapi_fullstack-0.2.2.dist-info → fastapi_fullstack-0.2.4.dist-info}/entry_points.txt +0 -0
  29. {fastapi_fullstack-0.2.2.dist-info → fastapi_fullstack-0.2.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-fullstack
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Full-stack FastAPI + Next.js template generator with PydanticAI/LangChain agents, WebSocket streaming, 20+ enterprise integrations, and Logfire/LangSmith observability. Ship AI apps fast. CLI tool to generate production-ready FastAPI + Next.js projects with AI agents, auth, and observability.
5
5
  Project-URL: Homepage, https://github.com/vstorm-co/full-stack-ai-agent-template
6
6
  Project-URL: Documentation, https://github.com/vstorm-co/full-stack-ai-agent-template#readme
@@ -27,11 +27,11 @@ Requires-Dist: pydantic>=2.0.0
27
27
  Requires-Dist: questionary>=2.0.0
28
28
  Requires-Dist: rich>=13.0.0
29
29
  Provides-Extra: dev
30
- Requires-Dist: mypy>=1.13.0; extra == 'dev'
31
30
  Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
32
31
  Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
33
32
  Requires-Dist: pytest>=8.0.0; extra == 'dev'
34
33
  Requires-Dist: ruff>=0.8.0; extra == 'dev'
34
+ Requires-Dist: ty>=0.0.29; extra == 'dev'
35
35
  Provides-Extra: docs
36
36
  Requires-Dist: mkdocs-material>=9.5.0; extra == 'docs'
37
37
  Requires-Dist: mkdocs>=1.6.0; extra == 'docs'
@@ -5,26 +5,37 @@ fastapi_gen/generator.py,sha256=aEYbD6eC_3B1Y7sY54AJkEWrXx58zVaTQrXqCy5r_Ac,8995
5
5
  fastapi_gen/prompts.py,sha256=jsSckM9hpoPH4CQvXaDkxya2JDswZ86bmLgaLttH4AU,32287
6
6
  fastapi_gen/template/VARIABLES.md,sha256=rB2N6RCvlARxw9hcvNxq2kSpiMjS2iQzryThioyx14o,19156
7
7
  fastapi_gen/template/cookiecutter.json,sha256=Pxh3yc-uegduhWf6jGxeFTujzfDOl5jGpnQR06e3VQ0,3783
8
- fastapi_gen/template/hooks/post_gen_project.py,sha256=vm2rz-wj6V4YRDFlZ-ztIr6OkQK0NAR-8Ouj_wi1gsI,17310
8
+ fastapi_gen/template/hooks/post_gen_project.py,sha256=Ou0Il1SZ6QX31VLND8NurhGgFEbcACI0c4n3YXBaoRU,17431
9
9
  fastapi_gen/template/{{cookiecutter.project_slug}}/.env.prod.example,sha256=AnG_jOM673bJhncXC1jeAegPzCOhcWWu1_eKFUleXNU,2370
10
- fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore,sha256=InfbjRDaumEu7EsgiUiBEbDAb8uwcJV2j6Mt2Y1EcIU,1091
11
- fastapi_gen/template/{{cookiecutter.project_slug}}/.gitlab-ci.yml,sha256=-LsuRAEva_sH4dPs54qx3RY3YV_-_YF4UqdISpiaGKo,4835
10
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore,sha256=Iasc-ahiDOjxHyAFJSIqBqgi6RkF8-yBp_wS_70oKkA,1064
11
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.gitlab-ci.yml,sha256=3ajZ4hIeWMcagAFOK7c54Ef66M6rqPBZP69BCfDWzQo,4833
12
12
  fastapi_gen/template/{{cookiecutter.project_slug}}/AGENTS.md,sha256=hShNTKVXEvBDRMcRfIqjbJnf2kQ-qMSAGR6b24B5lQs,2592
13
- fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md,sha256=jlYgF2RpekXoITtnuMgAVavMEM4VW3QjR-xHeL8FoVQ,4265
14
- fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile,sha256=epKiy_eXYT7PQZzFiujCTsDCU97Etxlwmi4Y5j0dmgg,12122
13
+ fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md,sha256=YaJpZ4CAHMnon0kC2-PvylCZpiSS3jIGZY4GVgjNnwc,7474
14
+ fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile,sha256=QhL-Djz_rOpx6uxXhHMKfSp29JiQzy1jwBf3lw51G0M,12120
15
15
  fastapi_gen/template/{{cookiecutter.project_slug}}/README.md,sha256=GFGxGKIk5mqKh40_j8s_ASdEyj2KHUljlbvFULAfQTM,7684
16
16
  fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml,sha256=GTiJ5IgP-YOO7xqVFheiE6unthkg9FZOyFH9JcddnoE,10626
17
17
  fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml,sha256=KvYVb7P1vlimx2_klOy-qnsh93tn40RfpUXV2qeH6Sw,1262
18
18
  fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml,sha256=7aCdZ7NtJOXNnlnki_Wu0INqprbaC2MYLZz1d8vfrc4,22759
19
19
  fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml,sha256=Yd8Q8nzESQi1QoURtK7oPeZWgHuYxpLevLkkA2-3NhM,11804
20
- fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml,sha256=KBWmJVuIRtXxL_MywU5j5YxBZ9CUIdn1fsZE4ZfcKy0,4928
20
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/settings.json,sha256=i6XfBDlx6N1AXg4-MrjmIzPhxRXvQC9PRXJFZyfNs1Y,363
21
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/commands/add-endpoint.md,sha256=p0EsXDvW23v4uOSPRlwA84nVrB7_VVL_FnKFEE442AQ,1648
22
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/commands/fix-issue.md,sha256=3bcQwYzf_EXTit9nlkplUd0-G5T9-gGranogIDS_kjU,800
23
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/commands/review.md,sha256=McWKMjMa-tCq8h_3-tx-uTUW4HoNAjb-H3zX8BnDne8,1179
24
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/api-conventions.md,sha256=FFNZrbOY6O2f65QOsyD-Oo2vDX60OEGsvdvK2uY49P4,2234
25
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/architecture.md,sha256=fG6vyFlrRnlS4BrBdR8G9Q_j455hxw9KJXdGaj7gaPs,3684
26
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/code-style.md,sha256=2lDRkb3hjHkZnU6LGBiHs4lCUgI7uzJ9Pwk60Qbs0gk,2163
27
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/exceptions-security.md,sha256=sJNlPg9FsqUBpotj9deoQOvkuX83WiVUJyn7T8G-NJE,1903
28
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/frontend.md,sha256=jj9Yao5VRhHXpvG7DwfW8FDI9m4TPv0x0M2VCjKrKUw,759
29
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/schemas-models.md,sha256=fUuyb8A0Zlzu4wpEjh-qrwFBfstv6b5Jr_-wmqNlttc,2901
30
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.claude/rules/testing.md,sha256=w-zM54qfLNSB3tCMxBuOp7jABHS6dG1s62SGvSSXlxU,2267
31
+ fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml,sha256=qR2eyiMNEhPo3cZ4HCS1dskVLK5O6xIaqP_zBtd6FDo,4926
21
32
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore,sha256=_iRaYQqTvt8_2yhJZUp60PPaL5XLiUFg0OSa-J_9XIQ,523
22
33
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.env.example,sha256=SqlXMIr3j441dR82tP34i45ArX7r6vM-U4JLphMvYm0,8102
23
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml,sha256=KL9PoA9VtzqzL3M99O_0JPyVh6uo_Pp52JflfzCK7qA,821
34
+ fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml,sha256=qnK25jUF_eaT8sSelAhcKQ5Y1Y2hY2UE7CcJ0KOVKro,680
24
35
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile,sha256=G5g7qCHBtMyZdhHC_BB2VfSa9ingMzoVVE1K4fah47g,1628
25
36
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini,sha256=Ol5tRsSTj20-C-9YOyQV17oEPFhPRi0aY859ZNc3cUs,872
26
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml,sha256=FtDWcBiAvNmjzGfFc2W0R9j6WFivi_MYn0U8Krt8ZWk,8763
27
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py,sha256=NjdHh6uPtdz-hp8b3Hjg-6rGxT0Ls8MTyj4BGc7NjpI,2163
37
+ fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml,sha256=hsKJiScIGdBAerqaeoUEnsn9jvEJGG-hAajrbta3s6I,9683
38
+ fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py,sha256=EhduURmw65KT58RHdemFiM4ZgNxaPtVOkJtUcsmMRLk,2169
28
39
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako,sha256=hSTffz_8YXK8N5IfhFpyXXXWIho_zWEzMgdzZzTQPGo,817
29
40
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
41
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py,sha256=vNnuyVXC5oZ3yq46meWLNhkUYlMgod70TgvWyvgNhBk,68
@@ -75,7 +86,7 @@ fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logging.py,s
75
86
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py,sha256=23eFiIULqywo3o7TsHf1ozUSmzJHxqRJHGVxn-JMqM4,3487
76
87
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py,sha256=aAv64WrGyX7WR6XXce2SJ2l02uxgtLemd11Ilqx4dkE,597
77
88
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py,sha256=emmc2orBEIT6yfrZvy1jxdr52_eVawYAQJx9MiFsQKk,1689
78
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py,sha256=wQn3h1pFL3aqemr6W2jWMqGPJGVnb8ipZwLOwHdr6aI,7372
89
+ fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py,sha256=-XQBTypMEjw0Llbx7bNlG-rLIMQ0OW7ySRL4N8cjFaU,12476
79
90
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py,sha256=AqT0q6y1PmhyhZeV97Ujtc2PQx8EdUKMeAa2VC2sCQY,2769
80
91
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py,sha256=y9by535cwbinmqOftskTk0iALAtckQfpjW-KmyzGwhw,362
81
92
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py,sha256=MZ-vPlY08QOesm7p3aFP2e8kzrmCeSM1fjbyRDm-ppQ,2289
@@ -134,7 +145,7 @@ fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/rag_sync
134
145
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py,sha256=n-ICXvEr2S2t_rUo8Za5WknZSs4YBYkZ24iH5jWVTN8,11343
135
146
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/sync_source.py,sha256=I_YO1A3HddSkSbiES26EfQl3xXqA4nrsezj95QGJdJ0,9533
136
147
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py,sha256=c3suO57uIbZKvlInWX4V-K6lAKshZQqVaqQUG2BkUYc,15020
137
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py,sha256=okZh6SqStwuy2IyqwOD1-wwBBUaHaiFMAIx9UrhzU7E,16930
148
+ fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py,sha256=tUi03HqHm9Rnn0AS2PI4VZ9uoZ1IOuxXFpIMxv67zqA,19825
138
149
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py,sha256=RRfSOD2hWUqo_rYkOc9KaE5BgqK7UkRC--Ni-BjMC3Q,169
139
150
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/arq_app.py,sha256=KLSBUUj9BY4pYYrJOpg9RM87ybZdSR4eDk9PoikawCA,4470
140
151
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py,sha256=FHp_tRNfEfqfE5qMpH3m1TkUn69T94DwPdjUKx8MdGQ,1829
@@ -159,6 +170,7 @@ fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositori
159
170
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py,sha256=6YJUtT9HqIkhcyAEmQSIiYNunNVC_hmsrfjscmdON-k,3752
160
171
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py,sha256=c2-rY6E9FQpb8U2ZCkPH1NvYOCJ-FS3crXlVXHKEQzU,13373
161
172
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services_conversation.py,sha256=20TKPPCR7TkESDSjdrqiInrXpt5_3oQAc2ABYHr3Mys,35054
173
+ fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_ssrf.py,sha256=Wez24gGr2np3doP9w0IYW08tAUyO58FtSuzPvhpkGyw,6832
162
174
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py,sha256=wnbYBPQvyr3Rr10dvoV6hvlJL2ChCIl77F47LSu1P3U,2502
163
175
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py,sha256=WV9U5TSWbUYHVA2F3CD7gfod4RdX8EM1s4ewEnwszr0,25
164
176
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py,sha256=Z_RWiwvQTx73F2z1XSYSHA-9UxcaLdKR8EBkzzyTs5A,7852
@@ -169,7 +181,7 @@ fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_openap
169
181
  fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py,sha256=0-GIPfch0nBFUKJL18HAzGNWOzc9TO04aMOOnGOrMMA,7674
170
182
  fastapi_gen/template/{{cookiecutter.project_slug}}/docs/adding_features.md,sha256=EbNBxbLkswwMwa-Z4IAd8TxF_GHTmkgwZSeyEqth_h0,6929
171
183
  fastapi_gen/template/{{cookiecutter.project_slug}}/docs/architecture.md,sha256=vU-7KQQp03uQOQ_jJ8INe5gks7TiUFHZ4JgzokGQDFc,11594
172
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/commands.md,sha256=GLgAcdzpmhEFpeonV5iPeaAA83CrgM0f-vIbBNPT_s0,11093
184
+ fastapi_gen/template/{{cookiecutter.project_slug}}/docs/commands.md,sha256=mFW8XvDrO14jW6kboCHjxYN0bZRt6c4h4UWp7_VPqho,11091
173
185
  fastapi_gen/template/{{cookiecutter.project_slug}}/docs/configuration.md,sha256=hV9KZAXp9QVZBTMjUo3XJ49FVJSHmMJiXs-TD4xUGBg,17272
174
186
  fastapi_gen/template/{{cookiecutter.project_slug}}/docs/file-processing.md,sha256=3BGOT7hUAsdQFDYMmkI9CRVgc1kmC8V2jVFw8oSP0gk,11081
175
187
  fastapi_gen/template/{{cookiecutter.project_slug}}/docs/patterns.md,sha256=YUTXhwP745bmilI8EeWXUOxqqU9RUBij6KFLFaPgA44,5949
@@ -346,8 +358,8 @@ fastapi_gen/template/{{cookiecutter.project_slug}}/kubernetes/secret.yaml,sha256
346
358
  fastapi_gen/template/{{cookiecutter.project_slug}}/kubernetes/service.yaml,sha256=o3bCXguj8E5aQ0H1o3cBD3z_iZ_RA_Pmx1bc0sVzK28,682
347
359
  fastapi_gen/template/{{cookiecutter.project_slug}}/nginx/nginx.conf,sha256=gJWUMMqNvr9TwNZOcc_WPPGZvdAlRUH3jLhbllOb_YY,7359
348
360
  fastapi_gen/template/{{cookiecutter.project_slug}}/nginx/ssl/.gitkeep,sha256=o_WQQOgVGOLttT48x0EXSC4BbbDFemApFpmwhybcVUU,617
349
- fastapi_fullstack-0.2.2.dist-info/METADATA,sha256=X0qsovd0RiTjYR76eKmZs2Uykcppf0Kl61-itqxiOww,43015
350
- fastapi_fullstack-0.2.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
351
- fastapi_fullstack-0.2.2.dist-info/entry_points.txt,sha256=s9JXrISZp8LMYJGeVofOAd1wPTzpq-jwjSgSf4hWzjs,59
352
- fastapi_fullstack-0.2.2.dist-info/licenses/LICENSE,sha256=bL4JuK_rcA8y__Gg7PEuTs3g2Qf6VvSz2w2Jajd6nVU,1063
353
- fastapi_fullstack-0.2.2.dist-info/RECORD,,
361
+ fastapi_fullstack-0.2.4.dist-info/METADATA,sha256=RpJhZdB8NXiWtay8qfakChDNvtiGzPpRjzPOGvdOXNk,43013
362
+ fastapi_fullstack-0.2.4.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
363
+ fastapi_fullstack-0.2.4.dist-info/entry_points.txt,sha256=s9JXrISZp8LMYJGeVofOAd1wPTzpq-jwjSgSf4hWzjs,59
364
+ fastapi_fullstack-0.2.4.dist-info/licenses/LICENSE,sha256=bL4JuK_rcA8y__Gg7PEuTs3g2Qf6VvSz2w2Jajd6nVU,1063
365
+ fastapi_fullstack-0.2.4.dist-info/RECORD,,
@@ -263,6 +263,8 @@ if not use_frontend:
263
263
  if os.path.exists(frontend_dir):
264
264
  shutil.rmtree(frontend_dir)
265
265
  print("Removed frontend/ directory (frontend not enabled)")
266
+ # Remove frontend-specific Claude rule
267
+ remove_file(os.path.join(os.getcwd(), ".claude", "rules", "frontend.md"))
266
268
 
267
269
 
268
270
  # Remove .env files if generate_env is false
@@ -0,0 +1,40 @@
1
+ ---
2
+ description: Scaffold a new API endpoint with full layering
3
+ ---
4
+
5
+ Create a new API endpoint: $ARGUMENTS
6
+
7
+ Follow the project's layered architecture. Create files in this order:
8
+
9
+ 1. **Schema** (`backend/app/schemas/<entity>.py`):
10
+ - Inherit `BaseSchema` (and `TimestampSchema` for Read)
11
+ - Create `*Create`, `*Update`, `*Read`, `*List` models
12
+ - Use `Field()` with constraints, `EmailStr` where applicable
13
+
14
+ 2. **DB Model** (`backend/app/db/models/<entity>.py`):
15
+ - Inherit `Base, TimestampMixin`
16
+ - Use `Mapped[type]` + `mapped_column()`
17
+ - Add `__repr__`, relationships with `cascade="all, delete-orphan"`
18
+
19
+ 3. **Repository** (`backend/app/repositories/<entity>_repo.py`):
20
+ - Stateless async functions: `get_by_id`, `get_multi`, `create`, `update`, `delete`
21
+ - Use `db.flush()` + `db.refresh()`, keyword-only args after `db`
22
+
23
+ 4. **Service** (`backend/app/services/<entity>.py`):
24
+ - Class with `__init__(self, db: AsyncSession)`
25
+ - Raise `NotFoundError`, `AlreadyExistsError` as appropriate
26
+
27
+ 5. **DI** (`backend/app/api/deps.py`):
28
+ - Add factory function and `Annotated` alias: `EntitySvc = Annotated[EntityService, Depends(get_entity_service)]`
29
+
30
+ 6. **Route** (`backend/app/api/routes/v1/<entity>.py`):
31
+ - CRUD: GET list, GET by id, POST (201), PATCH, DELETE (204)
32
+ - Use DI aliases, `response_model`, `-> Any` return type
33
+
34
+ 7. **Register** router in `backend/app/api/routes/v1/__init__.py`
35
+
36
+ 8. **Migration**: `cd backend && uv run alembic revision --autogenerate -m "Add <entity> table"`
37
+
38
+ 9. **Test** (`backend/tests/`): mirror source structure
39
+
40
+ 10. Lint: `cd backend && uv run ruff check . --fix && uv run ruff format .`
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: Investigate and fix an issue
3
+ ---
4
+
5
+ Fix the issue: $ARGUMENTS
6
+
7
+ 1. **Understand** — search the codebase for relevant code, read the files, understand current behavior
8
+ 2. **Reproduce** — if possible, identify a test case or request that triggers the issue
9
+ 3. **Root cause** — trace through Routes → Services → Repositories to find where the bug originates
10
+ 4. **Fix** — implement the fix following project conventions:
11
+ - Domain exceptions in services (not HTTP errors)
12
+ - `db.flush()` in repositories (not `commit`)
13
+ - Type hints on all changed signatures
14
+ 5. **Test** — run `cd backend && uv run pytest` to verify no regressions
15
+ 6. **Lint** — run `cd backend && uv run ruff check . --fix && uv run ruff format .`
16
+ 7. **Summary** — explain what was changed and why
@@ -0,0 +1,31 @@
1
+ ---
2
+ description: Review code changes against project conventions
3
+ ---
4
+
5
+ Review all staged and unstaged changes in the current branch.
6
+
7
+ For each changed file, verify:
8
+
9
+ **Architecture:**
10
+ - Routes only call services, never repositories
11
+ - Services raise domain exceptions (NotFoundError, AlreadyExistsError, etc.), not HTTP exceptions
12
+ - Repositories use `db.flush()` + `db.refresh()`, never `db.commit()`
13
+ - DI uses Annotated aliases from `deps.py` (CurrentUser, *Svc), not raw `Depends()` in signatures
14
+
15
+ **Schemas & Types:**
16
+ - Separate Create/Update/Read/List Pydantic models
17
+ - Type hints on all function signatures (params + return)
18
+ - Modern syntax: `str | None` not `Optional[str]`
19
+ - Route return type is `-> Any`
20
+
21
+ **Code Quality:**
22
+ - No debug code (print, commented-out code, TODO without issue reference)
23
+ - No security issues (SQL injection, exposed secrets, missing auth)
24
+ - Consistent naming (snake_case functions, PascalCase classes)
25
+ - Imports ordered: stdlib → third-party → local
26
+
27
+ **Validation:**
28
+ 1. Run `cd backend && uv run ruff check .`
29
+ 2. Run `cd backend && uv run pytest` (if test files changed)
30
+
31
+ Provide findings with specific file:line references and suggest fixes.
@@ -0,0 +1,90 @@
1
+ ---
2
+ description: API design, REST conventions, auth, pagination, response format
3
+ globs: ["backend/app/api/**/*.py"]
4
+ ---
5
+
6
+ # API Conventions
7
+
8
+ ## Route Structure
9
+
10
+ - All routes under `/api/v1/` prefix
11
+ - One file per domain entity in `api/routes/v1/`
12
+ - Use `APIRouter()` with tags
13
+
14
+ ## HTTP Methods & Status Codes
15
+
16
+ ```python
17
+ # GET — read
18
+ @router.get("/{id}", response_model=EntityRead)
19
+
20
+ # GET list — paginated
21
+ @router.get("", response_model=EntityList)
22
+
23
+ # POST — create (201)
24
+ @router.post("", response_model=EntityRead, status_code=status.HTTP_201_CREATED)
25
+
26
+ # PATCH — partial update
27
+ @router.patch("/{id}", response_model=EntityRead)
28
+
29
+ # DELETE — no content (204)
30
+ @router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
31
+ ```
32
+
33
+ ## Pagination
34
+
35
+ ```python
36
+ @router.get("", response_model=ConversationList)
37
+ async def list_items(
38
+ service: ConversationSvc,
39
+ user: CurrentUser,
40
+ skip: int = Query(0, ge=0, description="Items to skip"),
41
+ limit: int = Query(50, ge=1, le=100, description="Max items to return"),
42
+ ) -> Any:
43
+ items, total = await service.list(user_id=user.id, skip=skip, limit=limit)
44
+ return ConversationList(items=items, total=total)
45
+ ```
46
+
47
+ ## Authentication
48
+
49
+ - `CurrentUser` — JWT Bearer token (any authenticated user)
50
+ - `CurrentAdmin` — JWT + admin role check via `RoleChecker`
51
+ - `ValidAPIKey` — API key from header (service-to-service)
52
+
53
+ ```python
54
+ # Protected endpoint
55
+ async def get_profile(user: CurrentUser) -> Any: ...
56
+
57
+ # Admin-only endpoint
58
+ async def delete_user(user: CurrentAdmin) -> Any: ...
59
+
60
+ # API key endpoint
61
+ async def webhook_callback(api_key: ValidAPIKey) -> Any: ...
62
+ ```
63
+
64
+ ## Response Format
65
+
66
+ All route handlers return `-> Any`. The `response_model` parameter handles serialization.
67
+
68
+ Error responses follow this JSON structure:
69
+ ```json
70
+ {
71
+ "error": {
72
+ "code": "NOT_FOUND",
73
+ "message": "User not found",
74
+ "details": {"user_id": "..."}
75
+ }
76
+ }
77
+ ```
78
+
79
+ ## File Upload
80
+
81
+ ```python
82
+ @router.post("/me/avatar", response_model=UserRead)
83
+ async def upload_avatar(
84
+ file: UploadFile = File(...),
85
+ user: CurrentUser,
86
+ service: UserSvc,
87
+ ) -> Any:
88
+ data = await file.read()
89
+ return await service.update_avatar(user.id, data, file.filename or "avatar.jpg")
90
+ ```
@@ -0,0 +1,109 @@
1
+ ---
2
+ description: Layered architecture patterns — Routes, Services, Repositories, DI
3
+ globs: ["backend/app/**/*.py"]
4
+ ---
5
+
6
+ # Architecture
7
+
8
+ ## Layered Architecture: Routes → Services → Repositories
9
+
10
+ Routes NEVER import or call repositories directly. Always go through a service.
11
+
12
+ ## Repositories (`app/repositories/`)
13
+
14
+ Pure data access — stateless functions (not classes):
15
+
16
+ ```python
17
+ async def get_by_id(db: AsyncSession, entity_id: UUID) -> Entity | None:
18
+ result = await db.execute(select(Entity).where(Entity.id == entity_id))
19
+ return result.scalar_one_or_none()
20
+
21
+ async def create(db: AsyncSession, *, field1: str, field2: str) -> Entity:
22
+ entity = Entity(field1=field1, field2=field2)
23
+ db.add(entity)
24
+ await db.flush()
25
+ await db.refresh(entity)
26
+ return entity
27
+
28
+ async def update(db: AsyncSession, *, db_entity: Entity, update_data: dict[str, Any]) -> Entity:
29
+ for field, value in update_data.items():
30
+ setattr(db_entity, field, value)
31
+ await db.flush()
32
+ await db.refresh(db_entity)
33
+ return db_entity
34
+
35
+ async def delete(db: AsyncSession, entity_id: UUID) -> Entity | None:
36
+ entity = await get_by_id(db, entity_id)
37
+ if entity:
38
+ await db.delete(entity)
39
+ await db.flush()
40
+ return entity
41
+ ```
42
+
43
+ Rules:
44
+ - ALWAYS `db.flush()` + `db.refresh()`, NEVER `db.commit()` — session auto-commits in `get_db_session`
45
+ - Use keyword-only args after `db`: `create(db, *, email: str, name: str)`
46
+ - Return the entity (or None for get/delete), never return IDs or dicts
47
+ - Functions are async for PostgreSQL/MongoDB, sync for SQLite
48
+
49
+ ## Services (`app/services/`)
50
+
51
+ Business logic — class-based with DB session:
52
+
53
+ ```python
54
+ class UserService:
55
+ def __init__(self, db: AsyncSession):
56
+ self.db = db
57
+
58
+ async def get_by_id(self, user_id: UUID) -> User:
59
+ user = await user_repo.get_by_id(self.db, user_id)
60
+ if not user:
61
+ raise NotFoundError(message="User not found", details={"user_id": user_id})
62
+ return user
63
+
64
+ async def create(self, data: UserCreate) -> User:
65
+ existing = await user_repo.get_by_email(self.db, data.email)
66
+ if existing:
67
+ raise AlreadyExistsError(message="Email already registered", details={"email": data.email})
68
+ hashed_password = get_password_hash(data.password)
69
+ return await user_repo.create(self.db, email=data.email, hashed_password=hashed_password)
70
+ ```
71
+
72
+ Rules:
73
+ - Raise domain exceptions, NEVER return error codes or None for "not found"
74
+ - Services call repo functions, never build raw queries
75
+ - One service per domain entity
76
+
77
+ ## Dependency Injection (`app/api/deps.py`)
78
+
79
+ Use `Annotated` type aliases — never raw `Depends()` in route signatures:
80
+
81
+ ```python
82
+ DBSession = Annotated[AsyncSession, Depends(get_db_session)]
83
+ UserSvc = Annotated[UserService, Depends(get_user_service)]
84
+ CurrentUser = Annotated[User, Depends(get_current_user)]
85
+ CurrentAdmin = Annotated[User, Depends(RoleChecker(UserRole.ADMIN))]
86
+ ```
87
+
88
+ Service factories take `DBSession` and return service instances:
89
+
90
+ ```python
91
+ def get_user_service(db: DBSession) -> UserService:
92
+ return UserService(db)
93
+ ```
94
+
95
+ ## Routes (`app/api/routes/v1/`)
96
+
97
+ HTTP layer only — validate, delegate, return:
98
+
99
+ ```python
100
+ @router.get("/{user_id}", response_model=UserRead)
101
+ async def get_user(user_id: UUID, service: UserSvc, user: CurrentUser) -> Any:
102
+ return await service.get_by_id(user_id)
103
+ ```
104
+
105
+ Rules:
106
+ - Return type is always `-> Any` (response_model handles serialization)
107
+ - Use `status_code=status.HTTP_201_CREATED` for POST, `HTTP_204_NO_CONTENT` for DELETE
108
+ - DELETE endpoints: `response_model=None`
109
+ - Pagination: `skip: int = Query(0, ge=0)`, `limit: int = Query(50, ge=1, le=100)`
@@ -0,0 +1,67 @@
1
+ ---
2
+ description: Code style, formatting, naming, imports, and type hints
3
+ globs: ["backend/**/*.py", "*.py"]
4
+ ---
5
+
6
+ # Code Style
7
+
8
+ ## Formatting
9
+
10
+ - Use `ruff` for linting and formatting: `ruff check . --fix && ruff format .`
11
+ - Line length: 120 characters
12
+
13
+ ## Type Hints
14
+
15
+ - Type hints on ALL function signatures — parameters and return types
16
+ - Use modern syntax: `str | None` not `Optional[str]`, `list[User]` not `List[User]`
17
+ - Use `Annotated[Type, Depends(...)]` for DI (defined as aliases in `deps.py`)
18
+ - Use `dict[str, Any]` for generic dicts
19
+ - Use `Literal["value1", "value2"]` for string enums in schemas
20
+ - Use `TYPE_CHECKING` block for circular import resolution:
21
+ ```python
22
+ from typing import TYPE_CHECKING
23
+ if TYPE_CHECKING:
24
+ from app.db.models.session import Session
25
+ ```
26
+
27
+ ## Naming
28
+
29
+ | Element | Convention | Example |
30
+ |---------|-----------|---------|
31
+ | Files | snake_case | `user_repo.py`, `conversation_service.py` |
32
+ | Classes | PascalCase | `UserService`, `ConversationRead` |
33
+ | Functions/variables | snake_case | `get_by_id`, `user_service` |
34
+ | Constants | UPPER_CASE | `DEFAULT_SYSTEM_PROMPT` |
35
+ | Private | _leading_underscore | `_create_agent` |
36
+ | DB tables | snake_case plural | `users`, `conversations` |
37
+ | API URLs | kebab-case | `/api/v1/conversations` |
38
+
39
+ ## Imports — strictly ordered, separated by blank lines
40
+
41
+ ```python
42
+ # 1. Standard library
43
+ import logging
44
+ from collections.abc import AsyncGenerator
45
+ from datetime import UTC, datetime
46
+ from typing import Annotated, Any
47
+ from uuid import UUID
48
+
49
+ # 2. Third-party
50
+ from fastapi import APIRouter, Depends, Query, status
51
+ from pydantic import BaseModel, ConfigDict, Field
52
+ from sqlalchemy import select
53
+ from sqlalchemy.ext.asyncio import AsyncSession
54
+
55
+ # 3. Local application
56
+ from app.api.deps import CurrentUser, UserSvc
57
+ from app.core.exceptions import NotFoundError
58
+ from app.schemas.user import UserCreate, UserRead
59
+ ```
60
+
61
+ ## Other Conventions
62
+
63
+ - `datetime.now(UTC)` not `datetime.utcnow()`
64
+ - `secrets.compare_digest()` for constant-time comparisons
65
+ - `__repr__` on all DB models
66
+ - Async for PostgreSQL/MongoDB I/O, sync for SQLite
67
+ - Keyword-only args in repo functions after `db` parameter
@@ -0,0 +1,54 @@
1
+ ---
2
+ description: Exception handling patterns and security conventions
3
+ globs: ["backend/app/core/**/*.py", "backend/app/services/**/*.py"]
4
+ ---
5
+
6
+ # Exceptions & Security
7
+
8
+ ## Domain Exceptions (`app/core/exceptions.py`)
9
+
10
+ All extend `AppException`. Always pass `message` and `details`:
11
+
12
+ ```python
13
+ raise NotFoundError(message="User not found", details={"user_id": str(user_id)})
14
+ raise AlreadyExistsError(message="Email already registered", details={"email": email})
15
+ raise AuthenticationError(message="Invalid or expired token")
16
+ raise AuthorizationError(message="Role 'admin' required for this action")
17
+ ```
18
+
19
+ Exception handlers in `api/exception_handlers.py` automatically:
20
+ - Map to HTTP status codes
21
+ - Log with structured context (path, method, error code)
22
+ - Return consistent JSON error format
23
+ - Add `WWW-Authenticate: Bearer` header on 401
24
+
25
+ ## Security Patterns
26
+
27
+ JWT auth (`core/security.py`):
28
+ - `create_access_token(subject)` / `create_refresh_token(subject)` — encode with `jwt.encode()`
29
+ - `verify_token(token)` → `dict | None` — decode with `jwt.decode()`
30
+ - Token payload: `{"exp": ..., "sub": user_id, "type": "access"|"refresh"}`
31
+
32
+ Password hashing:
33
+ - `get_password_hash(password)` — bcrypt
34
+ - `verify_password(plain, hashed)` — bcrypt `checkpw`
35
+ - NEVER store plain passwords
36
+
37
+ API keys:
38
+ - `secrets.compare_digest()` for constant-time comparison
39
+ - `APIKeyHeader(name=settings.API_KEY_HEADER, auto_error=False)`
40
+
41
+ ## Role-Based Access Control
42
+
43
+ ```python
44
+ class RoleChecker:
45
+ def __init__(self, required_role: UserRole) -> None:
46
+ self.required_role = required_role
47
+
48
+ async def __call__(self, user: Annotated[User, Depends(get_current_user)]) -> User:
49
+ if not user.has_role(self.required_role):
50
+ raise AuthorizationError(message=f"Role '{self.required_role.value}' required")
51
+ return user
52
+
53
+ CurrentAdmin = Annotated[User, Depends(RoleChecker(UserRole.ADMIN))]
54
+ ```
@@ -0,0 +1,27 @@
1
+ ---
2
+ description: Frontend conventions for Next.js
3
+ globs: ["frontend/**/*.ts", "frontend/**/*.tsx", "frontend/**/*.css"]
4
+ ---
5
+
6
+ # Frontend Conventions
7
+
8
+ ## Stack
9
+
10
+ - Next.js 15 with App Router
11
+ - TypeScript strict mode
12
+ - Tailwind CSS for styling
13
+ - i18n support built-in
14
+
15
+ ## Structure
16
+
17
+ - Pages in `frontend/src/app/` following Next.js App Router conventions
18
+ - Reusable components in `frontend/src/components/`
19
+ - API client functions in `frontend/src/lib/`
20
+ - Types in `frontend/src/types/`
21
+
22
+ ## Conventions
23
+
24
+ - Use `"use client"` directive only when component needs client-side interactivity
25
+ - Prefer Server Components by default
26
+ - Use `fetch` with proper error handling for API calls
27
+ - Keep components small and focused — extract when a component exceeds ~100 lines
@@ -0,0 +1,90 @@
1
+ ---
2
+ description: Pydantic schema patterns and SQLAlchemy model conventions
3
+ globs: ["backend/app/schemas/**/*.py", "backend/app/db/models/**/*.py", "backend/app/db/base.py"]
4
+ ---
5
+
6
+ # Schemas & Models
7
+
8
+ ## Pydantic Schemas (`app/schemas/`)
9
+
10
+ Base schema with shared config:
11
+
12
+ ```python
13
+ class BaseSchema(BaseModel):
14
+ model_config = ConfigDict(
15
+ from_attributes=True,
16
+ populate_by_name=True,
17
+ str_strip_whitespace=True,
18
+ )
19
+ ```
20
+
21
+ Separate models per operation:
22
+
23
+ ```python
24
+ class UserCreate(BaseSchema):
25
+ email: EmailStr = Field(max_length=255)
26
+ password: str = Field(min_length=8, max_length=128)
27
+ full_name: str | None = Field(default=None, max_length=255)
28
+
29
+ class UserUpdate(BaseSchema):
30
+ email: EmailStr | None = Field(default=None, max_length=255)
31
+ password: str | None = Field(default=None, min_length=8, max_length=128)
32
+ full_name: str | None = Field(default=None, max_length=255)
33
+ is_active: bool | None = None
34
+
35
+ class UserRead(BaseSchema, TimestampSchema):
36
+ id: UUID
37
+ email: EmailStr
38
+ full_name: str | None = None
39
+ role: UserRole = UserRole.USER
40
+ avatar_url: str | None = None
41
+
42
+ class UserList(BaseSchema):
43
+ items: list[UserRead]
44
+ total: int
45
+ ```
46
+
47
+ Rules:
48
+ - `*Create` — required fields for creation, with `Field()` constraints
49
+ - `*Update` — all fields optional (`type | None = None`)
50
+ - `*Read` — includes `id` and timestamps, inherits `TimestampSchema`
51
+ - `*List` — `items` list + `total` count
52
+ - Use `@field_validator` for complex deserialization (e.g., JSON string → dict)
53
+
54
+ ## SQLAlchemy Models (`app/db/models/`)
55
+
56
+ ```python
57
+ class User(Base, TimestampMixin):
58
+ __tablename__ = "users"
59
+
60
+ id: Mapped[UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
61
+ email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False)
62
+ hashed_password: Mapped[str | None] = mapped_column(String(255), nullable=True)
63
+ is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
64
+
65
+ conversations: Mapped[list["Conversation"]] = relationship(
66
+ "Conversation", back_populates="user", cascade="all, delete-orphan"
67
+ )
68
+
69
+ def __repr__(self) -> str:
70
+ return f"<User(id={self.id}, email={self.email})>"
71
+ ```
72
+
73
+ Rules:
74
+ - Always inherit `Base` and `TimestampMixin` (provides `created_at`, `updated_at`)
75
+ - Use `Mapped[type]` with `mapped_column()` for all columns
76
+ - ForeignKey with `ondelete="CASCADE"` for parent references
77
+ - Always define `__repr__`
78
+ - Naming convention in `Base.metadata`: `{table}_{col}_key`, `{table}_{col}_fkey`, etc.
79
+
80
+ ## TimestampMixin
81
+
82
+ ```python
83
+ class TimestampMixin:
84
+ created_at: Mapped[datetime] = mapped_column(
85
+ DateTime(timezone=True), server_default=func.now(), nullable=False
86
+ )
87
+ updated_at: Mapped[datetime | None] = mapped_column(
88
+ DateTime(timezone=True), onupdate=func.now(), nullable=True
89
+ )
90
+ ```