focomy 0.1.107__tar.gz → 0.1.108__tar.gz

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 (210) hide show
  1. {focomy-0.1.107 → focomy-0.1.108}/PKG-INFO +1 -1
  2. {focomy-0.1.107 → focomy-0.1.108}/core/api/forms.py +3 -0
  3. {focomy-0.1.107 → focomy-0.1.108}/core/main.py +10 -16
  4. {focomy-0.1.107 → focomy-0.1.108}/core/services/settings.py +10 -0
  5. focomy-0.1.108/core/utils.py +90 -0
  6. {focomy-0.1.107 → focomy-0.1.108}/pyproject.toml +1 -1
  7. focomy-0.1.107/core/utils.py +0 -45
  8. {focomy-0.1.107 → focomy-0.1.108}/.gitignore +0 -0
  9. {focomy-0.1.107 → focomy-0.1.108}/LICENSE +0 -0
  10. {focomy-0.1.107 → focomy-0.1.108}/README.md +0 -0
  11. {focomy-0.1.107 → focomy-0.1.108}/core/__init__.py +0 -0
  12. {focomy-0.1.107 → focomy-0.1.108}/core/admin/__init__.py +0 -0
  13. {focomy-0.1.107 → focomy-0.1.108}/core/admin/routes.py +0 -0
  14. {focomy-0.1.107 → focomy-0.1.108}/core/admin/url.py +0 -0
  15. {focomy-0.1.107 → focomy-0.1.108}/core/api/__init__.py +0 -0
  16. {focomy-0.1.107 → focomy-0.1.108}/core/api/auth.py +0 -0
  17. {focomy-0.1.107 → focomy-0.1.108}/core/api/comments.py +0 -0
  18. {focomy-0.1.107 → focomy-0.1.108}/core/api/entities.py +0 -0
  19. {focomy-0.1.107 → focomy-0.1.108}/core/api/media.py +0 -0
  20. {focomy-0.1.107 → focomy-0.1.108}/core/api/relations.py +0 -0
  21. {focomy-0.1.107 → focomy-0.1.108}/core/api/revisions.py +0 -0
  22. {focomy-0.1.107 → focomy-0.1.108}/core/api/schema.py +0 -0
  23. {focomy-0.1.107 → focomy-0.1.108}/core/api/search.py +0 -0
  24. {focomy-0.1.107 → focomy-0.1.108}/core/api/seo.py +0 -0
  25. {focomy-0.1.107 → focomy-0.1.108}/core/cli.py +0 -0
  26. {focomy-0.1.107 → focomy-0.1.108}/core/config.py +0 -0
  27. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/__init__.py +0 -0
  28. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/audit_log.yaml +0 -0
  29. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/category.yaml +0 -0
  30. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/channel.yaml +0 -0
  31. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/comment.yaml +0 -0
  32. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/edit_lock.yaml +0 -0
  33. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/form.yaml +0 -0
  34. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/form_submission.yaml +0 -0
  35. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/menu_item.yaml +0 -0
  36. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/news.yaml +0 -0
  37. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/page.yaml +0 -0
  38. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/plugin.yaml +0 -0
  39. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/plugin_developer.yaml +0 -0
  40. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/plugin_review.yaml +0 -0
  41. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/post.yaml +0 -0
  42. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/redirect.yaml +0 -0
  43. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/scheduled_action.yaml +0 -0
  44. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/series.yaml +0 -0
  45. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/site_setting.yaml +0 -0
  46. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/tag.yaml +0 -0
  47. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/user.yaml +0 -0
  48. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/widget.yaml +0 -0
  49. {focomy-0.1.107 → focomy-0.1.108}/core/content_types/workflow_history.yaml +0 -0
  50. {focomy-0.1.107 → focomy-0.1.108}/core/database.py +0 -0
  51. {focomy-0.1.107 → focomy-0.1.108}/core/engine/__init__.py +0 -0
  52. {focomy-0.1.107 → focomy-0.1.108}/core/engine/routes.py +0 -0
  53. {focomy-0.1.107 → focomy-0.1.108}/core/migrations/env.py +0 -0
  54. {focomy-0.1.107 → focomy-0.1.108}/core/migrations/script.py.mako +0 -0
  55. {focomy-0.1.107 → focomy-0.1.108}/core/migrations/versions/2038bdf6693b_add_import_jobs_table.py +0 -0
  56. {focomy-0.1.107 → focomy-0.1.108}/core/migrations/versions/3a1b2c3d4e5f_add_file_hash_to_media.py +0 -0
  57. {focomy-0.1.107 → focomy-0.1.108}/core/models/__init__.py +0 -0
  58. {focomy-0.1.107 → focomy-0.1.108}/core/models/auth.py +0 -0
  59. {focomy-0.1.107 → focomy-0.1.108}/core/models/entity.py +0 -0
  60. {focomy-0.1.107 → focomy-0.1.108}/core/models/import_job.py +0 -0
  61. {focomy-0.1.107 → focomy-0.1.108}/core/models/media.py +0 -0
  62. {focomy-0.1.107 → focomy-0.1.108}/core/models/relation.py +0 -0
  63. {focomy-0.1.107 → focomy-0.1.108}/core/models/revision.py +0 -0
  64. {focomy-0.1.107 → focomy-0.1.108}/core/plugins/__init__.py +0 -0
  65. {focomy-0.1.107 → focomy-0.1.108}/core/plugins/base.py +0 -0
  66. {focomy-0.1.107 → focomy-0.1.108}/core/plugins/hooks.py +0 -0
  67. {focomy-0.1.107 → focomy-0.1.108}/core/plugins/loader.py +0 -0
  68. {focomy-0.1.107 → focomy-0.1.108}/core/plugins/manager.py +0 -0
  69. {focomy-0.1.107 → focomy-0.1.108}/core/rate_limit.py +0 -0
  70. {focomy-0.1.107 → focomy-0.1.108}/core/relations.yaml +0 -0
  71. {focomy-0.1.107 → focomy-0.1.108}/core/scaffold/.env.template +0 -0
  72. {focomy-0.1.107 → focomy-0.1.108}/core/scaffold/.gitignore.template +0 -0
  73. {focomy-0.1.107 → focomy-0.1.108}/core/scaffold/__init__.py +0 -0
  74. {focomy-0.1.107 → focomy-0.1.108}/core/scaffold/config.yaml.template +0 -0
  75. {focomy-0.1.107 → focomy-0.1.108}/core/scaffold/themes/default/templates/archive.html +0 -0
  76. {focomy-0.1.107 → focomy-0.1.108}/core/scaffold/themes/default/templates/base.html +0 -0
  77. {focomy-0.1.107 → focomy-0.1.108}/core/scaffold/themes/default/templates/category.html +0 -0
  78. {focomy-0.1.107 → focomy-0.1.108}/core/scaffold/themes/default/templates/home.html +0 -0
  79. {focomy-0.1.107 → focomy-0.1.108}/core/scaffold/themes/default/templates/post.html +0 -0
  80. {focomy-0.1.107 → focomy-0.1.108}/core/scaffold/themes/default/templates/search.html +0 -0
  81. {focomy-0.1.107 → focomy-0.1.108}/core/scaffold/themes/default/theme.yaml +0 -0
  82. {focomy-0.1.107 → focomy-0.1.108}/core/schemas/__init__.py +0 -0
  83. {focomy-0.1.107 → focomy-0.1.108}/core/schemas/import_schema.py +0 -0
  84. {focomy-0.1.107 → focomy-0.1.108}/core/seo/__init__.py +0 -0
  85. {focomy-0.1.107 → focomy-0.1.108}/core/services/__init__.py +0 -0
  86. {focomy-0.1.107 → focomy-0.1.108}/core/services/api_auth.py +0 -0
  87. {focomy-0.1.107 → focomy-0.1.108}/core/services/assets.py +0 -0
  88. {focomy-0.1.107 → focomy-0.1.108}/core/services/audit.py +0 -0
  89. {focomy-0.1.107 → focomy-0.1.108}/core/services/auth.py +0 -0
  90. {focomy-0.1.107 → focomy-0.1.108}/core/services/block_converter.py +0 -0
  91. {focomy-0.1.107 → focomy-0.1.108}/core/services/bulk.py +0 -0
  92. {focomy-0.1.107 → focomy-0.1.108}/core/services/cache.py +0 -0
  93. {focomy-0.1.107 → focomy-0.1.108}/core/services/cleanup.py +0 -0
  94. {focomy-0.1.107 → focomy-0.1.108}/core/services/comment.py +0 -0
  95. {focomy-0.1.107 → focomy-0.1.108}/core/services/config_priority.py +0 -0
  96. {focomy-0.1.107 → focomy-0.1.108}/core/services/deployment.py +0 -0
  97. {focomy-0.1.107 → focomy-0.1.108}/core/services/edit_lock.py +0 -0
  98. {focomy-0.1.107 → focomy-0.1.108}/core/services/entity.py +0 -0
  99. {focomy-0.1.107 → focomy-0.1.108}/core/services/export.py +0 -0
  100. {focomy-0.1.107 → focomy-0.1.108}/core/services/field.py +0 -0
  101. {focomy-0.1.107 → focomy-0.1.108}/core/services/formula.py +0 -0
  102. {focomy-0.1.107 → focomy-0.1.108}/core/services/i18n.py +0 -0
  103. {focomy-0.1.107 → focomy-0.1.108}/core/services/index.py +0 -0
  104. {focomy-0.1.107 → focomy-0.1.108}/core/services/invite.py +0 -0
  105. {focomy-0.1.107 → focomy-0.1.108}/core/services/link_validator.py +0 -0
  106. {focomy-0.1.107 → focomy-0.1.108}/core/services/logging.py +0 -0
  107. {focomy-0.1.107 → focomy-0.1.108}/core/services/mail.py +0 -0
  108. {focomy-0.1.107 → focomy-0.1.108}/core/services/marketplace.py +0 -0
  109. {focomy-0.1.107 → focomy-0.1.108}/core/services/marketplace_verify.py +0 -0
  110. {focomy-0.1.107 → focomy-0.1.108}/core/services/media.py +0 -0
  111. {focomy-0.1.107 → focomy-0.1.108}/core/services/media_cleanup.py +0 -0
  112. {focomy-0.1.107 → focomy-0.1.108}/core/services/menu.py +0 -0
  113. {focomy-0.1.107 → focomy-0.1.108}/core/services/migration_helpers.py +0 -0
  114. {focomy-0.1.107 → focomy-0.1.108}/core/services/oauth.py +0 -0
  115. {focomy-0.1.107 → focomy-0.1.108}/core/services/pagination.py +0 -0
  116. {focomy-0.1.107 → focomy-0.1.108}/core/services/plugin_resolver.py +0 -0
  117. {focomy-0.1.107 → focomy-0.1.108}/core/services/plugin_sandbox.py +0 -0
  118. {focomy-0.1.107 → focomy-0.1.108}/core/services/preview.py +0 -0
  119. {focomy-0.1.107 → focomy-0.1.108}/core/services/query_optimizer.py +0 -0
  120. {focomy-0.1.107 → focomy-0.1.108}/core/services/rbac.py +0 -0
  121. {focomy-0.1.107 → focomy-0.1.108}/core/services/redirect.py +0 -0
  122. {focomy-0.1.107 → focomy-0.1.108}/core/services/relation.py +0 -0
  123. {focomy-0.1.107 → focomy-0.1.108}/core/services/revision.py +0 -0
  124. {focomy-0.1.107 → focomy-0.1.108}/core/services/routing.py +0 -0
  125. {focomy-0.1.107 → focomy-0.1.108}/core/services/sanitizer.py +0 -0
  126. {focomy-0.1.107 → focomy-0.1.108}/core/services/schedule.py +0 -0
  127. {focomy-0.1.107 → focomy-0.1.108}/core/services/search.py +0 -0
  128. {focomy-0.1.107 → focomy-0.1.108}/core/services/sentry.py +0 -0
  129. {focomy-0.1.107 → focomy-0.1.108}/core/services/seo.py +0 -0
  130. {focomy-0.1.107 → focomy-0.1.108}/core/services/spam_filter.py +0 -0
  131. {focomy-0.1.107 → focomy-0.1.108}/core/services/storage.py +0 -0
  132. {focomy-0.1.107 → focomy-0.1.108}/core/services/theme.py +0 -0
  133. {focomy-0.1.107 → focomy-0.1.108}/core/services/theme_inheritance.py +0 -0
  134. {focomy-0.1.107 → focomy-0.1.108}/core/services/thumbnail.py +0 -0
  135. {focomy-0.1.107 → focomy-0.1.108}/core/services/update.py +0 -0
  136. {focomy-0.1.107 → focomy-0.1.108}/core/services/widget.py +0 -0
  137. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/__init__.py +0 -0
  138. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/acf.py +0 -0
  139. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/analyzer.py +0 -0
  140. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/constants.py +0 -0
  141. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/content_sanitizer.py +0 -0
  142. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/diff_detector.py +0 -0
  143. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/dry_run.py +0 -0
  144. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/error_collector.py +0 -0
  145. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/id_resolver.py +0 -0
  146. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/import_service.py +0 -0
  147. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/importer.py +0 -0
  148. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/link_fixer.py +0 -0
  149. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/media.py +0 -0
  150. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/preview.py +0 -0
  151. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/redirects.py +0 -0
  152. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/rest_client.py +0 -0
  153. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/rollback.py +0 -0
  154. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/verification.py +0 -0
  155. {focomy-0.1.107 → focomy-0.1.108}/core/services/wordpress_import/wxr_parser.py +0 -0
  156. {focomy-0.1.107 → focomy-0.1.108}/core/services/workflow.py +0 -0
  157. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/backup.html +0 -0
  158. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/base.html +0 -0
  159. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/comments.html +0 -0
  160. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/components/editor.html +0 -0
  161. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/customize.html +0 -0
  162. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/dashboard.html +0 -0
  163. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/entity_form.html +0 -0
  164. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/entity_list.html +0 -0
  165. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/forgot_password.html +0 -0
  166. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/import.html +0 -0
  167. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/link_validator.html +0 -0
  168. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/login.html +0 -0
  169. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/media.html +0 -0
  170. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/menus.html +0 -0
  171. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/plugins.html +0 -0
  172. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/redirects.html +0 -0
  173. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/reset_password.html +0 -0
  174. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/settings.html +0 -0
  175. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/sitemap.html +0 -0
  176. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/system.html +0 -0
  177. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/themes.html +0 -0
  178. {focomy-0.1.107 → focomy-0.1.108}/core/templates/admin/widgets.html +0 -0
  179. {focomy-0.1.107 → focomy-0.1.108}/core/themes/__init__.py +0 -0
  180. {focomy-0.1.107 → focomy-0.1.108}/core/themes/customizer.py +0 -0
  181. {focomy-0.1.107 → focomy-0.1.108}/core/themes/manager.py +0 -0
  182. {focomy-0.1.107 → focomy-0.1.108}/core/themes/marketplace.py +0 -0
  183. {focomy-0.1.107 → focomy-0.1.108}/static/favicon.svg +0 -0
  184. {focomy-0.1.107 → focomy-0.1.108}/themes/blog/templates/base.html +0 -0
  185. {focomy-0.1.107 → focomy-0.1.108}/themes/blog/templates/home.html +0 -0
  186. {focomy-0.1.107 → focomy-0.1.108}/themes/blog/templates/post.html +0 -0
  187. {focomy-0.1.107 → focomy-0.1.108}/themes/blog/theme.yaml +0 -0
  188. {focomy-0.1.107 → focomy-0.1.108}/themes/corporate/templates/base.html +0 -0
  189. {focomy-0.1.107 → focomy-0.1.108}/themes/corporate/templates/home.html +0 -0
  190. {focomy-0.1.107 → focomy-0.1.108}/themes/corporate/templates/page.html +0 -0
  191. {focomy-0.1.107 → focomy-0.1.108}/themes/corporate/theme.yaml +0 -0
  192. {focomy-0.1.107 → focomy-0.1.108}/themes/default/templates/404.html +0 -0
  193. {focomy-0.1.107 → focomy-0.1.108}/themes/default/templates/500.html +0 -0
  194. {focomy-0.1.107 → focomy-0.1.108}/themes/default/templates/archive.html +0 -0
  195. {focomy-0.1.107 → focomy-0.1.108}/themes/default/templates/base.html +0 -0
  196. {focomy-0.1.107 → focomy-0.1.108}/themes/default/templates/category.html +0 -0
  197. {focomy-0.1.107 → focomy-0.1.108}/themes/default/templates/channel.html +0 -0
  198. {focomy-0.1.107 → focomy-0.1.108}/themes/default/templates/form.html +0 -0
  199. {focomy-0.1.107 → focomy-0.1.108}/themes/default/templates/form_success.html +0 -0
  200. {focomy-0.1.107 → focomy-0.1.108}/themes/default/templates/home.html +0 -0
  201. {focomy-0.1.107 → focomy-0.1.108}/themes/default/templates/page.html +0 -0
  202. {focomy-0.1.107 → focomy-0.1.108}/themes/default/templates/post.html +0 -0
  203. {focomy-0.1.107 → focomy-0.1.108}/themes/default/templates/search.html +0 -0
  204. {focomy-0.1.107 → focomy-0.1.108}/themes/default/templates/series.html +0 -0
  205. {focomy-0.1.107 → focomy-0.1.108}/themes/default/theme.yaml +0 -0
  206. {focomy-0.1.107 → focomy-0.1.108}/themes/minimal/templates/base.html +0 -0
  207. {focomy-0.1.107 → focomy-0.1.108}/themes/minimal/templates/home.html +0 -0
  208. {focomy-0.1.107 → focomy-0.1.108}/themes/minimal/templates/page.html +0 -0
  209. {focomy-0.1.107 → focomy-0.1.108}/themes/minimal/templates/post.html +0 -0
  210. {focomy-0.1.107 → focomy-0.1.108}/themes/minimal/theme.yaml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: focomy
