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.
Files changed (573) hide show
  1. camply/__about__.py +18 -0
  2. camply/__init__.py +1 -0
  3. camply/cli/__init__.py +1 -0
  4. camply/cli/__main__.py +6 -0
  5. camply/cli/healthcheck.py +184 -0
  6. camply/cli/main.py +235 -0
  7. camply/cli/nginx/nginx.conf +25 -0
  8. camply/cli/nginx/server.conf +64 -0
  9. camply/cli/runtime.py +14 -0
  10. camply/db/README.md +50 -0
  11. camply/db/__init__.py +15 -0
  12. camply/db/alembic.ini +47 -0
  13. camply/db/availability.py +68 -0
  14. camply/db/config.py +159 -0
  15. camply/db/data/__init__.py +0 -0
  16. camply/db/data/providers.py +112 -0
  17. camply/db/eligibility.py +37 -0
  18. camply/db/migration.py +40 -0
  19. camply/db/migrations/__init__.py +1 -0
  20. camply/db/migrations/env.py +75 -0
  21. camply/db/migrations/script.py.mako +33 -0
  22. camply/db/migrations/versions/067d9d721253_init.py +65 -0
  23. camply/db/migrations/versions/09367969f1a4_preserve_campsite_details.py +43 -0
  24. camply/db/migrations/versions/2ac874f9d2ad_indexed_native_campground_search.py +44 -0
  25. camply/db/migrations/versions/36b6b307eef5_add_checklist_models.py +154 -0
  26. camply/db/migrations/versions/36dbff1f679f_user_password_credentials.py +44 -0
  27. camply/db/migrations/versions/4cedc8e5fa20_search.py +58 -0
  28. camply/db/migrations/versions/4d64e0e4ae18_merge_standalone_delivery_with_current_.py +31 -0
  29. camply/db/migrations/versions/624a4720b8c3_locations.py +107 -0
  30. camply/db/migrations/versions/77984a1ae368_merge_checklist_and_access_request.py +29 -0
  31. camply/db/migrations/versions/8a1b2c3d4e5f_admin_ui.py +99 -0
  32. camply/db/migrations/versions/9b2c3d4e5f6a_enriched_search.py +134 -0
  33. camply/db/migrations/versions/9b94e557767a_seed_reservecalifornia_provider.py +67 -0
  34. camply/db/migrations/versions/__init__.py +1 -0
  35. camply/db/migrations/versions/access_request.py +46 -0
  36. camply/db/migrations/versions/d8923fc28e9e_city.py +40 -0
  37. camply/db/migrations/versions/ea6111bf3bed_seed_verified_usedirect_providers.py +124 -0
  38. camply/db/migrations/versions/ed55b53b0cda_merge_admin_ui_and_usedirect_providers.py +29 -0
  39. camply/db/migrations/versions/ef2486672984_notification_outbox.py +79 -0
  40. camply/db/models/__init__.py +31 -0
  41. camply/db/models/access_request.py +40 -0
  42. camply/db/models/admin_audit.py +53 -0
  43. camply/db/models/base.py +11 -0
  44. camply/db/models/campgrounds.py +74 -0
  45. camply/db/models/notification_outbox.py +68 -0
  46. camply/db/models/providers.py +46 -0
  47. camply/db/models/recreation_area.py +58 -0
  48. camply/db/models/scan_results.py +56 -0
  49. camply/db/models/search.py +693 -0
  50. camply/db/models/unique_targets.py +98 -0
  51. camply/db/models/user_scans.py +60 -0
  52. camply/db/models/users.py +72 -0
  53. camply/db/search_bounds.py +49 -0
  54. camply/db/search_maps.py +301 -0
  55. camply/db/utils.py +31 -0
  56. camply/providers/__init__.py +35 -0
  57. camply/providers/base.py +752 -0
  58. camply/providers/cli.py +58 -0
  59. camply/providers/dto.py +35 -0
  60. camply/providers/errors.py +28 -0
  61. camply/providers/recreation_gov/__init__.py +0 -0
  62. camply/providers/recreation_gov/config.py +74 -0
  63. camply/providers/recreation_gov/models/__init__.py +0 -0
  64. camply/providers/recreation_gov/models/address.py +46 -0
  65. camply/providers/recreation_gov/models/api.py +110 -0
  66. camply/providers/recreation_gov/models/campgrounds.py +93 -0
  67. camply/providers/recreation_gov/models/enums.py +64 -0
  68. camply/providers/recreation_gov/models/recreation_area.py +86 -0
  69. camply/providers/recreation_gov/provider.py +505 -0
  70. camply/providers/usedirect/__init__.py +5 -0
  71. camply/providers/usedirect/models/__init__.py +1 -0
  72. camply/providers/usedirect/models/api.py +210 -0
  73. camply/providers/usedirect/models/metadata.py +27 -0
  74. camply/providers/usedirect/provider.py +257 -0
  75. camply/providers/usedirect/variations.py +158 -0
  76. camply/providers/utils.py +60 -0
  77. camply/py.typed +0 -0
  78. camply/server/__about__.py +5 -0
  79. camply/server/__init__.py +7 -0
  80. camply/server/_prometheus.py +310 -0
  81. camply/server/app.py +174 -0
  82. camply/server/auth.py +414 -0
  83. camply/server/config.py +132 -0
  84. camply/server/dependencies.py +12 -0
  85. camply/server/gunicorn.py +58 -0
  86. camply/server/metrics.py +37 -0
  87. camply/server/models/__init__.py +0 -0
  88. camply/server/models/campgrounds.py +37 -0
  89. camply/server/openapi.py +20 -0
  90. camply/server/provision.py +64 -0
  91. camply/server/py.typed +0 -0
  92. camply/server/routers/__init__.py +0 -0
  93. camply/server/routers/access.py +42 -0
  94. camply/server/routers/admin.py +1123 -0
  95. camply/server/routers/auth_config.py +41 -0
  96. camply/server/routers/campgrounds.py +42 -0
  97. camply/server/routers/health.py +59 -0
  98. camply/server/routers/me.py +82 -0
  99. camply/server/routers/providers.py +62 -0
  100. camply/server/routers/recreation_areas.py +107 -0
  101. camply/server/routers/scans.py +533 -0
  102. camply/server/routers/search.py +330 -0
  103. camply/server/routers/sessions.py +165 -0
  104. camply/server/schemas.py +619 -0
  105. camply/server/services/__init__.py +1 -0
  106. camply/server/services/local_operations.py +143 -0
  107. camply/server/services/operations.py +592 -0
  108. camply/server/services/trends.py +871 -0
  109. camply/server/standalone.py +77 -0
  110. camply/server/static/_routes.json +5 -0
  111. camply/server/static/assets/index-BIr8q8mk.css +1 -0
  112. camply/server/static/assets/index-COizb2l4.js +997 -0
  113. camply/server/static/assets/maplibre-gl-worker-Cc2ivl_Q.js +10 -0
  114. camply/server/static/branding/camply-horizontal-dark.svg +16 -0
  115. camply/server/static/branding/camply-horizontal.svg +16 -0
  116. camply/server/static/branding/camply-icon-dark.svg +16 -0
  117. camply/server/static/branding/camply-icon.svg +16 -0
  118. camply/server/static/branding/camply-stacked-dark.svg +16 -0
  119. camply/server/static/branding/camply-stacked.svg +16 -0
  120. camply/server/static/branding/camply.png +0 -0
  121. camply/server/static/branding/justin-flannery.png +0 -0
  122. camply/server/static/branding/recreation-gov.png +0 -0
  123. camply/server/static/docs/404.html +24 -0
  124. camply/server/static/docs/CHECKLIST.html +27 -0
  125. camply/server/static/docs/CONTRIBUTING.html +92 -0
  126. camply/server/static/docs/PLAN.html +27 -0
  127. camply/server/static/docs/architecture/ARCHITECTURE_DEEP_DIVE.html +43 -0
  128. camply/server/static/docs/architecture/DESIGN_API.html +55 -0
  129. camply/server/static/docs/architecture/DESIGN_DATA.html +27 -0
  130. camply/server/static/docs/architecture/DESIGN_FRONTEND.html +27 -0
  131. camply/server/static/docs/architecture/DESIGN_NOTIFICATIONS.html +39 -0
  132. camply/server/static/docs/architecture/DESIGN_PROVIDERS.html +62 -0
  133. camply/server/static/docs/assets/CHECKLIST.md.hWdSUfeD.js +1 -0
  134. camply/server/static/docs/assets/CHECKLIST.md.hWdSUfeD.lean.js +1 -0
  135. camply/server/static/docs/assets/CONTRIBUTING.md.C_0dYw2p.js +66 -0
  136. camply/server/static/docs/assets/CONTRIBUTING.md.C_0dYw2p.lean.js +1 -0
  137. camply/server/static/docs/assets/PLAN.md.BIejwBCQ.js +1 -0
  138. camply/server/static/docs/assets/PLAN.md.BIejwBCQ.lean.js +1 -0
  139. camply/server/static/docs/assets/app-docs.DXQE_AMG.png +0 -0
  140. camply/server/static/docs/assets/app-footer-mobile.B_7q9zSX.png +0 -0
  141. camply/server/static/docs/assets/app-footer.BKzirxoT.png +0 -0
  142. camply/server/static/docs/assets/app.BIBn1_0C.js +1 -0
  143. camply/server/static/docs/assets/architecture_ARCHITECTURE_DEEP_DIVE.md.qR2FYBxC.js +17 -0
  144. camply/server/static/docs/assets/architecture_ARCHITECTURE_DEEP_DIVE.md.qR2FYBxC.lean.js +1 -0
  145. camply/server/static/docs/assets/architecture_DESIGN_API.md.BiYvEFqk.js +29 -0
  146. camply/server/static/docs/assets/architecture_DESIGN_API.md.BiYvEFqk.lean.js +1 -0
  147. camply/server/static/docs/assets/architecture_DESIGN_DATA.md.Dv5xEwch.js +1 -0
  148. camply/server/static/docs/assets/architecture_DESIGN_DATA.md.Dv5xEwch.lean.js +1 -0
  149. camply/server/static/docs/assets/architecture_DESIGN_FRONTEND.md.CLw6-eHc.js +1 -0
  150. camply/server/static/docs/assets/architecture_DESIGN_FRONTEND.md.CLw6-eHc.lean.js +1 -0
  151. camply/server/static/docs/assets/architecture_DESIGN_NOTIFICATIONS.md.DTRN1Z6F.js +13 -0
  152. camply/server/static/docs/assets/architecture_DESIGN_NOTIFICATIONS.md.DTRN1Z6F.lean.js +1 -0
  153. camply/server/static/docs/assets/architecture_DESIGN_PROVIDERS.md.C2mgptbF.js +36 -0
  154. camply/server/static/docs/assets/architecture_DESIGN_PROVIDERS.md.C2mgptbF.lean.js +1 -0
  155. camply/server/static/docs/assets/camply.svg +17 -0
  156. camply/server/static/docs/assets/chunks/@localSearchIndexroot.CxBSeztH.js +1 -0
  157. camply/server/static/docs/assets/chunks/VPLocalSearchBox.CdPfQuEa.js +9 -0
  158. camply/server/static/docs/assets/chunks/framework.BM5YPQmL.js +20 -0
  159. camply/server/static/docs/assets/chunks/theme.DsmUp_5E.js +2 -0
  160. camply/server/static/docs/assets/configuration.md.CTjuVFBs.js +4 -0
  161. camply/server/static/docs/assets/configuration.md.CTjuVFBs.lean.js +1 -0
  162. camply/server/static/docs/assets/desktop.7jWU1gTt.png +0 -0
  163. camply/server/static/docs/assets/development.md.BvyDMyRv.js +7 -0
  164. camply/server/static/docs/assets/development.md.BvyDMyRv.lean.js +1 -0
  165. camply/server/static/docs/assets/development_AGENT_TROUBLESHOOTING.md.CJLWPYds.js +1 -0
  166. camply/server/static/docs/assets/development_AGENT_TROUBLESHOOTING.md.CJLWPYds.lean.js +1 -0
  167. camply/server/static/docs/assets/development_DESIGN_AGENTIC.md.CctuI_eG.js +1 -0
  168. camply/server/static/docs/assets/development_DESIGN_AGENTIC.md.CctuI_eG.lean.js +1 -0
  169. camply/server/static/docs/assets/development_DEVELOPER_GUIDE.md.GAkSFhfy.js +8 -0
  170. camply/server/static/docs/assets/development_DEVELOPER_GUIDE.md.GAkSFhfy.lean.js +1 -0
  171. camply/server/static/docs/assets/development_STYLE_GUIDE.md.5In2cXG3.js +1 -0
  172. camply/server/static/docs/assets/development_STYLE_GUIDE.md.5In2cXG3.lean.js +1 -0
  173. camply/server/static/docs/assets/index.md.mtfxlh4P.js +1 -0
  174. camply/server/static/docs/assets/index.md.mtfxlh4P.lean.js +1 -0
  175. camply/server/static/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  176. camply/server/static/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  177. camply/server/static/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  178. camply/server/static/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  179. camply/server/static/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  180. camply/server/static/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  181. camply/server/static/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  182. camply/server/static/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  183. camply/server/static/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  184. camply/server/static/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  185. camply/server/static/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  186. camply/server/static/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  187. camply/server/static/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  188. camply/server/static/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  189. camply/server/static/docs/assets/mobile.BHn3cOOl.png +0 -0
  190. camply/server/static/docs/assets/operations_CONFIGURATION.md.D6oPLkcv.js +1 -0
  191. camply/server/static/docs/assets/operations_CONFIGURATION.md.D6oPLkcv.lean.js +1 -0
  192. camply/server/static/docs/assets/operations_RELEASES.md.DJpsWvJW.js +18 -0
  193. camply/server/static/docs/assets/operations_RELEASES.md.DJpsWvJW.lean.js +1 -0
  194. camply/server/static/docs/assets/operations_RUNNING.md.Be3YOJ6L.js +27 -0
  195. camply/server/static/docs/assets/operations_RUNNING.md.Be3YOJ6L.lean.js +1 -0
  196. camply/server/static/docs/assets/reference.md.t88yfbnH.js +1 -0
  197. camply/server/static/docs/assets/reference.md.t88yfbnH.lean.js +1 -0
  198. camply/server/static/docs/assets/self-hosting_containers.md.5a1rSEtB.js +6 -0
  199. camply/server/static/docs/assets/self-hosting_containers.md.5a1rSEtB.lean.js +1 -0
  200. camply/server/static/docs/assets/self-hosting_standalone.md.CQahK6_G.js +3 -0
  201. camply/server/static/docs/assets/self-hosting_standalone.md.CQahK6_G.lean.js +1 -0
  202. camply/server/static/docs/assets/specs_001-checklist-data-layer_checklists_requirements.md.DfhQsALb.js +1 -0
  203. camply/server/static/docs/assets/specs_001-checklist-data-layer_checklists_requirements.md.DfhQsALb.lean.js +1 -0
  204. camply/server/static/docs/assets/specs_001-checklist-data-layer_data-model.md.Bu3SJa6R.js +1 -0
  205. camply/server/static/docs/assets/specs_001-checklist-data-layer_data-model.md.Bu3SJa6R.lean.js +1 -0
  206. camply/server/static/docs/assets/specs_001-checklist-data-layer_plan.md.D3nzZvpG.js +16 -0
  207. camply/server/static/docs/assets/specs_001-checklist-data-layer_plan.md.D3nzZvpG.lean.js +1 -0
  208. camply/server/static/docs/assets/specs_001-checklist-data-layer_quickstart.md.brT3yMdZ.js +5 -0
  209. camply/server/static/docs/assets/specs_001-checklist-data-layer_quickstart.md.brT3yMdZ.lean.js +1 -0
  210. camply/server/static/docs/assets/specs_001-checklist-data-layer_research.md.DjcknknI.js +1 -0
  211. camply/server/static/docs/assets/specs_001-checklist-data-layer_research.md.DjcknknI.lean.js +1 -0
  212. camply/server/static/docs/assets/specs_001-checklist-data-layer_spec.md.CRhdCu3D.js +1 -0
  213. camply/server/static/docs/assets/specs_001-checklist-data-layer_spec.md.CRhdCu3D.lean.js +1 -0
  214. camply/server/static/docs/assets/specs_001-checklist-data-layer_tasks.md.tXQD9pHw.js +7 -0
  215. camply/server/static/docs/assets/specs_001-checklist-data-layer_tasks.md.tXQD9pHw.lean.js +1 -0
  216. camply/server/static/docs/assets/specs_002-admin-ui_plan.md.1i7mOqnZ.js +8 -0
  217. camply/server/static/docs/assets/specs_002-admin-ui_plan.md.1i7mOqnZ.lean.js +1 -0
  218. camply/server/static/docs/assets/specs_002-admin-ui_spec.md.Bcc8UXaw.js +1 -0
  219. camply/server/static/docs/assets/specs_002-admin-ui_spec.md.Bcc8UXaw.lean.js +1 -0
  220. camply/server/static/docs/assets/specs_002-admin-ui_tasks.md.D4m8JDK5.js +1 -0
  221. camply/server/static/docs/assets/specs_002-admin-ui_tasks.md.D4m8JDK5.lean.js +1 -0
  222. camply/server/static/docs/assets/specs_003-auth-invite-only_plan.md.DKaGDvZk.js +1 -0
  223. camply/server/static/docs/assets/specs_003-auth-invite-only_plan.md.DKaGDvZk.lean.js +1 -0
  224. camply/server/static/docs/assets/specs_003-auth-invite-only_spec.md.DNtUiuzb.js +1 -0
  225. camply/server/static/docs/assets/specs_003-auth-invite-only_spec.md.DNtUiuzb.lean.js +1 -0
  226. camply/server/static/docs/assets/specs_003-auth-invite-only_tasks.md.BWkFMpMk.js +1 -0
  227. camply/server/static/docs/assets/specs_003-auth-invite-only_tasks.md.BWkFMpMk.lean.js +1 -0
  228. camply/server/static/docs/assets/specs_004-frontend-performance_plan.md.0egiciJu.js +1 -0
  229. camply/server/static/docs/assets/specs_004-frontend-performance_plan.md.0egiciJu.lean.js +1 -0
  230. camply/server/static/docs/assets/specs_004-frontend-performance_spec.md.D8F0bRTx.js +1 -0
  231. camply/server/static/docs/assets/specs_004-frontend-performance_spec.md.D8F0bRTx.lean.js +1 -0
  232. camply/server/static/docs/assets/specs_004-frontend-performance_tasks.md.BjrF_-ct.js +1 -0
  233. camply/server/static/docs/assets/specs_004-frontend-performance_tasks.md.BjrF_-ct.lean.js +1 -0
  234. camply/server/static/docs/assets/specs_004-frontend-performance_validation.md.4PK8p4L7.js +1 -0
  235. camply/server/static/docs/assets/specs_004-frontend-performance_validation.md.4PK8p4L7.lean.js +1 -0
  236. camply/server/static/docs/assets/specs_005-reserve-california_plan.md.C_4y1stq.js +1 -0
  237. camply/server/static/docs/assets/specs_005-reserve-california_plan.md.C_4y1stq.lean.js +1 -0
  238. camply/server/static/docs/assets/specs_005-reserve-california_review.md.vPfyZzT1.js +1 -0
  239. camply/server/static/docs/assets/specs_005-reserve-california_review.md.vPfyZzT1.lean.js +1 -0
  240. camply/server/static/docs/assets/specs_005-reserve-california_spec.md.rgMwjOCN.js +1 -0
  241. camply/server/static/docs/assets/specs_005-reserve-california_spec.md.rgMwjOCN.lean.js +1 -0
  242. camply/server/static/docs/assets/specs_005-reserve-california_tasks.md.Drrims_b.js +1 -0
  243. camply/server/static/docs/assets/specs_005-reserve-california_tasks.md.Drrims_b.lean.js +1 -0
  244. camply/server/static/docs/assets/specs_005-reserve-california_validation.md.4NbfQL-M.js +3 -0
  245. camply/server/static/docs/assets/specs_005-reserve-california_validation.md.4NbfQL-M.lean.js +1 -0
  246. camply/server/static/docs/assets/specs_006-search-revamp_plan.md.CgDr0o4d.js +1 -0
  247. camply/server/static/docs/assets/specs_006-search-revamp_plan.md.CgDr0o4d.lean.js +1 -0
  248. camply/server/static/docs/assets/specs_006-search-revamp_spec.md.BpswXgjY.js +1 -0
  249. camply/server/static/docs/assets/specs_006-search-revamp_spec.md.BpswXgjY.lean.js +1 -0
  250. camply/server/static/docs/assets/specs_006-search-revamp_tasks.md.BqfZvZ51.js +1 -0
  251. camply/server/static/docs/assets/specs_006-search-revamp_tasks.md.BqfZvZ51.lean.js +1 -0
  252. camply/server/static/docs/assets/specs_007-usedirect-providers_TODO.md.D-YlwIaX.js +1 -0
  253. camply/server/static/docs/assets/specs_007-usedirect-providers_TODO.md.D-YlwIaX.lean.js +1 -0
  254. camply/server/static/docs/assets/specs_007-usedirect-providers_plan.md.CAt16M-0.js +1 -0
  255. camply/server/static/docs/assets/specs_007-usedirect-providers_plan.md.CAt16M-0.lean.js +1 -0
  256. camply/server/static/docs/assets/specs_007-usedirect-providers_spec.md.DL5gRQoV.js +1 -0
  257. camply/server/static/docs/assets/specs_007-usedirect-providers_spec.md.DL5gRQoV.lean.js +1 -0
  258. camply/server/static/docs/assets/specs_007-usedirect-providers_tasks.md.x22tFDlq.js +1 -0
  259. camply/server/static/docs/assets/specs_007-usedirect-providers_tasks.md.x22tFDlq.lean.js +1 -0
  260. camply/server/static/docs/assets/specs_007-usedirect-providers_validation.md.Cq0jkjMQ.js +1 -0
  261. camply/server/static/docs/assets/specs_007-usedirect-providers_validation.md.Cq0jkjMQ.lean.js +1 -0
  262. camply/server/static/docs/assets/specs_008-scan-editing_plan.md.DLJmchfh.js +1 -0
  263. camply/server/static/docs/assets/specs_008-scan-editing_plan.md.DLJmchfh.lean.js +1 -0
  264. camply/server/static/docs/assets/specs_008-scan-editing_spec.md.CdwTtcx3.js +1 -0
  265. camply/server/static/docs/assets/specs_008-scan-editing_spec.md.CdwTtcx3.lean.js +1 -0
  266. camply/server/static/docs/assets/specs_008-scan-editing_tasks.md.BEgPa6-r.js +1 -0
  267. camply/server/static/docs/assets/specs_008-scan-editing_tasks.md.BEgPa6-r.lean.js +1 -0
  268. camply/server/static/docs/assets/specs_009-live-search_plan.md.BcoKTwHn.js +1 -0
  269. camply/server/static/docs/assets/specs_009-live-search_plan.md.BcoKTwHn.lean.js +1 -0
  270. camply/server/static/docs/assets/specs_009-live-search_spec.md.fUtsQI22.js +1 -0
  271. camply/server/static/docs/assets/specs_009-live-search_spec.md.fUtsQI22.lean.js +1 -0
  272. camply/server/static/docs/assets/specs_009-live-search_tasks.md.DarVWLUi.js +1 -0
  273. camply/server/static/docs/assets/specs_009-live-search_tasks.md.DarVWLUi.lean.js +1 -0
  274. camply/server/static/docs/assets/specs_010-open-source-explore-maps_TODO.md.gzEKqbSz.js +6 -0
  275. camply/server/static/docs/assets/specs_010-open-source-explore-maps_TODO.md.gzEKqbSz.lean.js +1 -0
  276. camply/server/static/docs/assets/specs_010-open-source-explore-maps_checklists_requirements.md.vru4OosJ.js +1 -0
  277. camply/server/static/docs/assets/specs_010-open-source-explore-maps_checklists_requirements.md.vru4OosJ.lean.js +1 -0
  278. camply/server/static/docs/assets/specs_010-open-source-explore-maps_contracts_map-ui-and-configuration.md.B10q5qL1.js +1 -0
  279. camply/server/static/docs/assets/specs_010-open-source-explore-maps_contracts_map-ui-and-configuration.md.B10q5qL1.lean.js +1 -0
  280. camply/server/static/docs/assets/specs_010-open-source-explore-maps_contracts_search-map-api.md.C_WjmPTY.js +6 -0
  281. camply/server/static/docs/assets/specs_010-open-source-explore-maps_contracts_search-map-api.md.C_WjmPTY.lean.js +1 -0
  282. camply/server/static/docs/assets/specs_010-open-source-explore-maps_data-model.md.5O-zMBeh.js +1 -0
  283. camply/server/static/docs/assets/specs_010-open-source-explore-maps_data-model.md.5O-zMBeh.lean.js +1 -0
  284. camply/server/static/docs/assets/specs_010-open-source-explore-maps_plan.md.jDZdRYcr.js +43 -0
  285. camply/server/static/docs/assets/specs_010-open-source-explore-maps_plan.md.jDZdRYcr.lean.js +1 -0
  286. camply/server/static/docs/assets/specs_010-open-source-explore-maps_quickstart.md.BE90M1AB.js +15 -0
  287. camply/server/static/docs/assets/specs_010-open-source-explore-maps_quickstart.md.BE90M1AB.lean.js +1 -0
  288. camply/server/static/docs/assets/specs_010-open-source-explore-maps_research.md.BlHNsukP.js +1 -0
  289. camply/server/static/docs/assets/specs_010-open-source-explore-maps_research.md.BlHNsukP.lean.js +1 -0
  290. camply/server/static/docs/assets/specs_010-open-source-explore-maps_spec.md.LYFig9_-.js +1 -0
  291. camply/server/static/docs/assets/specs_010-open-source-explore-maps_spec.md.LYFig9_-.lean.js +1 -0
  292. camply/server/static/docs/assets/specs_010-open-source-explore-maps_tasks.md.DIp1nm1_.js +10 -0
  293. camply/server/static/docs/assets/specs_010-open-source-explore-maps_tasks.md.DIp1nm1_.lean.js +1 -0
  294. camply/server/static/docs/assets/specs_010-open-source-explore-maps_validation.md.CD2LeHg0.js +1 -0
  295. camply/server/static/docs/assets/specs_010-open-source-explore-maps_validation.md.CD2LeHg0.lean.js +1 -0
  296. camply/server/static/docs/assets/specs_011-standalone-runtime_checklists_requirements.md.DWjHbv1i.js +1 -0
  297. camply/server/static/docs/assets/specs_011-standalone-runtime_checklists_requirements.md.DWjHbv1i.lean.js +1 -0
  298. camply/server/static/docs/assets/specs_011-standalone-runtime_checklists_runtime.md.BO6J8s0L.js +1 -0
  299. camply/server/static/docs/assets/specs_011-standalone-runtime_checklists_runtime.md.BO6J8s0L.lean.js +1 -0
  300. camply/server/static/docs/assets/specs_011-standalone-runtime_contracts_admin-api.md.thtXfhYe.js +1 -0
  301. camply/server/static/docs/assets/specs_011-standalone-runtime_contracts_admin-api.md.thtXfhYe.lean.js +1 -0
  302. camply/server/static/docs/assets/specs_011-standalone-runtime_contracts_runtime.md.BlZkAK7-.js +1 -0
  303. camply/server/static/docs/assets/specs_011-standalone-runtime_contracts_runtime.md.BlZkAK7-.lean.js +1 -0
  304. camply/server/static/docs/assets/specs_011-standalone-runtime_data-model.md.CFAVd4C5.js +11 -0
  305. camply/server/static/docs/assets/specs_011-standalone-runtime_data-model.md.CFAVd4C5.lean.js +1 -0
  306. camply/server/static/docs/assets/specs_011-standalone-runtime_plan.md.DPP4fdeb.js +31 -0
  307. camply/server/static/docs/assets/specs_011-standalone-runtime_plan.md.DPP4fdeb.lean.js +1 -0
  308. camply/server/static/docs/assets/specs_011-standalone-runtime_quickstart.md.Cn5n2TIW.js +11 -0
  309. camply/server/static/docs/assets/specs_011-standalone-runtime_quickstart.md.Cn5n2TIW.lean.js +1 -0
  310. camply/server/static/docs/assets/specs_011-standalone-runtime_research.md.CTOfB-iw.js +1 -0
  311. camply/server/static/docs/assets/specs_011-standalone-runtime_research.md.CTOfB-iw.lean.js +1 -0
  312. camply/server/static/docs/assets/specs_011-standalone-runtime_review.md.S7P6Ivy7.js +1 -0
  313. camply/server/static/docs/assets/specs_011-standalone-runtime_review.md.S7P6Ivy7.lean.js +1 -0
  314. camply/server/static/docs/assets/specs_011-standalone-runtime_spec.md.CNHFBW4a.js +1 -0
  315. camply/server/static/docs/assets/specs_011-standalone-runtime_spec.md.CNHFBW4a.lean.js +1 -0
  316. camply/server/static/docs/assets/specs_011-standalone-runtime_tasks.md.Ce8U3_-D.js +6 -0
  317. camply/server/static/docs/assets/specs_011-standalone-runtime_tasks.md.Ce8U3_-D.lean.js +1 -0
  318. camply/server/static/docs/assets/specs_012-branding_plan.md.A72jCO-_.js +1 -0
  319. camply/server/static/docs/assets/specs_012-branding_plan.md.A72jCO-_.lean.js +1 -0
  320. camply/server/static/docs/assets/specs_012-branding_spec.md.BueCHwuz.js +1 -0
  321. camply/server/static/docs/assets/specs_012-branding_spec.md.BueCHwuz.lean.js +1 -0
  322. camply/server/static/docs/assets/specs_012-branding_tasks.md.BTeyaLMD.js +1 -0
  323. camply/server/static/docs/assets/specs_012-branding_tasks.md.BTeyaLMD.lean.js +1 -0
  324. camply/server/static/docs/assets/specs_013-map-list-interaction_plan.md.DXgkLmDJ.js +1 -0
  325. camply/server/static/docs/assets/specs_013-map-list-interaction_plan.md.DXgkLmDJ.lean.js +1 -0
  326. camply/server/static/docs/assets/specs_013-map-list-interaction_spec.md.BVo8jhLm.js +1 -0
  327. camply/server/static/docs/assets/specs_013-map-list-interaction_spec.md.BVo8jhLm.lean.js +1 -0
  328. camply/server/static/docs/assets/specs_013-map-list-interaction_tasks.md.CFR8mkT3.js +1 -0
  329. camply/server/static/docs/assets/specs_013-map-list-interaction_tasks.md.CFR8mkT3.lean.js +1 -0
  330. camply/server/static/docs/assets/specs_013-map-list-interaction_validation.md.BNctsZBl.js +1 -0
  331. camply/server/static/docs/assets/specs_013-map-list-interaction_validation.md.BNctsZBl.lean.js +1 -0
  332. camply/server/static/docs/assets/specs_014-campsite-alerts_plan.md.Du4VPOlj.js +1 -0
  333. camply/server/static/docs/assets/specs_014-campsite-alerts_plan.md.Du4VPOlj.lean.js +1 -0
  334. camply/server/static/docs/assets/specs_014-campsite-alerts_spec.md.DPQGKMoN.js +1 -0
  335. camply/server/static/docs/assets/specs_014-campsite-alerts_spec.md.DPQGKMoN.lean.js +1 -0
  336. camply/server/static/docs/assets/specs_014-campsite-alerts_tasks.md.BqDrpSco.js +1 -0
  337. camply/server/static/docs/assets/specs_014-campsite-alerts_tasks.md.BqDrpSco.lean.js +1 -0
  338. camply/server/static/docs/assets/specs_015-release-ci_plan.md.s7IX1ar-.js +1 -0
  339. camply/server/static/docs/assets/specs_015-release-ci_plan.md.s7IX1ar-.lean.js +1 -0
  340. camply/server/static/docs/assets/specs_015-release-ci_spec.md.IA1oVhSE.js +1 -0
  341. camply/server/static/docs/assets/specs_015-release-ci_spec.md.IA1oVhSE.lean.js +1 -0
  342. camply/server/static/docs/assets/specs_015-release-ci_tasks.md.DCVUjCri.js +1 -0
  343. camply/server/static/docs/assets/specs_015-release-ci_tasks.md.DCVUjCri.lean.js +1 -0
  344. camply/server/static/docs/assets/specs_016-snapshot-gallery_plan.md.CS9nfykJ.js +1 -0
  345. camply/server/static/docs/assets/specs_016-snapshot-gallery_plan.md.CS9nfykJ.lean.js +1 -0
  346. camply/server/static/docs/assets/specs_016-snapshot-gallery_spec.md.Cha7R2Jr.js +1 -0
  347. camply/server/static/docs/assets/specs_016-snapshot-gallery_spec.md.Cha7R2Jr.lean.js +1 -0
  348. camply/server/static/docs/assets/specs_016-snapshot-gallery_tasks.md.DkrGHX9V.js +1 -0
  349. camply/server/static/docs/assets/specs_016-snapshot-gallery_tasks.md.DkrGHX9V.lean.js +1 -0
  350. camply/server/static/docs/assets/specs_017-compact-results_plan.md.BKHhDct3.js +1 -0
  351. camply/server/static/docs/assets/specs_017-compact-results_plan.md.BKHhDct3.lean.js +1 -0
  352. camply/server/static/docs/assets/specs_017-compact-results_spec.md.DzprVc_Y.js +1 -0
  353. camply/server/static/docs/assets/specs_017-compact-results_spec.md.DzprVc_Y.lean.js +1 -0
  354. camply/server/static/docs/assets/specs_017-compact-results_tasks.md.B7r7q6M6.js +1 -0
  355. camply/server/static/docs/assets/specs_017-compact-results_tasks.md.B7r7q6M6.lean.js +1 -0
  356. camply/server/static/docs/assets/specs_018-python-package_checklists_requirements.md.Ba9rZYIs.js +1 -0
  357. camply/server/static/docs/assets/specs_018-python-package_checklists_requirements.md.Ba9rZYIs.lean.js +1 -0
  358. camply/server/static/docs/assets/specs_018-python-package_contracts_installation.md.BpweOVRG.js +1 -0
  359. camply/server/static/docs/assets/specs_018-python-package_contracts_installation.md.BpweOVRG.lean.js +1 -0
  360. camply/server/static/docs/assets/specs_018-python-package_data-model.md.6Ln9iXUM.js +1 -0
  361. camply/server/static/docs/assets/specs_018-python-package_data-model.md.6Ln9iXUM.lean.js +1 -0
  362. camply/server/static/docs/assets/specs_018-python-package_plan.md.DCJTOtqH.js +23 -0
  363. camply/server/static/docs/assets/specs_018-python-package_plan.md.DCJTOtqH.lean.js +1 -0
  364. camply/server/static/docs/assets/specs_018-python-package_quickstart.md.KtQ5vkcd.js +18 -0
  365. camply/server/static/docs/assets/specs_018-python-package_quickstart.md.KtQ5vkcd.lean.js +1 -0
  366. camply/server/static/docs/assets/specs_018-python-package_research.md.VwZF0yyA.js +1 -0
  367. camply/server/static/docs/assets/specs_018-python-package_research.md.VwZF0yyA.lean.js +1 -0
  368. camply/server/static/docs/assets/specs_018-python-package_spec.md.Bkzw96f5.js +1 -0
  369. camply/server/static/docs/assets/specs_018-python-package_spec.md.Bkzw96f5.lean.js +1 -0
  370. camply/server/static/docs/assets/specs_018-python-package_tasks.md.68ZupRJE.js +9 -0
  371. camply/server/static/docs/assets/specs_018-python-package_tasks.md.68ZupRJE.lean.js +1 -0
  372. camply/server/static/docs/assets/specs_019-root-project-layout_checklists_requirements.md.efbJQUab.js +1 -0
  373. camply/server/static/docs/assets/specs_019-root-project-layout_checklists_requirements.md.efbJQUab.lean.js +1 -0
  374. camply/server/static/docs/assets/specs_019-root-project-layout_contracts_deployment.md.pILeXHAO.js +7 -0
  375. camply/server/static/docs/assets/specs_019-root-project-layout_contracts_deployment.md.pILeXHAO.lean.js +1 -0
  376. camply/server/static/docs/assets/specs_019-root-project-layout_contracts_packaging.md.DiDP5HWd.js +1 -0
  377. camply/server/static/docs/assets/specs_019-root-project-layout_contracts_packaging.md.DiDP5HWd.lean.js +1 -0
  378. camply/server/static/docs/assets/specs_019-root-project-layout_data-model.md.ebEzfYla.js +7 -0
  379. camply/server/static/docs/assets/specs_019-root-project-layout_data-model.md.ebEzfYla.lean.js +1 -0
  380. camply/server/static/docs/assets/specs_019-root-project-layout_plan.md.DvXBnPtu.js +57 -0
  381. camply/server/static/docs/assets/specs_019-root-project-layout_plan.md.DvXBnPtu.lean.js +1 -0
  382. camply/server/static/docs/assets/specs_019-root-project-layout_quickstart.md.BBsMI7O5.js +18 -0
  383. camply/server/static/docs/assets/specs_019-root-project-layout_quickstart.md.BBsMI7O5.lean.js +1 -0
  384. camply/server/static/docs/assets/specs_019-root-project-layout_research.md.CA4sN3hx.js +1 -0
  385. camply/server/static/docs/assets/specs_019-root-project-layout_research.md.CA4sN3hx.lean.js +1 -0
  386. camply/server/static/docs/assets/specs_019-root-project-layout_spec.md.DuKS8ugt.js +1 -0
  387. camply/server/static/docs/assets/specs_019-root-project-layout_spec.md.DuKS8ugt.lean.js +1 -0
  388. camply/server/static/docs/assets/specs_019-root-project-layout_tasks.md.CP5Z2AFX.js +1 -0
  389. camply/server/static/docs/assets/specs_019-root-project-layout_tasks.md.CP5Z2AFX.lean.js +1 -0
  390. camply/server/static/docs/assets/specs_020-unified-container_plan.md.xEvrD739.js +1 -0
  391. camply/server/static/docs/assets/specs_020-unified-container_plan.md.xEvrD739.lean.js +1 -0
  392. camply/server/static/docs/assets/specs_020-unified-container_spec.md.CwYqgXIo.js +1 -0
  393. camply/server/static/docs/assets/specs_020-unified-container_spec.md.CwYqgXIo.lean.js +1 -0
  394. camply/server/static/docs/assets/specs_020-unified-container_tasks.md.DlZym8Lk.js +1 -0
  395. camply/server/static/docs/assets/specs_020-unified-container_tasks.md.DlZym8Lk.lean.js +1 -0
  396. camply/server/static/docs/assets/specs_021-helm-chart_plan.md.BLo5bjmE.js +1 -0
  397. camply/server/static/docs/assets/specs_021-helm-chart_plan.md.BLo5bjmE.lean.js +1 -0
  398. camply/server/static/docs/assets/specs_021-helm-chart_spec.md.Dz_zvjRU.js +1 -0
  399. camply/server/static/docs/assets/specs_021-helm-chart_spec.md.Dz_zvjRU.lean.js +1 -0
  400. camply/server/static/docs/assets/specs_021-helm-chart_tasks.md.Ba53wvbA.js +1 -0
  401. camply/server/static/docs/assets/specs_021-helm-chart_tasks.md.Ba53wvbA.lean.js +1 -0
  402. camply/server/static/docs/assets/specs_022-documentation-site_plan.md.Bd_nIc0B.js +1 -0
  403. camply/server/static/docs/assets/specs_022-documentation-site_plan.md.Bd_nIc0B.lean.js +1 -0
  404. camply/server/static/docs/assets/specs_022-documentation-site_spec.md.mVEUAL4_.js +1 -0
  405. camply/server/static/docs/assets/specs_022-documentation-site_spec.md.mVEUAL4_.lean.js +1 -0
  406. camply/server/static/docs/assets/specs_022-documentation-site_tasks.md.D4Yx3Coa.js +1 -0
  407. camply/server/static/docs/assets/specs_022-documentation-site_tasks.md.D4Yx3Coa.lean.js +1 -0
  408. camply/server/static/docs/assets/specs_index.md.WrZfYvz7.js +1 -0
  409. camply/server/static/docs/assets/specs_index.md.WrZfYvz7.lean.js +1 -0
  410. camply/server/static/docs/assets/style.BLufiWcT.css +1 -0
  411. camply/server/static/docs/assets/using-camply.md.CdovgIax.js +1 -0
  412. camply/server/static/docs/assets/using-camply.md.CdovgIax.lean.js +1 -0
  413. camply/server/static/docs/branding/camply-horizontal-dark.svg +16 -0
  414. camply/server/static/docs/branding/camply-horizontal.svg +16 -0
  415. camply/server/static/docs/branding/camply-icon-dark.svg +16 -0
  416. camply/server/static/docs/branding/camply-icon.svg +16 -0
  417. camply/server/static/docs/branding/camply-stacked-dark.svg +16 -0
  418. camply/server/static/docs/branding/camply-stacked.svg +16 -0
  419. camply/server/static/docs/camply.svg +17 -0
  420. camply/server/static/docs/configuration.html +30 -0
  421. camply/server/static/docs/development/AGENT_TROUBLESHOOTING.html +27 -0
  422. camply/server/static/docs/development/DESIGN_AGENTIC.html +27 -0
  423. camply/server/static/docs/development/DEVELOPER_GUIDE.html +34 -0
  424. camply/server/static/docs/development/STYLE_GUIDE.html +27 -0
  425. camply/server/static/docs/development.html +33 -0
  426. camply/server/static/docs/favicon.ico +0 -0
  427. camply/server/static/docs/favicon.svg +17 -0
  428. camply/server/static/docs/hashmap.json +1 -0
  429. camply/server/static/docs/index.html +27 -0
  430. camply/server/static/docs/operations/CONFIGURATION.html +27 -0
  431. camply/server/static/docs/operations/RELEASES.html +44 -0
  432. camply/server/static/docs/operations/RUNNING.html +53 -0
  433. camply/server/static/docs/reference.html +27 -0
  434. camply/server/static/docs/self-hosting/containers.html +32 -0
  435. camply/server/static/docs/self-hosting/standalone.html +29 -0
  436. camply/server/static/docs/specs/001-checklist-data-layer/checklists/requirements.html +27 -0
  437. camply/server/static/docs/specs/001-checklist-data-layer/data-model.html +27 -0
  438. camply/server/static/docs/specs/001-checklist-data-layer/plan.html +42 -0
  439. camply/server/static/docs/specs/001-checklist-data-layer/quickstart.html +31 -0
  440. camply/server/static/docs/specs/001-checklist-data-layer/research.html +27 -0
  441. camply/server/static/docs/specs/001-checklist-data-layer/spec.html +27 -0
  442. camply/server/static/docs/specs/001-checklist-data-layer/tasks.html +33 -0
  443. camply/server/static/docs/specs/002-admin-ui/plan.html +34 -0
  444. camply/server/static/docs/specs/002-admin-ui/spec.html +27 -0
  445. camply/server/static/docs/specs/002-admin-ui/tasks.html +27 -0
  446. camply/server/static/docs/specs/003-auth-invite-only/plan.html +27 -0
  447. camply/server/static/docs/specs/003-auth-invite-only/spec.html +27 -0
  448. camply/server/static/docs/specs/003-auth-invite-only/tasks.html +27 -0
  449. camply/server/static/docs/specs/004-frontend-performance/plan.html +27 -0
  450. camply/server/static/docs/specs/004-frontend-performance/spec.html +27 -0
  451. camply/server/static/docs/specs/004-frontend-performance/tasks.html +27 -0
  452. camply/server/static/docs/specs/004-frontend-performance/validation.html +27 -0
  453. camply/server/static/docs/specs/005-reserve-california/plan.html +27 -0
  454. camply/server/static/docs/specs/005-reserve-california/review.html +27 -0
  455. camply/server/static/docs/specs/005-reserve-california/spec.html +27 -0
  456. camply/server/static/docs/specs/005-reserve-california/tasks.html +27 -0
  457. camply/server/static/docs/specs/005-reserve-california/validation.html +29 -0
  458. camply/server/static/docs/specs/006-search-revamp/plan.html +27 -0
  459. camply/server/static/docs/specs/006-search-revamp/spec.html +27 -0
  460. camply/server/static/docs/specs/006-search-revamp/tasks.html +27 -0
  461. camply/server/static/docs/specs/007-usedirect-providers/TODO.html +27 -0
  462. camply/server/static/docs/specs/007-usedirect-providers/plan.html +27 -0
  463. camply/server/static/docs/specs/007-usedirect-providers/spec.html +27 -0
  464. camply/server/static/docs/specs/007-usedirect-providers/tasks.html +27 -0
  465. camply/server/static/docs/specs/007-usedirect-providers/validation.html +27 -0
  466. camply/server/static/docs/specs/008-scan-editing/plan.html +27 -0
  467. camply/server/static/docs/specs/008-scan-editing/spec.html +27 -0
  468. camply/server/static/docs/specs/008-scan-editing/tasks.html +27 -0
  469. camply/server/static/docs/specs/009-live-search/plan.html +27 -0
  470. camply/server/static/docs/specs/009-live-search/spec.html +27 -0
  471. camply/server/static/docs/specs/009-live-search/tasks.html +27 -0
  472. camply/server/static/docs/specs/010-open-source-explore-maps/TODO.html +32 -0
  473. camply/server/static/docs/specs/010-open-source-explore-maps/checklists/requirements.html +27 -0
  474. camply/server/static/docs/specs/010-open-source-explore-maps/contracts/map-ui-and-configuration.html +27 -0
  475. camply/server/static/docs/specs/010-open-source-explore-maps/contracts/search-map-api.html +32 -0
  476. camply/server/static/docs/specs/010-open-source-explore-maps/data-model.html +27 -0
  477. camply/server/static/docs/specs/010-open-source-explore-maps/plan.html +69 -0
  478. camply/server/static/docs/specs/010-open-source-explore-maps/quickstart.html +41 -0
  479. camply/server/static/docs/specs/010-open-source-explore-maps/research.html +27 -0
  480. camply/server/static/docs/specs/010-open-source-explore-maps/spec.html +27 -0
  481. camply/server/static/docs/specs/010-open-source-explore-maps/tasks.html +36 -0
  482. camply/server/static/docs/specs/010-open-source-explore-maps/validation.html +27 -0
  483. camply/server/static/docs/specs/011-standalone-runtime/checklists/requirements.html +27 -0
  484. camply/server/static/docs/specs/011-standalone-runtime/checklists/runtime.html +27 -0
  485. camply/server/static/docs/specs/011-standalone-runtime/contracts/admin-api.html +27 -0
  486. camply/server/static/docs/specs/011-standalone-runtime/contracts/runtime.html +27 -0
  487. camply/server/static/docs/specs/011-standalone-runtime/data-model.html +37 -0
  488. camply/server/static/docs/specs/011-standalone-runtime/plan.html +57 -0
  489. camply/server/static/docs/specs/011-standalone-runtime/quickstart.html +37 -0
  490. camply/server/static/docs/specs/011-standalone-runtime/research.html +27 -0
  491. camply/server/static/docs/specs/011-standalone-runtime/review.html +27 -0
  492. camply/server/static/docs/specs/011-standalone-runtime/spec.html +27 -0
  493. camply/server/static/docs/specs/011-standalone-runtime/tasks.html +32 -0
  494. camply/server/static/docs/specs/012-branding/plan.html +27 -0
  495. camply/server/static/docs/specs/012-branding/spec.html +27 -0
  496. camply/server/static/docs/specs/012-branding/tasks.html +27 -0
  497. camply/server/static/docs/specs/013-map-list-interaction/plan.html +27 -0
  498. camply/server/static/docs/specs/013-map-list-interaction/spec.html +27 -0
  499. camply/server/static/docs/specs/013-map-list-interaction/tasks.html +27 -0
  500. camply/server/static/docs/specs/013-map-list-interaction/validation.html +27 -0
  501. camply/server/static/docs/specs/014-campsite-alerts/plan.html +27 -0
  502. camply/server/static/docs/specs/014-campsite-alerts/spec.html +27 -0
  503. camply/server/static/docs/specs/014-campsite-alerts/tasks.html +27 -0
  504. camply/server/static/docs/specs/015-release-ci/plan.html +27 -0
  505. camply/server/static/docs/specs/015-release-ci/spec.html +27 -0
  506. camply/server/static/docs/specs/015-release-ci/tasks.html +27 -0
  507. camply/server/static/docs/specs/016-snapshot-gallery/plan.html +27 -0
  508. camply/server/static/docs/specs/016-snapshot-gallery/spec.html +27 -0
  509. camply/server/static/docs/specs/016-snapshot-gallery/tasks.html +27 -0
  510. camply/server/static/docs/specs/017-compact-results/plan.html +27 -0
  511. camply/server/static/docs/specs/017-compact-results/spec.html +27 -0
  512. camply/server/static/docs/specs/017-compact-results/tasks.html +27 -0
  513. camply/server/static/docs/specs/018-python-package/checklists/requirements.html +27 -0
  514. camply/server/static/docs/specs/018-python-package/contracts/installation.html +27 -0
  515. camply/server/static/docs/specs/018-python-package/data-model.html +27 -0
  516. camply/server/static/docs/specs/018-python-package/plan.html +49 -0
  517. camply/server/static/docs/specs/018-python-package/quickstart.html +44 -0
  518. camply/server/static/docs/specs/018-python-package/research.html +27 -0
  519. camply/server/static/docs/specs/018-python-package/spec.html +27 -0
  520. camply/server/static/docs/specs/018-python-package/tasks.html +35 -0
  521. camply/server/static/docs/specs/019-root-project-layout/checklists/requirements.html +27 -0
  522. camply/server/static/docs/specs/019-root-project-layout/contracts/deployment.html +33 -0
  523. camply/server/static/docs/specs/019-root-project-layout/contracts/packaging.html +27 -0
  524. camply/server/static/docs/specs/019-root-project-layout/data-model.html +33 -0
  525. camply/server/static/docs/specs/019-root-project-layout/plan.html +83 -0
  526. camply/server/static/docs/specs/019-root-project-layout/quickstart.html +44 -0
  527. camply/server/static/docs/specs/019-root-project-layout/research.html +27 -0
  528. camply/server/static/docs/specs/019-root-project-layout/spec.html +27 -0
  529. camply/server/static/docs/specs/019-root-project-layout/tasks.html +27 -0
  530. camply/server/static/docs/specs/020-unified-container/plan.html +27 -0
  531. camply/server/static/docs/specs/020-unified-container/spec.html +27 -0
  532. camply/server/static/docs/specs/020-unified-container/tasks.html +27 -0
  533. camply/server/static/docs/specs/021-helm-chart/plan.html +27 -0
  534. camply/server/static/docs/specs/021-helm-chart/spec.html +27 -0
  535. camply/server/static/docs/specs/021-helm-chart/tasks.html +27 -0
  536. camply/server/static/docs/specs/022-documentation-site/plan.html +27 -0
  537. camply/server/static/docs/specs/022-documentation-site/spec.html +27 -0
  538. camply/server/static/docs/specs/022-documentation-site/tasks.html +27 -0
  539. camply/server/static/docs/specs/index.html +27 -0
  540. camply/server/static/docs/using-camply.html +27 -0
  541. camply/server/static/docs/vp-icons.css +1 -0
  542. camply/server/static/favicon.ico +0 -0
  543. camply/server/static/favicon.svg +17 -0
  544. camply/server/static/index.html +15 -0
  545. camply/server/static.py +55 -0
  546. camply/worker/__init__.py +14 -0
  547. camply/worker/celery_app.py +139 -0
  548. camply/worker/config.py +68 -0
  549. camply/worker/engine/__init__.py +1 -0
  550. camply/worker/engine/alerts.py +68 -0
  551. camply/worker/engine/catalog.py +46 -0
  552. camply/worker/engine/discovery.py +60 -0
  553. camply/worker/engine/notifications.py +103 -0
  554. camply/worker/engine/scanner.py +318 -0
  555. camply/worker/locks.py +101 -0
  556. camply/worker/metrics.py +177 -0
  557. camply/worker/notifications/__init__.py +3 -0
  558. camply/worker/notifications/base.py +53 -0
  559. camply/worker/notifications/pushover.py +104 -0
  560. camply/worker/outbox.py +236 -0
  561. camply/worker/runtime.py +313 -0
  562. camply/worker/scheduler.py +38 -0
  563. camply/worker/tasks/__init__.py +3 -0
  564. camply/worker/tasks/heartbeat.py +81 -0
  565. camply/worker/tasks/ingestion.py +106 -0
  566. camply/worker/tasks/notifications.py +63 -0
  567. camply/worker/tasks/scanner.py +135 -0
  568. camply/worker/telemetry.py +343 -0
  569. camply_web-2.8.0.dist-info/METADATA +67 -0
  570. camply_web-2.8.0.dist-info/RECORD +573 -0
  571. camply_web-2.8.0.dist-info/WHEEL +4 -0
  572. camply_web-2.8.0.dist-info/entry_points.txt +3 -0
  573. 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,6 @@
1
+ """Run the Camply CLI with ``python -m camply.cli``."""
2
+
3
+ from camply.cli.main import cli
4
+
5
+ if __name__ == "__main__":
6
+ cli()
@@ -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