solara 0.1.0

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 (175) hide show
  1. checksums.yaml +7 -0
  2. data/bin/solara +18 -0
  3. data/solara/lib/.DS_Store +0 -0
  4. data/solara/lib/core/.DS_Store +0 -0
  5. data/solara/lib/core/aliases/alias_generator.rb +128 -0
  6. data/solara/lib/core/aliases/alias_generator_manager.rb +28 -0
  7. data/solara/lib/core/aliases/solara_terminal_setup.rb +103 -0
  8. data/solara/lib/core/brands/brand_onboarder.rb +46 -0
  9. data/solara/lib/core/brands/brand_switcher.rb +204 -0
  10. data/solara/lib/core/brands/brands_manager.rb +154 -0
  11. data/solara/lib/core/dashboard/.DS_Store +0 -0
  12. data/solara/lib/core/dashboard/brand/.DS_Store +0 -0
  13. data/solara/lib/core/dashboard/brand/BrandDetail.js +11 -0
  14. data/solara/lib/core/dashboard/brand/BrandDetailController.js +361 -0
  15. data/solara/lib/core/dashboard/brand/BrandDetailModel.js +155 -0
  16. data/solara/lib/core/dashboard/brand/BrandDetailView.js +245 -0
  17. data/solara/lib/core/dashboard/brand/brand.html +477 -0
  18. data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +123 -0
  19. data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +260 -0
  20. data/solara/lib/core/dashboard/brands/Brands.js +10 -0
  21. data/solara/lib/core/dashboard/brands/BrandsController.js +155 -0
  22. data/solara/lib/core/dashboard/brands/BrandsModel.js +136 -0
  23. data/solara/lib/core/dashboard/brands/BrandsView.js +136 -0
  24. data/solara/lib/core/dashboard/brands/brands.html +345 -0
  25. data/solara/lib/core/dashboard/component/AddFieldSheet.js +212 -0
  26. data/solara/lib/core/dashboard/component/AliasesBottomSheet.js +128 -0
  27. data/solara/lib/core/dashboard/component/BrandOptionsBottomSheet.js +130 -0
  28. data/solara/lib/core/dashboard/component/ConfirmationDialog.js +103 -0
  29. data/solara/lib/core/dashboard/component/MessageBottomSheet.js +80 -0
  30. data/solara/lib/core/dashboard/component/OnboardBrandBottomSheet.js +214 -0
  31. data/solara/lib/core/dashboard/dashboard_manager.rb +19 -0
  32. data/solara/lib/core/dashboard/dashboard_server.rb +132 -0
  33. data/solara/lib/core/dashboard/handler/base_handler.rb +25 -0
  34. data/solara/lib/core/dashboard/handler/brand_alisases_handler.rb +33 -0
  35. data/solara/lib/core/dashboard/handler/brand_configurations_handler.rb +18 -0
  36. data/solara/lib/core/dashboard/handler/brand_configurations_manager.rb +73 -0
  37. data/solara/lib/core/dashboard/handler/brand_icon_handler.rb +20 -0
  38. data/solara/lib/core/dashboard/handler/brand_section_handler.rb +20 -0
  39. data/solara/lib/core/dashboard/handler/brands_handler.rb +14 -0
  40. data/solara/lib/core/dashboard/handler/current_brand_handler.rb +18 -0
  41. data/solara/lib/core/dashboard/handler/doctor_handler.rb +39 -0
  42. data/solara/lib/core/dashboard/handler/edit_section_handler.rb +55 -0
  43. data/solara/lib/core/dashboard/handler/offboard_brand_handler.rb +34 -0
  44. data/solara/lib/core/dashboard/handler/onboard_brand_handler.rb +53 -0
  45. data/solara/lib/core/dashboard/handler/redirect_handler.rb +12 -0
  46. data/solara/lib/core/dashboard/handler/switch_handler.rb +25 -0
  47. data/solara/lib/core/dashboard/index.html +36 -0
  48. data/solara/lib/core/dashboard/local.html +41 -0
  49. data/solara/lib/core/dashboard/res/favicon/android-chrome-192x192.png +0 -0
  50. data/solara/lib/core/dashboard/res/favicon/android-chrome-512x512.png +0 -0
  51. data/solara/lib/core/dashboard/res/favicon/apple-touch-icon.png +0 -0
  52. data/solara/lib/core/dashboard/res/favicon/favicon-16x16.png +0 -0
  53. data/solara/lib/core/dashboard/res/favicon/favicon-32x32.png +0 -0
  54. data/solara/lib/core/dashboard/res/favicon/favicon.ico +0 -0
  55. data/solara/lib/core/dashboard/res/favicon/site.webmanifest +1 -0
  56. data/solara/lib/core/dashboard/solara.png +0 -0
  57. data/solara/lib/core/doctor/brand_doctor.rb +94 -0
  58. data/solara/lib/core/doctor/doctor_manager.rb +35 -0
  59. data/solara/lib/core/doctor/project_doctor.rb +8 -0
  60. data/solara/lib/core/doctor/schema/brand_configurations.json +60 -0
  61. data/solara/lib/core/doctor/schema/platform/android/android_config.json +23 -0
  62. data/solara/lib/core/doctor/schema/platform/android/android_signing.json +23 -0
  63. data/solara/lib/core/doctor/schema/platform/ios/ios_config.json +27 -0
  64. data/solara/lib/core/doctor/schema/platform/ios/ios_signing.json +27 -0
  65. data/solara/lib/core/doctor/schema/platform/shared/theme.json +48 -0
  66. data/solara/lib/core/doctor/validator/brand_settings_validator.rb +55 -0
  67. data/solara/lib/core/doctor/validator/brand_settings_validator_manager.rb +82 -0
  68. data/solara/lib/core/doctor/validator/directory_structure_validator.rb +38 -0
  69. data/solara/lib/core/doctor/validator/file_structure_validator.rb +37 -0
  70. data/solara/lib/core/doctor/validator/json_file_validator.rb +21 -0
  71. data/solara/lib/core/doctor/validator/json_schema_validator.rb +32 -0
  72. data/solara/lib/core/doctor/validator/project_filesystem_validator.rb +70 -0
  73. data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +51 -0
  74. data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +53 -0
  75. data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +51 -0
  76. data/solara/lib/core/doctor/validator/template/template_validator.rb +108 -0
  77. data/solara/lib/core/doctor/validator/validation_strategy.rb +7 -0
  78. data/solara/lib/core/scripts/brand_config_generator.rb +245 -0
  79. data/solara/lib/core/scripts/brand_config_manager.rb +90 -0
  80. data/solara/lib/core/scripts/brand_exporter.rb +38 -0
  81. data/solara/lib/core/scripts/brand_importer.rb +84 -0
  82. data/solara/lib/core/scripts/brand_offboarder.rb +19 -0
  83. data/solara/lib/core/scripts/brand_resources_manager.rb +77 -0
  84. data/solara/lib/core/scripts/directory_creator.rb +22 -0
  85. data/solara/lib/core/scripts/file_manager.rb +90 -0
  86. data/solara/lib/core/scripts/file_path.rb +327 -0
  87. data/solara/lib/core/scripts/folder_copier.rb +41 -0
  88. data/solara/lib/core/scripts/gitignore_manager.rb +54 -0
  89. data/solara/lib/core/scripts/interactive_file_system_validator.rb +110 -0
  90. data/solara/lib/core/scripts/platform/android/android_manifest_switcher.rb +24 -0
  91. data/solara/lib/core/scripts/platform/android/android_strings_switcher.rb +39 -0
  92. data/solara/lib/core/scripts/platform/android/gradle_switcher.rb +233 -0
  93. data/solara/lib/core/scripts/platform/android/properties_generator.rb +31 -0
  94. data/solara/lib/core/scripts/platform/ios/ios_file_path_manager.rb +109 -0
  95. data/solara/lib/core/scripts/platform/ios/ios_plist_manager.rb +42 -0
  96. data/solara/lib/core/scripts/platform/ios/xcconfig_generator.rb +44 -0
  97. data/solara/lib/core/scripts/platform/ios/xcode_asset_manager.rb +56 -0
  98. data/solara/lib/core/scripts/platform/ios/xcode_project_manager.rb +82 -0
  99. data/solara/lib/core/scripts/platform/ios/xcode_project_switcher.rb +130 -0
  100. data/solara/lib/core/scripts/project_settings_manager.rb +39 -0
  101. data/solara/lib/core/scripts/solara_logger.rb +103 -0
  102. data/solara/lib/core/scripts/solara_settings_manager.rb +73 -0
  103. data/solara/lib/core/scripts/solara_status_manager.rb +55 -0
  104. data/solara/lib/core/scripts/solara_version_manager.rb +42 -0
  105. data/solara/lib/core/scripts/strings_xml_manager.rb +22 -0
  106. data/solara/lib/core/scripts/terminal_input_manager.rb +22 -0
  107. data/solara/lib/core/scripts/theme_generator.rb +250 -0
  108. data/solara/lib/core/scripts/yaml_manager.rb +72 -0
  109. data/solara/lib/core/solara_configurator.rb +15 -0
  110. data/solara/lib/core/template/brands/android/android_config.json +8 -0
  111. data/solara/lib/core/template/brands/android/android_signing.json +6 -0
  112. data/solara/lib/core/template/brands/android/res/.DS_Store +0 -0
  113. data/solara/lib/core/template/brands/android/res/mipmap-hdpi/ic_launcher.png +0 -0
  114. data/solara/lib/core/template/brands/android/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  115. data/solara/lib/core/template/brands/android/res/mipmap-mdpi/ic_launcher.png +0 -0
  116. data/solara/lib/core/template/brands/android/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  117. data/solara/lib/core/template/brands/android/res/mipmap-xhdpi/ic_launcher.png +0 -0
  118. data/solara/lib/core/template/brands/android/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  119. data/solara/lib/core/template/brands/android/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  120. data/solara/lib/core/template/brands/android/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  121. data/solara/lib/core/template/brands/android/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  122. data/solara/lib/core/template/brands/android/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  123. data/solara/lib/core/template/brands/brands.json +4 -0
  124. data/solara/lib/core/template/brands/ios/assets/.DS_Store +0 -0
  125. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/100.png +0 -0
  126. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/102.png +0 -0
  127. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/1024.png +0 -0
  128. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/114.png +0 -0
  129. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/120.png +0 -0
  130. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/128.png +0 -0
  131. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/144.png +0 -0
  132. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/152.png +0 -0
  133. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/16.png +0 -0
  134. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/167.png +0 -0
  135. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/172.png +0 -0
  136. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/180.png +0 -0
  137. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/196.png +0 -0
  138. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/20.png +0 -0
  139. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/216.png +0 -0
  140. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/256.png +0 -0
  141. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/29.png +0 -0
  142. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/32.png +0 -0
  143. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/40.png +0 -0
  144. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/48.png +0 -0
  145. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/50.png +0 -0
  146. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/512.png +0 -0
  147. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/55.png +0 -0
  148. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/57.png +0 -0
  149. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/58.png +0 -0
  150. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/60.png +0 -0
  151. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/64.png +0 -0
  152. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/66.png +0 -0
  153. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/72.png +0 -0
  154. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/76.png +0 -0
  155. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/80.png +0 -0
  156. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/87.png +0 -0
  157. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/88.png +0 -0
  158. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/92.png +0 -0
  159. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/Contents.json +1 -0
  160. data/solara/lib/core/template/brands/ios/ios_config.json +7 -0
  161. data/solara/lib/core/template/brands/ios/ios_signing.json +7 -0
  162. data/solara/lib/core/template/brands/shared/.DS_Store +0 -0
  163. data/solara/lib/core/template/brands/shared/brand_config.json +2 -0
  164. data/solara/lib/core/template/brands/shared/theme.json +46 -0
  165. data/solara/lib/core/template/config/android_template_config.json +57 -0
  166. data/solara/lib/core/template/config/flutter_template_config.json +62 -0
  167. data/solara/lib/core/template/config/ios_template_config.json +57 -0
  168. data/solara/lib/core/template/project_template_generator.rb +63 -0
  169. data/solara/lib/platform_detector.rb +84 -0
  170. data/solara/lib/solara/cli.rb +5 -0
  171. data/solara/lib/solara/version.rb +3 -0
  172. data/solara/lib/solara.rb +238 -0
  173. data/solara/lib/solara_initializer.rb +44 -0
  174. data/solara/lib/solara_manager.rb +73 -0
  175. metadata +346 -0
