@dewtech/dare-cli 3.3.0 → 3.4.0
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.
- package/LICENSE +21 -0
- package/README.md +764 -764
- package/dist/__tests__/confidence.test.js +13 -13
- package/dist/__tests__/dag-converter.test.js +56 -56
- package/dist/__tests__/mcp-server/server.test.js +3 -16
- package/dist/__tests__/mcp-server/server.test.js.map +1 -1
- package/dist/__tests__/project-generator.test.js +2 -2
- package/dist/__tests__/project-generator.test.js.map +1 -1
- package/dist/__tests__/refine.test.js +49 -49
- package/dist/__tests__/reverse-collection.test.js +6 -6
- package/dist/__tests__/review.test.js +38 -38
- package/dist/__tests__/security-hardening.test.d.ts +2 -0
- package/dist/__tests__/security-hardening.test.d.ts.map +1 -0
- package/dist/__tests__/security-hardening.test.js +101 -0
- package/dist/__tests__/security-hardening.test.js.map +1 -0
- package/dist/__tests__/validate.test.js +65 -65
- package/dist/bin/dare.js +0 -0
- package/dist/commands/__tests__/init-validation.test.d.ts +2 -0
- package/dist/commands/__tests__/init-validation.test.d.ts.map +1 -0
- package/dist/commands/__tests__/init-validation.test.js +81 -0
- package/dist/commands/__tests__/init-validation.test.js.map +1 -0
- package/dist/commands/__tests__/init.integration.spec.js +6 -4
- package/dist/commands/__tests__/init.integration.spec.js.map +1 -1
- package/dist/commands/__tests__/init.spec.d.ts +2 -0
- package/dist/commands/__tests__/init.spec.d.ts.map +1 -0
- package/dist/commands/__tests__/init.spec.js +88 -0
- package/dist/commands/__tests__/init.spec.js.map +1 -0
- package/dist/commands/blueprint.js +122 -122
- package/dist/commands/design.js +20 -20
- package/dist/commands/init-validation.d.ts +22 -0
- package/dist/commands/init-validation.d.ts.map +1 -0
- package/dist/commands/init-validation.js +54 -0
- package/dist/commands/init-validation.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +26 -10
- package/dist/commands/init.js.map +1 -1
- package/dist/graphrag/graph-rag.js +24 -24
- package/dist/mcp-server/__tests__/auth.test.d.ts +2 -0
- package/dist/mcp-server/__tests__/auth.test.d.ts.map +1 -0
- package/dist/mcp-server/__tests__/auth.test.js +72 -0
- package/dist/mcp-server/__tests__/auth.test.js.map +1 -0
- package/dist/mcp-server/__tests__/boot-config.test.d.ts +2 -0
- package/dist/mcp-server/__tests__/boot-config.test.d.ts.map +1 -0
- package/dist/mcp-server/__tests__/boot-config.test.js +29 -0
- package/dist/mcp-server/__tests__/boot-config.test.js.map +1 -0
- package/dist/mcp-server/__tests__/error-sanitize.test.d.ts +2 -0
- package/dist/mcp-server/__tests__/error-sanitize.test.d.ts.map +1 -0
- package/dist/mcp-server/__tests__/error-sanitize.test.js +66 -0
- package/dist/mcp-server/__tests__/error-sanitize.test.js.map +1 -0
- package/dist/mcp-server/__tests__/path-confinement.test.d.ts +2 -0
- package/dist/mcp-server/__tests__/path-confinement.test.d.ts.map +1 -0
- package/dist/mcp-server/__tests__/path-confinement.test.js +135 -0
- package/dist/mcp-server/__tests__/path-confinement.test.js.map +1 -0
- package/dist/mcp-server/bin/server.js +18 -6
- package/dist/mcp-server/bin/server.js.map +1 -1
- package/dist/mcp-server/boot-config.d.ts +6 -0
- package/dist/mcp-server/boot-config.d.ts.map +1 -0
- package/dist/mcp-server/boot-config.js +17 -0
- package/dist/mcp-server/boot-config.js.map +1 -0
- package/dist/mcp-server/middleware/auth.d.ts +10 -0
- package/dist/mcp-server/middleware/auth.d.ts.map +1 -0
- package/dist/mcp-server/middleware/auth.js +44 -0
- package/dist/mcp-server/middleware/auth.js.map +1 -0
- package/dist/mcp-server/middleware/cors.d.ts +6 -0
- package/dist/mcp-server/middleware/cors.d.ts.map +1 -0
- package/dist/mcp-server/middleware/cors.js +30 -0
- package/dist/mcp-server/middleware/cors.js.map +1 -0
- package/dist/mcp-server/middleware/error-handler.d.ts +11 -0
- package/dist/mcp-server/middleware/error-handler.d.ts.map +1 -0
- package/dist/mcp-server/middleware/error-handler.js +14 -0
- package/dist/mcp-server/middleware/error-handler.js.map +1 -0
- package/dist/mcp-server/server.d.ts +7 -2
- package/dist/mcp-server/server.d.ts.map +1 -1
- package/dist/mcp-server/server.js +185 -105
- package/dist/mcp-server/server.js.map +1 -1
- package/dist/skills/registry-mock.json +109 -109
- package/dist/skills/tests/manifest.spec.js +20 -20
- package/dist/stacks/__tests__/dna-emitter.spec.js +6 -6
- package/dist/stacks/dna-emitter.js +69 -69
- package/dist/stacks/ruby-rails-8/scaffold.js +15 -15
- package/dist/utils/project-generator.d.ts.map +1 -1
- package/dist/utils/project-generator.js +254 -252
- package/dist/utils/project-generator.js.map +1 -1
- package/dist/utils/stack-bootstrap.js +371 -371
- package/dist/utils/templates.js +394 -394
- package/dist/verification/__tests__/anti-tamper.test.js +13 -13
- package/package.json +96 -93
- package/templates/DARE-dag-example.yaml +280 -280
- package/templates/UPDATE-MANIFEST.json +68 -68
- package/templates/backend/node-nestjs/.env.example +9 -9
- package/templates/backend/node-nestjs/nest-cli.json +8 -8
- package/templates/backend/node-nestjs/package.json +50 -50
- package/templates/backend/node-nestjs/src/app.controller.ts +12 -12
- package/templates/backend/node-nestjs/src/app.module.ts +15 -15
- package/templates/backend/node-nestjs/src/app.service.ts +8 -8
- package/templates/backend/node-nestjs/src/main.ts +24 -24
- package/templates/backend/node-nestjs/tsconfig.json +21 -21
- package/templates/backend/php-laravel/.env.example +22 -22
- package/templates/backend/php-laravel/app/Http/Controllers/HealthController.php +15 -15
- package/templates/backend/php-laravel/composer.json +40 -40
- package/templates/backend/python-fastapi/.env.example +4 -4
- package/templates/backend/python-fastapi/app/api/router.py +8 -8
- package/templates/backend/python-fastapi/app/core/config.py +20 -20
- package/templates/backend/python-fastapi/main.py +35 -35
- package/templates/backend/python-fastapi/requirements.txt +13 -13
- package/templates/backend/rust-axum/.env.example +3 -3
- package/templates/backend/rust-axum/Cargo.toml +23 -23
- package/templates/backend/rust-axum/src/errors.rs +30 -30
- package/templates/backend/rust-axum/src/main.rs +32 -32
- package/templates/backend/rust-axum/src/routes.rs +6 -6
- package/templates/frontend/leptos-csr/.cargo/config.toml +2 -2
- package/templates/frontend/leptos-csr/Cargo.toml +16 -16
- package/templates/frontend/leptos-csr/Trunk.toml +10 -10
- package/templates/frontend/leptos-csr/index.html +11 -11
- package/templates/frontend/leptos-csr/src/lib.rs +20 -20
- package/templates/frontend/leptos-csr/style/main.scss +19 -19
- package/templates/frontend/leptos-fullstack/.cargo/config.toml +4 -4
- package/templates/frontend/leptos-fullstack/Cargo.toml +56 -56
- package/templates/frontend/leptos-fullstack/src/app.rs +49 -49
- package/templates/frontend/leptos-fullstack/src/lib.rs +9 -9
- package/templates/frontend/leptos-fullstack/src/main.rs +29 -29
- package/templates/frontend/leptos-fullstack/style/main.scss +19 -19
- package/templates/frontend/react/index.html +12 -12
- package/templates/frontend/react/package.json +35 -35
- package/templates/frontend/react/src/App.tsx +25 -25
- package/templates/frontend/react/src/main.tsx +9 -9
- package/templates/frontend/vue/package.json +32 -32
- package/templates/frontend/vue/src/App.vue +7 -7
- package/templates/frontend/vue/src/main.ts +10 -10
- package/templates/frontend/vue/src/router/index.ts +14 -14
- package/templates/frontend/vue/src/views/HomeView.vue +6 -6
- package/templates/hooks/pre-commit-dare-validate +24 -24
- package/templates/ide/antigravity/.agents/skills/dare-ax/SKILL.md +152 -152
- package/templates/ide/antigravity/.agents/skills/dare-bench/SKILL.md +21 -21
- package/templates/ide/antigravity/.agents/skills/dare-blueprint/SKILL.md +368 -368
- package/templates/ide/antigravity/.agents/skills/dare-bootstrap/SKILL.md +32 -32
- package/templates/ide/antigravity/.agents/skills/dare-bugfix-design/SKILL.md +76 -76
- package/templates/ide/antigravity/.agents/skills/dare-dag/SKILL.md +32 -32
- package/templates/ide/antigravity/.agents/skills/dare-dag-build/SKILL.md +154 -154
- package/templates/ide/antigravity/.agents/skills/dare-dag-run/SKILL.md +130 -130
- package/templates/ide/antigravity/.agents/skills/dare-dag-runner/SKILL.md +203 -203
- package/templates/ide/antigravity/.agents/skills/dare-design/SKILL.md +180 -180
- package/templates/ide/antigravity/.agents/skills/dare-discover/SKILL.md +33 -33
- package/templates/ide/antigravity/.agents/skills/dare-dna/SKILL.md +63 -63
- package/templates/ide/antigravity/.agents/skills/dare-docker/SKILL.md +315 -315
- package/templates/ide/antigravity/.agents/skills/dare-execute/SKILL.md +264 -264
- package/templates/ide/antigravity/.agents/skills/dare-feature-design/SKILL.md +74 -74
- package/templates/ide/antigravity/.agents/skills/dare-frontend-design/SKILL.md +192 -192
- package/templates/ide/antigravity/.agents/skills/dare-graph/SKILL.md +35 -35
- package/templates/ide/antigravity/.agents/skills/dare-info/SKILL.md +31 -31
- package/templates/ide/antigravity/.agents/skills/dare-init/SKILL.md +35 -35
- package/templates/ide/antigravity/.agents/skills/dare-laravel-api/SKILL.md +337 -337
- package/templates/ide/antigravity/.agents/skills/dare-layered-design/SKILL.md +166 -166
- package/templates/ide/antigravity/.agents/skills/dare-llm-integration/SKILL.md +217 -217
- package/templates/ide/antigravity/.agents/skills/dare-migrate/SKILL.md +61 -61
- package/templates/ide/antigravity/.agents/skills/dare-quality-telemetry/SKILL.md +187 -187
- package/templates/ide/antigravity/.agents/skills/dare-realtime/SKILL.md +217 -217
- package/templates/ide/antigravity/.agents/skills/dare-refine/SKILL.md +114 -114
- package/templates/ide/antigravity/.agents/skills/dare-reverse/SKILL.md +108 -108
- package/templates/ide/antigravity/.agents/skills/dare-review/SKILL.md +111 -111
- package/templates/ide/antigravity/.agents/skills/dare-rust-leptos/SKILL.md +263 -263
- package/templates/ide/antigravity/.agents/skills/dare-rust-workspace/SKILL.md +275 -275
- package/templates/ide/antigravity/.agents/skills/dare-security/SKILL.md +274 -274
- package/templates/ide/antigravity/.agents/skills/dare-skill/SKILL.md +35 -35
- package/templates/ide/antigravity/.agents/skills/dare-tasks/SKILL.md +265 -265
- package/templates/ide/antigravity/.agents/skills/dare-telemetry/SKILL.md +188 -188
- package/templates/ide/antigravity/.agents/skills/dare-update/SKILL.md +33 -33
- package/templates/ide/antigravity/.agents/skills/dare-validate/SKILL.md +33 -33
- package/templates/ide/antigravity/.agents/skills/dare-welcome/SKILL.md +30 -30
- package/templates/ide/antigravity/.agents/skills/skill-fastapi-api/SKILL.md +343 -343
- package/templates/ide/antigravity/.agents/skills/skill-go-gin-api/SKILL.md +377 -377
- package/templates/ide/antigravity/.agents/skills/skill-mcp-server/SKILL.md +382 -382
- package/templates/ide/antigravity/.agents/skills/skill-nestjs-api/SKILL.md +326 -326
- package/templates/ide/antigravity/.agents/skills/skill-rails-api/SKILL.md +393 -393
- package/templates/ide/antigravity/templates/BLUEPRINT-template.md +193 -193
- package/templates/ide/antigravity/templates/DESIGN-template.md +129 -129
- package/templates/ide/antigravity/templates/TASK-SPEC-template.md +141 -141
- package/templates/ide/antigravity/templates/TASKS-template.md +26 -26
- package/templates/ide/antigravity/templates/TELEMETRY-template.md +125 -125
- package/templates/ide/claude/.claude/commands/dare-ax.md +131 -131
- package/templates/ide/claude/.claude/commands/dare-bench.md +18 -18
- package/templates/ide/claude/.claude/commands/dare-blueprint.md +134 -134
- package/templates/ide/claude/.claude/commands/dare-bootstrap.md +27 -27
- package/templates/ide/claude/.claude/commands/dare-bugfix-design.md +119 -119
- package/templates/ide/claude/.claude/commands/dare-dag-build.md +151 -151
- package/templates/ide/claude/.claude/commands/dare-dag-run.md +109 -109
- package/templates/ide/claude/.claude/commands/dare-dag-runner.md +117 -117
- package/templates/ide/claude/.claude/commands/dare-dag-viz.md +197 -197
- package/templates/ide/claude/.claude/commands/dare-dag.md +27 -27
- package/templates/ide/claude/.claude/commands/dare-design.md +69 -69
- package/templates/ide/claude/.claude/commands/dare-discover.md +28 -28
- package/templates/ide/claude/.claude/commands/dare-dna.md +75 -75
- package/templates/ide/claude/.claude/commands/dare-docker.md +207 -207
- package/templates/ide/claude/.claude/commands/dare-execute.md +152 -152
- package/templates/ide/claude/.claude/commands/dare-feature-design.md +147 -147
- package/templates/ide/claude/.claude/commands/dare-frontend-design.md +149 -149
- package/templates/ide/claude/.claude/commands/dare-graph.md +30 -30
- package/templates/ide/claude/.claude/commands/dare-info.md +26 -26
- package/templates/ide/claude/.claude/commands/dare-init.md +30 -30
- package/templates/ide/claude/.claude/commands/dare-laravel-api.md +211 -211
- package/templates/ide/claude/.claude/commands/dare-layered-design.md +124 -124
- package/templates/ide/claude/.claude/commands/dare-llm-integration.md +148 -148
- package/templates/ide/claude/.claude/commands/dare-migrate.md +72 -72
- package/templates/ide/claude/.claude/commands/dare-quality-telemetry.md +166 -166
- package/templates/ide/claude/.claude/commands/dare-realtime.md +159 -159
- package/templates/ide/claude/.claude/commands/dare-refine.md +145 -145
- package/templates/ide/claude/.claude/commands/dare-reverse.md +139 -139
- package/templates/ide/claude/.claude/commands/dare-review.md +113 -113
- package/templates/ide/claude/.claude/commands/dare-rust-leptos.md +269 -269
- package/templates/ide/claude/.claude/commands/dare-rust-workspace.md +209 -209
- package/templates/ide/claude/.claude/commands/dare-security.md +232 -232
- package/templates/ide/claude/.claude/commands/dare-skill.md +30 -30
- package/templates/ide/claude/.claude/commands/dare-tasks.md +70 -70
- package/templates/ide/claude/.claude/commands/dare-telemetry.md +132 -132
- package/templates/ide/claude/.claude/commands/dare-update.md +28 -28
- package/templates/ide/claude/.claude/commands/dare-validate.md +28 -28
- package/templates/ide/claude/.claude/commands/dare-welcome.md +25 -25
- package/templates/ide/claude/.claude/commands/skill-fastapi-api.md +205 -205
- package/templates/ide/claude/.claude/commands/skill-go-gin-api.md +232 -232
- package/templates/ide/claude/.claude/commands/skill-mcp-server.md +228 -228
- package/templates/ide/claude/.claude/commands/skill-nestjs-api.md +210 -210
- package/templates/ide/claude/.claude/commands/skill-rails-api.md +236 -236
- package/templates/ide/claude/.claude/settings.example.json +35 -35
- package/templates/ide/claude/CLAUDE.md +146 -146
- package/templates/ide/claude/templates/BLUEPRINT-template.md +193 -193
- package/templates/ide/claude/templates/DESIGN-template.md +129 -129
- package/templates/ide/claude/templates/TASK-SPEC-template.md +141 -141
- package/templates/ide/claude/templates/TASKS-template.md +26 -26
- package/templates/ide/claude/templates/TELEMETRY-template.md +125 -125
- package/templates/ide/cursor/.cursor/commands/dare-bench.md +18 -18
- package/templates/ide/cursor/.cursor/commands/dare-blueprint.md +86 -86
- package/templates/ide/cursor/.cursor/commands/dare-bootstrap.md +27 -27
- package/templates/ide/cursor/.cursor/commands/dare-bugfix-design.md +64 -64
- package/templates/ide/cursor/.cursor/commands/dare-dag-run.md +110 -110
- package/templates/ide/cursor/.cursor/commands/dare-dag-viz.md +139 -139
- package/templates/ide/cursor/.cursor/commands/dare-dag.md +27 -27
- package/templates/ide/cursor/.cursor/commands/dare-design.md +35 -35
- package/templates/ide/cursor/.cursor/commands/dare-discover.md +28 -28
- package/templates/ide/cursor/.cursor/commands/dare-dna.md +75 -75
- package/templates/ide/cursor/.cursor/commands/dare-docker-compose.md +18 -18
- package/templates/ide/cursor/.cursor/commands/dare-dockerfile.md +17 -17
- package/templates/ide/cursor/.cursor/commands/dare-execute.md +19 -19
- package/templates/ide/cursor/.cursor/commands/dare-feature-design.md +64 -64
- package/templates/ide/cursor/.cursor/commands/dare-graph.md +30 -30
- package/templates/ide/cursor/.cursor/commands/dare-info.md +26 -26
- package/templates/ide/cursor/.cursor/commands/dare-init.md +30 -30
- package/templates/ide/cursor/.cursor/commands/dare-migrate.md +72 -72
- package/templates/ide/cursor/.cursor/commands/dare-refine.md +107 -107
- package/templates/ide/cursor/.cursor/commands/dare-reverse.md +139 -139
- package/templates/ide/cursor/.cursor/commands/dare-review.md +91 -91
- package/templates/ide/cursor/.cursor/commands/dare-skill.md +30 -30
- package/templates/ide/cursor/.cursor/commands/dare-tasks.md +184 -184
- package/templates/ide/cursor/.cursor/commands/dare-telemetry.md +42 -42
- package/templates/ide/cursor/.cursor/commands/dare-update.md +28 -28
- package/templates/ide/cursor/.cursor/commands/dare-validate.md +28 -28
- package/templates/ide/cursor/.cursor/commands/dare-welcome.md +25 -25
- package/templates/ide/cursor/.cursor/rules/skill-ax.mdc +263 -263
- package/templates/ide/cursor/.cursor/rules/skill-bugfix-design.mdc +51 -51
- package/templates/ide/cursor/.cursor/rules/skill-dag-build.mdc +173 -173
- package/templates/ide/cursor/.cursor/rules/skill-dag-run.mdc +134 -134
- package/templates/ide/cursor/.cursor/rules/skill-dag-runner.mdc +221 -221
- package/templates/ide/cursor/.cursor/rules/skill-dna.mdc +63 -63
- package/templates/ide/cursor/.cursor/rules/skill-docker.mdc +33 -33
- package/templates/ide/cursor/.cursor/rules/skill-fastapi-api.mdc +352 -352
- package/templates/ide/cursor/.cursor/rules/skill-feature-design.mdc +43 -43
- package/templates/ide/cursor/.cursor/rules/skill-frontend-design.mdc +244 -244
- package/templates/ide/cursor/.cursor/rules/skill-go-gin-api.mdc +371 -371
- package/templates/ide/cursor/.cursor/rules/skill-laravel-api.mdc +44 -44
- package/templates/ide/cursor/.cursor/rules/skill-layered-design.mdc +266 -266
- package/templates/ide/cursor/.cursor/rules/skill-llm-integration.mdc +295 -295
- package/templates/ide/cursor/.cursor/rules/skill-mcp-server.mdc +367 -367
- package/templates/ide/cursor/.cursor/rules/skill-migrate.mdc +58 -58
- package/templates/ide/cursor/.cursor/rules/skill-nestjs-api.mdc +346 -346
- package/templates/ide/cursor/.cursor/rules/skill-quality-telemetry.mdc +248 -248
- package/templates/ide/cursor/.cursor/rules/skill-rails-api.mdc +400 -400
- package/templates/ide/cursor/.cursor/rules/skill-realtime.mdc +262 -262
- package/templates/ide/cursor/.cursor/rules/skill-reverse.mdc +107 -107
- package/templates/ide/cursor/.cursor/rules/skill-rust-leptos.mdc +281 -281
- package/templates/ide/cursor/.cursor/rules/skill-rust-workspace.mdc +312 -312
- package/templates/ide/cursor/.cursor/rules/skill-security.mdc +245 -245
- package/templates/ide/cursor/.cursor/rules/skill-telemetry.mdc +156 -156
- package/templates/ide/cursor/templates/BLUEPRINT-template.md +193 -193
- package/templates/ide/cursor/templates/DESIGN-template.md +129 -129
- package/templates/ide/cursor/templates/TASK-SPEC-template.md +141 -141
- package/templates/ide/cursor/templates/TASKS-template.md +26 -26
- package/templates/ide/cursor/templates/TELEMETRY-template.md +125 -125
- package/templates/shared/docker-compose.yml +41 -41
- package/templates/stacks/go-gin/.dare/skills.yml +11 -11
- package/templates/stacks/go-gin/.env.example +24 -24
- package/templates/stacks/go-gin/.github/workflows/dare-ci.yml +42 -42
- package/templates/stacks/go-gin/README.md.tpl +38 -38
- package/templates/stacks/go-gin/cmd/server/main.go.tpl +78 -78
- package/templates/stacks/go-gin/db/migrations/0001_create_users.down.sql +2 -2
- package/templates/stacks/go-gin/db/migrations/0001_create_users.up.sql +12 -12
- package/templates/stacks/go-gin/db/queries/users.sql +23 -23
- package/templates/stacks/go-gin/gitignore +7 -7
- package/templates/stacks/go-gin/go.mod.tpl +17 -17
- package/templates/stacks/go-gin/internal/config/config.go +41 -41
- package/templates/stacks/go-gin/internal/db/postgres.go.tpl +25 -25
- package/templates/stacks/go-gin/internal/handler/auth_handler.go.tpl +72 -72
- package/templates/stacks/go-gin/internal/handler/users_handler.go.tpl +72 -72
- package/templates/stacks/go-gin/internal/handler/ws_handler.go +37 -37
- package/templates/stacks/go-gin/internal/llm/dummy.go +14 -14
- package/templates/stacks/go-gin/internal/llm/provider.go +8 -8
- package/templates/stacks/go-gin/internal/middleware/jwt.go.tpl +58 -58
- package/templates/stacks/go-gin/internal/middleware/rate_limit.go +55 -55
- package/templates/stacks/go-gin/internal/model/user.go +17 -17
- package/templates/stacks/go-gin/internal/repository/users_repository.go.tpl +79 -79
- package/templates/stacks/go-gin/internal/service/auth_service.go.tpl +55 -55
- package/templates/stacks/go-gin/internal/service/users_service.go.tpl +53 -53
- package/templates/stacks/go-gin/llms.txt.tpl +54 -54
- package/templates/stacks/go-gin/openapi.json.tpl +46 -46
- package/templates/stacks/go-gin/sqlc.yaml +14 -14
- package/templates/stacks/go-gin/tests/smoke_test.go.tpl +22 -22
- package/templates/stacks/go-stdlib/.dare/skills.yml +11 -11
- package/templates/stacks/go-stdlib/.env.example +24 -24
- package/templates/stacks/go-stdlib/.github/workflows/dare-ci.yml +42 -42
- package/templates/stacks/go-stdlib/README.md.tpl +41 -41
- package/templates/stacks/go-stdlib/cmd/server/main.go.tpl +82 -82
- package/templates/stacks/go-stdlib/db/migrations/0001_create_users.down.sql +2 -2
- package/templates/stacks/go-stdlib/db/migrations/0001_create_users.up.sql +12 -12
- package/templates/stacks/go-stdlib/db/queries/users.sql +23 -23
- package/templates/stacks/go-stdlib/gitignore +6 -6
- package/templates/stacks/go-stdlib/go.mod.tpl +15 -15
- package/templates/stacks/go-stdlib/internal/config/config.go +41 -41
- package/templates/stacks/go-stdlib/internal/db/postgres.go.tpl +24 -24
- package/templates/stacks/go-stdlib/internal/handler/auth_handler.go.tpl +71 -71
- package/templates/stacks/go-stdlib/internal/handler/users_handler.go.tpl +84 -84
- package/templates/stacks/go-stdlib/internal/handler/ws_handler.go +36 -36
- package/templates/stacks/go-stdlib/internal/httpx/json.go +32 -32
- package/templates/stacks/go-stdlib/internal/llm/dummy.go +14 -14
- package/templates/stacks/go-stdlib/internal/llm/provider.go +8 -8
- package/templates/stacks/go-stdlib/internal/middleware/chain.go +21 -21
- package/templates/stacks/go-stdlib/internal/middleware/cors.go +27 -27
- package/templates/stacks/go-stdlib/internal/middleware/jwt.go.tpl +51 -51
- package/templates/stacks/go-stdlib/internal/middleware/rate_limit.go +81 -81
- package/templates/stacks/go-stdlib/internal/model/user.go +17 -17
- package/templates/stacks/go-stdlib/internal/repository/users_repository.go.tpl +75 -75
- package/templates/stacks/go-stdlib/internal/service/auth_service.go.tpl +55 -55
- package/templates/stacks/go-stdlib/internal/service/users_service.go.tpl +53 -53
- package/templates/stacks/go-stdlib/llms.txt.tpl +60 -60
- package/templates/stacks/go-stdlib/openapi.json.tpl +46 -46
- package/templates/stacks/go-stdlib/sqlc.yaml +14 -14
- package/templates/stacks/go-stdlib/tests/smoke_test.go.tpl +45 -45
- package/templates/stacks/mcp-go/.dare/skills.yml +8 -8
- package/templates/stacks/mcp-go/.env.example +14 -14
- package/templates/stacks/mcp-go/.github/workflows/dare-ci.yml +42 -42
- package/templates/stacks/mcp-go/README.md.tpl +50 -50
- package/templates/stacks/mcp-go/cmd/server/main.go.tpl +62 -62
- package/templates/stacks/mcp-go/gitignore +6 -6
- package/templates/stacks/mcp-go/go.mod.tpl +9 -9
- package/templates/stacks/mcp-go/internal/prompts/summarize.go +9 -9
- package/templates/stacks/mcp-go/internal/server/server.go.tpl +80 -80
- package/templates/stacks/mcp-go/internal/tools/echo.go +15 -15
- package/templates/stacks/mcp-go/internal/transports/http.go.tpl +21 -21
- package/templates/stacks/mcp-go/internal/transports/sse.go.tpl +17 -17
- package/templates/stacks/mcp-go/internal/transports/stdio.go.tpl +14 -14
- package/templates/stacks/mcp-go/llms.txt.tpl +60 -60
- package/templates/stacks/mcp-go/openapi.json.tpl +31 -31
- package/templates/stacks/mcp-go/tests/echo_test.go.tpl +37 -37
- package/templates/stacks/mcp-node-ts/.dare/skills.yml +8 -8
- package/templates/stacks/mcp-node-ts/.env.example +16 -16
- package/templates/stacks/mcp-node-ts/.github/workflows/dare-ci.yml +54 -54
- package/templates/stacks/mcp-node-ts/README.md.hbs +49 -49
- package/templates/stacks/mcp-node-ts/gitignore +7 -7
- package/templates/stacks/mcp-node-ts/llms.txt.hbs +61 -61
- package/templates/stacks/mcp-node-ts/openapi.json.hbs +39 -39
- package/templates/stacks/mcp-node-ts/package.json.hbs +35 -35
- package/templates/stacks/mcp-node-ts/src/cli.ts.hbs +71 -71
- package/templates/stacks/mcp-node-ts/src/prompts/index.ts +36 -36
- package/templates/stacks/mcp-node-ts/src/server.ts.hbs +45 -45
- package/templates/stacks/mcp-node-ts/src/tools/echo.ts +23 -23
- package/templates/stacks/mcp-node-ts/src/tools/index.ts +18 -18
- package/templates/stacks/mcp-node-ts/src/transports/http.ts +68 -68
- package/templates/stacks/mcp-node-ts/src/transports/sse.ts +58 -58
- package/templates/stacks/mcp-node-ts/src/transports/stdio.ts +5 -5
- package/templates/stacks/mcp-node-ts/tests/echo.test.ts +50 -50
- package/templates/stacks/mcp-node-ts/tsconfig.json +17 -17
- package/templates/stacks/mcp-python/.dare/skills.yml +8 -8
- package/templates/stacks/mcp-python/.env.example +14 -14
- package/templates/stacks/mcp-python/.github/workflows/dare-ci.yml +42 -42
- package/templates/stacks/mcp-python/README.md.j2 +49 -49
- package/templates/stacks/mcp-python/gitignore +12 -12
- package/templates/stacks/mcp-python/llms.txt.j2 +56 -56
- package/templates/stacks/mcp-python/openapi.json.j2 +33 -33
- package/templates/stacks/mcp-python/pyproject.toml.j2 +37 -37
- package/templates/stacks/mcp-python/src/cli.py.j2 +68 -68
- package/templates/stacks/mcp-python/src/prompts/summarize.py +10 -10
- package/templates/stacks/mcp-python/src/server.py.j2 +28 -28
- package/templates/stacks/mcp-python/src/tools/echo.py +12 -12
- package/templates/stacks/mcp-python/src/transports/http.py +12 -12
- package/templates/stacks/mcp-python/src/transports/sse.py +13 -13
- package/templates/stacks/mcp-python/src/transports/stdio.py +6 -6
- package/templates/stacks/mcp-python/tests/test_echo.py +28 -28
- package/templates/stacks/mcp-rust/.dare/skills.yml +8 -8
- package/templates/stacks/mcp-rust/.env.example +14 -14
- package/templates/stacks/mcp-rust/.github/workflows/dare-ci.yml +38 -38
- package/templates/stacks/mcp-rust/Cargo.toml.tera +35 -35
- package/templates/stacks/mcp-rust/README.md.tera +50 -50
- package/templates/stacks/mcp-rust/gitignore +5 -5
- package/templates/stacks/mcp-rust/llms.txt.tera +60 -60
- package/templates/stacks/mcp-rust/openapi.json.tera +31 -31
- package/templates/stacks/mcp-rust/src/cli.rs.tera +33 -33
- package/templates/stacks/mcp-rust/src/lib.rs +6 -6
- package/templates/stacks/mcp-rust/src/main.rs.tera +30 -30
- package/templates/stacks/mcp-rust/src/prompts/mod.rs +1 -1
- package/templates/stacks/mcp-rust/src/prompts/summarize.rs +5 -5
- package/templates/stacks/mcp-rust/src/server.rs.tera +38 -38
- package/templates/stacks/mcp-rust/src/tools/echo.rs +18 -18
- package/templates/stacks/mcp-rust/src/tools/mod.rs +22 -22
- package/templates/stacks/mcp-rust/src/transports/http.rs +27 -27
- package/templates/stacks/mcp-rust/src/transports/mod.rs +3 -3
- package/templates/stacks/mcp-rust/src/transports/sse.rs +33 -33
- package/templates/stacks/mcp-rust/src/transports/stdio.rs +14 -14
- package/templates/stacks/mcp-rust/tests/echo_test.rs.tera +27 -27
- package/templates/stacks/node-nestjs/.dare/skills.yml +11 -11
- package/templates/stacks/node-nestjs/.env.example +21 -21
- package/templates/stacks/node-nestjs/.github/workflows/dare-ci.yml +54 -54
- package/templates/stacks/node-nestjs/README.md.hbs +35 -35
- package/templates/stacks/node-nestjs/gitignore +7 -7
- package/templates/stacks/node-nestjs/llms.txt.hbs +47 -47
- package/templates/stacks/node-nestjs/nest-cli.json +16 -16
- package/templates/stacks/node-nestjs/openapi.json.hbs +75 -75
- package/templates/stacks/node-nestjs/package.json.hbs +57 -57
- package/templates/stacks/node-nestjs/prisma/schema.prisma +25 -25
- package/templates/stacks/node-nestjs/prisma/seed.ts.hbs +25 -25
- package/templates/stacks/node-nestjs/src/app.module.ts +39 -39
- package/templates/stacks/node-nestjs/src/auth/auth.controller.ts +29 -29
- package/templates/stacks/node-nestjs/src/auth/auth.module.ts +25 -25
- package/templates/stacks/node-nestjs/src/auth/auth.service.ts +36 -36
- package/templates/stacks/node-nestjs/src/auth/dto/login-response.dto.ts +9 -9
- package/templates/stacks/node-nestjs/src/auth/dto/login.dto.ts +17 -17
- package/templates/stacks/node-nestjs/src/auth/jwt.strategy.ts +25 -25
- package/templates/stacks/node-nestjs/src/common/filters/problem-details.filter.ts +38 -38
- package/templates/stacks/node-nestjs/src/common/interceptors/json-response.interceptor.ts +13 -13
- package/templates/stacks/node-nestjs/src/main.ts.hbs +44 -44
- package/templates/stacks/node-nestjs/src/prisma/prisma.module.ts +9 -9
- package/templates/stacks/node-nestjs/src/prisma/prisma.service.ts +9 -9
- package/templates/stacks/node-nestjs/src/users/dto/create-user.dto.ts +22 -22
- package/templates/stacks/node-nestjs/src/users/dto/user.dto.ts +15 -15
- package/templates/stacks/node-nestjs/src/users/users.controller.ts +41 -41
- package/templates/stacks/node-nestjs/src/users/users.module.ts +11 -11
- package/templates/stacks/node-nestjs/src/users/users.repository.ts +38 -38
- package/templates/stacks/node-nestjs/src/users/users.service.ts +38 -38
- package/templates/stacks/node-nestjs/tsconfig.build.json +4 -4
- package/templates/stacks/node-nestjs/tsconfig.json +28 -28
- package/templates/stacks/php-laravel/.dare/skills.yml +11 -11
- package/templates/stacks/php-laravel/.env.example +41 -41
- package/templates/stacks/php-laravel/.github/workflows/dare-ci.yml +43 -43
- package/templates/stacks/php-laravel/README.md.hbs +36 -36
- package/templates/stacks/php-laravel/app/Http/Controllers/Api/AuthController.php +36 -36
- package/templates/stacks/php-laravel/app/Http/Controllers/Api/UsersController.php +33 -33
- package/templates/stacks/php-laravel/app/Http/Requests/CreateUserRequest.php +26 -26
- package/templates/stacks/php-laravel/app/Http/Requests/LoginRequest.php +34 -34
- package/templates/stacks/php-laravel/app/Llm/Contracts/LlmProvider.php +12 -12
- package/templates/stacks/php-laravel/app/Llm/Providers/DummyProvider.php +13 -13
- package/templates/stacks/php-laravel/app/Llm/Providers/OpenAiProvider.php +33 -33
- package/templates/stacks/php-laravel/app/Models/User.php +44 -44
- package/templates/stacks/php-laravel/app/Repositories/UsersRepository.php +32 -32
- package/templates/stacks/php-laravel/app/Services/AuthService.php +37 -37
- package/templates/stacks/php-laravel/app/Services/UsersService.php +57 -57
- package/templates/stacks/php-laravel/artisan +12 -12
- package/templates/stacks/php-laravel/bootstrap/app.php +29 -29
- package/templates/stacks/php-laravel/bootstrap/providers.php +5 -5
- package/templates/stacks/php-laravel/composer.json.hbs +58 -58
- package/templates/stacks/php-laravel/config/l5-swagger.php +41 -41
- package/templates/stacks/php-laravel/config/reverb.php +34 -34
- package/templates/stacks/php-laravel/config/sanctum.php +15 -15
- package/templates/stacks/php-laravel/database/migrations/2026_06_01_000001_create_users_table.php +27 -27
- package/templates/stacks/php-laravel/database/seeders/DatabaseSeeder.php +21 -21
- package/templates/stacks/php-laravel/gitignore +23 -23
- package/templates/stacks/php-laravel/llms.txt.hbs +53 -53
- package/templates/stacks/php-laravel/openapi.json.hbs +43 -43
- package/templates/stacks/php-laravel/phpstan.neon +9 -9
- package/templates/stacks/php-laravel/routes/api.php +13 -13
- package/templates/stacks/php-laravel/routes/channels.php +7 -7
- package/templates/stacks/php-laravel/tests/Feature/AuthTest.php +35 -35
- package/templates/stacks/php-laravel/tests/Feature/UsersTest.php +30 -30
- package/templates/stacks/php-laravel/tests/Pest.php +5 -5
- package/templates/stacks/python-fastapi/.dare/skills.yml +11 -11
- package/templates/stacks/python-fastapi/.env.example +21 -21
- package/templates/stacks/python-fastapi/.github/workflows/dare-ci.yml +43 -43
- package/templates/stacks/python-fastapi/README.md.j2 +35 -35
- package/templates/stacks/python-fastapi/alembic/env.py +46 -46
- package/templates/stacks/python-fastapi/alembic/script.py.mako +26 -26
- package/templates/stacks/python-fastapi/alembic/versions/0001_create_users.py.j2 +37 -37
- package/templates/stacks/python-fastapi/alembic.ini.j2 +39 -39
- package/templates/stacks/python-fastapi/app/core/config.py +24 -24
- package/templates/stacks/python-fastapi/app/core/security.py +34 -34
- package/templates/stacks/python-fastapi/app/db/session.py +22 -22
- package/templates/stacks/python-fastapi/app/main.py.j2 +36 -36
- package/templates/stacks/python-fastapi/app/models/__init__.py +3 -3
- package/templates/stacks/python-fastapi/app/models/user.py +30 -30
- package/templates/stacks/python-fastapi/app/repositories/user_repository.py +34 -34
- package/templates/stacks/python-fastapi/app/routers/auth.py +37 -37
- package/templates/stacks/python-fastapi/app/routers/users.py +46 -46
- package/templates/stacks/python-fastapi/app/schemas/user.py +56 -56
- package/templates/stacks/python-fastapi/app/services/auth_service.py +22 -22
- package/templates/stacks/python-fastapi/app/services/user_service.py +31 -31
- package/templates/stacks/python-fastapi/gitignore +12 -12
- package/templates/stacks/python-fastapi/llms.txt.j2 +53 -53
- package/templates/stacks/python-fastapi/openapi.json.j2 +43 -43
- package/templates/stacks/python-fastapi/pyproject.toml.j2 +45 -45
- package/templates/stacks/python-fastapi/tests/test_auth.py +22 -22
- package/templates/stacks/ruby-rails-8/.dare/skills.yml +50 -50
- package/templates/stacks/ruby-rails-8/.env.example +20 -20
- package/templates/stacks/ruby-rails-8/.github/workflows/dare-ci.yml +112 -112
- package/templates/stacks/ruby-rails-8/Gemfile.erb +61 -61
- package/templates/stacks/ruby-rails-8/app/channels/application_cable/channel.rb +11 -11
- package/templates/stacks/ruby-rails-8/app/channels/application_cable/connection.rb +34 -34
- package/templates/stacks/ruby-rails-8/app/channels/dare_updates_channel.rb +18 -18
- package/templates/stacks/ruby-rails-8/app/channels/user_updates_channel.rb +23 -23
- package/templates/stacks/ruby-rails-8/app/controllers/application_controller.rb +44 -44
- package/templates/stacks/ruby-rails-8/app/controllers/concerns/problem_details.rb +93 -93
- package/templates/stacks/ruby-rails-8/app/handlers/summarize_handler.rb +33 -33
- package/templates/stacks/ruby-rails-8/app/handlers/users_handler.rb +68 -68
- package/templates/stacks/ruby-rails-8/app/llm/cache/llm_cache.rb +44 -44
- package/templates/stacks/ruby-rails-8/app/llm/prompts/prompt_loader.rb +54 -54
- package/templates/stacks/ruby-rails-8/app/llm/prompts/summarize_v1.jinja2 +12 -12
- package/templates/stacks/ruby-rails-8/app/llm/providers/dummy_provider.rb +35 -35
- package/templates/stacks/ruby-rails-8/app/llm/providers/llm_provider.rb +67 -67
- package/templates/stacks/ruby-rails-8/app/llm/providers/openai_provider.rb +62 -62
- package/templates/stacks/ruby-rails-8/app/llm/rate_limit/token_bucket.rb +82 -82
- package/templates/stacks/ruby-rails-8/app/llm/validators/summarize_output_schema.json +21 -21
- package/templates/stacks/ruby-rails-8/app/llm/validators/validator.rb +52 -52
- package/templates/stacks/ruby-rails-8/app/models/user.rb +36 -36
- package/templates/stacks/ruby-rails-8/app/presenters/user_presenter.rb +48 -48
- package/templates/stacks/ruby-rails-8/app/repositories/document_repository.rb +57 -57
- package/templates/stacks/ruby-rails-8/app/repositories/user_repository.rb +73 -73
- package/templates/stacks/ruby-rails-8/app/services/create_user_service.rb +67 -67
- package/templates/stacks/ruby-rails-8/app/services/realtime_service.rb +53 -53
- package/templates/stacks/ruby-rails-8/app/services/summarize_document_service.rb +57 -57
- package/templates/stacks/ruby-rails-8/config/dare.yml +42 -42
- package/templates/stacks/ruby-rails-8/config/initializers/dare.rb +31 -31
- package/templates/stacks/ruby-rails-8/config/initializers/rack_attack.rb +64 -64
- package/templates/stacks/ruby-rails-8/config/initializers/rswag_api.rb +12 -12
- package/templates/stacks/ruby-rails-8/lib/tasks/dare.rake +159 -159
- package/templates/stacks/ruby-rails-8/llms.txt.erb +69 -69
- package/templates/stacks/ruby-rails-8/spec/api/summarize_spec.rb +56 -56
- package/templates/stacks/ruby-rails-8/spec/api/users_spec.rb +72 -72
- package/templates/stacks/ruby-rails-8/spec/channels/dare_updates_channel_spec.rb +61 -61
- package/templates/stacks/ruby-rails-8/spec/channels/user_updates_channel_spec.rb +56 -56
- package/templates/stacks/ruby-rails-8/spec/factories/users.rb +27 -27
- package/templates/stacks/ruby-rails-8/spec/handlers/users_handler_spec.rb +88 -88
- package/templates/stacks/ruby-rails-8/spec/rails_helper.rb +31 -31
- package/templates/stacks/ruby-rails-8/spec/services/create_user_service_spec.rb +88 -88
- package/templates/stacks/ruby-rails-8/spec/services/summarize_document_service_spec.rb +142 -142
- package/templates/stacks/ruby-rails-8/spec/swagger_helper.rb +73 -73
- package/templates/stacks/rust-axum/.dare/skills.yml +11 -11
- package/templates/stacks/rust-axum/.env.example +26 -26
- package/templates/stacks/rust-axum/.github/workflows/dare-ci.yml +40 -40
- package/templates/stacks/rust-axum/Cargo.toml.tera +53 -53
- package/templates/stacks/rust-axum/README.md.tera +37 -37
- package/templates/stacks/rust-axum/gitignore +5 -5
- package/templates/stacks/rust-axum/llms.txt.tera +54 -54
- package/templates/stacks/rust-axum/migrations/0001_create_users.sql +13 -13
- package/templates/stacks/rust-axum/openapi.json.tera +46 -46
- package/templates/stacks/rust-axum/src/config.rs +45 -45
- package/templates/stacks/rust-axum/src/errors.rs +48 -48
- package/templates/stacks/rust-axum/src/handlers/auth.rs +48 -48
- package/templates/stacks/rust-axum/src/handlers/mod.rs +3 -3
- package/templates/stacks/rust-axum/src/handlers/users.rs +81 -81
- package/templates/stacks/rust-axum/src/handlers/ws.rs +24 -24
- package/templates/stacks/rust-axum/src/lib.rs +19 -19
- package/templates/stacks/rust-axum/src/llm/mod.rs +1 -1
- package/templates/stacks/rust-axum/src/llm/provider.rs +48 -48
- package/templates/stacks/rust-axum/src/main.rs.tera +64 -64
- package/templates/stacks/rust-axum/src/middleware/auth.rs +20 -20
- package/templates/stacks/rust-axum/src/middleware/mod.rs +2 -2
- package/templates/stacks/rust-axum/src/middleware/rate_limit.rs +27 -27
- package/templates/stacks/rust-axum/src/models/mod.rs +1 -1
- package/templates/stacks/rust-axum/src/models/user.rs +13 -13
- package/templates/stacks/rust-axum/src/repositories/mod.rs +1 -1
- package/templates/stacks/rust-axum/src/repositories/user_repository.rs +62 -62
- package/templates/stacks/rust-axum/src/services/auth_service.rs +50 -50
- package/templates/stacks/rust-axum/src/services/mod.rs +2 -2
- package/templates/stacks/rust-axum/src/services/user_service.rs +53 -53
- package/templates/stacks/rust-axum/tests/integration_test.rs.tera +13 -13
- package/dist/commands/new.d.ts +0 -16
- package/dist/commands/new.d.ts.map +0 -1
- package/dist/commands/new.js +0 -104
- package/dist/commands/new.js.map +0 -1
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
"""SQLAlchemy engine + session + declarative Base."""
|
|
2
|
-
from collections.abc import Generator
|
|
3
|
-
|
|
4
|
-
from sqlalchemy import create_engine
|
|
5
|
-
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
|
6
|
-
|
|
7
|
-
from app.core.config import settings
|
|
8
|
-
|
|
9
|
-
engine = create_engine(settings.database_url, pool_pre_ping=True)
|
|
10
|
-
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class Base(DeclarativeBase):
|
|
14
|
-
"""Declarative base for all ORM models."""
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def get_db() -> Generator[Session, None, None]:
|
|
18
|
-
db = SessionLocal()
|
|
19
|
-
try:
|
|
20
|
-
yield db
|
|
21
|
-
finally:
|
|
22
|
-
db.close()
|
|
1
|
+
"""SQLAlchemy engine + session + declarative Base."""
|
|
2
|
+
from collections.abc import Generator
|
|
3
|
+
|
|
4
|
+
from sqlalchemy import create_engine
|
|
5
|
+
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
|
6
|
+
|
|
7
|
+
from app.core.config import settings
|
|
8
|
+
|
|
9
|
+
engine = create_engine(settings.database_url, pool_pre_ping=True)
|
|
10
|
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Base(DeclarativeBase):
|
|
14
|
+
"""Declarative base for all ORM models."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_db() -> Generator[Session, None, None]:
|
|
18
|
+
db = SessionLocal()
|
|
19
|
+
try:
|
|
20
|
+
yield db
|
|
21
|
+
finally:
|
|
22
|
+
db.close()
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
"""FastAPI app entrypoint."""
|
|
2
|
-
from fastapi import FastAPI
|
|
3
|
-
from fastapi.middleware.cors import CORSMiddleware
|
|
4
|
-
from slowapi import Limiter, _rate_limit_exceeded_handler
|
|
5
|
-
from slowapi.errors import RateLimitExceeded
|
|
6
|
-
from slowapi.util import get_remote_address
|
|
7
|
-
|
|
8
|
-
from app.core.config import settings
|
|
9
|
-
from app.routers import auth, users
|
|
10
|
-
|
|
11
|
-
limiter = Limiter(key_func=get_remote_address, default_limits=[f"{settings.rate_limit_per_min}/minute"])
|
|
12
|
-
|
|
13
|
-
app = FastAPI(
|
|
14
|
-
title="{{ projectName }}",
|
|
15
|
-
version="0.1.0",
|
|
16
|
-
description="DARE-shaped FastAPI API",
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
app.state.limiter = limiter
|
|
20
|
-
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
|
21
|
-
|
|
22
|
-
app.add_middleware(
|
|
23
|
-
CORSMiddleware,
|
|
24
|
-
allow_origins=settings.cors_origins,
|
|
25
|
-
allow_credentials=True,
|
|
26
|
-
allow_methods=["*"],
|
|
27
|
-
allow_headers=["*"],
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
app.include_router(auth.router, prefix="/auth", tags=["auth"])
|
|
31
|
-
app.include_router(users.router, prefix="/users", tags=["users"])
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@app.get("/healthz", include_in_schema=False)
|
|
35
|
-
def healthz() -> dict[str, str]:
|
|
36
|
-
return {"status": "ok"}
|
|
1
|
+
"""FastAPI app entrypoint."""
|
|
2
|
+
from fastapi import FastAPI
|
|
3
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
4
|
+
from slowapi import Limiter, _rate_limit_exceeded_handler
|
|
5
|
+
from slowapi.errors import RateLimitExceeded
|
|
6
|
+
from slowapi.util import get_remote_address
|
|
7
|
+
|
|
8
|
+
from app.core.config import settings
|
|
9
|
+
from app.routers import auth, users
|
|
10
|
+
|
|
11
|
+
limiter = Limiter(key_func=get_remote_address, default_limits=[f"{settings.rate_limit_per_min}/minute"])
|
|
12
|
+
|
|
13
|
+
app = FastAPI(
|
|
14
|
+
title="{{ projectName }}",
|
|
15
|
+
version="0.1.0",
|
|
16
|
+
description="DARE-shaped FastAPI API",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
app.state.limiter = limiter
|
|
20
|
+
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
|
21
|
+
|
|
22
|
+
app.add_middleware(
|
|
23
|
+
CORSMiddleware,
|
|
24
|
+
allow_origins=settings.cors_origins,
|
|
25
|
+
allow_credentials=True,
|
|
26
|
+
allow_methods=["*"],
|
|
27
|
+
allow_headers=["*"],
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
app.include_router(auth.router, prefix="/auth", tags=["auth"])
|
|
31
|
+
app.include_router(users.router, prefix="/users", tags=["users"])
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.get("/healthz", include_in_schema=False)
|
|
35
|
+
def healthz() -> dict[str, str]:
|
|
36
|
+
return {"status": "ok"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from app.models.user import User
|
|
2
|
-
|
|
3
|
-
__all__ = ["User"]
|
|
1
|
+
from app.models.user import User
|
|
2
|
+
|
|
3
|
+
__all__ = ["User"]
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
"""User ORM model."""
|
|
2
|
-
import uuid
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
from enum import Enum
|
|
5
|
-
|
|
6
|
-
from sqlalchemy import DateTime, Index, String, func
|
|
7
|
-
from sqlalchemy.dialects.postgresql import UUID
|
|
8
|
-
from sqlalchemy.orm import Mapped, mapped_column
|
|
9
|
-
|
|
10
|
-
from app.db.session import Base
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class Role(str, Enum):
|
|
14
|
-
USER = "USER"
|
|
15
|
-
ADMIN = "ADMIN"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class User(Base):
|
|
19
|
-
__tablename__ = "users"
|
|
20
|
-
|
|
21
|
-
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
22
|
-
email: Mapped[str] = mapped_column(String(254), unique=True, nullable=False)
|
|
23
|
-
password: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
24
|
-
role: Mapped[str] = mapped_column(String(16), nullable=False, default=Role.USER.value)
|
|
25
|
-
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
26
|
-
updated_at: Mapped[datetime] = mapped_column(
|
|
27
|
-
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
__table_args__ = (Index("ix_users_email", "email"),)
|
|
1
|
+
"""User ORM model."""
|
|
2
|
+
import uuid
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import DateTime, Index, String, func
|
|
7
|
+
from sqlalchemy.dialects.postgresql import UUID
|
|
8
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
9
|
+
|
|
10
|
+
from app.db.session import Base
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Role(str, Enum):
|
|
14
|
+
USER = "USER"
|
|
15
|
+
ADMIN = "ADMIN"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class User(Base):
|
|
19
|
+
__tablename__ = "users"
|
|
20
|
+
|
|
21
|
+
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
22
|
+
email: Mapped[str] = mapped_column(String(254), unique=True, nullable=False)
|
|
23
|
+
password: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
24
|
+
role: Mapped[str] = mapped_column(String(16), nullable=False, default=Role.USER.value)
|
|
25
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
26
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
27
|
+
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__table_args__ = (Index("ix_users_email", "email"),)
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
"""User repository — encapsulates SQLAlchemy queries."""
|
|
2
|
-
from sqlalchemy import func, select
|
|
3
|
-
from sqlalchemy.orm import Session
|
|
4
|
-
|
|
5
|
-
from app.models.user import User
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class UserRepository:
|
|
9
|
-
def __init__(self, db: Session) -> None:
|
|
10
|
-
self.db = db
|
|
11
|
-
|
|
12
|
-
def find_by_email(self, email: str) -> User | None:
|
|
13
|
-
return self.db.execute(select(User).where(User.email == email)).scalar_one_or_none()
|
|
14
|
-
|
|
15
|
-
def page(self, page: int, limit: int) -> tuple[list[User], int]:
|
|
16
|
-
page = max(1, page)
|
|
17
|
-
limit = min(100, max(1, limit))
|
|
18
|
-
offset = (page - 1) * limit
|
|
19
|
-
items = list(
|
|
20
|
-
self.db.execute(
|
|
21
|
-
select(User).order_by(User.created_at.desc()).offset(offset).limit(limit)
|
|
22
|
-
)
|
|
23
|
-
.scalars()
|
|
24
|
-
.all()
|
|
25
|
-
)
|
|
26
|
-
total = self.db.execute(select(func.count()).select_from(User)).scalar_one()
|
|
27
|
-
return items, total
|
|
28
|
-
|
|
29
|
-
def create(self, *, email: str, password: str, role: str) -> User:
|
|
30
|
-
user = User(email=email, password=password, role=role)
|
|
31
|
-
self.db.add(user)
|
|
32
|
-
self.db.commit()
|
|
33
|
-
self.db.refresh(user)
|
|
34
|
-
return user
|
|
1
|
+
"""User repository — encapsulates SQLAlchemy queries."""
|
|
2
|
+
from sqlalchemy import func, select
|
|
3
|
+
from sqlalchemy.orm import Session
|
|
4
|
+
|
|
5
|
+
from app.models.user import User
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UserRepository:
|
|
9
|
+
def __init__(self, db: Session) -> None:
|
|
10
|
+
self.db = db
|
|
11
|
+
|
|
12
|
+
def find_by_email(self, email: str) -> User | None:
|
|
13
|
+
return self.db.execute(select(User).where(User.email == email)).scalar_one_or_none()
|
|
14
|
+
|
|
15
|
+
def page(self, page: int, limit: int) -> tuple[list[User], int]:
|
|
16
|
+
page = max(1, page)
|
|
17
|
+
limit = min(100, max(1, limit))
|
|
18
|
+
offset = (page - 1) * limit
|
|
19
|
+
items = list(
|
|
20
|
+
self.db.execute(
|
|
21
|
+
select(User).order_by(User.created_at.desc()).offset(offset).limit(limit)
|
|
22
|
+
)
|
|
23
|
+
.scalars()
|
|
24
|
+
.all()
|
|
25
|
+
)
|
|
26
|
+
total = self.db.execute(select(func.count()).select_from(User)).scalar_one()
|
|
27
|
+
return items, total
|
|
28
|
+
|
|
29
|
+
def create(self, *, email: str, password: str, role: str) -> User:
|
|
30
|
+
user = User(email=email, password=password, role=role)
|
|
31
|
+
self.db.add(user)
|
|
32
|
+
self.db.commit()
|
|
33
|
+
self.db.refresh(user)
|
|
34
|
+
return user
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
"""Auth router: POST /auth/login, GET /auth/me."""
|
|
2
|
-
from fastapi import APIRouter, Depends, HTTPException, status
|
|
3
|
-
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
4
|
-
from sqlalchemy.orm import Session
|
|
5
|
-
|
|
6
|
-
from app.core.security import decode_access_token
|
|
7
|
-
from app.db.session import get_db
|
|
8
|
-
from app.repositories.user_repository import UserRepository
|
|
9
|
-
from app.schemas.user import LoginIn, LoginOut, UserOut
|
|
10
|
-
from app.services.auth_service import AuthService, InvalidCredentials
|
|
11
|
-
|
|
12
|
-
router = APIRouter()
|
|
13
|
-
bearer = HTTPBearer(auto_error=True)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@router.post("/login", response_model=LoginOut)
|
|
17
|
-
def login(dto: LoginIn, db: Session = Depends(get_db)) -> LoginOut:
|
|
18
|
-
service = AuthService(UserRepository(db))
|
|
19
|
-
try:
|
|
20
|
-
return service.login(dto)
|
|
21
|
-
except InvalidCredentials as exc:
|
|
22
|
-
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid credentials") from exc
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@router.get("/me", response_model=UserOut)
|
|
26
|
-
def me(
|
|
27
|
-
creds: HTTPAuthorizationCredentials = Depends(bearer),
|
|
28
|
-
db: Session = Depends(get_db),
|
|
29
|
-
) -> UserOut:
|
|
30
|
-
payload = decode_access_token(creds.credentials)
|
|
31
|
-
if not payload or "sub" not in payload:
|
|
32
|
-
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid token")
|
|
33
|
-
repo = UserRepository(db)
|
|
34
|
-
user = repo.find_by_email(payload.get("email", ""))
|
|
35
|
-
if user is None:
|
|
36
|
-
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "User not found")
|
|
37
|
-
return UserOut.model_validate(user)
|
|
1
|
+
"""Auth router: POST /auth/login, GET /auth/me."""
|
|
2
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
3
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
4
|
+
from sqlalchemy.orm import Session
|
|
5
|
+
|
|
6
|
+
from app.core.security import decode_access_token
|
|
7
|
+
from app.db.session import get_db
|
|
8
|
+
from app.repositories.user_repository import UserRepository
|
|
9
|
+
from app.schemas.user import LoginIn, LoginOut, UserOut
|
|
10
|
+
from app.services.auth_service import AuthService, InvalidCredentials
|
|
11
|
+
|
|
12
|
+
router = APIRouter()
|
|
13
|
+
bearer = HTTPBearer(auto_error=True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@router.post("/login", response_model=LoginOut)
|
|
17
|
+
def login(dto: LoginIn, db: Session = Depends(get_db)) -> LoginOut:
|
|
18
|
+
service = AuthService(UserRepository(db))
|
|
19
|
+
try:
|
|
20
|
+
return service.login(dto)
|
|
21
|
+
except InvalidCredentials as exc:
|
|
22
|
+
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid credentials") from exc
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@router.get("/me", response_model=UserOut)
|
|
26
|
+
def me(
|
|
27
|
+
creds: HTTPAuthorizationCredentials = Depends(bearer),
|
|
28
|
+
db: Session = Depends(get_db),
|
|
29
|
+
) -> UserOut:
|
|
30
|
+
payload = decode_access_token(creds.credentials)
|
|
31
|
+
if not payload or "sub" not in payload:
|
|
32
|
+
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid token")
|
|
33
|
+
repo = UserRepository(db)
|
|
34
|
+
user = repo.find_by_email(payload.get("email", ""))
|
|
35
|
+
if user is None:
|
|
36
|
+
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "User not found")
|
|
37
|
+
return UserOut.model_validate(user)
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
"""Users router: GET /users, POST /users."""
|
|
2
|
-
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
3
|
-
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
4
|
-
from sqlalchemy.orm import Session
|
|
5
|
-
|
|
6
|
-
from app.core.security import decode_access_token
|
|
7
|
-
from app.db.session import get_db
|
|
8
|
-
from app.repositories.user_repository import UserRepository
|
|
9
|
-
from app.schemas.user import UserCreate, UserOut, UserPage
|
|
10
|
-
from app.services.user_service import EmailAlreadyInUse, UserService
|
|
11
|
-
|
|
12
|
-
router = APIRouter()
|
|
13
|
-
bearer = HTTPBearer(auto_error=True)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def _require_admin(creds: HTTPAuthorizationCredentials) -> None:
|
|
17
|
-
payload = decode_access_token(creds.credentials)
|
|
18
|
-
if not payload or payload.get("role") != "ADMIN":
|
|
19
|
-
raise HTTPException(status.HTTP_403_FORBIDDEN, "admin role required")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@router.get("", response_model=UserPage)
|
|
23
|
-
def list_users(
|
|
24
|
-
page: int = Query(1, ge=1),
|
|
25
|
-
limit: int = Query(20, ge=1, le=100),
|
|
26
|
-
creds: HTTPAuthorizationCredentials = Depends(bearer),
|
|
27
|
-
db: Session = Depends(get_db),
|
|
28
|
-
) -> UserPage:
|
|
29
|
-
payload = decode_access_token(creds.credentials)
|
|
30
|
-
if not payload:
|
|
31
|
-
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid token")
|
|
32
|
-
return UserService(UserRepository(db)).list(page, limit)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@router.post("", response_model=UserOut, status_code=status.HTTP_201_CREATED)
|
|
36
|
-
def create_user(
|
|
37
|
-
dto: UserCreate,
|
|
38
|
-
creds: HTTPAuthorizationCredentials = Depends(bearer),
|
|
39
|
-
db: Session = Depends(get_db),
|
|
40
|
-
) -> UserOut:
|
|
41
|
-
_require_admin(creds)
|
|
42
|
-
service = UserService(UserRepository(db))
|
|
43
|
-
try:
|
|
44
|
-
return service.create(dto)
|
|
45
|
-
except EmailAlreadyInUse as exc:
|
|
46
|
-
raise HTTPException(status.HTTP_409_CONFLICT, "email already in use") from exc
|
|
1
|
+
"""Users router: GET /users, POST /users."""
|
|
2
|
+
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
3
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
4
|
+
from sqlalchemy.orm import Session
|
|
5
|
+
|
|
6
|
+
from app.core.security import decode_access_token
|
|
7
|
+
from app.db.session import get_db
|
|
8
|
+
from app.repositories.user_repository import UserRepository
|
|
9
|
+
from app.schemas.user import UserCreate, UserOut, UserPage
|
|
10
|
+
from app.services.user_service import EmailAlreadyInUse, UserService
|
|
11
|
+
|
|
12
|
+
router = APIRouter()
|
|
13
|
+
bearer = HTTPBearer(auto_error=True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _require_admin(creds: HTTPAuthorizationCredentials) -> None:
|
|
17
|
+
payload = decode_access_token(creds.credentials)
|
|
18
|
+
if not payload or payload.get("role") != "ADMIN":
|
|
19
|
+
raise HTTPException(status.HTTP_403_FORBIDDEN, "admin role required")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@router.get("", response_model=UserPage)
|
|
23
|
+
def list_users(
|
|
24
|
+
page: int = Query(1, ge=1),
|
|
25
|
+
limit: int = Query(20, ge=1, le=100),
|
|
26
|
+
creds: HTTPAuthorizationCredentials = Depends(bearer),
|
|
27
|
+
db: Session = Depends(get_db),
|
|
28
|
+
) -> UserPage:
|
|
29
|
+
payload = decode_access_token(creds.credentials)
|
|
30
|
+
if not payload:
|
|
31
|
+
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Invalid token")
|
|
32
|
+
return UserService(UserRepository(db)).list(page, limit)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@router.post("", response_model=UserOut, status_code=status.HTTP_201_CREATED)
|
|
36
|
+
def create_user(
|
|
37
|
+
dto: UserCreate,
|
|
38
|
+
creds: HTTPAuthorizationCredentials = Depends(bearer),
|
|
39
|
+
db: Session = Depends(get_db),
|
|
40
|
+
) -> UserOut:
|
|
41
|
+
_require_admin(creds)
|
|
42
|
+
service = UserService(UserRepository(db))
|
|
43
|
+
try:
|
|
44
|
+
return service.create(dto)
|
|
45
|
+
except EmailAlreadyInUse as exc:
|
|
46
|
+
raise HTTPException(status.HTTP_409_CONFLICT, "email already in use") from exc
|
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
"""Pydantic IN/OUT schemas for user resource."""
|
|
2
|
-
import re
|
|
3
|
-
import uuid
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
from typing import Annotated, Literal
|
|
6
|
-
|
|
7
|
-
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class LoginIn(BaseModel):
|
|
11
|
-
email: EmailStr
|
|
12
|
-
password: Annotated[str, Field(min_length=8)]
|
|
13
|
-
|
|
14
|
-
@field_validator("password")
|
|
15
|
-
@classmethod
|
|
16
|
-
def password_complexity(cls, v: str) -> str:
|
|
17
|
-
if not re.search(r"[A-Z]", v):
|
|
18
|
-
raise ValueError("password must contain an uppercase letter")
|
|
19
|
-
if not re.search(r"\d", v):
|
|
20
|
-
raise ValueError("password must contain a digit")
|
|
21
|
-
return v
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class LoginOut(BaseModel):
|
|
25
|
-
access_token: str
|
|
26
|
-
expires_in: int
|
|
27
|
-
token_type: Literal["bearer"] = "bearer"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class UserOut(BaseModel):
|
|
31
|
-
model_config = ConfigDict(from_attributes=True)
|
|
32
|
-
id: uuid.UUID
|
|
33
|
-
email: EmailStr
|
|
34
|
-
role: str
|
|
35
|
-
created_at: datetime
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class UserCreate(BaseModel):
|
|
39
|
-
email: EmailStr
|
|
40
|
-
password: Annotated[str, Field(min_length=8)]
|
|
41
|
-
role: Literal["USER", "ADMIN"] = "USER"
|
|
42
|
-
|
|
43
|
-
@field_validator("password")
|
|
44
|
-
@classmethod
|
|
45
|
-
def password_complexity(cls, v: str) -> str:
|
|
46
|
-
if not re.search(r"[A-Z]", v):
|
|
47
|
-
raise ValueError("password must contain an uppercase letter")
|
|
48
|
-
if not re.search(r"\d", v):
|
|
49
|
-
raise ValueError("password must contain a digit")
|
|
50
|
-
return v
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class UserPage(BaseModel):
|
|
54
|
-
items: list[UserOut]
|
|
55
|
-
total: int
|
|
56
|
-
page: int
|
|
1
|
+
"""Pydantic IN/OUT schemas for user resource."""
|
|
2
|
+
import re
|
|
3
|
+
import uuid
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Annotated, Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LoginIn(BaseModel):
|
|
11
|
+
email: EmailStr
|
|
12
|
+
password: Annotated[str, Field(min_length=8)]
|
|
13
|
+
|
|
14
|
+
@field_validator("password")
|
|
15
|
+
@classmethod
|
|
16
|
+
def password_complexity(cls, v: str) -> str:
|
|
17
|
+
if not re.search(r"[A-Z]", v):
|
|
18
|
+
raise ValueError("password must contain an uppercase letter")
|
|
19
|
+
if not re.search(r"\d", v):
|
|
20
|
+
raise ValueError("password must contain a digit")
|
|
21
|
+
return v
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LoginOut(BaseModel):
|
|
25
|
+
access_token: str
|
|
26
|
+
expires_in: int
|
|
27
|
+
token_type: Literal["bearer"] = "bearer"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class UserOut(BaseModel):
|
|
31
|
+
model_config = ConfigDict(from_attributes=True)
|
|
32
|
+
id: uuid.UUID
|
|
33
|
+
email: EmailStr
|
|
34
|
+
role: str
|
|
35
|
+
created_at: datetime
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class UserCreate(BaseModel):
|
|
39
|
+
email: EmailStr
|
|
40
|
+
password: Annotated[str, Field(min_length=8)]
|
|
41
|
+
role: Literal["USER", "ADMIN"] = "USER"
|
|
42
|
+
|
|
43
|
+
@field_validator("password")
|
|
44
|
+
@classmethod
|
|
45
|
+
def password_complexity(cls, v: str) -> str:
|
|
46
|
+
if not re.search(r"[A-Z]", v):
|
|
47
|
+
raise ValueError("password must contain an uppercase letter")
|
|
48
|
+
if not re.search(r"\d", v):
|
|
49
|
+
raise ValueError("password must contain a digit")
|
|
50
|
+
return v
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class UserPage(BaseModel):
|
|
54
|
+
items: list[UserOut]
|
|
55
|
+
total: int
|
|
56
|
+
page: int
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
"""Authentication service — login flow."""
|
|
2
|
-
from app.core.security import create_access_token, verify_password
|
|
3
|
-
from app.repositories.user_repository import UserRepository
|
|
4
|
-
from app.schemas.user import LoginIn, LoginOut
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class InvalidCredentials(Exception):
|
|
8
|
-
"""Raised when email or password don't match."""
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class AuthService:
|
|
12
|
-
def __init__(self, repo: UserRepository) -> None:
|
|
13
|
-
self.repo = repo
|
|
14
|
-
|
|
15
|
-
def login(self, dto: LoginIn) -> LoginOut:
|
|
16
|
-
user = self.repo.find_by_email(dto.email.lower().strip())
|
|
17
|
-
if user is None or not verify_password(dto.password, user.password):
|
|
18
|
-
raise InvalidCredentials
|
|
19
|
-
token, expires_in = create_access_token(
|
|
20
|
-
str(user.id), claims={"email": user.email, "role": user.role}
|
|
21
|
-
)
|
|
22
|
-
return LoginOut(access_token=token, expires_in=expires_in)
|
|
1
|
+
"""Authentication service — login flow."""
|
|
2
|
+
from app.core.security import create_access_token, verify_password
|
|
3
|
+
from app.repositories.user_repository import UserRepository
|
|
4
|
+
from app.schemas.user import LoginIn, LoginOut
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InvalidCredentials(Exception):
|
|
8
|
+
"""Raised when email or password don't match."""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AuthService:
|
|
12
|
+
def __init__(self, repo: UserRepository) -> None:
|
|
13
|
+
self.repo = repo
|
|
14
|
+
|
|
15
|
+
def login(self, dto: LoginIn) -> LoginOut:
|
|
16
|
+
user = self.repo.find_by_email(dto.email.lower().strip())
|
|
17
|
+
if user is None or not verify_password(dto.password, user.password):
|
|
18
|
+
raise InvalidCredentials
|
|
19
|
+
token, expires_in = create_access_token(
|
|
20
|
+
str(user.id), claims={"email": user.email, "role": user.role}
|
|
21
|
+
)
|
|
22
|
+
return LoginOut(access_token=token, expires_in=expires_in)
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
"""User service — create/list business logic."""
|
|
2
|
-
from app.core.security import hash_password
|
|
3
|
-
from app.repositories.user_repository import UserRepository
|
|
4
|
-
from app.schemas.user import UserCreate, UserOut, UserPage
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class EmailAlreadyInUse(Exception):
|
|
8
|
-
"""Raised when a user with the same email already exists."""
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class UserService:
|
|
12
|
-
def __init__(self, repo: UserRepository) -> None:
|
|
13
|
-
self.repo = repo
|
|
14
|
-
|
|
15
|
-
def list(self, page: int, limit: int) -> UserPage:
|
|
16
|
-
items, total = self.repo.page(page, limit)
|
|
17
|
-
return UserPage(
|
|
18
|
-
items=[UserOut.model_validate(u) for u in items],
|
|
19
|
-
total=total,
|
|
20
|
-
page=max(1, page),
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
def create(self, dto: UserCreate) -> UserOut:
|
|
24
|
-
if self.repo.find_by_email(dto.email.lower().strip()) is not None:
|
|
25
|
-
raise EmailAlreadyInUse
|
|
26
|
-
created = self.repo.create(
|
|
27
|
-
email=dto.email.lower().strip(),
|
|
28
|
-
password=hash_password(dto.password),
|
|
29
|
-
role=dto.role,
|
|
30
|
-
)
|
|
31
|
-
return UserOut.model_validate(created)
|
|
1
|
+
"""User service — create/list business logic."""
|
|
2
|
+
from app.core.security import hash_password
|
|
3
|
+
from app.repositories.user_repository import UserRepository
|
|
4
|
+
from app.schemas.user import UserCreate, UserOut, UserPage
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EmailAlreadyInUse(Exception):
|
|
8
|
+
"""Raised when a user with the same email already exists."""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UserService:
|
|
12
|
+
def __init__(self, repo: UserRepository) -> None:
|
|
13
|
+
self.repo = repo
|
|
14
|
+
|
|
15
|
+
def list(self, page: int, limit: int) -> UserPage:
|
|
16
|
+
items, total = self.repo.page(page, limit)
|
|
17
|
+
return UserPage(
|
|
18
|
+
items=[UserOut.model_validate(u) for u in items],
|
|
19
|
+
total=total,
|
|
20
|
+
page=max(1, page),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def create(self, dto: UserCreate) -> UserOut:
|
|
24
|
+
if self.repo.find_by_email(dto.email.lower().strip()) is not None:
|
|
25
|
+
raise EmailAlreadyInUse
|
|
26
|
+
created = self.repo.create(
|
|
27
|
+
email=dto.email.lower().strip(),
|
|
28
|
+
password=hash_password(dto.password),
|
|
29
|
+
role=dto.role,
|
|
30
|
+
)
|
|
31
|
+
return UserOut.model_validate(created)
|