3
- Version: 0.1.107
3
+ Version: 0.1.108
4
4
  Summary: The Most Beautiful CMS - A metadata-driven, zero-duplicate-code content management system
5
5
  Project-URL: Homepage, https://github.com/focomy/focomy
6
6
  Project-URL: Documentation, https://focomy.dev/docs
@@ -13,6 +13,7 @@ from ..rate_limit import limiter
13
13
  from ..services.entity import EntityService
14
14
  from ..services.mail import mail_service
15
15
  from ..services.theme import theme_service
16
+ from ..utils import require_feature_async
16
17
 
17
18
  router = APIRouter(prefix="/forms", tags=["forms"])
18
19
 
@@ -30,6 +31,7 @@ async def view_form(
30
31
  db: AsyncSession = Depends(get_db),
31
32
  ):
32
33
  """View a public form."""
34
+ await require_feature_async("form", db)
33
35
  entity_svc = EntityService(db)
34
36
 
35
37
  # Find form
@@ -83,6 +85,7 @@ async def submit_form(
83
85
  db: AsyncSession = Depends(get_db),
84
86
  ):
85
87
  """Submit a form."""
88
+ await require_feature_async("form", db)
86
89
  entity_svc = EntityService(db)
87
90
 
88
91
  # Find form
@@ -452,7 +452,6 @@ async def server_error_handler(request: Request, exc: Exception):
452
452
  from .admin import routes as admin
