Coverage for src / lexigram / admin / settings / panel / models.py: 100%

27 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-13 22:14 +0800

1"""Pydantic models bound to configuration specs.""" 

2 

3from __future__ import annotations 

4 

5from typing import Literal 

6 

7from lexigram.domain import DomainModel 

8from lexigram.validation import Field 

9 

10__all__ = [ 

11 "DEFAULT_CSP", 

12 "BrandingSettings", 

13 "CacheSettings", 

14 "I18nSettings", 

15 "ProfilerSettings", 

16 "RbacSettings", 

17 "SecuritySettings", 

18] 

19 

20DEFAULT_CSP = ( 

21 "default-src 'self'; " 

22 "script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.tailwindcss.com; " 

23 "style-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.tailwindcss.com; " 

24 "img-src 'self' data:; " 

25 "font-src 'self'; " 

26 "connect-src 'self' https://unpkg.com; " 

27 "frame-ancestors 'none';" 

28) 

29 

30 

31class BrandingSettings(DomainModel): 

32 """Site branding and theme settings consumed by the admin renderer.""" 

33 

34 site_name: str = Field( 

35 default="Lexigram Admin", 

36 title="Site Name", 

37 description="Name shown in the topbar, login page, and document title.", 

38 ) 

39 primary_color: str = Field( 

40 default="#6b7280", 

41 title="Primary Color", 

42 description="Hex color used for the primary UI accent.", 

43 ) 

44 logo_url: str = Field(default="", title="Logo URL") 

45 favicon_url: str = Field(default="", title="Favicon URL") 

46 dark_mode: Literal["system", "light", "dark"] = Field( 

47 default="system", 

48 title="Dark Mode", 

49 description="Theme preference: follow the system, force light, or force dark.", 

50 ) 

51 

52 

53class CacheSettings(DomainModel): 

54 """Response caching settings consumed by AdminCacheMiddleware.""" 

55 

56 enabled: bool = Field( 

57 default=True, 

58 title="Enabled", 

59 description="Cache successful GET responses.", 

60 ) 

61 default_ttl: int = Field( 

62 default=60, 

63 ge=0, 

64 title="Default TTL (seconds)", 

65 description="Default cache lifetime when no Cache-Control header is present.", 

66 ) 

67 

68 

69class SecuritySettings(DomainModel): 

70 """HTTP security header settings consumed by AdminSecurityHeaders.""" 

71 

72 csp: str = Field( 

73 default=DEFAULT_CSP, 

74 title="Content Security Policy", 

75 description="Content-Security-Policy header value.", 

76 ) 

77 hsts_max_age: int = Field( 

78 default=63072000, 

79 ge=0, 

80 title="HSTS Max Age (seconds)", 

81 description="Strict-Transport-Security max-age.", 

82 ) 

83 

84 

85class I18nSettings(DomainModel): 

86 """Internationalization defaults consumed by the i18n locale resolver.""" 

87 

88 default_locale: str = Field( 

89 default="en", 

90 title="Default Locale", 

91 description="Fallback BCP 47 locale tag used when a request resolves no locale.", 

92 ) 

93 default_timezone: str = Field( 

94 default="UTC", 

95 title="Default Timezone", 

96 description="Fallback IANA timezone name used when a request resolves no timezone.", 

97 ) 

98 

99 

100class RbacSettings(DomainModel): 

101 """RBAC defaults consumed by the permission service.""" 

102 

103 default_role: str = Field( 

104 default="viewer", 

105 title="Default Role", 

106 description="Role assigned to users with no explicit role mapping.", 

107 ) 

108 allow_anonymous: bool = Field( 

109 default=False, 

110 title="Allow Anonymous", 

111 description="Permit requests without an authenticated identity.", 

112 ) 

113 

114 

115class ProfilerSettings(DomainModel): 

116 """Profiler toggles. Rendering/persistence only — no consumer is wired (see plan).""" 

117 

118 enabled: bool = Field( 

119 default=False, 

120 title="Enabled", 

121 description="Enable request profiling.", 

122 ) 

123 slow_threshold_ms: int = Field( 

124 default=500, 

125 ge=1, 

126 title="Slow Threshold (ms)", 

127 description="Requests slower than this are flagged as slow.", 

128 )