@@ -0,0 +1,25 @@
1
+ class BaseHandler
2
+ def initialize(server, dashboard_server)
3
+ @server = server
4
+ @dashboard_server = dashboard_server
5
+ end
6
+
7
+ def mount
8
+ raise NotImplementedError, "Subclasses must implement the 'mount' method"
9
+ end
10
+
11
+ protected
12
+
13
+ def handle_error(res, error, message, status = 500)
14
+ Solara.logger.failure("#{message}: #{error.message}")
15
+ res.status = status
16
+ res.body = JSON.generate({ success: false, error: "#{message}: #{error.message}" })
17
+ res.content_type = 'application/json'
18
+ end
19
+
20
+ def method_not_allowed(res)
21
+ res.status = 405
22
+ res.body = JSON.generate({ success: false, error: 'Method Not Allowed' })
23
+ res.content_type = 'application/json'
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ class BrandAliasesHandler < BaseHandler
2
+ def mount
3
+ @server.mount_proc('/brand/aliases') do |req, res|
4
+ if req.request_method == 'GET'
5
+ begin
6
+ query = CGI.parse(req.query_string)
7
+ brand_key = query['brand_key']&.first
8
+
9
+ if brand_key
10
+ aliases = get_brand_aliases(brand_key)
11
+ res.status = 200
12
+ res.body = JSON.generate({ success: true, aliases: aliases })
13
+ else
14
+ res.status = 400
15
+ res.body = JSON.generate({ success: false, error: 'Missing brand_key parameter' })
16
+ end
17
+ rescue StandardError => e
18
+ handle_error(res, e, "Error fetching brand aliases")
19
+ end
20
+ else
21
+ method_not_allowed(res)
22
+ end
23
+ res.content_type = 'application/json'
24
+ end
25
+ end
26
+
27
+ def get_brand_aliases(brand_key)
28
+ AliasGeneratorManager.aliases_json
29
+ rescue StandardError => e
30
+ Solara.logger.failure("Error getting brand aliases: #{e.message}")
31
+ raise
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ class BrandConfigurationsHandler < BaseHandler
2
+ def mount
3
+ @server.mount_proc('/configurations') do |req, res|
4
+ begin
5
+ query = CGI.parse(req.query_string)
6
+ brand_key = query['brand_key']&.first
7
+
8
+ response_data = BrandsManager.instance.brand_with_configurations(brand_key)
9
+ res.body = JSON.generate({ success: true, message: "Configurations response", result: response_data })
10
+ res['Content-Type'] = 'application/json'
11
+ rescue StandardError => e
12
+ handle_error(res, e, "Error in brand configurations handler")
13
+ end
14
+ end
15
+ end
16
+
17
+ end
18
+
@@ -0,0 +1,73 @@
1
+ class BrandConfigurationsManager
2
+
3
+ def initialize(brand_key)
4
+ @brand_key = brand_key
5
+ end
6
+
7
+ def template_with_key(key)
8
+ templates.select { |section| section[:key] === key }.first
9
+ end
10
+
11
+ def templates
12
+ [
13
+ {
14
+ key: 'theme',
15
+ name: 'Theme Configuration',
16
+ input_type: 'color',
17
+ path: FilePath.brand_theme(@brand_key)
18
+ },
19
+ {
20
+ key: 'brand_config',
21
+ name: 'Brand Configuration',
22
+ input_type: 'text',
23
+ path: FilePath.brand_config(@brand_key)
24
+ },
25
+ {
26
+ key: 'android_config',
27
+ name: 'Android Platform Configuration',
28
+ input_type: 'text',
29
+ path: FilePath.android_brand_config(@brand_key)
30
+ },
31
+ {
32
+ key: 'android_signing',
33
+ name: 'Android Signing',
34
+ input_type: 'text',
35
+ path: FilePath.brand_signing(@brand_key, Platform::Android)
36
+ },
37
+ {
38
+ key: 'ios_config',
39
+ name: 'iOS Platform Configuration',
40
+ input_type: 'text',
41
+ path: FilePath.ios_config(@brand_key)
42
+ },
43
+ {
44
+ key: 'ios_signing',
45
+ name: 'iOS Signing',
46
+ input_type: 'text',
47
+ path: FilePath.brand_signing(@brand_key, Platform::IOS)
48
+ }
49
+ ]
50
+ end
51
+
52
+ def create
53
+ config_templates = templates
54
+
55
+ config_templates.map do |template|
56
+ create_config_item(
57
+ template[:key],
58
+ template[:name],
59
+ template[:input_type],
60
+ template[:path].tap { |p| File.expand_path(p) }
61
+ )
62
+ end
63
+ end
64
+
65
+ def create_config_item(key, name, input_type, path)
66
+ {
67
+ key: key,
68
+ name: name,
69
+ inputType: input_type,
70
+ content: JSON.parse(File.read(path)),
71
+ }
72
+ end
73
+ end
@@ -0,0 +1,20 @@
1
+ class BrandIconHandler < BaseHandler
2
+ def mount
3
+ @server.mount_proc("/brand/icon") do |req, res|
4
+ begin
5
+ brand_key = req.query['brand_key']
6
+ filepath = FilePath.launcher_icon(brand_key)
7
+ if File.exist?(filepath)
8
+ res.body = File.read(filepath)
9
+ res['Content-Type'] = 'image/png' # Adjust as necessary
10
+ else
11
+ res.status = 404
12
+ res.body = JSON.generate({ error: "Icon not found for brand: #{brand_key}" })
13
+ res['Content-Type'] = 'application/json'
14
+ end
15
+ rescue StandardError => e
16
+ handle_error(res, e, "Error fetching brand icon")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ class BrandSectionHandler < BaseHandler
2
+ def mount
3
+ @server.mount_proc("/section") do |req, res|
4
+ begin
5
+ query = CGI.parse(req.query_string)
6
+ path = query['path']&.first
7
+ if File.exist?(path)
8
+ res.body = File.read(path)
9
+ res['Content-Type'] = 'application/json'
10
+ else
11
+ res.status = 404
12
+ res.body = JSON.generate({ error: "Config file not found: #{path}" })
13
+ res['Content-Type'] = 'application/json'
14
+ end
15
+ rescue StandardError => e
16
+ handle_error(res, e, "Error in brand section handler")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ class BrandsHandler < BaseHandler
2
+ def mount
3
+ @server.mount_proc('/brands.json') do |req, res|
4
+ begin
5
+ brands_list = BrandsManager.instance.brands_list
6
+ json = JSON.generate(brands_list)
7
+ res.body = json
8
+ res['Content-Type'] = 'application/json'
9
+ rescue StandardError => e
10
+ handle_error(res, e, "Error fetching brands list")
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ class CurrentBrandHandler < BaseHandler
2
+ def mount
3
+ @server.mount_proc('/brand/current') do |req, res|
4
+ begin
5
+ current_brand = BrandsManager.instance.current_brand
6
+ if current_brand
7
+ res.status = 200
8
+ res.body = JSON.generate(current_brand)
9
+ else
10
+ res.status = 204 # No Content
11
+ end
12
+ res.content_type = 'application/json'
13
+ rescue StandardError => e
14
+ handle_error(res, e, "Error fetching current brand")
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ class DoctorHandler < BaseHandler
2
+ def mount
3
+ @server.mount_proc('/brand/doctor') do |req, res|
4
+ if req.request_method == 'GET'
5
+ begin
6
+ query = CGI.parse(req.query_string)
7
+ brand_key = query['brand_key']&.first
8
+
9
+ if brand_key
10
+ doctor_result = run_doctor(brand_key)
11
+ res.status = 200
12
+ res.body = JSON.generate({ success: true, result: doctor_result })
13
+ else
14
+ res.status = 400
15
+ res.body = JSON.generate({ success: false, error: 'Missing brand_key parameter' })
16
+ end
17
+ rescue StandardError => e
18
+ handle_error(res, e, "Error running doctor")
19
+ end
20
+ else
21
+ method_not_allowed(res)
22
+ end
23
+ res.content_type = 'application/json'
24
+ end
25
+ end
26
+
27
+ def run_doctor(brand_key)
28
+ errors = SolaraManager.new.doctor(brand_key)
29
+ {
30
+ message: "Health check completed.",
31
+ brand_key: brand_key,
32
+ passed: errors.empty?,
33
+ errors: errors
34
+ }
35
+ rescue StandardError => e
36
+ Solara.logger.failure("Error running doctor: #{e.message}")
37
+ raise
38
+ end
39
+ end
@@ -0,0 +1,55 @@
1
+ Dir['*.rb'].each { |file| require_relative file }
2
+
3
+ class EditSectionHandler < BaseHandler
4
+ def mount
5
+ @server.mount_proc('/section/edit') do |req, res|
6
+ if req.request_method == 'POST'
7
+ begin
8
+ request_payload = JSON.parse(req.body)
9
+ brand_key = request_payload['brand_key']
10
+ key = request_payload['key']
11
+ data = request_payload['data']
12
+
13
+ update_section(key, data, brand_key)
14
+ set_current_brand_content_changed(brand_key, true)
15
+
16
+ res.status = 200
17
+ res.body = JSON.generate({ success: true, message: "Configuration for #{key} updated successfully" })
18
+ res.content_type = 'application/json'
19
+ rescue JSON::ParserError => e
20
+ handle_error(res, e, "Invalid JSON in request body", 400)
21
+ rescue StandardError => e
22
+ handle_error(res, e, "Error updating configuration")
23
+ end
24
+ else
25
+ method_not_allowed(res)
26
+ end
27
+ end
28
+ end
29
+
30
+ def update_section(key, data, brand_key)
31
+ template = BrandConfigurationsManager.new(brand_key).template_with_key(key)
32
+
33
+ path = template[:path]
34
+ if File.exist?(path)
35
+ File.write(path, JSON.pretty_generate(data))
36
+ Solara.logger.debug("Updated Config for #{path}: #{data}")
37
+ else
38
+ raise "Config file not found: #{path}"
39
+ end
40
+ rescue StandardError => e
41
+ Solara.logger.failure("Error updating section: #{e.message}")
42
+ raise
43
+ end
44
+
45
+ def update_theme(path, data)
46
+ json = JSON.parse(File.read(path))
47
+ json['colors'] = data
48
+ File.write(path, JSON.pretty_generate(json))
49
+ end
50
+
51
+ def set_current_brand_content_changed(brand_key, changed)
52
+ BrandsManager.instance.set_current_brand_content_changed(brand_key, changed)
53
+ end
54
+
55
+ end
@@ -0,0 +1,34 @@
1
+ class OffboardBrandHandler < BaseHandler
2
+ def mount
3
+ @server.mount_proc('/brand/offboard') do |req, res|
4
+ if req.request_method == 'GET'
5
+ begin
6
+ query = CGI.parse(req.query_string)
7
+ brand_key = query['brand_key']&.first
8
+
9
+ if brand_key
10
+ offboard(brand_key)
11
+ res.status = 200
12
+ res.body = JSON.generate({ success: true, message: "Deleted brand: #{brand_key}" })
13
+ else
14
+ res.status = 400
15
+ res.body = JSON.generate({ success: false, error: 'Missing brand_key parameter' })
16
+ end
17
+ rescue StandardError => e
18
+ handle_error(res, e, "Error deleting brand")
19
+ end
20
+ else
21
+ method_not_allowed(res)
22
+ end
23
+ res.content_type = 'application/json'
24
+ end
25
+ end
26
+
27
+ def offboard(brand_key)
28
+ SolaraManager.new.offboard(brand_key, confirm: false)
29
+ rescue StandardError => e
30
+ Solara.logger.failure("Error deleting brand: #{e.message}")
31
+ raise
32
+ end
33
+
34
+ end
@@ -0,0 +1,53 @@
1
+ class OnboardBrandHandler < BaseHandler
2
+ def mount
3
+ @server.mount_proc('/brand/onboard') do |req, res|
4
+ if req.request_method == 'POST'
5
+ begin
6
+ data = JSON.parse(req.body)
7
+ brand_name = data['brand_name']
8
+ brand_key = data['brand_key']
9
+ clone_brand_key = data['clone_brand_key']
10
+
11
+ if brand_key
12
+ result = onboard_brand(brand_name, brand_key, clone_brand_key)
13
+ res.status = result[:success] ? 200 : 409 # 409 Conflict for existing brand
14
+ res.body = JSON.generate(result)
15
+ else
16
+ res.status = 400
17
+ res.body = JSON.generate({ success: false, error: 'Missing brand_key parameter' })
18
+ end
19
+ rescue JSON::ParserError => e
20
+ handle_error(res, e, "Invalid JSON in request body", 400)
21
+ rescue StandardError => e
22
+ handle_error(res, e, "Error adding brand")
23
+ end
24
+ else
25
+ method_not_allowed(res)
26
+ end
27
+ res.content_type = 'application/json'
28
+ end
29
+
30
+ def onboard_brand(brand_name, brand_key, clone_brand_key = nil)
31
+ if BrandsManager.instance.exists(brand_key)
32
+ return { success: false, message: "Brand with key (#{brand_key}) already added!" }
33
+ end
34
+ SolaraManager.new.onboard(brand_key, brand_name, clone_brand_key: clone_brand_key, open_dashboard: false)
35
+ { success: true, message: "Brand added successfully" }
36
+ rescue StandardError => e
37
+ Solara.logger.failure("Error adding brand: #{e.message}")
38
+ raise
39
+ end
40
+
41
+ end
42
+
43
+ def onboard_brand(brand_name, brand_key, clone_brand_key = nil)
44
+ if BrandsManager.instance.exists(brand_key)
45
+ return { success: false, message: "Brand with key (#{brand_key}) already added!" }
46
+ end
47
+ SolaraManager.new.onboard(brand_key, brand_name, clone_brand_key: clone_brand_key, open_dashboard: false)
48
+ { success: true, message: "Brand added successfully" }
49
+ rescue StandardError => e
50
+ Solara.logger.failure("Error adding brand: #{e.message}")
51
+ raise
52
+ end
53
+ end
@@ -0,0 +1,12 @@
1
+ class RedirectHandler < BaseHandler
2
+ def mount
3
+ @server.mount_proc('/redirect') do |req, res|
4
+ begin
5
+ res.content_type = 'application/json'
6
+ res.body = JSON.generate({ redirect_url: @dashboard_server.home })
7
+ rescue StandardError => e
8
+ handle_error(res, e, "Error in redirect handler")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,25 @@
1
+ class SwitchToBrandHandler < BaseHandler
2
+ def mount
3
+ @server.mount_proc('/switch') do |req, res|
4
+ if req.request_method == 'POST'
5
+ begin
6
+ request_payload = JSON.parse(req.body)
7
+ brand_key = request_payload['brand_key']
8
+
9
+ result = SolaraManager.new.switch(brand_key)
10
+ Solara.logger.debug(result)
11
+
12
+ res.status = 200
13
+ res.body = JSON.generate({ success: true, message: "Switched to brand: #{brand_key}", result: result })
14
+ res.content_type = 'application/json'
15
+ rescue JSON::ParserError => e
16
+ handle_error(res, e, "Invalid JSON in request body", 400)
17
+ rescue StandardError => e
18
+ handle_error(res, e, "Error switching to brand")
19
+ end
20
+ else
21
+ method_not_allowed(res)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Redirecting...</title>
7
+ <style>
8
+ body, html {
9
+ height: 100%;
10
+ margin: 0;
11
+ display: flex;
12
+ justify-content: center;
13
+ align-items: center;
14
+ background-color: #f0f0f0;
15
+ }
16
+ .container {
17
+ text-align: center;
18
+ }
19
+ img {
20
+ max-width: 50%;
21
+ max-height: 80vh;
22
+ object-fit: contain;
23
+ }
24
+ </style>
25
+ <script>
26
+ setTimeout(() => {
27
+ window.location.href = 'brand/brand.html?source=remote';
28
+ }, 500);
29
+ </script>
30
+ </head>
31
+ <body>
32
+ <div class="container">
33
+ <img src="solara.png" alt="Logo Image">
34
+ </div>
35
+ </body>
36
+ </html>
@@ -0,0 +1,41 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Redirecting...</title>
7
+ <style>
8
+ body, html {
9
+ height: 100%;
10
+ margin: 0;
11
+ display: flex;
12
+ justify-content: center;
13
+ align-items: center;
14
+ background-color: #f0f0f0;
15
+ }
16
+ .container {
17
+ text-align: center;
18
+ }
19
+ img {
20
+ max-width: 50%;
21
+ max-height: 80vh;
22
+ object-fit: contain;
23
+ }
24
+ </style>
25
+ <script>
26
+ fetch('/redirect')
27
+ .then(response => response.json())
28
+ .then(data => {
29
+ setTimeout(() => {
30
+ window.location.href = data.redirect_url;
31
+ }, 500);
32
+ })
33
+ .catch(error => console.error('Error:', error));
34
+ </script>
35
+ </head>
36
+ <body>
37
+ <div class="container">
38
+ <img src="solara.png" alt="Logo Image">
39
+ </div>
40
+ </body>
41
+ </html>
@@ -0,0 +1 @@
1
+ {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
@@ -0,0 +1,94 @@
1
+ Dir.glob("#{__dir__}/*.rb").each { |file| require file }
2
+ Dir.glob("#{__dir__}/../../.solara/core/brands/*.rb").each { |file| require file }
3
+ Dir.glob("#{__dir__}/validator/template/*.rb").each { |file| require file }
4
+
5
+ class Issue < StandardError
6
+ ERROR = 'ERROR'
7
+ WARNING = 'WARNING'
8
+
9
+ attr_reader :type, :error
10
+
11
+ def initialize(type, error)
12
+ @type = type
13
+ @error = error
14
+ end
15
+
16
+ def to_s
17
+ "#{@type}: #{@error}"
18
+ end
19
+
20
+ def <=>(other)
21
+ [type, error] <=> [other.type, other.error]
22
+ end
23
+
24
+ def self.error(error)
25
+ Issue.new(ERROR, error)
26
+ end
27
+
28
+ def self.warning(error)
29
+ Issue.new(WARNING, error)
30
+ end
31
+ end
32
+
33
+ class BrandDoctor
34
+ def initialize
35
+ end
36
+
37
+ def visit(brand_keys = [], print_logs: true)
38
+ keys = brand_keys.empty? ? BrandsManager.instance.brands_list.map { |brand| brand['key'] } : brand_keys
39
+ issues = []
40
+
41
+ keys.each do |brand_key|
42
+ validator = TemplateValidator.new(FilePath.brand(brand_key), FilePath.template_validation_config)
43
+ errors = validator.validate
44
+
45
+ errors.each { |error|
46
+ issues << Issue.error(error)
47
+ }
48
+ end
49
+
50
+ # Validate settings only if all validations so far are passed to avoid files issues
51
+ if issues.select { |issue| issue.type == Issue::ERROR }.empty?
52
+ issues += BrandSettingsValidatorManager.new.validate
53
+ end
54
+
55
+ if print_logs && !issues.empty?
56
+ Solara.logger.title("Healt Check Result")
57
+
58
+ issues.sort!.each { |issue|
59
+ case issue.type
60
+ when Issue::ERROR
61
+ Solara.logger.failure(issue.to_s)
62
+ when Issue::WARNING
63
+ Solara.logger.warn(issue.to_s)
64
+ else
65
+ Solara.logger.failure(issue.to_s)
66
+ issues.sort!
67
+ end
68
+ }
69
+ end
70
+
71
+ issues.sort!
72
+ end
73
+
74
+ def validate_brand(brand_key, strategies = [])
75
+ Solara.logger.start_step("Brand Health Check: #{brand_key}")
76
+
77
+ project_path = FilePath.brand(brand_key)
78
+ issues = []
79
+
80
+ strategies.each do |strategy|
81
+ Solara.logger.debug("Running #{strategy.class.name}...")
82
+ begin
83
+ strategy.validate(project_path)
84
+ Solara.logger.debug("#{strategy.class.name} passed.")
85
+ rescue => e
86
+ issues << Issue.error("#{e.message} (#{strategy.class.name})")
87
+ end
88
+ end
89
+
90
+ Solara.logger.end_step("Brand Health Check #{brand_key}")
91
+ issues
92
+ end
93
+ end
94
+