453
453
  from .api import auth, comments, entities, forms, media, relations, revisions, schema, search, seo
454
454
  from .engine import routes as engine
455
- from .utils import is_feature_enabled
456
455
 
457
456
  # Phase 1: Core APIs (always enabled)
458
457
  app.include_router(entities.router, prefix="/api")
@@ -462,21 +461,16 @@ app.include_router(auth.router, prefix="/api")
462
461
  app.include_router(seo.router)
463
462
  app.include_router(admin.router)
464
463
 
465
- # Phase 2: Media
466
- if is_feature_enabled("media"):
467
- app.include_router(media.router, prefix="/api")
468
-
469
- # Phase 4: Search, Revisions
470
- if is_feature_enabled("search"):
471
- app.include_router(search.router, prefix="/api")
472
- if is_feature_enabled("revision"):
473
- app.include_router(revisions.router, prefix="/api")
474
-
475
- # Phase 5: Comments, Forms
476
- if is_feature_enabled("comment"):
477
- app.include_router(comments.router, prefix="/api")
478
- if is_feature_enabled("form"):
479
- app.include_router(forms.router)
464
+ # Phase 2: Media (runtime check in endpoints)
465
+ app.include_router(media.router, prefix="/api")
466
+
467
+ # Phase 4: Search, Revisions (runtime check in endpoints)
468
+ app.include_router(search.router, prefix="/api")
469
+ app.include_router(revisions.router, prefix="/api")
470
+
471
+ # Phase 5: Comments, Forms (runtime check in endpoints)
472
+ app.include_router(comments.router, prefix="/api")
473
+ app.include_router(forms.router)
480
474
 
