@ngockhoale/ukit 1.1.6
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/CHANGELOG.md +179 -0
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/bin/ukit +30 -0
- package/manifests/platform.full.yaml +1194 -0
- package/package.json +71 -0
- package/scripts/bug/triage.mjs +37 -0
- package/scripts/index/build-index.mjs +35 -0
- package/scripts/index/query-index.mjs +92 -0
- package/scripts/index/refresh-index.mjs +85 -0
- package/scripts/release/verify-release.mjs +56 -0
- package/src/bug/triageBug.js +123 -0
- package/src/cli/adapters.js +148 -0
- package/src/cli/commands/diff.js +51 -0
- package/src/cli/commands/doctor.js +125 -0
- package/src/cli/commands/indexArgs.js +73 -0
- package/src/cli/commands/indexTools.js +509 -0
- package/src/cli/commands/install.js +293 -0
- package/src/cli/commands/memory.js +126 -0
- package/src/cli/commands/status.js +8 -0
- package/src/cli/commands/uninstall.js +51 -0
- package/src/cli/index.js +109 -0
- package/src/context/detectProjectContext.js +49 -0
- package/src/context/detectProviders.js +12 -0
- package/src/core/applyPlan.js +89 -0
- package/src/core/buildPlan.js +228 -0
- package/src/core/compact/index.js +294 -0
- package/src/core/compact/threshold.js +936 -0
- package/src/core/diffPlan.js +73 -0
- package/src/core/ensureGitignore.js +117 -0
- package/src/core/fileOps.js +188 -0
- package/src/core/memory/hygiene.js +160 -0
- package/src/core/memory/index.js +2 -0
- package/src/core/memory/retrieval.js +476 -0
- package/src/core/memory/store.js +202 -0
- package/src/core/metadata.js +132 -0
- package/src/core/migrateLegacy.js +139 -0
- package/src/core/output/index.js +1309 -0
- package/src/core/paths.js +13 -0
- package/src/core/report.js +17 -0
- package/src/core/router/advisor.js +42 -0
- package/src/core/router/index.js +2 -0
- package/src/core/router/router.js +164 -0
- package/src/core/runInstallPipeline.js +365 -0
- package/src/core/runtimeConfig.js +190 -0
- package/src/core/runtimePaths.js +24 -0
- package/src/core/status.js +186 -0
- package/src/core/token/index.js +328 -0
- package/src/core/uninstall.js +246 -0
- package/src/core/validation/confidence.js +89 -0
- package/src/core/validation/index.js +2 -0
- package/src/core/validation/validator.js +165 -0
- package/src/index/buildIndex.js +1392 -0
- package/src/index/gitHooks.js +109 -0
- package/src/index/importResolution.js +377 -0
- package/src/index/languageTools.js +127 -0
- package/src/index/paths.js +27 -0
- package/src/index/queryIndex.js +637 -0
- package/src/index/relatedTests.js +237 -0
- package/src/index/resolveContext.js +345 -0
- package/src/index/routeCatalog.js +258 -0
- package/src/index/taskRouting.js +677 -0
- package/src/index/verificationPlan.js +437 -0
- package/src/manifest/loadManifest.js +22 -0
- package/src/manifest/selectItems.js +78 -0
- package/src/manifest/validateManifest.js +115 -0
- package/src/render/buildVariables.js +39 -0
- package/src/render/renderTemplate.js +44 -0
- package/src/stack/detectStack.js +213 -0
- package/templates/.claude/agents/bug-debugger.md +57 -0
- package/templates/.claude/agents/feature-implementer.md +55 -0
- package/templates/.claude/config/providers.md +25 -0
- package/templates/.claude/hooks/auto-allow-bash.sh +155 -0
- package/templates/.claude/hooks/auto-prune-bash.sh +75 -0
- package/templates/.claude/hooks/block-dangerous.sh +54 -0
- package/templates/.claude/hooks/compress-output.sh +17 -0
- package/templates/.claude/hooks/protect-files.sh +37 -0
- package/templates/.claude/hooks/reinject-context.sh +28 -0
- package/templates/.claude/hooks/session-start.md +13 -0
- package/templates/.claude/hooks/skill-router.sh +1681 -0
- package/templates/.claude/hooks/verification-guard.sh +271 -0
- package/templates/.claude/settings.json +144 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/mce/mc.xsd +75 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/pack.py +159 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/unpack.py +29 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validate.py +69 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/__init__.py +15 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/base.py +951 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/docx.py +274 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/pptx.py +315 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/redlining.py +279 -0
- package/templates/.claude/skills/backend-api/SKILL.md +26 -0
- package/templates/.claude/skills/canvas-design/LICENSE.txt +202 -0
- package/templates/.claude/skills/canvas-design/SKILL.md +130 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/templates/.claude/skills/code-review/SKILL.md +97 -0
- package/templates/.claude/skills/debugging-toolkit/SKILL.md +156 -0
- package/templates/.claude/skills/delivery/SKILL.md +92 -0
- package/templates/.claude/skills/discover-security/SKILL.md +86 -0
- package/templates/.claude/skills/docker-packaging/SKILL.md +60 -0
- package/templates/.claude/skills/docs-manager/SKILL.md +465 -0
- package/templates/.claude/skills/docs-manager/init-project-docs.sh +70 -0
- package/templates/.claude/skills/docs-manager/templates/README.md.template +50 -0
- package/templates/.claude/skills/docs-manager/templates/agent-roles.md.template +24 -0
- package/templates/.claude/skills/docs-manager/templates/coding-conventions.md.template +28 -0
- package/templates/.claude/skills/docs-manager/templates/memory.md.template +30 -0
- package/templates/.claude/skills/docs-manager/templates/onboarding.md.template +20 -0
- package/templates/.claude/skills/docs-manager/templates/project.md.template +26 -0
- package/templates/.claude/skills/docs-quality/SKILL.md +148 -0
- package/templates/.claude/skills/docx/LICENSE.txt +30 -0
- package/templates/.claude/skills/docx/SKILL.md +197 -0
- package/templates/.claude/skills/docx/docx-js.md +350 -0
- package/templates/.claude/skills/docx/ooxml.md +610 -0
- package/templates/.claude/skills/docx/scripts/__init__.py +1 -0
- package/templates/.claude/skills/docx/scripts/document.py +1276 -0
- package/templates/.claude/skills/docx/scripts/templates/comments.xml +3 -0
- package/templates/.claude/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/templates/.claude/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/templates/.claude/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/templates/.claude/skills/docx/scripts/templates/people.xml +3 -0
- package/templates/.claude/skills/docx/scripts/utilities.py +374 -0
- package/templates/.claude/skills/duraone/SKILL.md +204 -0
- package/templates/.claude/skills/duraone/references/backend.md +636 -0
- package/templates/.claude/skills/duraone/references/frontend.md +1506 -0
- package/templates/.claude/skills/duraone/references/sql.md +631 -0
- package/templates/.claude/skills/duraone/references/workflow.md +520 -0
- package/templates/.claude/skills/executing-plans/SKILL.md +76 -0
- package/templates/.claude/skills/file-organizer/SKILL.md +433 -0
- package/templates/.claude/skills/frontend/SKILL.md +26 -0
- package/templates/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/templates/.claude/skills/frontend-design/SKILL.md +42 -0
- package/templates/.claude/skills/frontend-vue/SKILL.md +127 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Box.vue +137 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Button.vue +93 -0
- package/templates/.claude/skills/frontend-vue/components/Control/ButtonBar.vue +29 -0
- package/templates/.claude/skills/frontend-vue/components/Control/ButtonFloat.vue +62 -0
- package/templates/.claude/skills/frontend-vue/components/Control/CheckButton.vue +75 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Checkbox.vue +58 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Datetime.vue +148 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Dropdownlist.vue +156 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Input.vue +106 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Label.vue +38 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Master/BoxColumn.vue +24 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Popup/Confirm.vue +33 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Popup/Info.vue +32 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Popup/ModalInfo.vue +39 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Popup/Reject.vue +64 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Tag.vue +82 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Upload.vue +61 -0
- package/templates/.claude/skills/frontend-vue/components/ControlMobile/Dropdownlist.vue +103 -0
- package/templates/.claude/skills/frontend-vue/components/ControlMobile/PagingBar.vue +108 -0
- package/templates/.claude/skills/frontend-vue/components/ControlMobile/UploadImage.vue +137 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/AG.vue +806 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/AntTable.vue +253 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/CustomDropdownEditor.vue +43 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/CustomDropdownEditorEnable.vue +55 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/HtmlTable.vue +40 -0
- package/templates/.claude/skills/frontend-vue/components/PDFViewer.vue +25 -0
- package/templates/.claude/skills/frontend-vue/components/Panel/FormView.vue +309 -0
- package/templates/.claude/skills/frontend-vue/components/Partial/Footer.vue +23 -0
- package/templates/.claude/skills/frontend-vue/components/Partial/Header.vue +265 -0
- package/templates/.claude/skills/frontend-vue/components/Partial/Sidebar.vue +122 -0
- package/templates/.claude/skills/frontend-vue/components/Template.vue +16 -0
- package/templates/.claude/skills/frontend-vue/components/View/Form.vue +89 -0
- package/templates/.claude/skills/frontend-vue/composables/indexDBStore.js +140 -0
- package/templates/.claude/skills/frontend-vue/composables/masterApi.js +362 -0
- package/templates/.claude/skills/frontend-vue/composables/state.js +578 -0
- package/templates/.claude/skills/frontend-vue/composables/useRequest.js +221 -0
- package/templates/.claude/skills/frontend-vue/composables/useSession.js +179 -0
- package/templates/.claude/skills/frontend-vue/composables/useTranslation.js +54 -0
- package/templates/.claude/skills/frontend-vue/composables/useWebSocket.js +257 -0
- package/templates/.claude/skills/frontend-vue/composables/userObj.js +111 -0
- package/templates/.claude/skills/frontend-vue/composables/utils.js +322 -0
- package/templates/.claude/skills/frontend-vue/reference/composables-example.vue +320 -0
- package/templates/.claude/skills/frontend-vue/reference/form-example.vue +183 -0
- package/templates/.claude/skills/frontend-vue/reference/grid-example.vue +147 -0
- package/templates/.claude/skills/frontend-vue/reference/masterdata-example/[id].vue +106 -0
- package/templates/.claude/skills/frontend-vue/reference/masterdata-example/index.vue +58 -0
- package/templates/.claude/skills/frontend-vue/reference/popup-example.vue +159 -0
- package/templates/.claude/skills/pdf/LICENSE.txt +30 -0
- package/templates/.claude/skills/pdf/SKILL.md +294 -0
- package/templates/.claude/skills/pdf/forms.md +205 -0
- package/templates/.claude/skills/pdf/reference.md +612 -0
- package/templates/.claude/skills/pdf/scripts/check_bounding_boxes.py +70 -0
- package/templates/.claude/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
- package/templates/.claude/skills/pdf/scripts/check_fillable_fields.py +12 -0
- package/templates/.claude/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
- package/templates/.claude/skills/pdf/scripts/create_validation_image.py +41 -0
- package/templates/.claude/skills/pdf/scripts/extract_form_field_info.py +152 -0
- package/templates/.claude/skills/pdf/scripts/fill_fillable_fields.py +114 -0
- package/templates/.claude/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/templates/.claude/skills/pdf-processing/SKILL.md +107 -0
- package/templates/.claude/skills/pdf-processing-pro/FORMS.md +610 -0
- package/templates/.claude/skills/pdf-processing-pro/OCR.md +137 -0
- package/templates/.claude/skills/pdf-processing-pro/SKILL.md +296 -0
- package/templates/.claude/skills/pdf-processing-pro/TABLES.md +626 -0
- package/templates/.claude/skills/pdf-processing-pro/scripts/analyze_form.py +307 -0
- package/templates/.claude/skills/postgres/SKILL.md +69 -0
- package/templates/.claude/skills/postgres/reference/fn_get_examples.sql +208 -0
- package/templates/.claude/skills/postgres/reference/fn_rpt_examples.sql +239 -0
- package/templates/.claude/skills/postgres/reference/utility_functions.sql +94 -0
- package/templates/.claude/skills/pptx/LICENSE.txt +30 -0
- package/templates/.claude/skills/pptx/SKILL.md +484 -0
- package/templates/.claude/skills/pptx/html2pptx.md +625 -0
- package/templates/.claude/skills/pptx/ooxml.md +427 -0
- package/templates/.claude/skills/pptx/scripts/html2pptx.js +979 -0
- package/templates/.claude/skills/pptx/scripts/inventory.py +1020 -0
- package/templates/.claude/skills/pptx/scripts/rearrange.py +231 -0
- package/templates/.claude/skills/pptx/scripts/replace.py +385 -0
- package/templates/.claude/skills/pptx/scripts/thumbnail.py +450 -0
- package/templates/.claude/skills/repo-maintenance/SKILL.md +97 -0
- package/templates/.claude/skills/research/EXAMPLES.md +434 -0
- package/templates/.claude/skills/research/REFERENCE.md +399 -0
- package/templates/.claude/skills/research/SKILL.md +136 -0
- package/templates/.claude/skills/root-cause-tracing/SKILL.md +174 -0
- package/templates/.claude/skills/root-cause-tracing/find-polluter.sh +63 -0
- package/templates/.claude/skills/sharing-skills/SKILL.md +194 -0
- package/templates/.claude/skills/sql-optimization-patterns/SKILL.md +493 -0
- package/templates/.claude/skills/subagent-driven-development/SKILL.md +189 -0
- package/templates/.claude/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/templates/.claude/skills/systematic-debugging/SKILL.md +295 -0
- package/templates/.claude/skills/systematic-debugging/test-academic.md +14 -0
- package/templates/.claude/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/templates/.claude/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/templates/.claude/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/templates/.claude/skills/test-driven-development/SKILL.md +364 -0
- package/templates/.claude/skills/testing-anti-patterns/SKILL.md +302 -0
- package/templates/.claude/skills/testing-quality/SKILL.md +97 -0
- package/templates/.claude/skills/verification-before-completion/SKILL.md +139 -0
- package/templates/.claude/skills/webapp-testing/LICENSE.txt +202 -0
- package/templates/.claude/skills/webapp-testing/SKILL.md +96 -0
- package/templates/.claude/skills/webapp-testing/examples/console_logging.py +35 -0
- package/templates/.claude/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/templates/.claude/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/templates/.claude/skills/webapp-testing/scripts/with_server.py +106 -0
- package/templates/.claude/ukit/index/build-index.mjs +28 -0
- package/templates/.claude/ukit/index/cache-utils.mjs +140 -0
- package/templates/.claude/ukit/index/lib/index-core.mjs +2800 -0
- package/templates/.claude/ukit/index/query-index.mjs +150 -0
- package/templates/.claude/ukit/index/refresh-index.mjs +57 -0
- package/templates/.claude/ukit/index/reset-auto-permissions.mjs +76 -0
- package/templates/.claude/ukit/index/resolve-context.mjs +279 -0
- package/templates/.claude/ukit/index/route-catalog.mjs +258 -0
- package/templates/.claude/ukit/index/route-task.mjs +1994 -0
- package/templates/.claude/ukit/index/triage.mjs +133 -0
- package/templates/.claude/ukit/index/verify-context.mjs +689 -0
- package/templates/.claude/ukit/runtime/compact-threshold.mjs +1013 -0
- package/templates/.claude/ukit/runtime/output-compression.mjs +1340 -0
- package/templates/.claude/ukit/runtime/reinject-context.mjs +874 -0
- package/templates/.claude/ukit/runtime/token-utils.mjs +500 -0
- package/templates/.codex/README.md +83 -0
- package/templates/.codex/settings.json +187 -0
- package/templates/.gitignore +75 -0
- package/templates/AGENTS.md +116 -0
- package/templates/CLAUDE.md +93 -0
- package/templates/adapter-presets/antigravity/README.md +22 -0
- package/templates/adapter-presets/antigravity/rules.md +49 -0
- package/templates/adapter-presets/claude/settings.local.json +42 -0
- package/templates/adapter-presets/codex/settings.local.json +6 -0
- package/templates/adapter-presets/opencode/opencode.template.json +1 -0
- package/templates/docs/BUGFIX.md +20 -0
- package/templates/docs/BUG_INDEX.md +12 -0
- package/templates/docs/BUG_METRICS.md +7 -0
- package/templates/docs/BUG_TEMPLATE.md +13 -0
- package/templates/docs/CODE_MAP.md +35 -0
- package/templates/docs/INSTALL.md +113 -0
- package/templates/docs/MEMORY.md +49 -0
- package/templates/docs/PROJECT.md +50 -0
- package/templates/docs/UKIT_USAGE_GUIDE.md +147 -0
- package/templates/docs/WORKLOG.md +10 -0
- package/templates/ukit/README.md +14 -0
- package/templates/ukit/storage/cache/compact-history.json +3 -0
- package/templates/ukit/storage/cache/compact-pressure.json +1 -0
- package/templates/ukit/storage/cache/output-history.json +3 -0
- package/templates/ukit/storage/cache/prompt-cache.json +3 -0
- package/templates/ukit/storage/config.json +37 -0
- package/templates/ukit/storage/memory/projects/.gitkeep +2 -0
- package/templates/ukit/storage/memory/sessions/.gitkeep +0 -0
- package/templates/ukit/storage/memory/user.json +5 -0
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
# DuraOne Backend Reference
|
|
2
|
+
## Python Sanic + PostgreSQL (asyncpg)
|
|
3
|
+
|
|
4
|
+
> Nguồn sự thật cho phong cách code backend của LeNK.
|
|
5
|
+
> Stack: Python, Sanic (async web framework), asyncpg, psycopg2+pandas
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## MỤC LỤC
|
|
10
|
+
1. [Cấu trúc thư mục BE](#1-cấu-trúc-thư-mục-be)
|
|
11
|
+
2. [Sanic App Setup (app.py)](#2-sanic-app-setup)
|
|
12
|
+
3. [Blueprint API Pattern](#3-blueprint-api-pattern)
|
|
13
|
+
4. [DB Helper — Async (v2_db.py)](#4-db-helper--async)
|
|
14
|
+
5. [DB Helper — Sync (db.py)](#5-db-helper--sync)
|
|
15
|
+
6. [Generic Endpoints (đã có sẵn)](#6-generic-endpoints)
|
|
16
|
+
7. [Custom Endpoint Pattern](#7-custom-endpoint-pattern)
|
|
17
|
+
8. [Config Management](#8-config-management)
|
|
18
|
+
9. [Naming Conventions](#9-naming-conventions)
|
|
19
|
+
10. [Error Handling](#10-error-handling)
|
|
20
|
+
11. [Deploy](#11-deploy)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. Cấu Trúc Thư Mục BE
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
BE_DuraOne/
|
|
28
|
+
├── app.py # Main Sanic application entry point
|
|
29
|
+
├── mainapp/
|
|
30
|
+
│ ├── __init__.py
|
|
31
|
+
│ ├── config.py # Config loader (qas/prd/local)
|
|
32
|
+
│ ├── helper.py # Helper functions
|
|
33
|
+
│ └── lib/
|
|
34
|
+
│ ├── api.py # API routes (sync version)
|
|
35
|
+
│ ├── v2_api.py # API routes (async version — dùng cái này)
|
|
36
|
+
│ ├── db.py # DB_Helper class (sync, psycopg2 + pandas)
|
|
37
|
+
│ ├── v2_db.py # DB_Helper class (async, asyncpg)
|
|
38
|
+
│ ├── compress.py # GZIP compression
|
|
39
|
+
│ ├── google_storage.py # GCS file operations
|
|
40
|
+
│ ├── pandas_manage.py # Pandas utilities
|
|
41
|
+
│ ├── string_manage.py # String utilities
|
|
42
|
+
│ ├── log_manage.py # Logging
|
|
43
|
+
│ ├── pdf_manage.py # PDF generation
|
|
44
|
+
│ ├── file_handle.py # File operations
|
|
45
|
+
│ ├── chatgpt_func.py # OpenAI integration
|
|
46
|
+
│ └── send_noti.py # Notifications (Line/email)
|
|
47
|
+
│
|
|
48
|
+
├── mainapp/SQL/
|
|
49
|
+
│ └── duraone/
|
|
50
|
+
│ ├── qas/ # QA/testing schema SQL
|
|
51
|
+
│ │ ├── view/ # v_*.sql files
|
|
52
|
+
│ │ ├── function/ # fn_*.sql files
|
|
53
|
+
│ │ ├── store/ # sp_*.sql files
|
|
54
|
+
│ │ └── [feature]/ # feature-specific SQL
|
|
55
|
+
│ └── prd/ # Production schema SQL (mirrors qas)
|
|
56
|
+
│
|
|
57
|
+
├── config/
|
|
58
|
+
│ ├── db_ssl/ # PostgreSQL SSL certificates
|
|
59
|
+
│ │ ├── ca.crt
|
|
60
|
+
│ │ ├── client.crt
|
|
61
|
+
│ │ └── client.key
|
|
62
|
+
│ ├── fonts/
|
|
63
|
+
│ └── config.ini # DB credentials, paths
|
|
64
|
+
│
|
|
65
|
+
├── duraone_m/ # Mobile app static files
|
|
66
|
+
├── Dockerfile
|
|
67
|
+
├── docker-compose.yml
|
|
68
|
+
├── deploy_qas.sh
|
|
69
|
+
└── deploy_prd.sh
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 2. Sanic App Setup
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
# app.py
|
|
78
|
+
from sanic import Sanic
|
|
79
|
+
from sanic_cors import CORS
|
|
80
|
+
from mainapp.lib.v2_api import b_api
|
|
81
|
+
from mainapp.lib.v2_db import DB_Helper
|
|
82
|
+
|
|
83
|
+
app = Sanic(__name__)
|
|
84
|
+
CORS(app, resources={r"/*": {"origins": [
|
|
85
|
+
"http://localhost:3000",
|
|
86
|
+
"https://your-domain.com",
|
|
87
|
+
]}})
|
|
88
|
+
|
|
89
|
+
# Register blueprint
|
|
90
|
+
app.blueprint(b_api)
|
|
91
|
+
|
|
92
|
+
# DB connection pool setup
|
|
93
|
+
db = DB_Helper()
|
|
94
|
+
|
|
95
|
+
@app.listener("before_server_start")
|
|
96
|
+
async def setup_db(app, loop):
|
|
97
|
+
await db.connect_db()
|
|
98
|
+
|
|
99
|
+
@app.listener("after_server_stop")
|
|
100
|
+
async def close_db(app, loop):
|
|
101
|
+
if db._pool:
|
|
102
|
+
await db._pool.close()
|
|
103
|
+
|
|
104
|
+
# Security headers
|
|
105
|
+
@app.middleware("response")
|
|
106
|
+
async def set_secure_headers(request, response):
|
|
107
|
+
response.headers["X-Content-Type-Options"] = "nosniff"
|
|
108
|
+
response.headers["X-Frame-Options"] = "SAMEORIGIN"
|
|
109
|
+
|
|
110
|
+
# Static files
|
|
111
|
+
PATH = os.path.dirname(os.path.abspath(__file__))
|
|
112
|
+
app.static("/duraone_m", PATH + "/duraone_m")
|
|
113
|
+
app.static("/static", PATH + "/www/static")
|
|
114
|
+
|
|
115
|
+
# SPA fallback — serve index.html for all FE routes
|
|
116
|
+
@app.route("/<path:path>", methods=["GET"])
|
|
117
|
+
async def index(request, path):
|
|
118
|
+
return await response.file(PATH + "/www/index.html")
|
|
119
|
+
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
app.run(host="0.0.0.0", port=8000, workers=4, debug=False)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 3. Blueprint API Pattern
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# mainapp/lib/v2_api.py
|
|
130
|
+
from sanic import Blueprint, response
|
|
131
|
+
from mainapp.lib.v2_db import DB_Helper
|
|
132
|
+
import json
|
|
133
|
+
|
|
134
|
+
b_api = Blueprint("b_api")
|
|
135
|
+
db = DB_Helper()
|
|
136
|
+
|
|
137
|
+
# ──────────────────────────────────────────────
|
|
138
|
+
# Pattern cho custom endpoint
|
|
139
|
+
# ──────────────────────────────────────────────
|
|
140
|
+
@b_api.route("/api/[feature]/[action]", methods=["POST", "GET"])
|
|
141
|
+
async def feature_action(request):
|
|
142
|
+
try:
|
|
143
|
+
val = request.json or {}
|
|
144
|
+
|
|
145
|
+
# Extract params
|
|
146
|
+
id_record = val.get("id_record")
|
|
147
|
+
schema = val.get("schema", "qas")
|
|
148
|
+
|
|
149
|
+
# Business logic
|
|
150
|
+
query = f"""
|
|
151
|
+
SELECT * FROM {schema}.v_[entity]
|
|
152
|
+
WHERE id_record = $1
|
|
153
|
+
"""
|
|
154
|
+
result = await db.execute(query, (id_record,))
|
|
155
|
+
|
|
156
|
+
return response.json(result)
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
return response.json({"success": False, "message": str(e)}, status=500)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ──────────────────────────────────────────────
|
|
163
|
+
# Pattern chạy stored procedure
|
|
164
|
+
# ──────────────────────────────────────────────
|
|
165
|
+
@b_api.route("/api/run_proc", methods=["POST", "GET"])
|
|
166
|
+
async def run_proc(request):
|
|
167
|
+
try:
|
|
168
|
+
val = request.json or {}
|
|
169
|
+
schema = val.get("schema", "qas")
|
|
170
|
+
proc_name = val.get("proc_name")
|
|
171
|
+
params = val.get("params", {})
|
|
172
|
+
|
|
173
|
+
# Build param list
|
|
174
|
+
param_values = list(params.values())
|
|
175
|
+
param_placeholders = ", ".join([f"${i+1}" for i in range(len(param_values))])
|
|
176
|
+
|
|
177
|
+
query = f"SELECT * FROM {schema}.{proc_name}({param_placeholders})"
|
|
178
|
+
result = await db.execute(query, param_values)
|
|
179
|
+
|
|
180
|
+
return response.json(result)
|
|
181
|
+
|
|
182
|
+
except Exception as e:
|
|
183
|
+
return response.json({"success": False, "message": str(e)}, status=500)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 4. DB Helper — Async
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
# mainapp/lib/v2_db.py
|
|
192
|
+
import asyncpg
|
|
193
|
+
import json
|
|
194
|
+
from decimal import Decimal
|
|
195
|
+
from mainapp.config import get_config
|
|
196
|
+
|
|
197
|
+
class DB_Helper:
|
|
198
|
+
_pool = None
|
|
199
|
+
|
|
200
|
+
async def connect_db(self):
|
|
201
|
+
"""Create asyncpg connection pool with SSL"""
|
|
202
|
+
config = get_config()
|
|
203
|
+
self._pool = await asyncpg.create_pool(
|
|
204
|
+
host=config["host"],
|
|
205
|
+
port=config["port"],
|
|
206
|
+
database=config["database"],
|
|
207
|
+
user=config["user"],
|
|
208
|
+
password=config["password"],
|
|
209
|
+
ssl=self._get_ssl_context(),
|
|
210
|
+
min_size=2,
|
|
211
|
+
max_size=10,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
async def execute(self, query: str, params: tuple = ()) -> list:
|
|
215
|
+
"""Execute query and return list of dicts"""
|
|
216
|
+
async with self._pool.acquire() as conn:
|
|
217
|
+
rows = await conn.fetch(query, *params)
|
|
218
|
+
result = [dict(row) for row in rows]
|
|
219
|
+
return self.convert_to_serializable(result)
|
|
220
|
+
|
|
221
|
+
async def execute_one(self, query: str, params: tuple = ()):
|
|
222
|
+
"""Execute query and return single dict or None"""
|
|
223
|
+
result = await self.execute(query, params)
|
|
224
|
+
return result[0] if result else None
|
|
225
|
+
|
|
226
|
+
async def execute_void(self, query: str, params: tuple = ()):
|
|
227
|
+
"""Execute INSERT/UPDATE/DELETE with no return"""
|
|
228
|
+
async with self._pool.acquire() as conn:
|
|
229
|
+
await conn.execute(query, *params)
|
|
230
|
+
|
|
231
|
+
def convert_to_serializable(self, data):
|
|
232
|
+
"""Convert Decimal, datetime to JSON-serializable types"""
|
|
233
|
+
if isinstance(data, list):
|
|
234
|
+
return [self.convert_to_serializable(item) for item in data]
|
|
235
|
+
if isinstance(data, dict):
|
|
236
|
+
return {k: self.convert_to_serializable(v) for k, v in data.items()}
|
|
237
|
+
if isinstance(data, Decimal):
|
|
238
|
+
return float(data)
|
|
239
|
+
if hasattr(data, "isoformat"): # datetime, date
|
|
240
|
+
return data.isoformat()
|
|
241
|
+
return data
|
|
242
|
+
|
|
243
|
+
def check_form_json(self, v):
|
|
244
|
+
"""Check if string is JSON array or object"""
|
|
245
|
+
if isinstance(v, str):
|
|
246
|
+
return v.startswith("[") or v.startswith("{")
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
def _get_ssl_context(self):
|
|
250
|
+
"""Load SSL context for PostgreSQL connection"""
|
|
251
|
+
import ssl
|
|
252
|
+
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
|
253
|
+
ctx.load_cert_chain("config/db_ssl/client.crt", "config/db_ssl/client.key")
|
|
254
|
+
ctx.load_verify_locations("config/db_ssl/ca.crt")
|
|
255
|
+
return ctx
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 5. DB Helper — Sync
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
# mainapp/lib/db.py — dùng khi cần pandas hoặc sync context
|
|
264
|
+
import psycopg2 as pg
|
|
265
|
+
import pandas as pd
|
|
266
|
+
import json
|
|
267
|
+
from mainapp.config import set_cdn
|
|
268
|
+
|
|
269
|
+
class DB_Helper:
|
|
270
|
+
|
|
271
|
+
@staticmethod
|
|
272
|
+
def selectbyquery(query, params=None):
|
|
273
|
+
"""Execute SELECT, return pandas DataFrame"""
|
|
274
|
+
connection = pg.connect(set_cdn())
|
|
275
|
+
try:
|
|
276
|
+
df = pd.read_sql(query, connection, params=params)
|
|
277
|
+
# Handle nulls
|
|
278
|
+
numeric_cols = df.select_dtypes(include=["number"]).columns
|
|
279
|
+
df[numeric_cols] = df[numeric_cols].fillna(0)
|
|
280
|
+
df = df.fillna("")
|
|
281
|
+
return df.to_dict("records")
|
|
282
|
+
except Exception as e:
|
|
283
|
+
print(f"DB Error: {e}")
|
|
284
|
+
return []
|
|
285
|
+
finally:
|
|
286
|
+
connection.close()
|
|
287
|
+
|
|
288
|
+
@staticmethod
|
|
289
|
+
def insert(table_name: str, obj: dict, pk_field: str = None) -> str:
|
|
290
|
+
"""Insert record, return new id (UUID string).
|
|
291
|
+
|
|
292
|
+
⚠️ DuraOne convention: PK field là id_[entity] (e.g. id_agreement), KHÔNG phải 'id'.
|
|
293
|
+
Luôn truyền pk_field='id_agreement' (hoặc tên PK thực tế) để loại bỏ PK khỏi INSERT.
|
|
294
|
+
Nếu không truyền pk_field, PK sẽ bị include vào INSERT statement.
|
|
295
|
+
|
|
296
|
+
Note: Sync DB_Helper này dùng trong context pandas/scripts.
|
|
297
|
+
Cho generic CRUD từ FE, dùng generic /api/save endpoint (async) thay thế.
|
|
298
|
+
"""
|
|
299
|
+
connection = pg.connect(set_cdn())
|
|
300
|
+
try:
|
|
301
|
+
# Loại bỏ PK field (vì DB tự generate UUID default)
|
|
302
|
+
exclude = {pk_field} if pk_field else set()
|
|
303
|
+
keys = [k for k in obj.keys() if k not in exclude]
|
|
304
|
+
values = []
|
|
305
|
+
for k in keys:
|
|
306
|
+
v = obj[k]
|
|
307
|
+
if isinstance(v, (dict, list)):
|
|
308
|
+
v = json.dumps(v, ensure_ascii=False)
|
|
309
|
+
values.append(v)
|
|
310
|
+
|
|
311
|
+
placeholders = ", ".join(["%s"] * len(keys))
|
|
312
|
+
columns = ", ".join(keys)
|
|
313
|
+
returning = pk_field if pk_field else "id"
|
|
314
|
+
query = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders}) RETURNING {returning}"
|
|
315
|
+
|
|
316
|
+
with connection.cursor() as cursor:
|
|
317
|
+
cursor.execute(query, values)
|
|
318
|
+
new_id = cursor.fetchone()[0]
|
|
319
|
+
connection.commit()
|
|
320
|
+
return str(new_id)
|
|
321
|
+
except Exception as e:
|
|
322
|
+
connection.rollback()
|
|
323
|
+
print(f"Insert Error: {e}")
|
|
324
|
+
return ""
|
|
325
|
+
finally:
|
|
326
|
+
connection.close()
|
|
327
|
+
|
|
328
|
+
@staticmethod
|
|
329
|
+
def update(table_name: str, obj: dict, key_field: str = "id"):
|
|
330
|
+
"""Update record by key field.
|
|
331
|
+
|
|
332
|
+
⚠️ DuraOne convention: PK field là id_[entity], KHÔNG phải 'id'.
|
|
333
|
+
Luôn truyền key_field='id_agreement' (hoặc tên PK thực tế).
|
|
334
|
+
Ví dụ: DB_Helper.update("agreement", obj, key_field="id_agreement")
|
|
335
|
+
"""
|
|
336
|
+
connection = pg.connect(set_cdn())
|
|
337
|
+
try:
|
|
338
|
+
key_val = obj[key_field]
|
|
339
|
+
fields = {k: v for k, v in obj.items() if k != key_field}
|
|
340
|
+
|
|
341
|
+
set_clause = ", ".join([f"{k} = %s" for k in fields.keys()])
|
|
342
|
+
values = list(fields.values()) + [key_val]
|
|
343
|
+
|
|
344
|
+
query = f"UPDATE {table_name} SET {set_clause} WHERE {key_field} = %s"
|
|
345
|
+
|
|
346
|
+
with connection.cursor() as cursor:
|
|
347
|
+
cursor.execute(query, values)
|
|
348
|
+
connection.commit()
|
|
349
|
+
except Exception as e:
|
|
350
|
+
connection.rollback()
|
|
351
|
+
print(f"Update Error: {e}")
|
|
352
|
+
finally:
|
|
353
|
+
connection.close()
|
|
354
|
+
|
|
355
|
+
@staticmethod
|
|
356
|
+
def removebykey(table_name: str, key_field: str, key_value):
|
|
357
|
+
"""Delete record by key"""
|
|
358
|
+
connection = pg.connect(set_cdn())
|
|
359
|
+
try:
|
|
360
|
+
query = f"DELETE FROM {table_name} WHERE {key_field} = %s"
|
|
361
|
+
with connection.cursor() as cursor:
|
|
362
|
+
cursor.execute(query, (key_value,))
|
|
363
|
+
connection.commit()
|
|
364
|
+
except Exception as e:
|
|
365
|
+
connection.rollback()
|
|
366
|
+
finally:
|
|
367
|
+
connection.close()
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 6. Generic Endpoints (Đã Có Sẵn)
|
|
373
|
+
|
|
374
|
+
Frontend dùng những endpoints này — KHÔNG cần tạo thêm cho CRUD thông thường:
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
POST/GET /api/select — Query data (qua table/view + conditions)
|
|
378
|
+
POST /api/save — Insert hoặc Update (auto detect bằng id == '' → insert, id != '' → update)
|
|
379
|
+
POST /api/insert — Force insert
|
|
380
|
+
POST /api/update — Force update
|
|
381
|
+
POST /api/delete — Delete by key
|
|
382
|
+
POST/GET /api/run_proc — Run stored procedure
|
|
383
|
+
GET /api/clearcache — Clear server cache
|
|
384
|
+
POST /user/login — Authentication
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Request format cho /api/select:**
|
|
388
|
+
```json
|
|
389
|
+
{
|
|
390
|
+
"schema": "qas",
|
|
391
|
+
"table": "v_agreement",
|
|
392
|
+
"columns": "*",
|
|
393
|
+
"conditions": "{\"id_branch\": 5, \"status\": \"active\"}",
|
|
394
|
+
"order_by": ["created_at desc"],
|
|
395
|
+
"limit": 1000
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**Request format cho /api/save:**
|
|
400
|
+
```json
|
|
401
|
+
{
|
|
402
|
+
"table": "agreement",
|
|
403
|
+
"key_array": ["id_agreement"],
|
|
404
|
+
"id_agreement": "",
|
|
405
|
+
"agreement_number": "AGR-001",
|
|
406
|
+
"id_branch": 5,
|
|
407
|
+
"amount": 50000
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## 7. Custom Endpoint Pattern
|
|
414
|
+
|
|
415
|
+
Chỉ tạo custom endpoint khi có business logic đặc biệt không xử lý được bằng generic:
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
# Naming: /api/[feature]/[action]
|
|
419
|
+
@b_api.route("/api/agreement/approve", methods=["POST"])
|
|
420
|
+
async def approve_agreement(request):
|
|
421
|
+
"""Approve agreement — gửi notification + update status"""
|
|
422
|
+
try:
|
|
423
|
+
val = request.json or {}
|
|
424
|
+
schema = val.get("schema", "qas")
|
|
425
|
+
id_agreement = val.get("id_agreement")
|
|
426
|
+
# ✅ FE phải truyền: { username: state.username, ... }
|
|
427
|
+
# state.username là username của user đang login (từ session)
|
|
428
|
+
approved_by = val.get("username")
|
|
429
|
+
|
|
430
|
+
# 1. Update status
|
|
431
|
+
await db.execute_void(f"""
|
|
432
|
+
UPDATE {schema}.agreement
|
|
433
|
+
SET status = 'approved',
|
|
434
|
+
approved_by = $1,
|
|
435
|
+
approved_at = NOW()
|
|
436
|
+
WHERE id_agreement = $2
|
|
437
|
+
""", (approved_by, id_agreement))
|
|
438
|
+
|
|
439
|
+
# 2. Run stored procedure for post-processing
|
|
440
|
+
result = await db.execute(f"""
|
|
441
|
+
SELECT * FROM {schema}.sp_after_approve($1)
|
|
442
|
+
""", (id_agreement,))
|
|
443
|
+
|
|
444
|
+
# 3. Send notification
|
|
445
|
+
await send_line_notification(id_agreement, schema)
|
|
446
|
+
|
|
447
|
+
return response.json({"success": True, "data": result})
|
|
448
|
+
|
|
449
|
+
except Exception as e:
|
|
450
|
+
return response.json({"success": False, "message": str(e)}, status=500)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
# Pattern: upload file
|
|
454
|
+
@b_api.route("/api/upload_to_cloud", methods=["POST"])
|
|
455
|
+
async def upload_to_cloud(request):
|
|
456
|
+
try:
|
|
457
|
+
file = request.files.get("file")
|
|
458
|
+
folder = request.form.get("folder", "uploads")
|
|
459
|
+
schema = request.form.get("schema", "qas")
|
|
460
|
+
|
|
461
|
+
# Upload to GCS
|
|
462
|
+
from mainapp.lib.google_storage import upload_blob
|
|
463
|
+
url = await upload_blob(file.body, file.name, folder)
|
|
464
|
+
|
|
465
|
+
return response.json({"success": True, "url": url})
|
|
466
|
+
except Exception as e:
|
|
467
|
+
return response.json({"success": False, "message": str(e)}, status=500)
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## 8. Config Management
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
# mainapp/config.py
|
|
476
|
+
import configparser
|
|
477
|
+
import os
|
|
478
|
+
|
|
479
|
+
config = configparser.ConfigParser()
|
|
480
|
+
config.read(os.path.join(os.path.dirname(__file__), "../config/config.ini"))
|
|
481
|
+
|
|
482
|
+
def get_env() -> str:
|
|
483
|
+
"""Get current environment: local | qas | prd"""
|
|
484
|
+
return os.environ.get("APP_ENV", "local")
|
|
485
|
+
|
|
486
|
+
def get_config() -> dict:
|
|
487
|
+
env = get_env()
|
|
488
|
+
return {
|
|
489
|
+
"host": config[env]["DB_HOST"],
|
|
490
|
+
"port": int(config[env]["DB_PORT"]),
|
|
491
|
+
"database": config[env]["DB_NAME"],
|
|
492
|
+
"user": config[env]["DB_USER"],
|
|
493
|
+
"password": config[env]["DB_PASSWORD"],
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
def set_cdn() -> str:
|
|
497
|
+
"""Return psycopg2 connection string"""
|
|
498
|
+
c = get_config()
|
|
499
|
+
return f"host={c['host']} port={c['port']} dbname={c['database']} user={c['user']} password={c['password']} sslmode=require"
|
|
500
|
+
|
|
501
|
+
def get_schema() -> str:
|
|
502
|
+
"""Return schema name: qas | prd"""
|
|
503
|
+
env = get_env()
|
|
504
|
+
return "prd" if env == "prd" else "qas"
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
```ini
|
|
508
|
+
# config/config.ini
|
|
509
|
+
[local]
|
|
510
|
+
DB_HOST = localhost
|
|
511
|
+
DB_PORT = 5432
|
|
512
|
+
DB_NAME = duraone
|
|
513
|
+
DB_USER = postgres
|
|
514
|
+
DB_PASSWORD = password
|
|
515
|
+
|
|
516
|
+
[qas]
|
|
517
|
+
DB_HOST = <cloud-sql-host>
|
|
518
|
+
DB_PORT = 5432
|
|
519
|
+
DB_NAME = duraone
|
|
520
|
+
DB_USER = duraone_user
|
|
521
|
+
DB_PASSWORD = <secret>
|
|
522
|
+
|
|
523
|
+
[prd]
|
|
524
|
+
DB_HOST = <prd-cloud-sql-host>
|
|
525
|
+
DB_PORT = 5432
|
|
526
|
+
DB_NAME = duraone_prd
|
|
527
|
+
DB_USER = duraone_prd_user
|
|
528
|
+
DB_PASSWORD = <secret>
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## 9. Naming Conventions
|
|
534
|
+
|
|
535
|
+
```python
|
|
536
|
+
# ── Files: snake_case ──
|
|
537
|
+
# v2_api.py, v2_db.py, string_manage.py, pdf_manage.py
|
|
538
|
+
|
|
539
|
+
# ── Classes: PascalCase ──
|
|
540
|
+
class DB_Helper: ...
|
|
541
|
+
class GoogleStorage: ...
|
|
542
|
+
|
|
543
|
+
# ── Functions: snake_case ──
|
|
544
|
+
async def get_agreement_by_id(id_agreement: str): ...
|
|
545
|
+
async def run_proc(schema: str, proc_name: str): ...
|
|
546
|
+
def format_date(date_str: str) -> str: ...
|
|
547
|
+
|
|
548
|
+
# ── Variables: snake_case ──
|
|
549
|
+
id_agreement = val.get("id_agreement")
|
|
550
|
+
schema = val.get("schema", "qas")
|
|
551
|
+
result_list = []
|
|
552
|
+
|
|
553
|
+
# ── Constants: UPPER_SNAKE ──
|
|
554
|
+
DEFAULT_SCHEMA = "qas"
|
|
555
|
+
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
|
|
556
|
+
|
|
557
|
+
# ── Route paths: lowercase + underscore ──
|
|
558
|
+
# /api/agreement/get_list
|
|
559
|
+
# /api/clearance/approve
|
|
560
|
+
# /api/upload_to_cloud
|
|
561
|
+
|
|
562
|
+
# ── Blueprint name ──
|
|
563
|
+
b_api = Blueprint("b_api")
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
## 10. Error Handling
|
|
569
|
+
|
|
570
|
+
```python
|
|
571
|
+
# Pattern chuẩn cho mọi endpoint
|
|
572
|
+
@b_api.route("/api/feature/action", methods=["POST"])
|
|
573
|
+
async def endpoint(request):
|
|
574
|
+
try:
|
|
575
|
+
val = request.json or {}
|
|
576
|
+
# ... logic ...
|
|
577
|
+
return response.json(result)
|
|
578
|
+
|
|
579
|
+
except ValueError as e:
|
|
580
|
+
# Input validation error
|
|
581
|
+
return response.json({"success": False, "message": str(e)}, status=400)
|
|
582
|
+
except Exception as e:
|
|
583
|
+
# Unexpected error — log và trả về 500
|
|
584
|
+
print(f"[ERROR] endpoint: {e}")
|
|
585
|
+
return response.json({"success": False, "message": "Internal server error"}, status=500)
|
|
586
|
+
|
|
587
|
+
# DB error handling trong DB_Helper
|
|
588
|
+
async def execute(self, query, params=()):
|
|
589
|
+
try:
|
|
590
|
+
async with self._pool.acquire() as conn:
|
|
591
|
+
rows = await conn.fetch(query, *params)
|
|
592
|
+
return [dict(row) for row in rows]
|
|
593
|
+
except asyncpg.PostgresError as e:
|
|
594
|
+
print(f"[DB ERROR] {e}")
|
|
595
|
+
return []
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
## 11. Deploy
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
# deploy_qas.sh
|
|
604
|
+
#!/bin/bash
|
|
605
|
+
export APP_ENV=qas
|
|
606
|
+
pip install -r requirements.txt
|
|
607
|
+
python app.py
|
|
608
|
+
|
|
609
|
+
# deploy_prd.sh
|
|
610
|
+
#!/bin/bash
|
|
611
|
+
export APP_ENV=prd
|
|
612
|
+
# ... same pattern
|
|
613
|
+
|
|
614
|
+
# Dockerfile pattern
|
|
615
|
+
FROM python:3.11-slim
|
|
616
|
+
WORKDIR /app
|
|
617
|
+
COPY requirements.txt .
|
|
618
|
+
RUN pip install -r requirements.txt
|
|
619
|
+
COPY . .
|
|
620
|
+
ENV APP_ENV=qas
|
|
621
|
+
CMD ["python", "app.py"]
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
```yaml
|
|
625
|
+
# docker-compose.yml pattern
|
|
626
|
+
version: "3.8"
|
|
627
|
+
services:
|
|
628
|
+
backend:
|
|
629
|
+
build: .
|
|
630
|
+
ports:
|
|
631
|
+
- "8000:8000"
|
|
632
|
+
environment:
|
|
633
|
+
- APP_ENV=qas
|
|
634
|
+
volumes:
|
|
635
|
+
- ./config:/app/config
|
|
636
|
+
```
|