camply-web 2.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- camply/__about__.py +18 -0
- camply/__init__.py +1 -0
- camply/cli/__init__.py +1 -0
- camply/cli/__main__.py +6 -0
- camply/cli/healthcheck.py +184 -0
- camply/cli/main.py +235 -0
- camply/cli/nginx/nginx.conf +25 -0
- camply/cli/nginx/server.conf +64 -0
- camply/cli/runtime.py +14 -0
- camply/db/README.md +50 -0
- camply/db/__init__.py +15 -0
- camply/db/alembic.ini +47 -0
- camply/db/availability.py +68 -0
- camply/db/config.py +159 -0
- camply/db/data/__init__.py +0 -0
- camply/db/data/providers.py +112 -0
- camply/db/eligibility.py +37 -0
- camply/db/migration.py +40 -0
- camply/db/migrations/__init__.py +1 -0
- camply/db/migrations/env.py +75 -0
- camply/db/migrations/script.py.mako +33 -0
- camply/db/migrations/versions/067d9d721253_init.py +65 -0
- camply/db/migrations/versions/09367969f1a4_preserve_campsite_details.py +43 -0
- camply/db/migrations/versions/2ac874f9d2ad_indexed_native_campground_search.py +44 -0
- camply/db/migrations/versions/36b6b307eef5_add_checklist_models.py +154 -0
- camply/db/migrations/versions/36dbff1f679f_user_password_credentials.py +44 -0
- camply/db/migrations/versions/4cedc8e5fa20_search.py +58 -0
- camply/db/migrations/versions/4d64e0e4ae18_merge_standalone_delivery_with_current_.py +31 -0
- camply/db/migrations/versions/624a4720b8c3_locations.py +107 -0
- camply/db/migrations/versions/77984a1ae368_merge_checklist_and_access_request.py +29 -0
- camply/db/migrations/versions/8a1b2c3d4e5f_admin_ui.py +99 -0
- camply/db/migrations/versions/9b2c3d4e5f6a_enriched_search.py +134 -0
- camply/db/migrations/versions/9b94e557767a_seed_reservecalifornia_provider.py +67 -0
- camply/db/migrations/versions/__init__.py +1 -0
- camply/db/migrations/versions/access_request.py +46 -0
- camply/db/migrations/versions/d8923fc28e9e_city.py +40 -0
- camply/db/migrations/versions/ea6111bf3bed_seed_verified_usedirect_providers.py +124 -0
- camply/db/migrations/versions/ed55b53b0cda_merge_admin_ui_and_usedirect_providers.py +29 -0
- camply/db/migrations/versions/ef2486672984_notification_outbox.py +79 -0
- camply/db/models/__init__.py +31 -0
- camply/db/models/access_request.py +40 -0
- camply/db/models/admin_audit.py +53 -0
- camply/db/models/base.py +11 -0
- camply/db/models/campgrounds.py +74 -0
- camply/db/models/notification_outbox.py +68 -0
- camply/db/models/providers.py +46 -0
- camply/db/models/recreation_area.py +58 -0
- camply/db/models/scan_results.py +56 -0
- camply/db/models/search.py +693 -0
- camply/db/models/unique_targets.py +98 -0
- camply/db/models/user_scans.py +60 -0
- camply/db/models/users.py +72 -0
- camply/db/search_bounds.py +49 -0
- camply/db/search_maps.py +301 -0
- camply/db/utils.py +31 -0
- camply/providers/__init__.py +35 -0
- camply/providers/base.py +752 -0
- camply/providers/cli.py +58 -0
- camply/providers/dto.py +35 -0
- camply/providers/errors.py +28 -0
- camply/providers/recreation_gov/__init__.py +0 -0
- camply/providers/recreation_gov/config.py +74 -0
- camply/providers/recreation_gov/models/__init__.py +0 -0
- camply/providers/recreation_gov/models/address.py +46 -0
- camply/providers/recreation_gov/models/api.py +110 -0
- camply/providers/recreation_gov/models/campgrounds.py +93 -0
- camply/providers/recreation_gov/models/enums.py +64 -0
- camply/providers/recreation_gov/models/recreation_area.py +86 -0
- camply/providers/recreation_gov/provider.py +505 -0
- camply/providers/usedirect/__init__.py +5 -0
- camply/providers/usedirect/models/__init__.py +1 -0
- camply/providers/usedirect/models/api.py +210 -0
- camply/providers/usedirect/models/metadata.py +27 -0
- camply/providers/usedirect/provider.py +257 -0
- camply/providers/usedirect/variations.py +158 -0
- camply/providers/utils.py +60 -0
- camply/py.typed +0 -0
- camply/server/__about__.py +5 -0
- camply/server/__init__.py +7 -0
- camply/server/_prometheus.py +310 -0
- camply/server/app.py +174 -0
- camply/server/auth.py +414 -0
- camply/server/config.py +132 -0
- camply/server/dependencies.py +12 -0
- camply/server/gunicorn.py +58 -0
- camply/server/metrics.py +37 -0
- camply/server/models/__init__.py +0 -0
- camply/server/models/campgrounds.py +37 -0
- camply/server/openapi.py +20 -0
- camply/server/provision.py +64 -0
- camply/server/py.typed +0 -0
- camply/server/routers/__init__.py +0 -0
- camply/server/routers/access.py +42 -0
- camply/server/routers/admin.py +1123 -0
- camply/server/routers/auth_config.py +41 -0
- camply/server/routers/campgrounds.py +42 -0
- camply/server/routers/health.py +59 -0
- camply/server/routers/me.py +82 -0
- camply/server/routers/providers.py +62 -0
- camply/server/routers/recreation_areas.py +107 -0
- camply/server/routers/scans.py +533 -0
- camply/server/routers/search.py +330 -0
- camply/server/routers/sessions.py +165 -0
- camply/server/schemas.py +619 -0
- camply/server/services/__init__.py +1 -0
- camply/server/services/local_operations.py +143 -0
- camply/server/services/operations.py +592 -0
- camply/server/services/trends.py +871 -0
- camply/server/standalone.py +77 -0
- camply/server/static/_routes.json +5 -0
- camply/server/static/assets/index-BIr8q8mk.css +1 -0
- camply/server/static/assets/index-COizb2l4.js +997 -0
- camply/server/static/assets/maplibre-gl-worker-Cc2ivl_Q.js +10 -0
- camply/server/static/branding/camply-horizontal-dark.svg +16 -0
- camply/server/static/branding/camply-horizontal.svg +16 -0
- camply/server/static/branding/camply-icon-dark.svg +16 -0
- camply/server/static/branding/camply-icon.svg +16 -0
- camply/server/static/branding/camply-stacked-dark.svg +16 -0
- camply/server/static/branding/camply-stacked.svg +16 -0
- camply/server/static/branding/camply.png +0 -0
- camply/server/static/branding/justin-flannery.png +0 -0
- camply/server/static/branding/recreation-gov.png +0 -0
- camply/server/static/docs/404.html +24 -0
- camply/server/static/docs/CHECKLIST.html +27 -0
- camply/server/static/docs/CONTRIBUTING.html +92 -0
- camply/server/static/docs/PLAN.html +27 -0
- camply/server/static/docs/architecture/ARCHITECTURE_DEEP_DIVE.html +43 -0
- camply/server/static/docs/architecture/DESIGN_API.html +55 -0
- camply/server/static/docs/architecture/DESIGN_DATA.html +27 -0
- camply/server/static/docs/architecture/DESIGN_FRONTEND.html +27 -0
- camply/server/static/docs/architecture/DESIGN_NOTIFICATIONS.html +39 -0
- camply/server/static/docs/architecture/DESIGN_PROVIDERS.html +62 -0
- camply/server/static/docs/assets/CHECKLIST.md.hWdSUfeD.js +1 -0
- camply/server/static/docs/assets/CHECKLIST.md.hWdSUfeD.lean.js +1 -0
- camply/server/static/docs/assets/CONTRIBUTING.md.C_0dYw2p.js +66 -0
- camply/server/static/docs/assets/CONTRIBUTING.md.C_0dYw2p.lean.js +1 -0
- camply/server/static/docs/assets/PLAN.md.BIejwBCQ.js +1 -0
- camply/server/static/docs/assets/PLAN.md.BIejwBCQ.lean.js +1 -0
- camply/server/static/docs/assets/app-docs.DXQE_AMG.png +0 -0
- camply/server/static/docs/assets/app-footer-mobile.B_7q9zSX.png +0 -0
- camply/server/static/docs/assets/app-footer.BKzirxoT.png +0 -0
- camply/server/static/docs/assets/app.BIBn1_0C.js +1 -0
- camply/server/static/docs/assets/architecture_ARCHITECTURE_DEEP_DIVE.md.qR2FYBxC.js +17 -0
- camply/server/static/docs/assets/architecture_ARCHITECTURE_DEEP_DIVE.md.qR2FYBxC.lean.js +1 -0
- camply/server/static/docs/assets/architecture_DESIGN_API.md.BiYvEFqk.js +29 -0
- camply/server/static/docs/assets/architecture_DESIGN_API.md.BiYvEFqk.lean.js +1 -0
- camply/server/static/docs/assets/architecture_DESIGN_DATA.md.Dv5xEwch.js +1 -0
- camply/server/static/docs/assets/architecture_DESIGN_DATA.md.Dv5xEwch.lean.js +1 -0
- camply/server/static/docs/assets/architecture_DESIGN_FRONTEND.md.CLw6-eHc.js +1 -0
- camply/server/static/docs/assets/architecture_DESIGN_FRONTEND.md.CLw6-eHc.lean.js +1 -0
- camply/server/static/docs/assets/architecture_DESIGN_NOTIFICATIONS.md.DTRN1Z6F.js +13 -0
- camply/server/static/docs/assets/architecture_DESIGN_NOTIFICATIONS.md.DTRN1Z6F.lean.js +1 -0
- camply/server/static/docs/assets/architecture_DESIGN_PROVIDERS.md.C2mgptbF.js +36 -0
- camply/server/static/docs/assets/architecture_DESIGN_PROVIDERS.md.C2mgptbF.lean.js +1 -0
- camply/server/static/docs/assets/camply.svg +17 -0
- camply/server/static/docs/assets/chunks/@localSearchIndexroot.CxBSeztH.js +1 -0
- camply/server/static/docs/assets/chunks/VPLocalSearchBox.CdPfQuEa.js +9 -0
- camply/server/static/docs/assets/chunks/framework.BM5YPQmL.js +20 -0
- camply/server/static/docs/assets/chunks/theme.DsmUp_5E.js +2 -0
- camply/server/static/docs/assets/configuration.md.CTjuVFBs.js +4 -0
- camply/server/static/docs/assets/configuration.md.CTjuVFBs.lean.js +1 -0
- camply/server/static/docs/assets/desktop.7jWU1gTt.png +0 -0
- camply/server/static/docs/assets/development.md.BvyDMyRv.js +7 -0
- camply/server/static/docs/assets/development.md.BvyDMyRv.lean.js +1 -0
- camply/server/static/docs/assets/development_AGENT_TROUBLESHOOTING.md.CJLWPYds.js +1 -0
- camply/server/static/docs/assets/development_AGENT_TROUBLESHOOTING.md.CJLWPYds.lean.js +1 -0
- camply/server/static/docs/assets/development_DESIGN_AGENTIC.md.CctuI_eG.js +1 -0
- camply/server/static/docs/assets/development_DESIGN_AGENTIC.md.CctuI_eG.lean.js +1 -0
- camply/server/static/docs/assets/development_DEVELOPER_GUIDE.md.GAkSFhfy.js +8 -0
- camply/server/static/docs/assets/development_DEVELOPER_GUIDE.md.GAkSFhfy.lean.js +1 -0
- camply/server/static/docs/assets/development_STYLE_GUIDE.md.5In2cXG3.js +1 -0
- camply/server/static/docs/assets/development_STYLE_GUIDE.md.5In2cXG3.lean.js +1 -0
- camply/server/static/docs/assets/index.md.mtfxlh4P.js +1 -0
- camply/server/static/docs/assets/index.md.mtfxlh4P.lean.js +1 -0
- camply/server/static/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- camply/server/static/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- camply/server/static/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- camply/server/static/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- camply/server/static/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- camply/server/static/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- camply/server/static/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- camply/server/static/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- camply/server/static/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- camply/server/static/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- camply/server/static/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- camply/server/static/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- camply/server/static/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- camply/server/static/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- camply/server/static/docs/assets/mobile.BHn3cOOl.png +0 -0
- camply/server/static/docs/assets/operations_CONFIGURATION.md.D6oPLkcv.js +1 -0
- camply/server/static/docs/assets/operations_CONFIGURATION.md.D6oPLkcv.lean.js +1 -0
- camply/server/static/docs/assets/operations_RELEASES.md.DJpsWvJW.js +18 -0
- camply/server/static/docs/assets/operations_RELEASES.md.DJpsWvJW.lean.js +1 -0
- camply/server/static/docs/assets/operations_RUNNING.md.Be3YOJ6L.js +27 -0
- camply/server/static/docs/assets/operations_RUNNING.md.Be3YOJ6L.lean.js +1 -0
- camply/server/static/docs/assets/reference.md.t88yfbnH.js +1 -0
- camply/server/static/docs/assets/reference.md.t88yfbnH.lean.js +1 -0
- camply/server/static/docs/assets/self-hosting_containers.md.5a1rSEtB.js +6 -0
- camply/server/static/docs/assets/self-hosting_containers.md.5a1rSEtB.lean.js +1 -0
- camply/server/static/docs/assets/self-hosting_standalone.md.CQahK6_G.js +3 -0
- camply/server/static/docs/assets/self-hosting_standalone.md.CQahK6_G.lean.js +1 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_checklists_requirements.md.DfhQsALb.js +1 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_checklists_requirements.md.DfhQsALb.lean.js +1 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_data-model.md.Bu3SJa6R.js +1 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_data-model.md.Bu3SJa6R.lean.js +1 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_plan.md.D3nzZvpG.js +16 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_plan.md.D3nzZvpG.lean.js +1 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_quickstart.md.brT3yMdZ.js +5 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_quickstart.md.brT3yMdZ.lean.js +1 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_research.md.DjcknknI.js +1 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_research.md.DjcknknI.lean.js +1 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_spec.md.CRhdCu3D.js +1 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_spec.md.CRhdCu3D.lean.js +1 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_tasks.md.tXQD9pHw.js +7 -0
- camply/server/static/docs/assets/specs_001-checklist-data-layer_tasks.md.tXQD9pHw.lean.js +1 -0
- camply/server/static/docs/assets/specs_002-admin-ui_plan.md.1i7mOqnZ.js +8 -0
- camply/server/static/docs/assets/specs_002-admin-ui_plan.md.1i7mOqnZ.lean.js +1 -0
- camply/server/static/docs/assets/specs_002-admin-ui_spec.md.Bcc8UXaw.js +1 -0
- camply/server/static/docs/assets/specs_002-admin-ui_spec.md.Bcc8UXaw.lean.js +1 -0
- camply/server/static/docs/assets/specs_002-admin-ui_tasks.md.D4m8JDK5.js +1 -0
- camply/server/static/docs/assets/specs_002-admin-ui_tasks.md.D4m8JDK5.lean.js +1 -0
- camply/server/static/docs/assets/specs_003-auth-invite-only_plan.md.DKaGDvZk.js +1 -0
- camply/server/static/docs/assets/specs_003-auth-invite-only_plan.md.DKaGDvZk.lean.js +1 -0
- camply/server/static/docs/assets/specs_003-auth-invite-only_spec.md.DNtUiuzb.js +1 -0
- camply/server/static/docs/assets/specs_003-auth-invite-only_spec.md.DNtUiuzb.lean.js +1 -0
- camply/server/static/docs/assets/specs_003-auth-invite-only_tasks.md.BWkFMpMk.js +1 -0
- camply/server/static/docs/assets/specs_003-auth-invite-only_tasks.md.BWkFMpMk.lean.js +1 -0
- camply/server/static/docs/assets/specs_004-frontend-performance_plan.md.0egiciJu.js +1 -0
- camply/server/static/docs/assets/specs_004-frontend-performance_plan.md.0egiciJu.lean.js +1 -0
- camply/server/static/docs/assets/specs_004-frontend-performance_spec.md.D8F0bRTx.js +1 -0
- camply/server/static/docs/assets/specs_004-frontend-performance_spec.md.D8F0bRTx.lean.js +1 -0
- camply/server/static/docs/assets/specs_004-frontend-performance_tasks.md.BjrF_-ct.js +1 -0
- camply/server/static/docs/assets/specs_004-frontend-performance_tasks.md.BjrF_-ct.lean.js +1 -0
- camply/server/static/docs/assets/specs_004-frontend-performance_validation.md.4PK8p4L7.js +1 -0
- camply/server/static/docs/assets/specs_004-frontend-performance_validation.md.4PK8p4L7.lean.js +1 -0
- camply/server/static/docs/assets/specs_005-reserve-california_plan.md.C_4y1stq.js +1 -0
- camply/server/static/docs/assets/specs_005-reserve-california_plan.md.C_4y1stq.lean.js +1 -0
- camply/server/static/docs/assets/specs_005-reserve-california_review.md.vPfyZzT1.js +1 -0
- camply/server/static/docs/assets/specs_005-reserve-california_review.md.vPfyZzT1.lean.js +1 -0
- camply/server/static/docs/assets/specs_005-reserve-california_spec.md.rgMwjOCN.js +1 -0
- camply/server/static/docs/assets/specs_005-reserve-california_spec.md.rgMwjOCN.lean.js +1 -0
- camply/server/static/docs/assets/specs_005-reserve-california_tasks.md.Drrims_b.js +1 -0
- camply/server/static/docs/assets/specs_005-reserve-california_tasks.md.Drrims_b.lean.js +1 -0
- camply/server/static/docs/assets/specs_005-reserve-california_validation.md.4NbfQL-M.js +3 -0
- camply/server/static/docs/assets/specs_005-reserve-california_validation.md.4NbfQL-M.lean.js +1 -0
- camply/server/static/docs/assets/specs_006-search-revamp_plan.md.CgDr0o4d.js +1 -0
- camply/server/static/docs/assets/specs_006-search-revamp_plan.md.CgDr0o4d.lean.js +1 -0
- camply/server/static/docs/assets/specs_006-search-revamp_spec.md.BpswXgjY.js +1 -0
- camply/server/static/docs/assets/specs_006-search-revamp_spec.md.BpswXgjY.lean.js +1 -0
- camply/server/static/docs/assets/specs_006-search-revamp_tasks.md.BqfZvZ51.js +1 -0
- camply/server/static/docs/assets/specs_006-search-revamp_tasks.md.BqfZvZ51.lean.js +1 -0
- camply/server/static/docs/assets/specs_007-usedirect-providers_TODO.md.D-YlwIaX.js +1 -0
- camply/server/static/docs/assets/specs_007-usedirect-providers_TODO.md.D-YlwIaX.lean.js +1 -0
- camply/server/static/docs/assets/specs_007-usedirect-providers_plan.md.CAt16M-0.js +1 -0
- camply/server/static/docs/assets/specs_007-usedirect-providers_plan.md.CAt16M-0.lean.js +1 -0
- camply/server/static/docs/assets/specs_007-usedirect-providers_spec.md.DL5gRQoV.js +1 -0
- camply/server/static/docs/assets/specs_007-usedirect-providers_spec.md.DL5gRQoV.lean.js +1 -0
- camply/server/static/docs/assets/specs_007-usedirect-providers_tasks.md.x22tFDlq.js +1 -0
- camply/server/static/docs/assets/specs_007-usedirect-providers_tasks.md.x22tFDlq.lean.js +1 -0
- camply/server/static/docs/assets/specs_007-usedirect-providers_validation.md.Cq0jkjMQ.js +1 -0
- camply/server/static/docs/assets/specs_007-usedirect-providers_validation.md.Cq0jkjMQ.lean.js +1 -0
- camply/server/static/docs/assets/specs_008-scan-editing_plan.md.DLJmchfh.js +1 -0
- camply/server/static/docs/assets/specs_008-scan-editing_plan.md.DLJmchfh.lean.js +1 -0
- camply/server/static/docs/assets/specs_008-scan-editing_spec.md.CdwTtcx3.js +1 -0
- camply/server/static/docs/assets/specs_008-scan-editing_spec.md.CdwTtcx3.lean.js +1 -0
- camply/server/static/docs/assets/specs_008-scan-editing_tasks.md.BEgPa6-r.js +1 -0
- camply/server/static/docs/assets/specs_008-scan-editing_tasks.md.BEgPa6-r.lean.js +1 -0
- camply/server/static/docs/assets/specs_009-live-search_plan.md.BcoKTwHn.js +1 -0
- camply/server/static/docs/assets/specs_009-live-search_plan.md.BcoKTwHn.lean.js +1 -0
- camply/server/static/docs/assets/specs_009-live-search_spec.md.fUtsQI22.js +1 -0
- camply/server/static/docs/assets/specs_009-live-search_spec.md.fUtsQI22.lean.js +1 -0
- camply/server/static/docs/assets/specs_009-live-search_tasks.md.DarVWLUi.js +1 -0
- camply/server/static/docs/assets/specs_009-live-search_tasks.md.DarVWLUi.lean.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_TODO.md.gzEKqbSz.js +6 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_TODO.md.gzEKqbSz.lean.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_checklists_requirements.md.vru4OosJ.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_checklists_requirements.md.vru4OosJ.lean.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_contracts_map-ui-and-configuration.md.B10q5qL1.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_contracts_map-ui-and-configuration.md.B10q5qL1.lean.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_contracts_search-map-api.md.C_WjmPTY.js +6 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_contracts_search-map-api.md.C_WjmPTY.lean.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_data-model.md.5O-zMBeh.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_data-model.md.5O-zMBeh.lean.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_plan.md.jDZdRYcr.js +43 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_plan.md.jDZdRYcr.lean.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_quickstart.md.BE90M1AB.js +15 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_quickstart.md.BE90M1AB.lean.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_research.md.BlHNsukP.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_research.md.BlHNsukP.lean.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_spec.md.LYFig9_-.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_spec.md.LYFig9_-.lean.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_tasks.md.DIp1nm1_.js +10 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_tasks.md.DIp1nm1_.lean.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_validation.md.CD2LeHg0.js +1 -0
- camply/server/static/docs/assets/specs_010-open-source-explore-maps_validation.md.CD2LeHg0.lean.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_checklists_requirements.md.DWjHbv1i.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_checklists_requirements.md.DWjHbv1i.lean.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_checklists_runtime.md.BO6J8s0L.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_checklists_runtime.md.BO6J8s0L.lean.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_contracts_admin-api.md.thtXfhYe.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_contracts_admin-api.md.thtXfhYe.lean.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_contracts_runtime.md.BlZkAK7-.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_contracts_runtime.md.BlZkAK7-.lean.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_data-model.md.CFAVd4C5.js +11 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_data-model.md.CFAVd4C5.lean.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_plan.md.DPP4fdeb.js +31 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_plan.md.DPP4fdeb.lean.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_quickstart.md.Cn5n2TIW.js +11 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_quickstart.md.Cn5n2TIW.lean.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_research.md.CTOfB-iw.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_research.md.CTOfB-iw.lean.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_review.md.S7P6Ivy7.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_review.md.S7P6Ivy7.lean.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_spec.md.CNHFBW4a.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_spec.md.CNHFBW4a.lean.js +1 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_tasks.md.Ce8U3_-D.js +6 -0
- camply/server/static/docs/assets/specs_011-standalone-runtime_tasks.md.Ce8U3_-D.lean.js +1 -0
- camply/server/static/docs/assets/specs_012-branding_plan.md.A72jCO-_.js +1 -0
- camply/server/static/docs/assets/specs_012-branding_plan.md.A72jCO-_.lean.js +1 -0
- camply/server/static/docs/assets/specs_012-branding_spec.md.BueCHwuz.js +1 -0
- camply/server/static/docs/assets/specs_012-branding_spec.md.BueCHwuz.lean.js +1 -0
- camply/server/static/docs/assets/specs_012-branding_tasks.md.BTeyaLMD.js +1 -0
- camply/server/static/docs/assets/specs_012-branding_tasks.md.BTeyaLMD.lean.js +1 -0
- camply/server/static/docs/assets/specs_013-map-list-interaction_plan.md.DXgkLmDJ.js +1 -0
- camply/server/static/docs/assets/specs_013-map-list-interaction_plan.md.DXgkLmDJ.lean.js +1 -0
- camply/server/static/docs/assets/specs_013-map-list-interaction_spec.md.BVo8jhLm.js +1 -0
- camply/server/static/docs/assets/specs_013-map-list-interaction_spec.md.BVo8jhLm.lean.js +1 -0
- camply/server/static/docs/assets/specs_013-map-list-interaction_tasks.md.CFR8mkT3.js +1 -0
- camply/server/static/docs/assets/specs_013-map-list-interaction_tasks.md.CFR8mkT3.lean.js +1 -0
- camply/server/static/docs/assets/specs_013-map-list-interaction_validation.md.BNctsZBl.js +1 -0
- camply/server/static/docs/assets/specs_013-map-list-interaction_validation.md.BNctsZBl.lean.js +1 -0
- camply/server/static/docs/assets/specs_014-campsite-alerts_plan.md.Du4VPOlj.js +1 -0
- camply/server/static/docs/assets/specs_014-campsite-alerts_plan.md.Du4VPOlj.lean.js +1 -0
- camply/server/static/docs/assets/specs_014-campsite-alerts_spec.md.DPQGKMoN.js +1 -0
- camply/server/static/docs/assets/specs_014-campsite-alerts_spec.md.DPQGKMoN.lean.js +1 -0
- camply/server/static/docs/assets/specs_014-campsite-alerts_tasks.md.BqDrpSco.js +1 -0
- camply/server/static/docs/assets/specs_014-campsite-alerts_tasks.md.BqDrpSco.lean.js +1 -0
- camply/server/static/docs/assets/specs_015-release-ci_plan.md.s7IX1ar-.js +1 -0
- camply/server/static/docs/assets/specs_015-release-ci_plan.md.s7IX1ar-.lean.js +1 -0
- camply/server/static/docs/assets/specs_015-release-ci_spec.md.IA1oVhSE.js +1 -0
- camply/server/static/docs/assets/specs_015-release-ci_spec.md.IA1oVhSE.lean.js +1 -0
- camply/server/static/docs/assets/specs_015-release-ci_tasks.md.DCVUjCri.js +1 -0
- camply/server/static/docs/assets/specs_015-release-ci_tasks.md.DCVUjCri.lean.js +1 -0
- camply/server/static/docs/assets/specs_016-snapshot-gallery_plan.md.CS9nfykJ.js +1 -0
- camply/server/static/docs/assets/specs_016-snapshot-gallery_plan.md.CS9nfykJ.lean.js +1 -0
- camply/server/static/docs/assets/specs_016-snapshot-gallery_spec.md.Cha7R2Jr.js +1 -0
- camply/server/static/docs/assets/specs_016-snapshot-gallery_spec.md.Cha7R2Jr.lean.js +1 -0
- camply/server/static/docs/assets/specs_016-snapshot-gallery_tasks.md.DkrGHX9V.js +1 -0
- camply/server/static/docs/assets/specs_016-snapshot-gallery_tasks.md.DkrGHX9V.lean.js +1 -0
- camply/server/static/docs/assets/specs_017-compact-results_plan.md.BKHhDct3.js +1 -0
- camply/server/static/docs/assets/specs_017-compact-results_plan.md.BKHhDct3.lean.js +1 -0
- camply/server/static/docs/assets/specs_017-compact-results_spec.md.DzprVc_Y.js +1 -0
- camply/server/static/docs/assets/specs_017-compact-results_spec.md.DzprVc_Y.lean.js +1 -0
- camply/server/static/docs/assets/specs_017-compact-results_tasks.md.B7r7q6M6.js +1 -0
- camply/server/static/docs/assets/specs_017-compact-results_tasks.md.B7r7q6M6.lean.js +1 -0
- camply/server/static/docs/assets/specs_018-python-package_checklists_requirements.md.Ba9rZYIs.js +1 -0
- camply/server/static/docs/assets/specs_018-python-package_checklists_requirements.md.Ba9rZYIs.lean.js +1 -0
- camply/server/static/docs/assets/specs_018-python-package_contracts_installation.md.BpweOVRG.js +1 -0
- camply/server/static/docs/assets/specs_018-python-package_contracts_installation.md.BpweOVRG.lean.js +1 -0
- camply/server/static/docs/assets/specs_018-python-package_data-model.md.6Ln9iXUM.js +1 -0
- camply/server/static/docs/assets/specs_018-python-package_data-model.md.6Ln9iXUM.lean.js +1 -0
- camply/server/static/docs/assets/specs_018-python-package_plan.md.DCJTOtqH.js +23 -0
- camply/server/static/docs/assets/specs_018-python-package_plan.md.DCJTOtqH.lean.js +1 -0
- camply/server/static/docs/assets/specs_018-python-package_quickstart.md.KtQ5vkcd.js +18 -0
- camply/server/static/docs/assets/specs_018-python-package_quickstart.md.KtQ5vkcd.lean.js +1 -0
- camply/server/static/docs/assets/specs_018-python-package_research.md.VwZF0yyA.js +1 -0
- camply/server/static/docs/assets/specs_018-python-package_research.md.VwZF0yyA.lean.js +1 -0
- camply/server/static/docs/assets/specs_018-python-package_spec.md.Bkzw96f5.js +1 -0
- camply/server/static/docs/assets/specs_018-python-package_spec.md.Bkzw96f5.lean.js +1 -0
- camply/server/static/docs/assets/specs_018-python-package_tasks.md.68ZupRJE.js +9 -0
- camply/server/static/docs/assets/specs_018-python-package_tasks.md.68ZupRJE.lean.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_checklists_requirements.md.efbJQUab.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_checklists_requirements.md.efbJQUab.lean.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_contracts_deployment.md.pILeXHAO.js +7 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_contracts_deployment.md.pILeXHAO.lean.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_contracts_packaging.md.DiDP5HWd.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_contracts_packaging.md.DiDP5HWd.lean.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_data-model.md.ebEzfYla.js +7 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_data-model.md.ebEzfYla.lean.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_plan.md.DvXBnPtu.js +57 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_plan.md.DvXBnPtu.lean.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_quickstart.md.BBsMI7O5.js +18 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_quickstart.md.BBsMI7O5.lean.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_research.md.CA4sN3hx.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_research.md.CA4sN3hx.lean.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_spec.md.DuKS8ugt.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_spec.md.DuKS8ugt.lean.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_tasks.md.CP5Z2AFX.js +1 -0
- camply/server/static/docs/assets/specs_019-root-project-layout_tasks.md.CP5Z2AFX.lean.js +1 -0
- camply/server/static/docs/assets/specs_020-unified-container_plan.md.xEvrD739.js +1 -0
- camply/server/static/docs/assets/specs_020-unified-container_plan.md.xEvrD739.lean.js +1 -0
- camply/server/static/docs/assets/specs_020-unified-container_spec.md.CwYqgXIo.js +1 -0
- camply/server/static/docs/assets/specs_020-unified-container_spec.md.CwYqgXIo.lean.js +1 -0
- camply/server/static/docs/assets/specs_020-unified-container_tasks.md.DlZym8Lk.js +1 -0
- camply/server/static/docs/assets/specs_020-unified-container_tasks.md.DlZym8Lk.lean.js +1 -0
- camply/server/static/docs/assets/specs_021-helm-chart_plan.md.BLo5bjmE.js +1 -0
- camply/server/static/docs/assets/specs_021-helm-chart_plan.md.BLo5bjmE.lean.js +1 -0
- camply/server/static/docs/assets/specs_021-helm-chart_spec.md.Dz_zvjRU.js +1 -0
- camply/server/static/docs/assets/specs_021-helm-chart_spec.md.Dz_zvjRU.lean.js +1 -0
- camply/server/static/docs/assets/specs_021-helm-chart_tasks.md.Ba53wvbA.js +1 -0
- camply/server/static/docs/assets/specs_021-helm-chart_tasks.md.Ba53wvbA.lean.js +1 -0
- camply/server/static/docs/assets/specs_022-documentation-site_plan.md.Bd_nIc0B.js +1 -0
- camply/server/static/docs/assets/specs_022-documentation-site_plan.md.Bd_nIc0B.lean.js +1 -0
- camply/server/static/docs/assets/specs_022-documentation-site_spec.md.mVEUAL4_.js +1 -0
- camply/server/static/docs/assets/specs_022-documentation-site_spec.md.mVEUAL4_.lean.js +1 -0
- camply/server/static/docs/assets/specs_022-documentation-site_tasks.md.D4Yx3Coa.js +1 -0
- camply/server/static/docs/assets/specs_022-documentation-site_tasks.md.D4Yx3Coa.lean.js +1 -0
- camply/server/static/docs/assets/specs_index.md.WrZfYvz7.js +1 -0
- camply/server/static/docs/assets/specs_index.md.WrZfYvz7.lean.js +1 -0
- camply/server/static/docs/assets/style.BLufiWcT.css +1 -0
- camply/server/static/docs/assets/using-camply.md.CdovgIax.js +1 -0
- camply/server/static/docs/assets/using-camply.md.CdovgIax.lean.js +1 -0
- camply/server/static/docs/branding/camply-horizontal-dark.svg +16 -0
- camply/server/static/docs/branding/camply-horizontal.svg +16 -0
- camply/server/static/docs/branding/camply-icon-dark.svg +16 -0
- camply/server/static/docs/branding/camply-icon.svg +16 -0
- camply/server/static/docs/branding/camply-stacked-dark.svg +16 -0
- camply/server/static/docs/branding/camply-stacked.svg +16 -0
- camply/server/static/docs/camply.svg +17 -0
- camply/server/static/docs/configuration.html +30 -0
- camply/server/static/docs/development/AGENT_TROUBLESHOOTING.html +27 -0
- camply/server/static/docs/development/DESIGN_AGENTIC.html +27 -0
- camply/server/static/docs/development/DEVELOPER_GUIDE.html +34 -0
- camply/server/static/docs/development/STYLE_GUIDE.html +27 -0
- camply/server/static/docs/development.html +33 -0
- camply/server/static/docs/favicon.ico +0 -0
- camply/server/static/docs/favicon.svg +17 -0
- camply/server/static/docs/hashmap.json +1 -0
- camply/server/static/docs/index.html +27 -0
- camply/server/static/docs/operations/CONFIGURATION.html +27 -0
- camply/server/static/docs/operations/RELEASES.html +44 -0
- camply/server/static/docs/operations/RUNNING.html +53 -0
- camply/server/static/docs/reference.html +27 -0
- camply/server/static/docs/self-hosting/containers.html +32 -0
- camply/server/static/docs/self-hosting/standalone.html +29 -0
- camply/server/static/docs/specs/001-checklist-data-layer/checklists/requirements.html +27 -0
- camply/server/static/docs/specs/001-checklist-data-layer/data-model.html +27 -0
- camply/server/static/docs/specs/001-checklist-data-layer/plan.html +42 -0
- camply/server/static/docs/specs/001-checklist-data-layer/quickstart.html +31 -0
- camply/server/static/docs/specs/001-checklist-data-layer/research.html +27 -0
- camply/server/static/docs/specs/001-checklist-data-layer/spec.html +27 -0
- camply/server/static/docs/specs/001-checklist-data-layer/tasks.html +33 -0
- camply/server/static/docs/specs/002-admin-ui/plan.html +34 -0
- camply/server/static/docs/specs/002-admin-ui/spec.html +27 -0
- camply/server/static/docs/specs/002-admin-ui/tasks.html +27 -0
- camply/server/static/docs/specs/003-auth-invite-only/plan.html +27 -0
- camply/server/static/docs/specs/003-auth-invite-only/spec.html +27 -0
- camply/server/static/docs/specs/003-auth-invite-only/tasks.html +27 -0
- camply/server/static/docs/specs/004-frontend-performance/plan.html +27 -0
- camply/server/static/docs/specs/004-frontend-performance/spec.html +27 -0
- camply/server/static/docs/specs/004-frontend-performance/tasks.html +27 -0
- camply/server/static/docs/specs/004-frontend-performance/validation.html +27 -0
- camply/server/static/docs/specs/005-reserve-california/plan.html +27 -0
- camply/server/static/docs/specs/005-reserve-california/review.html +27 -0
- camply/server/static/docs/specs/005-reserve-california/spec.html +27 -0
- camply/server/static/docs/specs/005-reserve-california/tasks.html +27 -0
- camply/server/static/docs/specs/005-reserve-california/validation.html +29 -0
- camply/server/static/docs/specs/006-search-revamp/plan.html +27 -0
- camply/server/static/docs/specs/006-search-revamp/spec.html +27 -0
- camply/server/static/docs/specs/006-search-revamp/tasks.html +27 -0
- camply/server/static/docs/specs/007-usedirect-providers/TODO.html +27 -0
- camply/server/static/docs/specs/007-usedirect-providers/plan.html +27 -0
- camply/server/static/docs/specs/007-usedirect-providers/spec.html +27 -0
- camply/server/static/docs/specs/007-usedirect-providers/tasks.html +27 -0
- camply/server/static/docs/specs/007-usedirect-providers/validation.html +27 -0
- camply/server/static/docs/specs/008-scan-editing/plan.html +27 -0
- camply/server/static/docs/specs/008-scan-editing/spec.html +27 -0
- camply/server/static/docs/specs/008-scan-editing/tasks.html +27 -0
- camply/server/static/docs/specs/009-live-search/plan.html +27 -0
- camply/server/static/docs/specs/009-live-search/spec.html +27 -0
- camply/server/static/docs/specs/009-live-search/tasks.html +27 -0
- camply/server/static/docs/specs/010-open-source-explore-maps/TODO.html +32 -0
- camply/server/static/docs/specs/010-open-source-explore-maps/checklists/requirements.html +27 -0
- camply/server/static/docs/specs/010-open-source-explore-maps/contracts/map-ui-and-configuration.html +27 -0
- camply/server/static/docs/specs/010-open-source-explore-maps/contracts/search-map-api.html +32 -0
- camply/server/static/docs/specs/010-open-source-explore-maps/data-model.html +27 -0
- camply/server/static/docs/specs/010-open-source-explore-maps/plan.html +69 -0
- camply/server/static/docs/specs/010-open-source-explore-maps/quickstart.html +41 -0
- camply/server/static/docs/specs/010-open-source-explore-maps/research.html +27 -0
- camply/server/static/docs/specs/010-open-source-explore-maps/spec.html +27 -0
- camply/server/static/docs/specs/010-open-source-explore-maps/tasks.html +36 -0
- camply/server/static/docs/specs/010-open-source-explore-maps/validation.html +27 -0
- camply/server/static/docs/specs/011-standalone-runtime/checklists/requirements.html +27 -0
- camply/server/static/docs/specs/011-standalone-runtime/checklists/runtime.html +27 -0
- camply/server/static/docs/specs/011-standalone-runtime/contracts/admin-api.html +27 -0
- camply/server/static/docs/specs/011-standalone-runtime/contracts/runtime.html +27 -0
- camply/server/static/docs/specs/011-standalone-runtime/data-model.html +37 -0
- camply/server/static/docs/specs/011-standalone-runtime/plan.html +57 -0
- camply/server/static/docs/specs/011-standalone-runtime/quickstart.html +37 -0
- camply/server/static/docs/specs/011-standalone-runtime/research.html +27 -0
- camply/server/static/docs/specs/011-standalone-runtime/review.html +27 -0
- camply/server/static/docs/specs/011-standalone-runtime/spec.html +27 -0
- camply/server/static/docs/specs/011-standalone-runtime/tasks.html +32 -0
- camply/server/static/docs/specs/012-branding/plan.html +27 -0
- camply/server/static/docs/specs/012-branding/spec.html +27 -0
- camply/server/static/docs/specs/012-branding/tasks.html +27 -0
- camply/server/static/docs/specs/013-map-list-interaction/plan.html +27 -0
- camply/server/static/docs/specs/013-map-list-interaction/spec.html +27 -0
- camply/server/static/docs/specs/013-map-list-interaction/tasks.html +27 -0
- camply/server/static/docs/specs/013-map-list-interaction/validation.html +27 -0
- camply/server/static/docs/specs/014-campsite-alerts/plan.html +27 -0
- camply/server/static/docs/specs/014-campsite-alerts/spec.html +27 -0
- camply/server/static/docs/specs/014-campsite-alerts/tasks.html +27 -0
- camply/server/static/docs/specs/015-release-ci/plan.html +27 -0
- camply/server/static/docs/specs/015-release-ci/spec.html +27 -0
- camply/server/static/docs/specs/015-release-ci/tasks.html +27 -0
- camply/server/static/docs/specs/016-snapshot-gallery/plan.html +27 -0
- camply/server/static/docs/specs/016-snapshot-gallery/spec.html +27 -0
- camply/server/static/docs/specs/016-snapshot-gallery/tasks.html +27 -0
- camply/server/static/docs/specs/017-compact-results/plan.html +27 -0
- camply/server/static/docs/specs/017-compact-results/spec.html +27 -0
- camply/server/static/docs/specs/017-compact-results/tasks.html +27 -0
- camply/server/static/docs/specs/018-python-package/checklists/requirements.html +27 -0
- camply/server/static/docs/specs/018-python-package/contracts/installation.html +27 -0
- camply/server/static/docs/specs/018-python-package/data-model.html +27 -0
- camply/server/static/docs/specs/018-python-package/plan.html +49 -0
- camply/server/static/docs/specs/018-python-package/quickstart.html +44 -0
- camply/server/static/docs/specs/018-python-package/research.html +27 -0
- camply/server/static/docs/specs/018-python-package/spec.html +27 -0
- camply/server/static/docs/specs/018-python-package/tasks.html +35 -0
- camply/server/static/docs/specs/019-root-project-layout/checklists/requirements.html +27 -0
- camply/server/static/docs/specs/019-root-project-layout/contracts/deployment.html +33 -0
- camply/server/static/docs/specs/019-root-project-layout/contracts/packaging.html +27 -0
- camply/server/static/docs/specs/019-root-project-layout/data-model.html +33 -0
- camply/server/static/docs/specs/019-root-project-layout/plan.html +83 -0
- camply/server/static/docs/specs/019-root-project-layout/quickstart.html +44 -0
- camply/server/static/docs/specs/019-root-project-layout/research.html +27 -0
- camply/server/static/docs/specs/019-root-project-layout/spec.html +27 -0
- camply/server/static/docs/specs/019-root-project-layout/tasks.html +27 -0
- camply/server/static/docs/specs/020-unified-container/plan.html +27 -0
- camply/server/static/docs/specs/020-unified-container/spec.html +27 -0
- camply/server/static/docs/specs/020-unified-container/tasks.html +27 -0
- camply/server/static/docs/specs/021-helm-chart/plan.html +27 -0
- camply/server/static/docs/specs/021-helm-chart/spec.html +27 -0
- camply/server/static/docs/specs/021-helm-chart/tasks.html +27 -0
- camply/server/static/docs/specs/022-documentation-site/plan.html +27 -0
- camply/server/static/docs/specs/022-documentation-site/spec.html +27 -0
- camply/server/static/docs/specs/022-documentation-site/tasks.html +27 -0
- camply/server/static/docs/specs/index.html +27 -0
- camply/server/static/docs/using-camply.html +27 -0
- camply/server/static/docs/vp-icons.css +1 -0
- camply/server/static/favicon.ico +0 -0
- camply/server/static/favicon.svg +17 -0
- camply/server/static/index.html +15 -0
- camply/server/static.py +55 -0
- camply/worker/__init__.py +14 -0
- camply/worker/celery_app.py +139 -0
- camply/worker/config.py +68 -0
- camply/worker/engine/__init__.py +1 -0
- camply/worker/engine/alerts.py +68 -0
- camply/worker/engine/catalog.py +46 -0
- camply/worker/engine/discovery.py +60 -0
- camply/worker/engine/notifications.py +103 -0
- camply/worker/engine/scanner.py +318 -0
- camply/worker/locks.py +101 -0
- camply/worker/metrics.py +177 -0
- camply/worker/notifications/__init__.py +3 -0
- camply/worker/notifications/base.py +53 -0
- camply/worker/notifications/pushover.py +104 -0
- camply/worker/outbox.py +236 -0
- camply/worker/runtime.py +313 -0
- camply/worker/scheduler.py +38 -0
- camply/worker/tasks/__init__.py +3 -0
- camply/worker/tasks/heartbeat.py +81 -0
- camply/worker/tasks/ingestion.py +106 -0
- camply/worker/tasks/notifications.py +63 -0
- camply/worker/tasks/scanner.py +135 -0
- camply/worker/telemetry.py +343 -0
- camply_web-2.8.0.dist-info/METADATA +67 -0
- camply_web-2.8.0.dist-info/RECORD +573 -0
- camply_web-2.8.0.dist-info/WHEEL +4 -0
- camply_web-2.8.0.dist-info/entry_points.txt +3 -0
- camply_web-2.8.0.dist-info/licenses/LICENSE +133 -0
camply/__about__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Shared installed distribution metadata for Camply."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
__application__ = "camply"
|
|
6
|
+
"""Python application name."""
|
|
7
|
+
|
|
8
|
+
__package_name__ = "camply-web"
|
|
9
|
+
"""Python distribution name."""
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
__version__ = version(__package_name__)
|
|
13
|
+
except PackageNotFoundError: # pragma: no cover
|
|
14
|
+
try:
|
|
15
|
+
__version__ = version(__application__)
|
|
16
|
+
except PackageNotFoundError:
|
|
17
|
+
__version__ = "0.0.0"
|
|
18
|
+
"""Installed distribution version shared by all runtimes."""
|
camply/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Campsite providers, persistence, and optional application runtimes."""
|
camply/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Camply command-line interface using only base distribution dependencies."""
|
camply/cli/__main__.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Read-only health probes for the application's foreground serving roles."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import socket
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
9
|
+
import time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
|
|
14
|
+
from camply.cli.runtime import require_modules
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def beat_health_path() -> Path:
|
|
18
|
+
"""Locate the scheduler heartbeat shared by serving and probing in one container."""
|
|
19
|
+
return (
|
|
20
|
+
Path(
|
|
21
|
+
os.environ.get("CAMPLY_BEAT_HEALTH_FILE")
|
|
22
|
+
or str(Path(tempfile.gettempdir()) / "camply-beat-health.json")
|
|
23
|
+
)
|
|
24
|
+
.expanduser()
|
|
25
|
+
.resolve()
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _check_http(url: str, *, timeout: float) -> None:
|
|
30
|
+
"""Check HTTP status without reading an unbounded body or following redirects."""
|
|
31
|
+
import httpx
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
with httpx.stream(
|
|
35
|
+
"GET", url, timeout=timeout, trust_env=False, follow_redirects=False
|
|
36
|
+
) as response:
|
|
37
|
+
if not response.is_success:
|
|
38
|
+
raise click.ClickException(
|
|
39
|
+
f"HTTP healthcheck failed with status {response.status_code}."
|
|
40
|
+
)
|
|
41
|
+
except (httpx.HTTPError, ValueError) as exc:
|
|
42
|
+
raise click.ClickException(
|
|
43
|
+
"HTTP healthcheck could not reach the service."
|
|
44
|
+
) from exc
|
|
45
|
+
click.echo("healthy")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@click.group(invoke_without_command=True)
|
|
49
|
+
@click.option(
|
|
50
|
+
"--url", help="Standalone health URL (default: http://127.0.0.1:8000/api/health)."
|
|
51
|
+
)
|
|
52
|
+
@click.option(
|
|
53
|
+
"--timeout",
|
|
54
|
+
type=click.FloatRange(min=0, min_open=True),
|
|
55
|
+
help="Network timeout in seconds (default: 5).",
|
|
56
|
+
)
|
|
57
|
+
@click.pass_context
|
|
58
|
+
def healthcheck(ctx: click.Context, url: str | None, timeout: float | None) -> None:
|
|
59
|
+
"""Check standalone health, or select an individual serving role."""
|
|
60
|
+
if ctx.invoked_subcommand is not None:
|
|
61
|
+
if url is not None or timeout is not None:
|
|
62
|
+
raise click.UsageError("Place healthcheck options after the role name.")
|
|
63
|
+
return
|
|
64
|
+
_check_http(url or "http://127.0.0.1:8000/api/health", timeout=timeout or 5)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@healthcheck.command(name="backend")
|
|
68
|
+
@click.option("--url", default="http://127.0.0.1:8000/api/health", show_default=True)
|
|
69
|
+
@click.option(
|
|
70
|
+
"--timeout",
|
|
71
|
+
default=5.0,
|
|
72
|
+
type=click.FloatRange(min=0, min_open=True),
|
|
73
|
+
show_default=True,
|
|
74
|
+
)
|
|
75
|
+
def backend(url: str, timeout: float) -> None:
|
|
76
|
+
"""Check the API's existing HTTP health endpoint."""
|
|
77
|
+
_check_http(url, timeout=timeout)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@healthcheck.command(name="frontend")
|
|
81
|
+
@click.option("--url", default="http://127.0.0.1:8080/healthz", show_default=True)
|
|
82
|
+
@click.option(
|
|
83
|
+
"--timeout",
|
|
84
|
+
default=5.0,
|
|
85
|
+
type=click.FloatRange(min=0, min_open=True),
|
|
86
|
+
show_default=True,
|
|
87
|
+
)
|
|
88
|
+
def frontend(url: str, timeout: float) -> None:
|
|
89
|
+
"""Check Nginx's health endpoint independently of its API upstream."""
|
|
90
|
+
_check_http(url, timeout=timeout)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@healthcheck.command(name="worker")
|
|
94
|
+
@click.option(
|
|
95
|
+
"--destination", help="Celery node name (default: celery@<local hostname>)."
|
|
96
|
+
)
|
|
97
|
+
@click.option(
|
|
98
|
+
"--timeout",
|
|
99
|
+
default=5.0,
|
|
100
|
+
type=click.FloatRange(min=0, min_open=True),
|
|
101
|
+
show_default=True,
|
|
102
|
+
)
|
|
103
|
+
def worker(destination: str | None, timeout: float) -> None:
|
|
104
|
+
"""Require a reply from this worker through its configured broker."""
|
|
105
|
+
require_modules("distributed", ("celery", "redis", "prometheus_client"))
|
|
106
|
+
target = destination or f"celery@{socket.gethostname()}"
|
|
107
|
+
if "," in target:
|
|
108
|
+
raise click.BadParameter(
|
|
109
|
+
"Specify exactly one Celery node.", param_hint="--destination"
|
|
110
|
+
)
|
|
111
|
+
try:
|
|
112
|
+
result = subprocess.run(
|
|
113
|
+
[
|
|
114
|
+
sys.executable,
|
|
115
|
+
"-m",
|
|
116
|
+
"celery",
|
|
117
|
+
"-A",
|
|
118
|
+
"camply.worker.celery_app",
|
|
119
|
+
"inspect",
|
|
120
|
+
"--destination",
|
|
121
|
+
target,
|
|
122
|
+
"--timeout",
|
|
123
|
+
str(timeout / 2),
|
|
124
|
+
"ping",
|
|
125
|
+
],
|
|
126
|
+
capture_output=True,
|
|
127
|
+
text=True,
|
|
128
|
+
check=False,
|
|
129
|
+
timeout=timeout,
|
|
130
|
+
)
|
|
131
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
132
|
+
raise click.ClickException(
|
|
133
|
+
"Worker healthcheck did not receive a response in time."
|
|
134
|
+
) from exc
|
|
135
|
+
if result.returncode != 0:
|
|
136
|
+
raise click.ClickException(
|
|
137
|
+
"Worker healthcheck failed; check the target worker and broker."
|
|
138
|
+
)
|
|
139
|
+
click.echo("healthy")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@healthcheck.command(name="beat")
|
|
143
|
+
@click.option(
|
|
144
|
+
"--heartbeat-file",
|
|
145
|
+
type=click.Path(path_type=Path),
|
|
146
|
+
help="Override CAMPLY_BEAT_HEALTH_FILE.",
|
|
147
|
+
)
|
|
148
|
+
@click.option(
|
|
149
|
+
"--max-age",
|
|
150
|
+
default=60.0,
|
|
151
|
+
type=click.FloatRange(min=0, min_open=True),
|
|
152
|
+
show_default=True,
|
|
153
|
+
)
|
|
154
|
+
def beat(heartbeat_file: Path | None, max_age: float) -> None:
|
|
155
|
+
"""Require a fresh scheduler-loop heartbeat from a live local process."""
|
|
156
|
+
if os.name == "nt":
|
|
157
|
+
raise click.ClickException("Beat healthchecks require a Unix PID namespace.")
|
|
158
|
+
path = heartbeat_file or beat_health_path()
|
|
159
|
+
try:
|
|
160
|
+
state = json.loads(path.read_text())
|
|
161
|
+
if not isinstance(state, dict):
|
|
162
|
+
raise ValueError("Invalid heartbeat")
|
|
163
|
+
pid, stamp = state.get("pid"), state.get("timestamp")
|
|
164
|
+
if (
|
|
165
|
+
type(pid) is not int
|
|
166
|
+
or pid <= 0
|
|
167
|
+
or not isinstance(stamp, (int, float))
|
|
168
|
+
or isinstance(stamp, bool)
|
|
169
|
+
):
|
|
170
|
+
raise ValueError("Invalid heartbeat")
|
|
171
|
+
timestamp = float(stamp)
|
|
172
|
+
except (OSError, ValueError, OverflowError) as exc:
|
|
173
|
+
raise click.ClickException("Beat heartbeat is missing or invalid.") from exc
|
|
174
|
+
if not 0 <= time.time() - timestamp <= max_age:
|
|
175
|
+
raise click.ClickException(
|
|
176
|
+
"Beat heartbeat is stale or has an invalid timestamp."
|
|
177
|
+
)
|
|
178
|
+
try:
|
|
179
|
+
os.kill(pid, 0)
|
|
180
|
+
except (OSError, OverflowError) as exc:
|
|
181
|
+
raise click.ClickException(
|
|
182
|
+
"Beat scheduler process is not running or accessible."
|
|
183
|
+
) from exc
|
|
184
|
+
click.echo("healthy")
|
camply/cli/main.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Camply CLI entrypoint.
|
|
3
|
+
|
|
4
|
+
The ``camply`` console script resolves to :func:`cli`. Subcommands are
|
|
5
|
+
registered on this group as the CLI grows.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
from importlib.resources import files
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
|
|
19
|
+
from camply.__about__ import __application__, __version__
|
|
20
|
+
from camply.cli.healthcheck import beat_health_path, healthcheck
|
|
21
|
+
from camply.cli.runtime import require_modules
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.group(
|
|
25
|
+
name=__application__,
|
|
26
|
+
invoke_without_command=True,
|
|
27
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
28
|
+
)
|
|
29
|
+
@click.version_option(
|
|
30
|
+
version=__version__,
|
|
31
|
+
prog_name=__application__,
|
|
32
|
+
message="%(prog)s, version %(version)s",
|
|
33
|
+
)
|
|
34
|
+
@click.pass_context
|
|
35
|
+
def cli(ctx: click.Context) -> None:
|
|
36
|
+
"""
|
|
37
|
+
camply, the campsite finder ⛺️.
|
|
38
|
+
|
|
39
|
+
Find campsites at sold-out campgrounds.
|
|
40
|
+
"""
|
|
41
|
+
if ctx.invoked_subcommand is None:
|
|
42
|
+
click.echo(ctx.get_help())
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _executable(name: str, *, hint: str) -> str:
|
|
46
|
+
"""Find a runtime executable before performing startup side effects."""
|
|
47
|
+
executable = shutil.which(name)
|
|
48
|
+
if executable is None:
|
|
49
|
+
raise click.ClickException(f"{name} is required. {hint}")
|
|
50
|
+
return executable
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _require_server(*modules: str) -> None:
|
|
54
|
+
"""Check shared server dependencies plus any role-specific Python modules."""
|
|
55
|
+
require_modules(
|
|
56
|
+
"server",
|
|
57
|
+
(
|
|
58
|
+
"fastapi",
|
|
59
|
+
"uvicorn",
|
|
60
|
+
"orjson",
|
|
61
|
+
"jwt",
|
|
62
|
+
"cryptography",
|
|
63
|
+
"prometheus_client",
|
|
64
|
+
*modules,
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@cli.group(invoke_without_command=True)
|
|
70
|
+
@click.option("--host", help="Standalone bind address (default: 127.0.0.1).")
|
|
71
|
+
@click.option(
|
|
72
|
+
"--port", type=click.IntRange(1, 65535), help="Standalone port (default: 8000)."
|
|
73
|
+
)
|
|
74
|
+
@click.pass_context
|
|
75
|
+
def serve(ctx: click.Context, host: str | None, port: int | None) -> None:
|
|
76
|
+
"""Serve the standalone app, or select an individual application role."""
|
|
77
|
+
if ctx.invoked_subcommand is not None:
|
|
78
|
+
if host is not None or port is not None:
|
|
79
|
+
raise click.UsageError(
|
|
80
|
+
"--host and --port apply to standalone serving; omit the subcommand."
|
|
81
|
+
)
|
|
82
|
+
return
|
|
83
|
+
_require_server()
|
|
84
|
+
args = [sys.executable, "-m", "camply.server.standalone"]
|
|
85
|
+
if host is not None:
|
|
86
|
+
args.extend(["--host", host])
|
|
87
|
+
if port is not None:
|
|
88
|
+
args.extend(["--port", str(port)])
|
|
89
|
+
os.environ["CAMPLY_RUNTIME_MODE"] = "standalone"
|
|
90
|
+
os.execv(sys.executable, args)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@serve.command(name="frontend")
|
|
94
|
+
def serve_frontend() -> None:
|
|
95
|
+
"""Serve bundled assets and proxy /api to CAMPLY_BACKEND_URL using Nginx."""
|
|
96
|
+
executable = _executable("nginx", hint="Install Nginx or use the camply-web image.")
|
|
97
|
+
directory = Path(__file__).resolve().parents[1] / "server" / "static"
|
|
98
|
+
if not (directory / "index.html").is_file():
|
|
99
|
+
raise click.ClickException(
|
|
100
|
+
"Frontend assets are missing. Install a prepared camply wheel."
|
|
101
|
+
)
|
|
102
|
+
templates = files("camply.cli").joinpath("nginx")
|
|
103
|
+
with tempfile.TemporaryDirectory(prefix="camply-nginx-") as temporary:
|
|
104
|
+
runtime = Path(temporary).resolve()
|
|
105
|
+
server = runtime / "server.conf"
|
|
106
|
+
config = runtime / "nginx.conf"
|
|
107
|
+
server.write_text(
|
|
108
|
+
templates.joinpath("server.conf")
|
|
109
|
+
.read_text()
|
|
110
|
+
.replace(
|
|
111
|
+
"${CAMPLY_FRONTEND_DIR}", json.dumps(str(directory), ensure_ascii=False)
|
|
112
|
+
)
|
|
113
|
+
.replace(
|
|
114
|
+
"${CAMPLY_BACKEND_URL}",
|
|
115
|
+
json.dumps(
|
|
116
|
+
os.environ.get("CAMPLY_BACKEND_URL") or "http://backend:8000",
|
|
117
|
+
ensure_ascii=False,
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
config.write_text(
|
|
122
|
+
templates.joinpath("nginx.conf")
|
|
123
|
+
.read_text()
|
|
124
|
+
.replace(
|
|
125
|
+
"${CAMPLY_NGINX_SERVER_CONFIG}",
|
|
126
|
+
json.dumps(str(server), ensure_ascii=False),
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
os.execv(
|
|
130
|
+
executable,
|
|
131
|
+
[executable, "-p", f"{runtime}/", "-c", str(config), "-g", "daemon off;"],
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@serve.command(name="backend", context_settings={"ignore_unknown_options": True})
|
|
136
|
+
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
|
137
|
+
def serve_backend(args: tuple[str, ...]) -> None:
|
|
138
|
+
"""Migrate the database and serve the API; forward arguments to Gunicorn."""
|
|
139
|
+
_require_server("gunicorn", "uvicorn_worker")
|
|
140
|
+
if os.environ.get("SKIP_MIGRATIONS", "0") != "1":
|
|
141
|
+
from camply.db.migration import upgrade
|
|
142
|
+
|
|
143
|
+
upgrade()
|
|
144
|
+
os.execv(
|
|
145
|
+
sys.executable,
|
|
146
|
+
[
|
|
147
|
+
sys.executable,
|
|
148
|
+
"-m",
|
|
149
|
+
"gunicorn",
|
|
150
|
+
"--config",
|
|
151
|
+
"python:camply.server.gunicorn",
|
|
152
|
+
*args,
|
|
153
|
+
"camply.server.app:app",
|
|
154
|
+
],
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@serve.command(name="worker", context_settings={"ignore_unknown_options": True})
|
|
159
|
+
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
|
160
|
+
def serve_worker(args: tuple[str, ...]) -> None:
|
|
161
|
+
"""Consume background tasks; forward arguments to the Celery worker."""
|
|
162
|
+
_worker_process("worker", ("-Q", "celery", "--concurrency=4", *args))
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@serve.command(name="beat", context_settings={"ignore_unknown_options": True})
|
|
166
|
+
@click.option(
|
|
167
|
+
"--sync-providers",
|
|
168
|
+
is_flag=True,
|
|
169
|
+
help="Enqueue an initial provider catalog sync before starting the scheduler.",
|
|
170
|
+
)
|
|
171
|
+
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
|
172
|
+
def serve_beat(sync_providers: bool, args: tuple[str, ...]) -> None:
|
|
173
|
+
"""Schedule background tasks; optionally enqueue a provider sync before exec."""
|
|
174
|
+
_worker_process(
|
|
175
|
+
"beat",
|
|
176
|
+
(
|
|
177
|
+
"--scheduler",
|
|
178
|
+
"camply.worker.scheduler:HealthScheduler",
|
|
179
|
+
"--max-interval=30",
|
|
180
|
+
*args,
|
|
181
|
+
),
|
|
182
|
+
sync_providers=sync_providers,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _worker_process(
|
|
187
|
+
command: str, args: tuple[str, ...], *, sync_providers: bool = False
|
|
188
|
+
) -> None:
|
|
189
|
+
"""Check dependencies, optionally dispatch a sync, then exec the distributed role."""
|
|
190
|
+
require_modules("distributed", ("celery", "redis", "prometheus_client"))
|
|
191
|
+
if command == "beat":
|
|
192
|
+
path = beat_health_path()
|
|
193
|
+
try:
|
|
194
|
+
path.unlink(missing_ok=True)
|
|
195
|
+
except OSError as exc:
|
|
196
|
+
raise click.ClickException(
|
|
197
|
+
"Cannot prepare the beat heartbeat file."
|
|
198
|
+
) from exc
|
|
199
|
+
os.environ["CAMPLY_BEAT_HEALTH_FILE"] = str(path)
|
|
200
|
+
if sync_providers:
|
|
201
|
+
try:
|
|
202
|
+
subprocess.run(
|
|
203
|
+
[
|
|
204
|
+
sys.executable,
|
|
205
|
+
"-m",
|
|
206
|
+
"celery",
|
|
207
|
+
"-A",
|
|
208
|
+
"camply.worker.celery_app",
|
|
209
|
+
"call",
|
|
210
|
+
"camply.catalog.sync_all_providers",
|
|
211
|
+
"--expires=600",
|
|
212
|
+
],
|
|
213
|
+
check=True,
|
|
214
|
+
)
|
|
215
|
+
except subprocess.CalledProcessError as exc:
|
|
216
|
+
raise click.ClickException(
|
|
217
|
+
f"Could not enqueue provider sync (exit status {exc.returncode})."
|
|
218
|
+
) from exc
|
|
219
|
+
os.execv(
|
|
220
|
+
sys.executable,
|
|
221
|
+
[
|
|
222
|
+
sys.executable,
|
|
223
|
+
"-m",
|
|
224
|
+
"celery",
|
|
225
|
+
"-A",
|
|
226
|
+
"camply.worker.celery_app",
|
|
227
|
+
command,
|
|
228
|
+
"-l",
|
|
229
|
+
"info",
|
|
230
|
+
*args,
|
|
231
|
+
],
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
serve.add_command(healthcheck)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
worker_processes auto;
|
|
2
|
+
error_log stderr notice;
|
|
3
|
+
pid nginx.pid;
|
|
4
|
+
|
|
5
|
+
events {
|
|
6
|
+
worker_connections 1024;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
http {
|
|
10
|
+
include /etc/nginx/mime.types;
|
|
11
|
+
default_type application/octet-stream;
|
|
12
|
+
|
|
13
|
+
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
|
14
|
+
'$status $body_bytes_sent "$http_referer" '
|
|
15
|
+
'"$http_user_agent" "$http_x_forwarded_for"';
|
|
16
|
+
|
|
17
|
+
access_log /dev/stdout main;
|
|
18
|
+
|
|
19
|
+
sendfile on;
|
|
20
|
+
tcp_nopush on;
|
|
21
|
+
keepalive_timeout 65;
|
|
22
|
+
gzip on;
|
|
23
|
+
|
|
24
|
+
include ${CAMPLY_NGINX_SERVER_CONFIG};
|
|
25
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
server {
|
|
2
|
+
listen 8080;
|
|
3
|
+
server_name _;
|
|
4
|
+
|
|
5
|
+
root ${CAMPLY_FRONTEND_DIR};
|
|
6
|
+
index index.html;
|
|
7
|
+
|
|
8
|
+
# Resolve temp paths under the writable Nginx runtime prefix.
|
|
9
|
+
client_body_temp_path client_body_temp;
|
|
10
|
+
proxy_temp_path proxy_temp;
|
|
11
|
+
fastcgi_temp_path fastcgi_temp;
|
|
12
|
+
uwsgi_temp_path uwsgi_temp;
|
|
13
|
+
scgi_temp_path scgi_temp;
|
|
14
|
+
|
|
15
|
+
# Gzip compression for text, script, and style assets
|
|
16
|
+
gzip on;
|
|
17
|
+
gzip_vary on;
|
|
18
|
+
gzip_proxied any;
|
|
19
|
+
gzip_comp_level 6;
|
|
20
|
+
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
|
|
21
|
+
|
|
22
|
+
location = /healthz {
|
|
23
|
+
access_log off;
|
|
24
|
+
add_header Content-Type text/plain;
|
|
25
|
+
return 200 'OK';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Content-hashed static assets (safe to cache forever)
|
|
29
|
+
location /assets/ {
|
|
30
|
+
expires 1y;
|
|
31
|
+
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
32
|
+
access_log off;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Ensure index.html is always revalidated for new deployments
|
|
36
|
+
location = /index.html {
|
|
37
|
+
add_header Cache-Control "no-cache";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
location /api/ {
|
|
41
|
+
proxy_pass ${CAMPLY_BACKEND_URL};
|
|
42
|
+
proxy_ssl_server_name on;
|
|
43
|
+
proxy_http_version 1.1;
|
|
44
|
+
proxy_set_header Connection "";
|
|
45
|
+
proxy_set_header Host $host;
|
|
46
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
47
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
48
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Documentation has directory indexes and must never fall back to the app.
|
|
52
|
+
location = /docs {
|
|
53
|
+
return 301 /docs/;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
location /docs/ {
|
|
57
|
+
try_files $uri $uri/ $uri.html =404;
|
|
58
|
+
add_header Cache-Control "no-cache";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
location / {
|
|
62
|
+
try_files $uri $uri/ /index.html;
|
|
63
|
+
}
|
|
64
|
+
}
|
camply/cli/runtime.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Validate optional runtime dependencies without importing application bootstraps."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def require_modules(extra: str, modules: tuple[str, ...]) -> None:
|
|
9
|
+
"""Reject incomplete extras in this interpreter before runtime side effects."""
|
|
10
|
+
missing = [name for name in modules if importlib.util.find_spec(name) is None]
|
|
11
|
+
if missing:
|
|
12
|
+
raise click.ClickException(
|
|
13
|
+
f"Missing runtime dependencies: {', '.join(missing)}. Install camply[{extra}]."
|
|
14
|
+
)
|
camply/db/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# camply.db
|
|
2
|
+
|
|
3
|
+
Database models and migrations for the camply platform.
|
|
4
|
+
|
|
5
|
+
## Checklist Data Layer Models
|
|
6
|
+
|
|
7
|
+
These models support the smart de-duplicated poller.
|
|
8
|
+
|
|
9
|
+
### User
|
|
10
|
+
|
|
11
|
+
Represents a platform user with early access flags and notification tokens.
|
|
12
|
+
|
|
13
|
+
- `id`: UUID (Primary Key)
|
|
14
|
+
- `auth0_id`: String (Unique, Nullable)
|
|
15
|
+
- `email`: String (Unique, Index)
|
|
16
|
+
- `is_invited`: Boolean (defaults to False; stored in the existing `is_early_access_user` column)
|
|
17
|
+
- `pushover_token`: String (Nullable)
|
|
18
|
+
|
|
19
|
+
### UniqueTarget
|
|
20
|
+
|
|
21
|
+
The de-duplicated "what" to scan. Represents a unique combination of provider, campground, and date range.
|
|
22
|
+
|
|
23
|
+
- `id`: UUID (Primary Key)
|
|
24
|
+
- `provider_id`: Integer (Foreign Key)
|
|
25
|
+
- `campground_id`: String (Foreign Key)
|
|
26
|
+
- `start_date`: Date
|
|
27
|
+
- `end_date`: Date
|
|
28
|
+
- `hash`: String (Unique Index) - SHA256 of composite fields, automatically calculated.
|
|
29
|
+
|
|
30
|
+
### UserScan
|
|
31
|
+
|
|
32
|
+
Links a user to a target with specific filtering preferences.
|
|
33
|
+
|
|
34
|
+
- `id`: UUID (Primary Key)
|
|
35
|
+
- `user_id`: UUID (Foreign Key)
|
|
36
|
+
- `target_id`: UUID (Foreign Key)
|
|
37
|
+
- `is_active`: Boolean (Defaults to True)
|
|
38
|
+
- `min_stay_length`: Integer
|
|
39
|
+
- `preferred_types`: ARRAY of Strings
|
|
40
|
+
- `require_electric`: Boolean
|
|
41
|
+
|
|
42
|
+
### ScanResult
|
|
43
|
+
|
|
44
|
+
Cached findings from a scan execution.
|
|
45
|
+
|
|
46
|
+
- `id`: UUID (Primary Key)
|
|
47
|
+
- `target_id`: UUID (Foreign Key)
|
|
48
|
+
- `campsite_id`: String
|
|
49
|
+
- `available_dates`: JSONB (Array of ISO date strings)
|
|
50
|
+
- `found_at`: Timestamp (Index)
|
camply/db/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database Package for camply.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from camply.db.eligibility import (
|
|
6
|
+
eligible_scan_condition,
|
|
7
|
+
is_scan_eligible,
|
|
8
|
+
is_user_scan_eligible,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"eligible_scan_condition",
|
|
13
|
+
"is_scan_eligible",
|
|
14
|
+
"is_user_scan_eligible",
|
|
15
|
+
]
|
camply/db/alembic.ini
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[alembic]
|
|
2
|
+
script_location = camply.db:migrations
|
|
3
|
+
timezone = utc
|
|
4
|
+
version_path_separator = os
|
|
5
|
+
|
|
6
|
+
[post_write_hooks]
|
|
7
|
+
hooks = ruff_format, ruff_check
|
|
8
|
+
ruff_format.type = exec
|
|
9
|
+
ruff_format.executable = %(here)s/../../../.venv/bin/ruff
|
|
10
|
+
ruff_format.options = format REVISION_SCRIPT_FILENAME
|
|
11
|
+
ruff_check.type = exec
|
|
12
|
+
ruff_check.executable = %(here)s/../../../.venv/bin/ruff
|
|
13
|
+
ruff_check.options = check --fix REVISION_SCRIPT_FILENAME
|
|
14
|
+
|
|
15
|
+
[loggers]
|
|
16
|
+
keys = root,sqlalchemy,alembic
|
|
17
|
+
|
|
18
|
+
[handlers]
|
|
19
|
+
keys = console
|
|
20
|
+
|
|
21
|
+
[formatters]
|
|
22
|
+
keys = generic
|
|
23
|
+
|
|
24
|
+
[logger_root]
|
|
25
|
+
level = WARNING
|
|
26
|
+
handlers = console
|
|
27
|
+
qualname =
|
|
28
|
+
|
|
29
|
+
[logger_sqlalchemy]
|
|
30
|
+
level = WARNING
|
|
31
|
+
handlers = console
|
|
32
|
+
qualname = sqlalchemy.engine
|
|
33
|
+
|
|
34
|
+
[logger_alembic]
|
|
35
|
+
level = INFO
|
|
36
|
+
handlers = console
|
|
37
|
+
qualname = alembic
|
|
38
|
+
|
|
39
|
+
[handler_console]
|
|
40
|
+
class = StreamHandler
|
|
41
|
+
args = (sys.stderr,)
|
|
42
|
+
level = NOTSET
|
|
43
|
+
formatter = generic
|
|
44
|
+
|
|
45
|
+
[formatter_generic]
|
|
46
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
47
|
+
datefmt = %H:%M:%S
|