481
475
 
482
476
  @app.get("/api/health")
@@ -39,6 +39,14 @@ DEFAULT_SETTINGS = {
39
39
  "lockout_duration": 900,
40
40
  "password_min_length": 12,
41
41
  },
42
+ "features": {
43
+ "media": True,
44
+ "comment": False,
45
+ "form": True,
46
+ "wordpress_import": False,
47
+ "menu": True,
48
+ "widget": True,
49
+ },
42
50
  }
43
51
 
44
52
 
@@ -235,6 +243,7 @@ class SettingsService:
235
243
  "media": app_settings.media,
236
244
  "security": app_settings.security,
237
245
  "theme": app_settings.theme,
246
+ "features": app_settings.features,
238
247
  }
239
248
 
240
249
  config_obj = config_map.get(category)
@@ -253,6 +262,7 @@ class SettingsService:
253
262
  "media": app_settings.media,
254
263
  "security": app_settings.security,
255
264
  "theme": app_settings.theme,
265
+ "features": app_settings.features,
256
266
  }
257
267
 
258
268
  categories = [category] if category else config_map.keys()
@@ -0,0 +1,90 @@
1
+ """Utility functions for Focomy."""
2
+
3
+ from datetime import datetime, timezone
4
+ from functools import lru_cache
5
+
6
+ from fastapi import HTTPException
7
+
8
+
9
+ def utcnow() -> datetime:
10
+ """Return current UTC time as naive datetime for DB storage.
11
+
12
+ PostgreSQL TIMESTAMP WITHOUT TIME ZONE expects naive datetimes.
13
+ This function returns UTC time without tzinfo to avoid mismatch errors.
14
+ """
15
+ return datetime.now(timezone.utc).replace(tzinfo=None)
16
+
17
+
18
+ def is_feature_enabled(feature: str) -> bool:
19
+ """Check if a feature is enabled.
20
+
21
+ Args:
22
+ feature: Feature name (e.g., 'media', 'comment', 'wordpress_import')
23
+
24
+ Returns:
25
+ True if feature is enabled, False otherwise
26
+ """
27
+ from .config import get_settings
28
+
29
+ settings = get_settings()
30
+ return getattr(settings.features, feature, False)
31
+
32
+
33
+ def require_feature(feature: str) -> None:
34
+ """Raise 404 if feature is disabled.
35
+
36
+ Use this at the start of API endpoints to disable them when feature is off.
37
+
38
+ Args:
39
+ feature: Feature name
40
+
41
+ Raises:
42
+ HTTPException: 404 if feature is disabled
43
+ """
44
+ if not is_feature_enabled(feature):
45
+ raise HTTPException(status_code=404, detail="Not Found")
46
+
47
+
48
+ async def is_feature_enabled_async(feature: str, db) -> bool:
49
+ """Check if a feature is enabled (async version, DB-first).
50
+
51
+ Priority: DB settings > config.yaml > False
52
+
53
+ Args:
54
+ feature: Feature name (e.g., 'form', 'comment')
55
+ db: AsyncSession database session
56
+
57
+ Returns:
58
+ True if feature is enabled, False otherwise
59
+ """
60
+ from .services.settings import SettingsService
61
+
62
+ settings_svc = SettingsService(db)
63
+ db_value = await settings_svc.get(f"features.{feature}")
64
+
65
+ if db_value is not None:
66
+ # DB value exists, use it
67
+ if isinstance(db_value, bool):
68
+ return db_value
69
+ if isinstance(db_value, str):
70
+ return db_value.lower() in ("true", "1", "yes")
71
+ return bool(db_value)
72
+
73
+ # Fallback to config.yaml
74
+ return is_feature_enabled(feature)
75
+
76
+
77
+ async def require_feature_async(feature: str, db) -> None:
78
+ """Raise 404 if feature is disabled (async version, DB-first).
79
+
80
+ Use this at the start of API endpoints for dynamic feature checking.
81
+
82
+ Args:
83
+ feature: Feature name
84
+ db: AsyncSession database session
85
+
86
+ Raises:
87
+ HTTPException: 404 if feature is disabled
88
+ """
89
+ if not await is_feature_enabled_async(feature, db):
90
+ raise HTTPException(status_code=404, detail="Not Found")
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "focomy"
7
- version = "0.1.107"
7
+ version = "0.1.108"
8
8
  description = "The Most Beautiful CMS - A metadata-driven, zero-duplicate-code content management system"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,45 +0,0 @@
