oh-my-agent-commandcode 0.0.0-stage → 20.38.4
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/README.md +35 -2
- package/filter-test-output.sh +52 -0
- package/mods/oma.ts +347 -0
- package/package.json +30 -4
- package/skills/_shared/conditional/experiment-ledger.md +28 -0
- package/skills/_shared/conditional/exploration-loop.md +22 -0
- package/skills/_shared/conditional/quality-score.md +33 -0
- package/skills/_shared/core/anti-ai-prose.md +191 -0
- package/skills/_shared/core/api-contracts/README.md +28 -0
- package/skills/_shared/core/api-contracts/template.md +90 -0
- package/skills/_shared/core/clarification-protocol.md +23 -0
- package/skills/_shared/core/code-intelligence.md +46 -0
- package/skills/_shared/core/collaboration-policy.md +28 -0
- package/skills/_shared/core/common-checklist.md +28 -0
- package/skills/_shared/core/context-budget.md +22 -0
- package/skills/_shared/core/context-loading.md +34 -0
- package/skills/_shared/core/difficulty-guide.md +26 -0
- package/skills/_shared/core/execution-policy.md +31 -0
- package/skills/_shared/core/lessons-learned.md +33 -0
- package/skills/_shared/core/prompt-structure.md +18 -0
- package/skills/_shared/core/quality-principles.md +11 -0
- package/skills/_shared/core/session-metrics.md +35 -0
- package/skills/_shared/core/skill-routing.md +39 -0
- package/skills/_shared/core/test-approach.md +54 -0
- package/skills/_shared/core/vendor-detection.md +52 -0
- package/skills/_shared/runtime/memory-protocol.md +118 -0
- package/skills/_shared/runtime/result-contract.md +64 -0
- package/skills/_shared/ui-interaction-quality.md +133 -0
- package/skills/oma-academic-writing/SKILL.md +181 -0
- package/skills/oma-academic-writing/resources/abstract-guide.md +51 -0
- package/skills/oma-academic-writing/resources/academic-verb-tiers.md +42 -0
- package/skills/oma-academic-writing/resources/anti-ai-checklist.md +52 -0
- package/skills/oma-academic-writing/resources/hedging-guide.md +132 -0
- package/skills/oma-academic-writing/resources/sentence-structure-reference.md +151 -0
- package/skills/oma-architecture/SKILL.md +203 -0
- package/skills/oma-architecture/resources/api-evolution.md +41 -0
- package/skills/oma-architecture/resources/checklist.md +42 -0
- package/skills/oma-architecture/resources/execution-protocol.md +115 -0
- package/skills/oma-architecture/resources/methodology-selection.md +92 -0
- package/skills/oma-architecture/resources/migration-patterns.md +28 -0
- package/skills/oma-architecture/resources/output-templates.md +111 -0
- package/skills/oma-architecture/resources/stakeholder-synthesis.md +80 -0
- package/skills/oma-backend/SKILL.md +202 -0
- package/skills/oma-backend/resources/checklist.md +45 -0
- package/skills/oma-backend/resources/error-playbook.md +97 -0
- package/skills/oma-backend/resources/execution-protocol.md +39 -0
- package/skills/oma-backend/resources/orm-reference.md +61 -0
- package/skills/oma-backend/variants/node/api-template.ts +315 -0
- package/skills/oma-backend/variants/node/snippets.md +475 -0
- package/skills/oma-backend/variants/node/stack.yaml +21 -0
- package/skills/oma-backend/variants/node/tech-stack.md +33 -0
- package/skills/oma-backend/variants/python/api-template.py +378 -0
- package/skills/oma-backend/variants/python/snippets.md +199 -0
- package/skills/oma-backend/variants/python/stack.yaml +18 -0
- package/skills/oma-backend/variants/python/tech-stack.md +45 -0
- package/skills/oma-backend/variants/rust/api-template.rs +635 -0
- package/skills/oma-backend/variants/rust/snippets.md +471 -0
- package/skills/oma-backend/variants/rust/stack.yaml +20 -0
- package/skills/oma-backend/variants/rust/tech-stack.md +36 -0
- package/skills/oma-backend/variants/stack.schema.json +73 -0
- package/skills/oma-brainstorm/SKILL.md +166 -0
- package/skills/oma-brainstorm/resources/triz-lite.md +117 -0
- package/skills/oma-coordination/SKILL.md +171 -0
- package/skills/oma-db/SKILL.md +201 -0
- package/skills/oma-db/resources/anti-patterns.md +177 -0
- package/skills/oma-db/resources/checklist.md +67 -0
- package/skills/oma-db/resources/document-templates.md +107 -0
- package/skills/oma-db/resources/error-playbook.md +50 -0
- package/skills/oma-db/resources/examples.md +170 -0
- package/skills/oma-db/resources/execution-protocol.md +127 -0
- package/skills/oma-db/resources/iso-controls.md +98 -0
- package/skills/oma-db/resources/migration-playbook.md +93 -0
- package/skills/oma-db/resources/query-tuning.md +57 -0
- package/skills/oma-db/resources/vector-db.md +159 -0
- package/skills/oma-debug/SKILL.md +146 -0
- package/skills/oma-debug/resources/bug-report-template.md +327 -0
- package/skills/oma-debug/resources/checklist.md +31 -0
- package/skills/oma-debug/resources/common-patterns.md +734 -0
- package/skills/oma-debug/resources/debugging-checklist.md +364 -0
- package/skills/oma-debug/resources/error-playbook.md +94 -0
- package/skills/oma-debug/resources/execution-protocol.md +40 -0
- package/skills/oma-design/SKILL.md +135 -0
- package/skills/oma-design/resources/asset-strategy.md +34 -0
- package/skills/oma-design/resources/checklist.md +63 -0
- package/skills/oma-design/resources/data-grid.md +48 -0
- package/skills/oma-design/resources/design-md-spec.md +107 -0
- package/skills/oma-design/resources/design-tokens.md +53 -0
- package/skills/oma-design/resources/error-playbook.md +18 -0
- package/skills/oma-design/resources/execution-protocol.md +95 -0
- package/skills/oma-design/resources/external-design-skills.md +130 -0
- package/skills/oma-design/resources/getdesign-fetcher.md +104 -0
- package/skills/oma-design/resources/product-interface-review.md +59 -0
- package/skills/oma-design/resources/redesign-protocol.md +52 -0
- package/skills/oma-design/resources/stitch-integration.md +244 -0
- package/skills/oma-dev-workflow/SKILL.md +375 -0
- package/skills/oma-dev-workflow/resources/affected-checks.py +58 -0
- package/skills/oma-dev-workflow/resources/api-workflows.md +58 -0
- package/skills/oma-dev-workflow/resources/database-patterns.md +107 -0
- package/skills/oma-dev-workflow/resources/i18n-patterns.md +63 -0
- package/skills/oma-dev-workflow/resources/release-coordination.md +240 -0
- package/skills/oma-dev-workflow/resources/tooling-profiles.md +217 -0
- package/skills/oma-dev-workflow/resources/troubleshooting.md +78 -0
- package/skills/oma-dev-workflow/resources/validation-pipeline.md +246 -0
- package/skills/oma-docs/SKILL.md +77 -0
- package/skills/oma-docs/resources/commands.md +76 -0
- package/skills/oma-explanation/SKILL.md +212 -0
- package/skills/oma-explanation/resources/document-structure.md +115 -0
- package/skills/oma-explanation/resources/draft-format.md +199 -0
- package/skills/oma-explanation/resources/html-contract.md +102 -0
- package/skills/oma-frontend/SKILL.md +227 -0
- package/skills/oma-frontend/resources/angular-rules.md +110 -0
- package/skills/oma-frontend/resources/checklist.md +76 -0
- package/skills/oma-frontend/resources/error-playbook.md +125 -0
- package/skills/oma-frontend/resources/execution-protocol.md +46 -0
- package/skills/oma-frontend/resources/snippets.md +354 -0
- package/skills/oma-frontend/resources/specialized-visual-implementation.md +193 -0
- package/skills/oma-frontend/resources/tailwind-rules.md +83 -0
- package/skills/oma-frontend/resources/tech-stack.md +156 -0
- package/skills/oma-hwp/SKILL.md +166 -0
- package/skills/oma-hwp/config/hwp-config.yaml +35 -0
- package/skills/oma-hwp/resources/balanced-tables.ts +39 -0
- package/skills/oma-hwp/resources/execution-protocol.md +145 -0
- package/skills/oma-hwp/resources/flatten-tables.ts +101 -0
- package/skills/oma-hwp/resources/package.json +13 -0
- package/skills/oma-hwp/resources/troubleshooting.md +70 -0
- package/skills/oma-hwp/resources/tsconfig.json +10 -0
- package/skills/oma-hwp/resources/turndown-plugin-gfm.d.ts +9 -0
- package/skills/oma-image/SKILL.md +142 -0
- package/skills/oma-image/config/image-config.yaml +70 -0
- package/skills/oma-image/resources/checklist.md +18 -0
- package/skills/oma-image/resources/execution-protocol.md +117 -0
- package/skills/oma-image/resources/invocation.md +105 -0
- package/skills/oma-image/resources/prompt-tips.md +71 -0
- package/skills/oma-image/resources/vendor-matrix.md +90 -0
- package/skills/oma-market/SKILL.md +142 -0
- package/skills/oma-market/resources/checklist.md +12 -0
- package/skills/oma-market/resources/error-playbook.md +12 -0
- package/skills/oma-market/resources/execution-protocol.md +60 -0
- package/skills/oma-market/resources/frameworks/pestel.md +101 -0
- package/skills/oma-market/resources/frameworks/porters-5f.md +90 -0
- package/skills/oma-market/resources/frameworks/swot.md +61 -0
- package/skills/oma-market/resources/intent-rules.md +35 -0
- package/skills/oma-market/resources/output-laws.md +53 -0
- package/skills/oma-mobile/SKILL.md +168 -0
- package/skills/oma-mobile/resources/checklist.md +77 -0
- package/skills/oma-mobile/resources/error-playbook.md +198 -0
- package/skills/oma-mobile/resources/execution-protocol.md +47 -0
- package/skills/oma-mobile/resources/screen-template.dart +331 -0
- package/skills/oma-mobile/resources/screen-template.swift +239 -0
- package/skills/oma-mobile/resources/screen-template.tsx +179 -0
- package/skills/oma-mobile/resources/tech-stack.md +69 -0
- package/skills/oma-mobile/variants/README.md +45 -0
- package/skills/oma-mobile/variants/flutter/api-template.dart +461 -0
- package/skills/oma-mobile/variants/flutter/snippets.md +1353 -0
- package/skills/oma-mobile/variants/flutter/stack.yaml +21 -0
- package/skills/oma-mobile/variants/flutter/tech-stack.md +201 -0
- package/skills/oma-mobile/variants/kotlin-android/api-template.kt +182 -0
- package/skills/oma-mobile/variants/kotlin-android/snippets.md +871 -0
- package/skills/oma-mobile/variants/kotlin-android/stack.yaml +32 -0
- package/skills/oma-mobile/variants/kotlin-android/tech-stack.md +159 -0
- package/skills/oma-mobile/variants/react-native/api-template.ts +531 -0
- package/skills/oma-mobile/variants/react-native/snippets.md +1014 -0
- package/skills/oma-mobile/variants/react-native/stack.yaml +22 -0
- package/skills/oma-mobile/variants/react-native/tech-stack.md +192 -0
- package/skills/oma-mobile/variants/stack.schema.json +68 -0
- package/skills/oma-mobile/variants/swift-ios/api-template.swift +165 -0
- package/skills/oma-mobile/variants/swift-ios/snippets.md +1028 -0
- package/skills/oma-mobile/variants/swift-ios/stack.yaml +25 -0
- package/skills/oma-mobile/variants/swift-ios/tech-stack.md +174 -0
- package/skills/oma-observability/SKILL.md +225 -0
- package/skills/oma-observability/resources/anti-patterns.md +555 -0
- package/skills/oma-observability/resources/boundaries/cross-application.md +348 -0
- package/skills/oma-observability/resources/boundaries/multi-tenant.md +290 -0
- package/skills/oma-observability/resources/boundaries/release.md +372 -0
- package/skills/oma-observability/resources/boundaries/slo.md +225 -0
- package/skills/oma-observability/resources/checklist.md +123 -0
- package/skills/oma-observability/resources/examples.md +448 -0
- package/skills/oma-observability/resources/execution-protocol.md +166 -0
- package/skills/oma-observability/resources/incident-forensics.md +348 -0
- package/skills/oma-observability/resources/intent-rules.md +307 -0
- package/skills/oma-observability/resources/layers/L3-network.md +281 -0
- package/skills/oma-observability/resources/layers/L4-transport.md +299 -0
- package/skills/oma-observability/resources/layers/L7-application/crash-analytics.md +359 -0
- package/skills/oma-observability/resources/layers/L7-application/mobile-rum.md +278 -0
- package/skills/oma-observability/resources/layers/L7-application/waf.md +246 -0
- package/skills/oma-observability/resources/layers/L7-application/web-rum.md +350 -0
- package/skills/oma-observability/resources/layers/mesh.md +368 -0
- package/skills/oma-observability/resources/matrix.md +177 -0
- package/skills/oma-observability/resources/meta-observability.md +605 -0
- package/skills/oma-observability/resources/observability-as-code.md +666 -0
- package/skills/oma-observability/resources/signals/audit.md +310 -0
- package/skills/oma-observability/resources/signals/cost.md +303 -0
- package/skills/oma-observability/resources/signals/logs.md +263 -0
- package/skills/oma-observability/resources/signals/metrics.md +478 -0
- package/skills/oma-observability/resources/signals/privacy.md +351 -0
- package/skills/oma-observability/resources/signals/profiles.md +245 -0
- package/skills/oma-observability/resources/signals/traces.md +360 -0
- package/skills/oma-observability/resources/standards.md +214 -0
- package/skills/oma-observability/resources/transport/collector-topology.md +308 -0
- package/skills/oma-observability/resources/transport/otlp-grpc-vs-http.md +256 -0
- package/skills/oma-observability/resources/transport/sampling-recipes.md +297 -0
- package/skills/oma-observability/resources/transport/udp-statsd-mtu.md +167 -0
- package/skills/oma-observability/resources/vendor-categories.md +352 -0
- package/skills/oma-orchestration/SKILL.md +302 -0
- package/skills/oma-orchestration/config/cli-config.yaml +92 -0
- package/skills/oma-orchestration/resources/memory-schema.md +232 -0
- package/skills/oma-orchestration/resources/subagent-prompt-template.md +30 -0
- package/skills/oma-orchestration/scripts/parallel-run.sh +6 -0
- package/skills/oma-orchestration/scripts/spawn-agent.sh +5 -0
- package/skills/oma-orchestration/scripts/verify.sh +20 -0
- package/skills/oma-orchestration/templates/backend-task.md +19 -0
- package/skills/oma-orchestration/templates/debug-task.md +16 -0
- package/skills/oma-orchestration/templates/frontend-task.md +18 -0
- package/skills/oma-orchestration/templates/mobile-task.md +18 -0
- package/skills/oma-orchestration/templates/qa-task.md +16 -0
- package/skills/oma-orchestration/templates/tasks-example.yaml +15 -0
- package/skills/oma-pdf/SKILL.md +162 -0
- package/skills/oma-pdf/config/pdf-config.yaml +27 -0
- package/skills/oma-pdf/resources/execution-protocol.md +127 -0
- package/skills/oma-pm/SKILL.md +157 -0
- package/skills/oma-pm/resources/error-playbook.md +75 -0
- package/skills/oma-pm/resources/examples.md +439 -0
- package/skills/oma-pm/resources/execution-protocol.md +62 -0
- package/skills/oma-pm/resources/iso-planning.md +110 -0
- package/skills/oma-pm/resources/plan-phase-protocol.md +66 -0
- package/skills/oma-pm/resources/task-template.json +86 -0
- package/skills/oma-qa/SKILL.md +141 -0
- package/skills/oma-qa/resources/checklist.md +339 -0
- package/skills/oma-qa/resources/error-playbook.md +98 -0
- package/skills/oma-qa/resources/execution-protocol.md +169 -0
- package/skills/oma-qa/resources/iso-quality.md +82 -0
- package/skills/oma-qa/resources/verify-ship-protocol.md +116 -0
- package/skills/oma-recap/SKILL.md +245 -0
- package/skills/oma-recap/resources/output-formats.md +83 -0
- package/skills/oma-refactor/SKILL.md +184 -0
- package/skills/oma-refactor/resources/definition.md +108 -0
- package/skills/oma-refactor/resources/governance.md +33 -0
- package/skills/oma-refactor/resources/hotspots.py +44 -0
- package/skills/oma-refactor/resources/measurement.md +93 -0
- package/skills/oma-scholar/SKILL.md +276 -0
- package/skills/oma-scholar/config/scholar-config.yaml +79 -0
- package/skills/oma-scholar/resources/api-endpoints.md +174 -0
- package/skills/oma-scholar/resources/checklist.md +92 -0
- package/skills/oma-scholar/resources/execution-protocol.md +224 -0
- package/skills/oma-scholar/resources/fallback-providers.md +143 -0
- package/skills/oma-scholar/resources/setup-openalex.md +85 -0
- package/skills/oma-scholar/resources/sidecar-spec.md +341 -0
- package/skills/oma-scholar/resources/upstream-spec-cache.md +972 -0
- package/skills/oma-scm/SKILL.md +103 -0
- package/skills/oma-scm/resources/cm-operations.md +56 -0
- package/skills/oma-scm/resources/codeowners-playbook.md +45 -0
- package/skills/oma-scm/resources/conventional-commits.md +108 -0
- package/skills/oma-scm/resources/merge-risk.md +76 -0
- package/skills/oma-scm/resources/onboarding-risk-signals.md +71 -0
- package/skills/oma-search/SKILL.md +125 -0
- package/skills/oma-search/resources/checklist.md +36 -0
- package/skills/oma-search/resources/error-playbook.md +66 -0
- package/skills/oma-search/resources/examples.md +80 -0
- package/skills/oma-search/resources/execution-protocol.md +86 -0
- package/skills/oma-search/resources/intent-rules.md +47 -0
- package/skills/oma-search/resources/trust-registry.md +53 -0
- package/skills/oma-security/SKILL.md +172 -0
- package/skills/oma-security/resources/artex.md +108 -0
- package/skills/oma-security/resources/ci.md +134 -0
- package/skills/oma-security/resources/cisco-source.md +108 -0
- package/skills/oma-security/resources/deepsec-config.md +45 -0
- package/skills/oma-security/resources/deepsec-matchers.md +35 -0
- package/skills/oma-security/resources/deepsec-scanning.md +45 -0
- package/skills/oma-security/resources/deepsec-setup.md +41 -0
- package/skills/oma-security/resources/findings-contract.md +408 -0
- package/skills/oma-security/resources/mcp-scanning.md +122 -0
- package/skills/oma-security/resources/skill-scanning.md +106 -0
- package/skills/oma-security/resources/validation.md +71 -0
- package/skills/oma-skill-creation/SKILL.md +84 -0
- package/skills/oma-skill-creation/resources/prompt-evaluation.md +36 -0
- package/skills/oma-skill-creation/resources/ssl-lite-template.md +108 -0
- package/skills/oma-skill-creation/resources/validation-checklist.md +101 -0
- package/skills/oma-slide/SKILL.md +277 -0
- package/skills/oma-slide/resources/animation-patterns.md +245 -0
- package/skills/oma-slide/resources/assets/deck-stage.js +514 -0
- package/skills/oma-slide/resources/assets/viewport-base.css +257 -0
- package/skills/oma-slide/resources/canva-integration.md +203 -0
- package/skills/oma-slide/resources/checklist.md +46 -0
- package/skills/oma-slide/resources/design-doctrine.md +260 -0
- package/skills/oma-slide/resources/fixed-stage.md +232 -0
- package/skills/oma-slide/resources/generation-protocol.md +393 -0
- package/skills/oma-slide/resources/native-pptx.md +84 -0
- package/skills/oma-slide/resources/selection-index.json +461 -0
- package/skills/oma-slide/resources/style-presets.LICENSE +10 -0
- package/skills/oma-slide/resources/style-presets.md +520 -0
- package/skills/oma-tf-infra/SKILL.md +201 -0
- package/skills/oma-tf-infra/resources/checklist.md +53 -0
- package/skills/oma-tf-infra/resources/cost-optimization.md +574 -0
- package/skills/oma-tf-infra/resources/error-playbook.md +68 -0
- package/skills/oma-tf-infra/resources/examples.md +38 -0
- package/skills/oma-tf-infra/resources/execution-protocol.md +129 -0
- package/skills/oma-tf-infra/resources/iso-42001-infra.md +229 -0
- package/skills/oma-tf-infra/resources/multi-cloud-examples.md +241 -0
- package/skills/oma-tf-infra/resources/policy-testing-examples.md +332 -0
- package/skills/oma-translation/SKILL.md +229 -0
- package/skills/oma-translation/resources/anti-ai-patterns.md +104 -0
- package/skills/oma-translation/resources/lang/_template.md +73 -0
- package/skills/oma-translation/resources/lang/en.md +123 -0
- package/skills/oma-translation/resources/lang/ja.md +274 -0
- package/skills/oma-translation/resources/lang/ko.md +228 -0
- package/skills/oma-translation/resources/lang/zh.md +285 -0
- package/skills/oma-translation/resources/style-skills.md +104 -0
- package/skills/oma-translation/resources/translation-rubric.md +126 -0
- package/skills/oma-video/SKILL.md +229 -0
- package/skills/oma-video/config/video-config.yaml +60 -0
- package/skills/oma-video/resources/checklist.md +26 -0
- package/skills/oma-video/resources/execution-protocol.md +122 -0
- package/skills/oma-video/resources/existing-media.md +185 -0
- package/skills/oma-video/resources/hyperframes-authoring/README.md +44 -0
- package/skills/oma-video/resources/hyperframes-authoring/demo.md +31 -0
- package/skills/oma-video/resources/hyperframes-authoring/explainer.md +10 -0
- package/skills/oma-video/resources/hyperframes-authoring/shorts.md +10 -0
- package/skills/oma-video/resources/motion-principles.md +81 -0
- package/skills/oma-video/resources/mpt/driver.py +301 -0
- package/skills/oma-video/resources/prompt-tips.md +75 -0
- package/skills/oma-video/resources/script-schema.md +81 -0
- package/skills/oma-video/resources/sound-design.md +127 -0
- package/skills/oma-video/resources/strudel/package.json +11 -0
- package/skills/oma-video/resources/strudel/page.html +87 -0
- package/skills/oma-video/resources/strudel/render.mjs +295 -0
- package/skills/oma-video/resources/vendor-matrix.md +124 -0
- package/skills/oma-voice/SKILL.md +249 -0
- package/skills/oma-voice/config/voice-config.yaml +30 -0
- package/skills/oma-voice/resources/audio-processing.md +119 -0
- package/skills/oma-voice/resources/checklist.md +47 -0
- package/skills/oma-voice/resources/execution-protocol.md +215 -0
- package/skills/oma-voice/resources/prompt-tips.md +43 -0
- package/skills/oma-voice/resources/voice-matrix.md +51 -0
- package/skills/oma-voice/resources/voicestudio.md +87 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""
|
|
2
|
+
API Endpoint Template for Backend Agent
|
|
3
|
+
|
|
4
|
+
This template demonstrates best practices for FastAPI endpoints.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
|
8
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
9
|
+
from typing import Annotated, List
|
|
10
|
+
from uuid import UUID
|
|
11
|
+
|
|
12
|
+
from app.database import get_db
|
|
13
|
+
from app.auth import get_current_user
|
|
14
|
+
from app.models import User, Resource
|
|
15
|
+
from app.schemas import ResourceCreate, ResourceUpdate, ResourceResponse
|
|
16
|
+
from app.services import ResourceService
|
|
17
|
+
|
|
18
|
+
# Type aliases for cleaner code
|
|
19
|
+
DatabaseDep = Annotated[AsyncSession, Depends(get_db)]
|
|
20
|
+
UserDep = Annotated[User, Depends(get_current_user)]
|
|
21
|
+
|
|
22
|
+
# Router setup
|
|
23
|
+
router = APIRouter(
|
|
24
|
+
prefix="/api/resources",
|
|
25
|
+
tags=["resources"],
|
|
26
|
+
responses={404: {"description": "Not found"}},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# List endpoint with pagination and filtering
|
|
31
|
+
@router.get(
|
|
32
|
+
"/",
|
|
33
|
+
response_model=List[ResourceResponse],
|
|
34
|
+
summary="List resources",
|
|
35
|
+
description="Retrieve a paginated list of resources with optional filtering"
|
|
36
|
+
)
|
|
37
|
+
async def list_resources(
|
|
38
|
+
db: DatabaseDep,
|
|
39
|
+
current_user: UserDep,
|
|
40
|
+
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
|
41
|
+
limit: int = Query(100, ge=1, le=1000, description="Max records to return"),
|
|
42
|
+
search: str | None = Query(None, description="Search query"),
|
|
43
|
+
status: str | None = Query(None, description="Filter by status"),
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
List resources with pagination.
|
|
47
|
+
|
|
48
|
+
- **skip**: Offset for pagination
|
|
49
|
+
- **limit**: Maximum number of records
|
|
50
|
+
- **search**: Optional search term
|
|
51
|
+
- **status**: Optional status filter
|
|
52
|
+
"""
|
|
53
|
+
service = ResourceService(db)
|
|
54
|
+
resources = await service.list_resources(
|
|
55
|
+
user_id=current_user.id,
|
|
56
|
+
skip=skip,
|
|
57
|
+
limit=limit,
|
|
58
|
+
search=search,
|
|
59
|
+
status=status,
|
|
60
|
+
)
|
|
61
|
+
return resources
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Get single resource
|
|
65
|
+
@router.get(
|
|
66
|
+
"/{resource_id}",
|
|
67
|
+
response_model=ResourceResponse,
|
|
68
|
+
summary="Get resource",
|
|
69
|
+
responses={
|
|
70
|
+
200: {"description": "Resource found"},
|
|
71
|
+
404: {"description": "Resource not found"},
|
|
72
|
+
403: {"description": "Access denied"}
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
async def get_resource(
|
|
76
|
+
resource_id: UUID,
|
|
77
|
+
db: DatabaseDep,
|
|
78
|
+
current_user: UserDep,
|
|
79
|
+
):
|
|
80
|
+
"""
|
|
81
|
+
Get a specific resource by ID.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
404: Resource not found
|
|
85
|
+
403: User doesn't own this resource
|
|
86
|
+
"""
|
|
87
|
+
service = ResourceService(db)
|
|
88
|
+
resource = await service.get_resource(resource_id)
|
|
89
|
+
|
|
90
|
+
if not resource:
|
|
91
|
+
raise HTTPException(
|
|
92
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
93
|
+
detail=f"Resource {resource_id} not found"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Authorization check
|
|
97
|
+
if resource.user_id != current_user.id:
|
|
98
|
+
raise HTTPException(
|
|
99
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
100
|
+
detail="Access denied"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return resource
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# Create resource
|
|
107
|
+
@router.post(
|
|
108
|
+
"/",
|
|
109
|
+
response_model=ResourceResponse,
|
|
110
|
+
status_code=status.HTTP_201_CREATED,
|
|
111
|
+
summary="Create resource",
|
|
112
|
+
)
|
|
113
|
+
async def create_resource(
|
|
114
|
+
resource_data: ResourceCreate,
|
|
115
|
+
db: DatabaseDep,
|
|
116
|
+
current_user: UserDep,
|
|
117
|
+
):
|
|
118
|
+
"""
|
|
119
|
+
Create a new resource.
|
|
120
|
+
|
|
121
|
+
- **name**: Resource name (required)
|
|
122
|
+
- **description**: Optional description
|
|
123
|
+
- **status**: Initial status (default: active)
|
|
124
|
+
"""
|
|
125
|
+
service = ResourceService(db)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
resource = await service.create_resource(
|
|
129
|
+
user_id=current_user.id,
|
|
130
|
+
data=resource_data
|
|
131
|
+
)
|
|
132
|
+
return resource
|
|
133
|
+
except ValueError as e:
|
|
134
|
+
raise HTTPException(
|
|
135
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
136
|
+
detail=str(e)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Update resource
|
|
141
|
+
@router.patch(
|
|
142
|
+
"/{resource_id}",
|
|
143
|
+
response_model=ResourceResponse,
|
|
144
|
+
summary="Update resource",
|
|
145
|
+
)
|
|
146
|
+
async def update_resource(
|
|
147
|
+
resource_id: UUID,
|
|
148
|
+
resource_data: ResourceUpdate,
|
|
149
|
+
db: DatabaseDep,
|
|
150
|
+
current_user: UserDep,
|
|
151
|
+
):
|
|
152
|
+
"""
|
|
153
|
+
Update an existing resource (partial update).
|
|
154
|
+
|
|
155
|
+
Only provided fields will be updated.
|
|
156
|
+
"""
|
|
157
|
+
service = ResourceService(db)
|
|
158
|
+
resource = await service.get_resource(resource_id)
|
|
159
|
+
|
|
160
|
+
if not resource:
|
|
161
|
+
raise HTTPException(
|
|
162
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
163
|
+
detail=f"Resource {resource_id} not found"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
if resource.user_id != current_user.id:
|
|
167
|
+
raise HTTPException(
|
|
168
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
169
|
+
detail="Access denied"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
updated_resource = await service.update_resource(resource, resource_data)
|
|
174
|
+
return updated_resource
|
|
175
|
+
except ValueError as e:
|
|
176
|
+
raise HTTPException(
|
|
177
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
178
|
+
detail=str(e)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# Delete resource
|
|
183
|
+
@router.delete(
|
|
184
|
+
"/{resource_id}",
|
|
185
|
+
status_code=status.HTTP_204_NO_CONTENT,
|
|
186
|
+
summary="Delete resource",
|
|
187
|
+
)
|
|
188
|
+
async def delete_resource(
|
|
189
|
+
resource_id: UUID,
|
|
190
|
+
db: DatabaseDep,
|
|
191
|
+
current_user: UserDep,
|
|
192
|
+
hard: bool = Query(False, description="Perform hard delete instead of soft delete"),
|
|
193
|
+
):
|
|
194
|
+
"""
|
|
195
|
+
Delete a resource.
|
|
196
|
+
|
|
197
|
+
- **hard=false**: Soft delete (default) - sets deleted_at timestamp
|
|
198
|
+
- **hard=true**: Hard delete - permanently removes from database
|
|
199
|
+
"""
|
|
200
|
+
service = ResourceService(db)
|
|
201
|
+
resource = await service.get_resource(resource_id)
|
|
202
|
+
|
|
203
|
+
if not resource:
|
|
204
|
+
raise HTTPException(
|
|
205
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
206
|
+
detail=f"Resource {resource_id} not found"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if resource.user_id != current_user.id:
|
|
210
|
+
raise HTTPException(
|
|
211
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
212
|
+
detail="Access denied"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
await service.delete_resource(resource, hard=hard)
|
|
216
|
+
# 204 No Content - no response body
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# Bulk operations example
|
|
220
|
+
@router.post(
|
|
221
|
+
"/bulk",
|
|
222
|
+
response_model=List[ResourceResponse],
|
|
223
|
+
status_code=status.HTTP_201_CREATED,
|
|
224
|
+
summary="Bulk create resources",
|
|
225
|
+
)
|
|
226
|
+
async def bulk_create_resources(
|
|
227
|
+
resources_data: List[ResourceCreate],
|
|
228
|
+
db: DatabaseDep,
|
|
229
|
+
current_user: UserDep,
|
|
230
|
+
):
|
|
231
|
+
"""
|
|
232
|
+
Create multiple resources in one request.
|
|
233
|
+
|
|
234
|
+
Useful for batch imports.
|
|
235
|
+
"""
|
|
236
|
+
if len(resources_data) > 100:
|
|
237
|
+
raise HTTPException(
|
|
238
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
239
|
+
detail="Maximum 100 resources per bulk operation"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
service = ResourceService(db)
|
|
243
|
+
try:
|
|
244
|
+
return await service.bulk_create_resources(
|
|
245
|
+
user_id=current_user.id,
|
|
246
|
+
resources_data=resources_data,
|
|
247
|
+
)
|
|
248
|
+
except ValueError as e:
|
|
249
|
+
raise HTTPException(
|
|
250
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
251
|
+
detail=str(e)
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# Repository and service templates (separate files in app/repositories and app/services)
|
|
256
|
+
"""
|
|
257
|
+
from datetime import datetime, timezone
|
|
258
|
+
from typing import Sequence
|
|
259
|
+
from uuid import UUID
|
|
260
|
+
|
|
261
|
+
from sqlalchemy import select
|
|
262
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
263
|
+
|
|
264
|
+
from app.models import Resource
|
|
265
|
+
from app.schemas import ResourceCreate, ResourceUpdate
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class ResourceRepository:
|
|
269
|
+
def __init__(self, db: AsyncSession):
|
|
270
|
+
self.db = db
|
|
271
|
+
|
|
272
|
+
async def list_for_user(
|
|
273
|
+
self,
|
|
274
|
+
user_id: UUID,
|
|
275
|
+
skip: int = 0,
|
|
276
|
+
limit: int = 100,
|
|
277
|
+
search: str | None = None,
|
|
278
|
+
status: str | None = None,
|
|
279
|
+
) -> Sequence[Resource]:
|
|
280
|
+
statement = select(Resource).where(
|
|
281
|
+
Resource.user_id == user_id,
|
|
282
|
+
Resource.deleted_at.is_(None),
|
|
283
|
+
)
|
|
284
|
+
if search:
|
|
285
|
+
statement = statement.where(Resource.name.ilike(f"%{search}%"))
|
|
286
|
+
if status:
|
|
287
|
+
statement = statement.where(Resource.status == status)
|
|
288
|
+
result = await self.db.execute(statement.offset(skip).limit(limit))
|
|
289
|
+
return result.scalars().all()
|
|
290
|
+
|
|
291
|
+
async def get(self, resource_id: UUID) -> Resource | None:
|
|
292
|
+
statement = select(Resource).where(
|
|
293
|
+
Resource.id == resource_id,
|
|
294
|
+
Resource.deleted_at.is_(None),
|
|
295
|
+
)
|
|
296
|
+
result = await self.db.execute(statement)
|
|
297
|
+
return result.scalar_one_or_none()
|
|
298
|
+
|
|
299
|
+
def add(self, resource: Resource) -> None:
|
|
300
|
+
self.db.add(resource)
|
|
301
|
+
|
|
302
|
+
async def delete(self, resource: Resource) -> None:
|
|
303
|
+
await self.db.delete(resource)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class ResourceService:
|
|
307
|
+
def __init__(self, db: AsyncSession):
|
|
308
|
+
self.db = db
|
|
309
|
+
self.repository = ResourceRepository(db)
|
|
310
|
+
|
|
311
|
+
async def list_resources(
|
|
312
|
+
self,
|
|
313
|
+
user_id: UUID,
|
|
314
|
+
skip: int = 0,
|
|
315
|
+
limit: int = 100,
|
|
316
|
+
search: str | None = None,
|
|
317
|
+
status: str | None = None,
|
|
318
|
+
) -> Sequence[Resource]:
|
|
319
|
+
return await self.repository.list_for_user(
|
|
320
|
+
user_id=user_id,
|
|
321
|
+
skip=skip,
|
|
322
|
+
limit=limit,
|
|
323
|
+
search=search,
|
|
324
|
+
status=status,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
async def get_resource(self, resource_id: UUID) -> Resource | None:
|
|
328
|
+
return await self.repository.get(resource_id)
|
|
329
|
+
|
|
330
|
+
async def create_resource(self, user_id: UUID, data: ResourceCreate) -> Resource:
|
|
331
|
+
resource = Resource(**data.model_dump(), user_id=user_id)
|
|
332
|
+
self.repository.add(resource)
|
|
333
|
+
try:
|
|
334
|
+
await self.db.commit()
|
|
335
|
+
except Exception:
|
|
336
|
+
await self.db.rollback()
|
|
337
|
+
raise
|
|
338
|
+
await self.db.refresh(resource)
|
|
339
|
+
return resource
|
|
340
|
+
|
|
341
|
+
async def update_resource(self, resource: Resource, data: ResourceUpdate) -> Resource:
|
|
342
|
+
for field, value in data.model_dump(exclude_unset=True).items():
|
|
343
|
+
setattr(resource, field, value)
|
|
344
|
+
|
|
345
|
+
await self.db.commit()
|
|
346
|
+
await self.db.refresh(resource)
|
|
347
|
+
return resource
|
|
348
|
+
|
|
349
|
+
async def delete_resource(self, resource: Resource, hard: bool = False) -> None:
|
|
350
|
+
if hard:
|
|
351
|
+
await self.repository.delete(resource)
|
|
352
|
+
else:
|
|
353
|
+
resource.deleted_at = datetime.now(timezone.utc)
|
|
354
|
+
|
|
355
|
+
await self.db.commit()
|
|
356
|
+
|
|
357
|
+
async def bulk_create_resources(
|
|
358
|
+
self,
|
|
359
|
+
user_id: UUID,
|
|
360
|
+
resources_data: list[ResourceCreate],
|
|
361
|
+
) -> list[Resource]:
|
|
362
|
+
resources = [
|
|
363
|
+
Resource(**data.model_dump(), user_id=user_id)
|
|
364
|
+
for data in resources_data
|
|
365
|
+
]
|
|
366
|
+
for resource in resources:
|
|
367
|
+
self.repository.add(resource)
|
|
368
|
+
|
|
369
|
+
try:
|
|
370
|
+
await self.db.commit()
|
|
371
|
+
except Exception:
|
|
372
|
+
await self.db.rollback()
|
|
373
|
+
raise
|
|
374
|
+
|
|
375
|
+
for resource in resources:
|
|
376
|
+
await self.db.refresh(resource)
|
|
377
|
+
return resources
|
|
378
|
+
"""
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Backend Agent - Code Snippets
|
|
2
|
+
|
|
3
|
+
Copy-paste ready patterns. Use these as starting points, adapt to the specific task.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## FastAPI Route with Auth
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
11
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
12
|
+
|
|
13
|
+
from app.core.deps import get_current_user, get_db
|
|
14
|
+
from app.models.user import User
|
|
15
|
+
from app.schemas.resource import ResourceCreate, ResourceResponse
|
|
16
|
+
|
|
17
|
+
router = APIRouter(prefix="/api/resources", tags=["resources"])
|
|
18
|
+
|
|
19
|
+
@router.post("/", response_model=ResourceResponse, status_code=status.HTTP_201_CREATED)
|
|
20
|
+
async def create_resource(
|
|
21
|
+
data: ResourceCreate,
|
|
22
|
+
db: AsyncSession = Depends(get_db),
|
|
23
|
+
current_user: User = Depends(get_current_user),
|
|
24
|
+
):
|
|
25
|
+
resource = Resource(**data.model_dump(), user_id=current_user.id)
|
|
26
|
+
db.add(resource)
|
|
27
|
+
await db.commit()
|
|
28
|
+
await db.refresh(resource)
|
|
29
|
+
return resource
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Pydantic Schema Pair
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from datetime import datetime
|
|
38
|
+
from uuid import UUID
|
|
39
|
+
from pydantic import BaseModel, Field
|
|
40
|
+
|
|
41
|
+
class ResourceCreate(BaseModel):
|
|
42
|
+
title: str = Field(..., min_length=1, max_length=200)
|
|
43
|
+
description: str | None = None
|
|
44
|
+
|
|
45
|
+
class ResourceResponse(BaseModel):
|
|
46
|
+
id: UUID
|
|
47
|
+
title: str
|
|
48
|
+
description: str | None
|
|
49
|
+
user_id: UUID
|
|
50
|
+
created_at: datetime
|
|
51
|
+
|
|
52
|
+
model_config = {"from_attributes": True}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## SQLAlchemy Model
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import uuid
|
|
61
|
+
from datetime import datetime
|
|
62
|
+
from sqlalchemy import ForeignKey, String, Text
|
|
63
|
+
from sqlalchemy.dialects.postgresql import UUID
|
|
64
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
65
|
+
|
|
66
|
+
from app.core.database import Base
|
|
67
|
+
|
|
68
|
+
class Resource(Base):
|
|
69
|
+
__tablename__ = "resources"
|
|
70
|
+
|
|
71
|
+
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
72
|
+
title: Mapped[str] = mapped_column(String(200), nullable=False)
|
|
73
|
+
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
74
|
+
user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id"), nullable=False, index=True)
|
|
75
|
+
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
|
|
76
|
+
|
|
77
|
+
user: Mapped["User"] = relationship(back_populates="resources")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## JWT Auth Dependency
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from fastapi import Depends, HTTPException, status
|
|
86
|
+
from fastapi.security import OAuth2PasswordBearer
|
|
87
|
+
from jose import JWTError, jwt
|
|
88
|
+
|
|
89
|
+
from app.core.config import settings
|
|
90
|
+
|
|
91
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")
|
|
92
|
+
|
|
93
|
+
async def get_current_user(
|
|
94
|
+
token: str = Depends(oauth2_scheme),
|
|
95
|
+
db: AsyncSession = Depends(get_db),
|
|
96
|
+
) -> User:
|
|
97
|
+
try:
|
|
98
|
+
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
|
|
99
|
+
user_id = payload.get("sub")
|
|
100
|
+
if user_id is None:
|
|
101
|
+
raise HTTPException(status_code=401, detail="Invalid token")
|
|
102
|
+
except JWTError:
|
|
103
|
+
raise HTTPException(status_code=401, detail="Invalid token")
|
|
104
|
+
|
|
105
|
+
user = await db.get(User, user_id)
|
|
106
|
+
if user is None:
|
|
107
|
+
raise HTTPException(status_code=401, detail="User not found")
|
|
108
|
+
return user
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Password Hashing
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from passlib.context import CryptContext
|
|
117
|
+
|
|
118
|
+
# argon2 (Argon2id) is the default; bcrypt stays verifiable so legacy hashes
|
|
119
|
+
# re-hash to argon2 transparently on next successful login
|
|
120
|
+
pwd_context = CryptContext(schemes=["argon2", "bcrypt"], deprecated="auto")
|
|
121
|
+
|
|
122
|
+
def hash_password(password: str) -> str:
|
|
123
|
+
return pwd_context.hash(password)
|
|
124
|
+
|
|
125
|
+
def verify_password(plain: str, hashed: str) -> bool:
|
|
126
|
+
return pwd_context.verify(plain, hashed)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Paginated Query
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from sqlalchemy import select, func
|
|
135
|
+
|
|
136
|
+
async def paginate(
|
|
137
|
+
db: AsyncSession,
|
|
138
|
+
query,
|
|
139
|
+
page: int = 1,
|
|
140
|
+
size: int = 20,
|
|
141
|
+
):
|
|
142
|
+
total = await db.scalar(select(func.count()).select_from(query.subquery()))
|
|
143
|
+
items = await db.scalars(query.offset((page - 1) * size).limit(size))
|
|
144
|
+
return {
|
|
145
|
+
"items": list(items),
|
|
146
|
+
"total": total,
|
|
147
|
+
"page": page,
|
|
148
|
+
"size": size,
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Alembic Migration
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
"""add resources table
|
|
158
|
+
|
|
159
|
+
Revision ID: xxxx
|
|
160
|
+
"""
|
|
161
|
+
from alembic import op
|
|
162
|
+
import sqlalchemy as sa
|
|
163
|
+
from sqlalchemy.dialects.postgresql import UUID
|
|
164
|
+
|
|
165
|
+
def upgrade() -> None:
|
|
166
|
+
op.create_table(
|
|
167
|
+
"resources",
|
|
168
|
+
sa.Column("id", UUID(as_uuid=True), primary_key=True),
|
|
169
|
+
sa.Column("title", sa.String(200), nullable=False),
|
|
170
|
+
sa.Column("description", sa.Text, nullable=True),
|
|
171
|
+
sa.Column("user_id", UUID(as_uuid=True), sa.ForeignKey("users.id"), nullable=False),
|
|
172
|
+
sa.Column("created_at", sa.DateTime, server_default=sa.func.now()),
|
|
173
|
+
)
|
|
174
|
+
op.create_index("ix_resources_user_id", "resources", ["user_id"])
|
|
175
|
+
|
|
176
|
+
def downgrade() -> None:
|
|
177
|
+
op.drop_table("resources")
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Test with httpx
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
import pytest
|
|
186
|
+
from httpx import AsyncClient
|
|
187
|
+
|
|
188
|
+
@pytest.mark.asyncio
|
|
189
|
+
async def test_create_resource(client: AsyncClient, auth_headers: dict):
|
|
190
|
+
response = await client.post(
|
|
191
|
+
"/api/resources/",
|
|
192
|
+
json={"title": "Test", "description": "Test desc"},
|
|
193
|
+
headers=auth_headers,
|
|
194
|
+
)
|
|
195
|
+
assert response.status_code == 201
|
|
196
|
+
data = response.json()
|
|
197
|
+
assert data["title"] == "Test"
|
|
198
|
+
assert "id" in data
|
|
199
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# yaml-language-server: $schema=../stack.schema.json
|
|
2
|
+
language: python
|
|
3
|
+
framework: fastapi
|
|
4
|
+
orm: sqlalchemy
|
|
5
|
+
source: variant
|
|
6
|
+
|
|
7
|
+
verify:
|
|
8
|
+
detect: pyproject.toml
|
|
9
|
+
syntax:
|
|
10
|
+
cmd: "python -m compileall -q ."
|
|
11
|
+
tests:
|
|
12
|
+
cmd: "uv run pytest"
|
|
13
|
+
skip_if_missing: uv
|
|
14
|
+
raw_sql:
|
|
15
|
+
patterns:
|
|
16
|
+
- "f[\"'].*(SELECT|INSERT|UPDATE|DELETE)"
|
|
17
|
+
include_glob: "*.py"
|
|
18
|
+
exclude_dirs: [test, tests, node_modules, .venv]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Backend Agent - Tech Stack Reference
|
|
2
|
+
|
|
3
|
+
## Python (Preferred)
|
|
4
|
+
- **Framework**: FastAPI 0.110+
|
|
5
|
+
- **ORM**: SQLAlchemy 2.0 (async)
|
|
6
|
+
- **Validation**: Pydantic v2
|
|
7
|
+
- **Database**: PostgreSQL 16+, Redis 7+
|
|
8
|
+
- **Auth**: python-jose (JWT), passlib[argon2] (Argon2id; bcrypt verify for legacy hashes)
|
|
9
|
+
- **Testing**: pytest, httpx (async test client)
|
|
10
|
+
- **Migrations**: Alembic
|
|
11
|
+
|
|
12
|
+
## Node.js (Alternative)
|
|
13
|
+
- **Framework**: Express.js, NestJS, Hono
|
|
14
|
+
- **ORM**: Prisma, Drizzle
|
|
15
|
+
- **Validation**: Zod
|
|
16
|
+
- **Auth**: jsonwebtoken, argon2 (node-argon2)
|
|
17
|
+
- **Testing**: Jest, Supertest
|
|
18
|
+
|
|
19
|
+
## Architecture
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
backend/
|
|
23
|
+
domain/ # Business logic (pure Python, no framework deps)
|
|
24
|
+
application/ # Use cases, services
|
|
25
|
+
infrastructure/ # Database, cache, external APIs
|
|
26
|
+
presentation/ # API endpoints, middleware
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Security Requirements
|
|
30
|
+
- Password hashing: Argon2id (bcrypt cost 10-12 acceptable for legacy compatibility)
|
|
31
|
+
- JWT: 15min access tokens, 7 day refresh tokens
|
|
32
|
+
- Rate limiting on auth endpoints
|
|
33
|
+
- Input validation with Pydantic/Zod
|
|
34
|
+
- Parameterized queries (never string interpolation)
|
|
35
|
+
|
|
36
|
+
## ORM Guidance
|
|
37
|
+
- Cross-ORM operational rules and official vendor references: `resources/orm-reference.md`
|
|
38
|
+
- Primary references covered there: Prisma, SQLAlchemy, TypeORM, Sequelize, Hibernate
|
|
39
|
+
|
|
40
|
+
## Optional Serena examples
|
|
41
|
+
|
|
42
|
+
These examples apply only when Serena is the configured provider and its tools are available. Otherwise use the project-selected provider or native search under the shared code-intelligence contract.
|
|
43
|
+
- `find_symbol("create_todo")`: Locate existing function
|
|
44
|
+
- `get_symbols_overview("app/api")`: List all endpoints
|
|
45
|
+
- `find_referencing_symbols("User")`: Find all usages of a model
|