focomy 0.1.107__py3-none-any.whl → 0.1.109__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.
core/api/forms.py CHANGED
@@ -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
core/main.py CHANGED
@@ -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")
core/services/settings.py CHANGED
@@ -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()
core/utils.py CHANGED
@@ -43,3 +43,50 @@ def require_feature(feature: str) -> None:
43
43
  """
44
44
  if not is_feature_enabled(feature):
45
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 > code default (True)
52
+ Note: config.yaml is ignored for features to allow DB override without restart.
53
+
54
+ Args:
55
+ feature: Feature name (e.g., 'form', 'comment')
56
+ db: AsyncSession database session
57
+
58
+ Returns:
59
+ True if feature is enabled, False otherwise
60
+ """
61
+ from .services.settings import SettingsService, DEFAULT_SETTINGS
62
+
63
+ settings_svc = SettingsService(db)
64
+ # Check DB directly (not through get() which includes config.yaml)
65
+ db_value = await settings_svc._get_db_value(f"features.{feature}")
66
+
67
+ if db_value is not None:
68
+ # DB value exists, use it
69
+ if isinstance(db_value, bool):
70
+ return db_value
71
+ if isinstance(db_value, str):
72
+ return db_value.lower() in ("true", "1", "yes")
73
+ return bool(db_value)
74
+
75
+ # Fallback to code default (ignore config.yaml)
76
+ return DEFAULT_SETTINGS.get("features", {}).get(feature, True)
77
+
78
+
79
+ async def require_feature_async(feature: str, db) -> None:
80
+ """Raise 404 if feature is disabled (async version, DB-first).
81
+
82
+ Use this at the start of API endpoints for dynamic feature checking.
83
+
84
+ Args:
85
+ feature: Feature name
86
+ db: AsyncSession database session
87
+
88
+ Raises:
89
+ HTTPException: 404 if feature is disabled
90
+ """
91
+ if not await is_feature_enabled_async(feature, db):
92
+ raise HTTPException(status_code=404, detail="Not Found")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: focomy
3
- Version: 0.1.107
3
+ Version: 0.1.109
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
@@ -2,10 +2,10 @@ core/__init__.py,sha256=SHwUSW_pu27kEARvwJFRdY2ZkVu6wzFh0eikYJXjbTY,1123
2
2
  core/cli.py,sha256=h5jnf4EJCZ1JZMnDl7W1IaL153UD3dXyKBbxhv1P-KA,35536
3
3
  core/config.py,sha256=VfRNfh5dAw9ax1MIjzaYN_KEvcNjy7X5-LxE22Rm2-I,8199
4
4
  core/database.py,sha256=wqvRuwpDxy25svag9NqreVcNA_VRDDjWsxOMUopsffs,2222
5
- core/main.py,sha256=em4na_OJu6E3y_nkeYxH_QvugiLdPzKCMbc5Jbc1y1k,16783
5
+ core/main.py,sha256=gc2s5La1qC6WCEq1Bc3UoUK1kKRzO7866-xSeYs_L4I,16647
6
6
  core/rate_limit.py,sha256=CX5UjmsU03aFWKXSKjweoHvH2xn0v4NBHNN5ynJC8LE,180
7
7
  core/relations.yaml,sha256=7GUCrphKaouEXNkyd8Ht99e6TcUPERhc4m36RGcc41U,2128
8
- core/utils.py,sha256=7myy64jI7T4WQ_C3Q7j0RPaYKQhfVzGkpNrw9c4yakc,1203
8
+ core/utils.py,sha256=Rqs1WStB0JjTb8-750jL-xJ_kaPH3ddupvqt46BXIBc,2754
9
9
  core/admin/__init__.py,sha256=IXrr-z-IDXmYodaZ-cVDou6wr_vsVhyWmXHdSNKkQsk,94
10
10
  core/admin/routes.py,sha256=h9yl5THrlM74ZUPiiFoAlAVN-p3kMR1h-UH_xK1_T0k,147979
11
11
  core/admin/url.py,sha256=FlusKnSz3bZgPSBmRu-dI3W-bQo7lKBDZ3zN8cFHwQc,2243
@@ -13,7 +13,7 @@ core/api/__init__.py,sha256=H1StbYGDVRS6g-Jk3UUf17ibAz1K8IUa27NfPMkaNrA,19
13
13
  core/api/auth.py,sha256=Zb37IHcUSjf8_hXiVzhoZPQw6WAiOOS_AoMqE96yat8,11565
14
14
  core/api/comments.py,sha256=Eu_0a_JqgiOP1GPsPoslxDztkCv_M324x9qAuBOWPOE,8397
15
15
  core/api/entities.py,sha256=sAS12hgjt0Zgudrq2BzfXdCU25myh-8whUxsU_z_jt8,11766
16
- core/api/forms.py,sha256=tw7oS5cqflTBBAMu5MG-GNx8ndNYxeQV1v2tOQr-Q1s,4702
16
+ core/api/forms.py,sha256=pJc0f72-F1QOTSI7aNDMaqScCmJnHUWbigvmSN4Or7k,4832
17
17
  core/api/media.py,sha256=iCsgRBS_-raYK99sksudyAINnTWFl1GhTMBaXJGWkek,6767
18
18
  core/api/relations.py,sha256=sErG5lNLBjmWZ5qSXyhGkU7F8iVZAYpFc-_5mY3ZkzU,4874
19
19
  core/api/revisions.py,sha256=8BJXtCsq8OJV1R5s33bl369lQrRDNkztWoS0tX70uAA,6960
@@ -120,7 +120,7 @@ core/services/schedule.py,sha256=eISYvq5iHyKXdngJNk5PIjTAKeB6O0Rj2j4ktoTSQNU,813
120
120
  core/services/search.py,sha256=OSwzQkUHnOLly6yqyDNaj1-RRUNar4QPlWCLhojVtdM,14964
121
121
  core/services/sentry.py,sha256=Soilp-qbRDDcvXfeaaS9Bq_trlaRvjNAyHIgvBkplXc,6275
122
122
  core/services/seo.py,sha256=qFKKnAzZ2CspWzlHHxvfKx4JBVgNhPWxCSn-adX-2z4,15658
123
- core/services/settings.py,sha256=FZrXd8k6X_0WQOO3I20lyrksNXzr4nL_yBJm9PmMuDI,8551
123
+ core/services/settings.py,sha256=oZJx1HgFNeSicagtURXHbqq0_O3yPwvcwosuqDmZ13c,8822
124
124
  core/services/spam_filter.py,sha256=2O8YWDlZoCr7MhGzsEsx6AKm2SgBP3-kxsXaETVd-0A,11314
125
125
  core/services/storage.py,sha256=gaaVf594Ck-zkZMtdt--YcIMvWgPBs7ZGPY0FRIVIzQ,8807
126
126
  core/services/theme.py,sha256=-sXcWC0qggV9pmDimKjpmaK0Il9MNCb5urWLgYcWpYk,49304
@@ -201,8 +201,8 @@ themes/minimal/templates/base.html,sha256=LFkx-XLDMGH7oFHHa0e6KPB0DJITOBvr6GtPkD
201
201
  themes/minimal/templates/home.html,sha256=ygYQgYj1OGCiKwmfsxwkPselVKT8vDH3jLLbfphpqKI,1577
202
202
  themes/minimal/templates/page.html,sha256=7Xcoq-ryaxlp913H2S1ishrAro2wsqqGmvsm1osXxd4,389
203
203
  themes/minimal/templates/post.html,sha256=FkTRHci8HNIIi3DU6Mb3oL0aDisGyDcsT_IUDwHmrvo,1387
204
- focomy-0.1.107.dist-info/METADATA,sha256=3ET-lqecSEWEWYhsRY5f3Fy9kBOJ_KZ5E_y1JOlUpcM,7042
205
- focomy-0.1.107.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
206
- focomy-0.1.107.dist-info/entry_points.txt,sha256=_rF-wxGI1axY7gox3DBsTLHq-JrFKkMCjA65a6b_oqE,41
207
- focomy-0.1.107.dist-info/licenses/LICENSE,sha256=z9Z7gN7NNV7zYCaY-Knh3bv8RBCu89VueYtAlN_-lro,1063
208
- focomy-0.1.107.dist-info/RECORD,,
204
+ focomy-0.1.109.dist-info/METADATA,sha256=KdDxlADdFCsXNDgMH4WcLM49cMPyVwmw-oKKVQGbziE,7042
205
+ focomy-0.1.109.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
206
+ focomy-0.1.109.dist-info/entry_points.txt,sha256=_rF-wxGI1axY7gox3DBsTLHq-JrFKkMCjA65a6b_oqE,41
207
+ focomy-0.1.109.dist-info/licenses/LICENSE,sha256=z9Z7gN7NNV7zYCaY-Knh3bv8RBCu89VueYtAlN_-lro,1063
208
+ focomy-0.1.109.dist-info/RECORD,,