1
- """Utility functions for Focomy."""
2
-
3
- from datetime import datetime, timezone
4
- from functools import lru_cache
5
-
6
- from fastapi import HTTPException
7
-
8
-
9
- def utcnow() -> datetime:
10
- """Return current UTC time as naive datetime for DB storage.
11
-
12
- PostgreSQL TIMESTAMP WITHOUT TIME ZONE expects naive datetimes.
13
- This function returns UTC time without tzinfo to avoid mismatch errors.
14
- """
15
- return datetime.now(timezone.utc).replace(tzinfo=None)
16
-
17
-
18
- def is_feature_enabled(feature: str) -> bool:
19
- """Check if a feature is enabled.
20
-
21
- Args:
22
- feature: Feature name (e.g., 'media', 'comment', 'wordpress_import')
23
-
24
- Returns:
25
- True if feature is enabled, False otherwise
26
- """
27
- from .config import get_settings
28
-
29
- settings = get_settings()
30
- return getattr(settings.features, feature, False)
31
-
32
-
33
- def require_feature(feature: str) -> None:
34
- """Raise 404 if feature is disabled.
35
-
36
- Use this at the start of API endpoints to disable them when feature is off.
37
-
38
- Args:
39
- feature: Feature name
40
-
41
- Raises:
42
- HTTPException: 404 if feature is disabled
43
- """
44
- if not is_feature_enabled(feature):
45
- raise HTTPException(status_code=404, detail="Not Found")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes