kasy-cli 1.21.8 → 1.22.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 (269) hide show
  1. package/lib/commands/add.js +93 -80
  2. package/lib/commands/configure.js +100 -32
  3. package/lib/commands/doctor.js +28 -2
  4. package/lib/commands/new.js +86 -38
  5. package/lib/commands/notifications.js +1 -1
  6. package/lib/commands/remove.js +43 -15
  7. package/lib/commands/run.js +2 -2
  8. package/lib/commands/update.js +2 -2
  9. package/lib/scaffold/CHANGELOG.json +14 -0
  10. package/lib/scaffold/backends/api/generator.js +14 -14
  11. package/lib/scaffold/backends/api/patch/README.md +83 -0
  12. package/lib/scaffold/backends/api/patch/lib/core/data/api/storage_api.dart +1 -1
  13. package/lib/scaffold/backends/api/patch/lib/core/data/entities/user_entity.dart +5 -0
  14. package/lib/scaffold/backends/api/patch/lib/{environnements.dart → environments.dart} +3 -11
  15. package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_api.dart +108 -0
  16. package/lib/scaffold/backends/api/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +51 -0
  17. package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +5 -5
  18. package/lib/scaffold/backends/api/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +71 -38
  19. package/lib/scaffold/backends/api/patch/lib/features/authentication/api/authentication_api.dart +2 -2
  20. package/lib/scaffold/backends/api/patch/lib/features/feedbacks/api/feature_request_api.dart +54 -0
  21. package/lib/scaffold/backends/api/patch/lib/features/onboarding/models/user_info.dart +16 -16
  22. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  23. package/lib/scaffold/backends/api/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  24. package/lib/scaffold/backends/api/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +100 -0
  25. package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  26. package/lib/scaffold/backends/api/patch/lib/features/subscriptions/api/stripe_backend_api.dart +60 -0
  27. package/lib/scaffold/backends/api/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  28. package/lib/scaffold/backends/api/pubspec.yaml.tpl +4 -5
  29. package/lib/scaffold/backends/firebase/deploy.js +87 -13
  30. package/lib/scaffold/backends/firebase/enable-auth-via-cli.js +14 -6
  31. package/lib/scaffold/backends/firebase/generator.js +5 -5
  32. package/lib/scaffold/backends/firebase/setup-from-scratch.js +69 -45
  33. package/lib/scaffold/backends/firebase/tokens.js +4 -4
  34. package/lib/scaffold/backends/supabase/deploy.js +63 -11
  35. package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +149 -0
  36. package/lib/scaffold/backends/supabase/edge-functions/{llm-chat → ai-chat}/index.ts +19 -19
  37. package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +2 -0
  38. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +123 -0
  39. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +97 -0
  40. package/lib/scaffold/backends/supabase/edge-functions/stripe-list-prices/index.ts +83 -0
  41. package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +138 -0
  42. package/lib/scaffold/backends/supabase/generator.js +17 -17
  43. package/lib/scaffold/backends/supabase/migrations/20240101000009_ai_messages.sql +50 -0
  44. package/lib/scaffold/backends/supabase/migrations/20240101000012_stripe_customers.sql +36 -0
  45. package/lib/scaffold/backends/supabase/migrations/20240101000013_admin_role.sql +62 -0
  46. package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +4 -0
  47. package/lib/scaffold/backends/supabase/patch/lib/{environnements.dart → environments.dart} +3 -13
  48. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_api.dart +95 -0
  49. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +52 -0
  50. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  51. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +63 -35
  52. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +1 -1
  53. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +46 -0
  54. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +16 -16
  55. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  56. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  57. package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +93 -0
  58. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  59. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +52 -0
  60. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  61. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -5
  62. package/lib/scaffold/backends/supabase/tokens.js +3 -3
  63. package/lib/scaffold/catalog.js +9 -11
  64. package/lib/scaffold/generate.js +45 -31
  65. package/lib/scaffold/shared/generator-utils.js +188 -81
  66. package/lib/scaffold/shared/sort-imports.js +191 -0
  67. package/lib/scaffold/shared/template-strings.js +3 -3
  68. package/lib/utils/checks.js +2 -2
  69. package/lib/utils/i18n/messages-en.js +50 -35
  70. package/lib/utils/i18n/messages-es.js +50 -35
  71. package/lib/utils/i18n/messages-pt.js +52 -37
  72. package/lib/utils/updates.js +15 -15
  73. package/package.json +1 -1
  74. package/templates/firebase/.env.example +2 -2
  75. package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
  76. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  77. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  78. package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
  79. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
  80. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
  81. package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
  82. package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
  83. package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
  84. package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
  85. package/templates/firebase/firestore.rules +24 -5
  86. package/templates/firebase/functions/package-lock.json +22 -1
  87. package/templates/firebase/functions/package.json +2 -1
  88. package/templates/firebase/functions/src/admin/functions.ts +113 -0
  89. package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
  90. package/templates/firebase/functions/src/index.ts +8 -2
  91. package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
  92. package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
  93. package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
  94. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
  95. package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
  96. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
  97. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  98. package/templates/firebase/lib/components/components.dart +4 -1
  99. package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
  100. package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
  101. package/templates/firebase/lib/components/kasy_button.dart +23 -99
  102. package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
  103. package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
  104. package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +692 -425
  105. package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
  106. package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
  107. package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
  108. package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
  109. package/templates/firebase/lib/components/kasy_toast.dart +2 -2
  110. package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
  111. package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
  112. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +57 -4
  113. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
  114. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
  115. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +41 -0
  116. package/templates/firebase/lib/core/config/app_env.dart +5 -11
  117. package/templates/firebase/lib/core/config/features.dart +5 -4
  118. package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
  119. package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
  120. package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
  121. package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
  122. package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
  123. package/templates/firebase/lib/core/data/models/user.dart +11 -0
  124. package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
  125. package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
  126. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
  127. package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
  128. package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
  129. package/templates/firebase/lib/core/states/logout_action.dart +25 -0
  130. package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
  131. package/templates/firebase/lib/core/theme/colors.dart +488 -188
  132. package/templates/firebase/lib/core/theme/radius.dart +22 -11
  133. package/templates/firebase/lib/core/theme/shadows.dart +66 -0
  134. package/templates/firebase/lib/core/theme/texts.dart +75 -41
  135. package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
  136. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
  137. package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
  138. package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
  139. package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
  140. package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
  141. package/templates/firebase/lib/core/widgets/kasy_hover.dart +33 -13
  142. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
  143. package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
  144. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
  145. package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
  146. package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
  147. package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  148. package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
  149. package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
  150. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
  151. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
  152. package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
  153. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
  154. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
  155. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
  156. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
  157. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
  158. package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
  159. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
  160. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
  161. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +118 -0
  162. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
  163. package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
  164. package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
  165. package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
  166. package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
  167. package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
  168. package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
  169. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
  170. package/templates/firebase/lib/features/home/home_feed.dart +289 -0
  171. package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
  172. package/templates/firebase/lib/features/home/home_page.dart +11 -250
  173. package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
  174. package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
  175. package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
  176. package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
  177. package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
  178. package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
  179. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  180. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
  181. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
  182. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
  183. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
  184. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
  185. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
  186. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
  187. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
  188. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
  189. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
  190. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  191. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
  192. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
  193. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
  194. package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
  195. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
  196. package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
  197. package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
  198. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
  199. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
  200. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
  201. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
  202. package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
  203. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
  204. package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
  205. package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
  206. package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
  207. package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
  208. package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
  209. package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
  210. package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  211. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
  212. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
  213. package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
  214. package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
  215. package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
  216. package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
  217. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
  218. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
  219. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
  220. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
  221. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
  222. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
  223. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
  224. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
  225. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
  226. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
  227. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
  228. package/templates/firebase/lib/i18n/en.i18n.json +171 -46
  229. package/templates/firebase/lib/i18n/es.i18n.json +175 -50
  230. package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
  231. package/templates/firebase/lib/main.dart +6 -3
  232. package/templates/firebase/lib/router.dart +15 -23
  233. package/templates/firebase/pubspec.yaml +4 -5
  234. package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
  235. package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
  236. package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
  237. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
  238. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
  239. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
  240. package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
  241. package/templates/firebase/test/test_utils.dart +6 -6
  242. package/templates/firebase/web/index.html +5 -2
  243. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
  244. package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
  245. package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
  246. package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
  247. package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
  248. package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
  249. package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
  250. package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
  251. package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
  252. package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
  253. package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
  254. package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
  255. package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
  256. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
  257. package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
  258. /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
  259. /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
  260. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
  261. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
  262. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
  263. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
  264. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
  265. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
  266. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
  267. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
  268. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
  269. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Supabase Edge Function: Stripe — Create Checkout Session
3
+ *
4
+ * Creates a hosted Stripe Checkout session (mode=subscription) for the
5
+ * authenticated user and returns its URL. The user identity is taken from the
6
+ * verified JWT, never trusted from the request body, so a client cannot check
7
+ * out on behalf of someone else.
8
+ *
9
+ * Deployed WITH JWT verification.
10
+ *
11
+ * Secrets required:
12
+ * - STRIPE_SECRET_KEY: sk_test_... / sk_live_...
13
+ * - SUPABASE_URL / SUPABASE_ANON_KEY / SUPABASE_SERVICE_ROLE_KEY (auto-provided)
14
+ *
15
+ * Body: { priceId: string, successUrl?: string, cancelUrl?: string }
16
+ */
17
+
18
+ import Stripe from "npm:stripe@18";
19
+ import { createClient } from "https://esm.sh/@supabase/supabase-js@2.47.10";
20
+
21
+ const corsHeaders = {
22
+ "Access-Control-Allow-Origin": "*",
23
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
24
+ "Access-Control-Allow-Headers": "Authorization, Content-Type",
25
+ };
26
+
27
+ // Maps a Supabase auth user -> its Stripe customer id.
28
+ const CUSTOMERS_TABLE = "stripe_customers";
29
+
30
+ function trialDaysFor(price: Stripe.Price, product: Stripe.Product): number | null {
31
+ const meta = price.metadata?.trial_days ?? product.metadata?.trial_days;
32
+ return meta ? Number(meta) : null;
33
+ }
34
+
35
+ async function getUid(req: Request, supabaseUrl: string, anonKey: string): Promise<string | null> {
36
+ const authHeader = req.headers.get("Authorization");
37
+ if (!authHeader?.startsWith("Bearer ")) return null;
38
+ const token = authHeader.replace("Bearer ", "");
39
+ const client = createClient(supabaseUrl, anonKey, {
40
+ global: { headers: { Authorization: authHeader } },
41
+ });
42
+ const { data: { user }, error } = await client.auth.getUser(token);
43
+ if (error || !user) return null;
44
+ return user.id;
45
+ }
46
+
47
+ // deno-lint-ignore no-explicit-any
48
+ async function getOrCreateCustomer(stripe: Stripe, admin: any, uid: string): Promise<string> {
49
+ const { data } = await admin
50
+ .from(CUSTOMERS_TABLE)
51
+ .select("customer_id")
52
+ .eq("user_id", uid)
53
+ .maybeSingle();
54
+ const existing = data?.customer_id as string | undefined;
55
+ if (existing) return existing;
56
+ const customer = await stripe.customers.create({ metadata: { supabaseUID: uid } });
57
+ await admin.from(CUSTOMERS_TABLE).upsert({ user_id: uid, customer_id: customer.id });
58
+ return customer.id;
59
+ }
60
+
61
+ Deno.serve(async (req: Request) => {
62
+ if (req.method === "OPTIONS") {
63
+ return new Response(null, { status: 204, headers: corsHeaders });
64
+ }
65
+ if (req.method !== "POST") {
66
+ return Response.json({ error: "Method not allowed" }, { status: 405, headers: corsHeaders });
67
+ }
68
+
69
+ const secretKey = Deno.env.get("STRIPE_SECRET_KEY");
70
+ const supabaseUrl = Deno.env.get("SUPABASE_URL");
71
+ const anonKey = Deno.env.get("SUPABASE_ANON_KEY");
72
+ const serviceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
73
+ if (!secretKey || !supabaseUrl || !anonKey || !serviceRoleKey) {
74
+ return Response.json({ error: "Server configuration error" }, { status: 500, headers: corsHeaders });
75
+ }
76
+
77
+ const uid = await getUid(req, supabaseUrl, anonKey);
78
+ if (!uid) {
79
+ return Response.json({ error: "Sign in required" }, { status: 401, headers: corsHeaders });
80
+ }
81
+
82
+ let body: { priceId?: string; successUrl?: string; cancelUrl?: string };
83
+ try {
84
+ body = await req.json();
85
+ } catch {
86
+ return Response.json({ error: "Invalid JSON" }, { status: 400, headers: corsHeaders });
87
+ }
88
+ const priceId = body.priceId;
89
+ if (!priceId) {
90
+ return Response.json({ error: "priceId is required" }, { status: 400, headers: corsHeaders });
91
+ }
92
+ const successUrl = body.successUrl ?? "";
93
+ const cancelUrl = body.cancelUrl ?? successUrl;
94
+
95
+ try {
96
+ const stripe = new Stripe(secretKey);
97
+ const admin = createClient(supabaseUrl, serviceRoleKey);
98
+ const customerId = await getOrCreateCustomer(stripe, admin, uid);
99
+
100
+ const price = await stripe.prices.retrieve(priceId, { expand: ["product"] });
101
+ const trialDays = trialDaysFor(price, price.product as Stripe.Product);
102
+
103
+ const session = await stripe.checkout.sessions.create({
104
+ mode: "subscription",
105
+ customer: customerId,
106
+ client_reference_id: uid,
107
+ line_items: [{ price: priceId, quantity: 1 }],
108
+ success_url: successUrl,
109
+ cancel_url: cancelUrl,
110
+ subscription_data: {
111
+ metadata: { supabaseUID: uid },
112
+ ...(trialDays ? { trial_period_days: trialDays } : {}),
113
+ },
114
+ });
115
+ return Response.json({ url: session.url }, { headers: corsHeaders });
116
+ } catch (err) {
117
+ console.error("[stripe-create-checkout-session]", err);
118
+ return Response.json(
119
+ { error: err instanceof Error ? err.message : "Internal error" },
120
+ { status: 500, headers: corsHeaders },
121
+ );
122
+ }
123
+ });
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Supabase Edge Function: Stripe — Create Customer Portal Session
3
+ *
4
+ * Creates a Stripe Customer Portal session (manage / cancel) for the
5
+ * authenticated user and returns its URL. The user is identified by the
6
+ * verified JWT and the Stripe customer is looked up server-side.
7
+ *
8
+ * Deployed WITH JWT verification.
9
+ *
10
+ * Secrets required:
11
+ * - STRIPE_SECRET_KEY
12
+ * - SUPABASE_URL / SUPABASE_ANON_KEY / SUPABASE_SERVICE_ROLE_KEY (auto-provided)
13
+ *
14
+ * Body: { returnUrl?: string }
15
+ */
16
+
17
+ import Stripe from "npm:stripe@18";
18
+ import { createClient } from "https://esm.sh/@supabase/supabase-js@2.47.10";
19
+
20
+ const corsHeaders = {
21
+ "Access-Control-Allow-Origin": "*",
22
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
23
+ "Access-Control-Allow-Headers": "Authorization, Content-Type",
24
+ };
25
+
26
+ const CUSTOMERS_TABLE = "stripe_customers";
27
+
28
+ async function getUid(req: Request, supabaseUrl: string, anonKey: string): Promise<string | null> {
29
+ const authHeader = req.headers.get("Authorization");
30
+ if (!authHeader?.startsWith("Bearer ")) return null;
31
+ const token = authHeader.replace("Bearer ", "");
32
+ const client = createClient(supabaseUrl, anonKey, {
33
+ global: { headers: { Authorization: authHeader } },
34
+ });
35
+ const { data: { user }, error } = await client.auth.getUser(token);
36
+ if (error || !user) return null;
37
+ return user.id;
38
+ }
39
+
40
+ Deno.serve(async (req: Request) => {
41
+ if (req.method === "OPTIONS") {
42
+ return new Response(null, { status: 204, headers: corsHeaders });
43
+ }
44
+ if (req.method !== "POST") {
45
+ return Response.json({ error: "Method not allowed" }, { status: 405, headers: corsHeaders });
46
+ }
47
+
48
+ const secretKey = Deno.env.get("STRIPE_SECRET_KEY");
49
+ const supabaseUrl = Deno.env.get("SUPABASE_URL");
50
+ const anonKey = Deno.env.get("SUPABASE_ANON_KEY");
51
+ const serviceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
52
+ if (!secretKey || !supabaseUrl || !anonKey || !serviceRoleKey) {
53
+ return Response.json({ error: "Server configuration error" }, { status: 500, headers: corsHeaders });
54
+ }
55
+
56
+ const uid = await getUid(req, supabaseUrl, anonKey);
57
+ if (!uid) {
58
+ return Response.json({ error: "Sign in required" }, { status: 401, headers: corsHeaders });
59
+ }
60
+
61
+ let returnUrl = "";
62
+ try {
63
+ const body = await req.json();
64
+ returnUrl = (body?.returnUrl as string | undefined) ?? "";
65
+ } catch {
66
+ // body is optional
67
+ }
68
+
69
+ try {
70
+ const admin = createClient(supabaseUrl, serviceRoleKey);
71
+ const { data } = await admin
72
+ .from(CUSTOMERS_TABLE)
73
+ .select("customer_id")
74
+ .eq("user_id", uid)
75
+ .maybeSingle();
76
+ const customerId = data?.customer_id as string | undefined;
77
+ if (!customerId) {
78
+ return Response.json(
79
+ { error: "No Stripe customer for user" },
80
+ { status: 400, headers: corsHeaders },
81
+ );
82
+ }
83
+
84
+ const stripe = new Stripe(secretKey);
85
+ const session = await stripe.billingPortal.sessions.create({
86
+ customer: customerId,
87
+ return_url: returnUrl,
88
+ });
89
+ return Response.json({ url: session.url }, { headers: corsHeaders });
90
+ } catch (err) {
91
+ console.error("[stripe-create-portal-session]", err);
92
+ return Response.json(
93
+ { error: err instanceof Error ? err.message : "Internal error" },
94
+ { status: 500, headers: corsHeaders },
95
+ );
96
+ }
97
+ });
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Supabase Edge Function: Stripe — List Prices
3
+ *
4
+ * Returns the active recurring Stripe prices mapped to the paywall offer
5
+ * contract used by the Flutter client (mirrors the Firebase `listPrices`
6
+ * callable). The Stripe secret key never leaves the server.
7
+ *
8
+ * Deployed WITH JWT verification (the platform checks the caller's token), so
9
+ * only an authenticated app user can reach it.
10
+ *
11
+ * Secrets required (set via `supabase secrets set`):
12
+ * - STRIPE_SECRET_KEY: sk_test_... / sk_live_...
13
+ * - STRIPE_PRODUCT_ID (optional): restrict prices to a single Stripe product.
14
+ */
15
+
16
+ import Stripe from "npm:stripe@18";
17
+
18
+ const corsHeaders = {
19
+ "Access-Control-Allow-Origin": "*",
20
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
21
+ "Access-Control-Allow-Headers": "Authorization, Content-Type",
22
+ };
23
+
24
+ function trialDaysFor(price: Stripe.Price, product: Stripe.Product): number | null {
25
+ const meta = price.metadata?.trial_days ?? product.metadata?.trial_days;
26
+ return meta ? Number(meta) : null;
27
+ }
28
+
29
+ Deno.serve(async (req: Request) => {
30
+ if (req.method === "OPTIONS") {
31
+ return new Response(null, { status: 204, headers: corsHeaders });
32
+ }
33
+
34
+ const secretKey = Deno.env.get("STRIPE_SECRET_KEY");
35
+ if (!secretKey) {
36
+ return Response.json(
37
+ { error: "STRIPE_SECRET_KEY not configured. Run: supabase secrets set STRIPE_SECRET_KEY=..." },
38
+ { status: 500, headers: corsHeaders },
39
+ );
40
+ }
41
+
42
+ const stripe = new Stripe(secretKey);
43
+ try {
44
+ const params: Stripe.PriceListParams = {
45
+ active: true,
46
+ type: "recurring",
47
+ expand: ["data.product"],
48
+ limit: 100,
49
+ };
50
+ const productFilter = Deno.env.get("STRIPE_PRODUCT_ID");
51
+ if (productFilter) params.product = productFilter;
52
+
53
+ const prices = await stripe.prices.list(params);
54
+ const offers = prices.data
55
+ .filter((p) => Boolean(p.recurring))
56
+ .map((p) => {
57
+ const product = p.product as Stripe.Product;
58
+ const features = (product.marketing_features ?? [])
59
+ .map((f) => f.name)
60
+ .filter((n): n is string => Boolean(n));
61
+ return {
62
+ priceId: p.id,
63
+ productId: typeof p.product === "string" ? p.product : product.id,
64
+ productName: product.name ?? "",
65
+ description: product.description ?? "",
66
+ unitAmount: p.unit_amount ?? 0,
67
+ currency: p.currency,
68
+ interval: p.recurring?.interval ?? "month",
69
+ intervalCount: p.recurring?.interval_count ?? 1,
70
+ trialDays: trialDaysFor(p, product),
71
+ features,
72
+ };
73
+ });
74
+
75
+ return Response.json(offers, { headers: corsHeaders });
76
+ } catch (err) {
77
+ console.error("[stripe-list-prices]", err);
78
+ return Response.json(
79
+ { error: err instanceof Error ? err.message : "Internal error" },
80
+ { status: 500, headers: corsHeaders },
81
+ );
82
+ }
83
+ });
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Supabase Edge Function: Stripe Webhook
3
+ *
4
+ * The ONLY writer of the `subscriptions` table for Stripe (web) subscriptions.
5
+ * Verifies the Stripe signature, then upserts the user's subscription with
6
+ * store='STRIPE'. The user id is read from the subscription metadata
7
+ * (supabaseUID), which the checkout function set server-side.
8
+ *
9
+ * Deployed WITHOUT JWT verification (Stripe calls it with a stripe-signature,
10
+ * not a Supabase JWT) — authenticity is enforced by the signature check.
11
+ *
12
+ * Secrets required (set via `supabase secrets set`):
13
+ * - STRIPE_SECRET_KEY
14
+ * - STRIPE_WEBHOOK_SECRET: whsec_... (from the Stripe webhook endpoint config)
15
+ * - SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY (auto-provided)
16
+ */
17
+
18
+ import Stripe from "npm:stripe@18";
19
+ import { createClient } from "https://esm.sh/@supabase/supabase-js@2.47.10";
20
+
21
+ const SubscriptionStatus = {
22
+ ACTIVE: "ACTIVE",
23
+ EXPIRED: "EXPIRED",
24
+ } as const;
25
+
26
+ function statusFromStripe(sub: Stripe.Subscription): string {
27
+ switch (sub.status) {
28
+ case "active":
29
+ case "trialing":
30
+ return SubscriptionStatus.ACTIVE;
31
+ default:
32
+ // canceled, unpaid, past_due, incomplete, incomplete_expired, paused
33
+ return SubscriptionStatus.EXPIRED;
34
+ }
35
+ }
36
+
37
+ // In Stripe API v18 the billing period lives on each subscription item; older
38
+ // versions exposed it on the subscription. Read whichever is present.
39
+ function periodEndMs(sub: Stripe.Subscription): number | null {
40
+ const item = sub.items?.data?.[0] as { current_period_end?: number } | undefined;
41
+ const fromItem = item?.current_period_end;
42
+ const fromSub = (sub as unknown as { current_period_end?: number }).current_period_end;
43
+ const sec = fromItem ?? fromSub;
44
+ return sec ? sec * 1000 : null;
45
+ }
46
+
47
+ Deno.serve(async (req: Request) => {
48
+ const signature = req.headers.get("stripe-signature");
49
+ if (!signature) return new Response("Missing signature", { status: 400 });
50
+
51
+ const secretKey = Deno.env.get("STRIPE_SECRET_KEY");
52
+ const webhookSecret = Deno.env.get("STRIPE_WEBHOOK_SECRET");
53
+ const supabaseUrl = Deno.env.get("SUPABASE_URL");
54
+ const serviceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
55
+ if (!secretKey || !webhookSecret || !supabaseUrl || !serviceRoleKey) {
56
+ console.error("[stripe-webhook] missing secrets");
57
+ return new Response("Server configuration error", { status: 500 });
58
+ }
59
+
60
+ const stripe = new Stripe(secretKey);
61
+ const bodyText = await req.text();
62
+
63
+ let event: Stripe.Event;
64
+ try {
65
+ event = await stripe.webhooks.constructEventAsync(bodyText, signature, webhookSecret);
66
+ } catch (e) {
67
+ console.log(`[stripe-webhook] signature verification failed: ${e}`);
68
+ return new Response("Invalid signature", { status: 400 });
69
+ }
70
+
71
+ const handled = [
72
+ "customer.subscription.created",
73
+ "customer.subscription.updated",
74
+ "customer.subscription.deleted",
75
+ ];
76
+ if (!handled.includes(event.type)) {
77
+ return new Response("ok");
78
+ }
79
+
80
+ const sub = event.data.object as Stripe.Subscription;
81
+ const uid = sub.metadata?.supabaseUID;
82
+ if (!uid) {
83
+ console.log("[stripe-webhook] subscription without supabaseUID metadata, skipping");
84
+ return new Response("ok");
85
+ }
86
+
87
+ const supabase = createClient(supabaseUrl, serviceRoleKey);
88
+
89
+ try {
90
+ // The subscriptions row references public.users(id). Skip unknown users.
91
+ const { data: userRows } = await supabase
92
+ .from("users")
93
+ .select("id")
94
+ .eq("id", uid)
95
+ .limit(1);
96
+ if (!userRows?.length) {
97
+ console.log("[stripe-webhook] user not found:", uid);
98
+ return new Response("ok");
99
+ }
100
+
101
+ const item = sub.items?.data?.[0];
102
+ const priceId = item?.price?.id ?? "";
103
+ const ms = periodEndMs(sub);
104
+ const now = new Date().toISOString();
105
+ const payload = {
106
+ status: statusFromStripe(sub),
107
+ last_update_date: now,
108
+ period_end_date: ms ? new Date(ms).toISOString() : null,
109
+ sku_id: priceId,
110
+ offer_id: priceId,
111
+ store: "STRIPE",
112
+ };
113
+
114
+ const { data: existing } = await supabase
115
+ .from("subscriptions")
116
+ .select("user_id")
117
+ .eq("user_id", uid)
118
+ .maybeSingle();
119
+
120
+ if (existing) {
121
+ const { error: updateErr } = await supabase
122
+ .from("subscriptions")
123
+ .update(payload)
124
+ .eq("user_id", uid);
125
+ if (updateErr) console.error("[stripe-webhook] update error:", updateErr);
126
+ } else {
127
+ const { error: insertErr } = await supabase
128
+ .from("subscriptions")
129
+ .insert({ user_id: uid, creation_date: now, ...payload });
130
+ if (insertErr) console.error("[stripe-webhook] insert error:", insertErr);
131
+ }
132
+
133
+ return new Response("ok");
134
+ } catch (err) {
135
+ console.error("[stripe-webhook] error:", err);
136
+ return new Response(err instanceof Error ? err.message : "Internal error", { status: 500 });
137
+ }
138
+ });
@@ -1,23 +1,23 @@
1
1
  /**
2
2
  * Supabase project generator.
3
3
  *
4
- * Wrapper fino sobre generateProject().
5
- * A lógica de geração comum (cópia, pub get, slang, build_runner, flutterfire)
6
- * vive em cli/lib/scaffold/generate.js.
4
+ * Thin wrapper over generateProject().
5
+ * The common generation logic (copy, pub get, slang, build_runner, flutterfire)
6
+ * lives in cli/lib/scaffold/generate.js.
7
7
  *
8
- * Hook específico (applyBackendSetup):
9
- * 1. Aplica supabase/patch/ sobre o template Firebase copiado
10
- * 2. Substitui pubspec.yaml pelo template Supabase (pubspec.yaml.tpl)
11
- * 3. Remove artefatos Firebase exclusivos (ex: GoogleService-Info.plist paths)
12
- * 4. Escreve overrides de environment (supabaseUrl, supabaseAnonKey)
13
- * 5. Copia config.toml, migrations e edge-functions do backend Supabase
8
+ * Specific hook (applyBackendSetup):
9
+ * 1. Applies supabase/patch/ over the copied Firebase template
10
+ * 2. Replaces pubspec.yaml with the Supabase template (pubspec.yaml.tpl)
11
+ * 3. Removes Firebase-only artifacts (e.g. GoogleService-Info.plist paths)
12
+ * 4. Writes environment overrides (supabaseUrl, supabaseAnonKey)
13
+ * 5. Copies config.toml, migrations and edge-functions for the Supabase backend
14
14
  */
15
15
 
16
16
  const path = require('node:path');
17
17
  const fs = require('fs-extra');
18
18
  const { applyPatch, copyWithTokens } = require('../../engine');
19
19
  const { generateProject } = require('../../generate');
20
- const { writeEnvironnementsOverrides } = require('../../shared/generator-utils');
20
+ const { writeEnvironmentsOverrides } = require('../../shared/generator-utils');
21
21
  const { removeBackendSpecificArtifacts } = require('../../shared/backend-config');
22
22
 
23
23
  const SUPABASE_PATCH_DIR = path.join(__dirname, 'patch');
@@ -40,7 +40,7 @@ async function generateSupabaseProject(targetDir, options) {
40
40
  // 1. Patch Supabase (auth, storage, notifications, etc.)
41
41
  const { filesApplied } = await applyPatch(SUPABASE_PATCH_DIR, dir, tokens, pathReplacements);
42
42
 
43
- // 1b. README do backend (excluído do applyPatch) — copiar manualmente
43
+ // 1b. Backend README (excluded from applyPatch) — copy it manually
44
44
  const language = opts.language ?? 'pt';
45
45
  const readmeLang = ['en', 'pt', 'es'].includes(language) ? language : 'en';
46
46
  const candidates = [
@@ -54,7 +54,7 @@ async function generateSupabaseProject(targetDir, options) {
54
54
  }
55
55
  }
56
56
 
57
- // 2. Substituir pubspec.yaml pelos deps do Supabase
57
+ // 2. Replace pubspec.yaml with the Supabase deps
58
58
  const pubspecContent = await fs.readFile(SUPABASE_PUBSPEC, 'utf8');
59
59
  let pubspecReplaced = pubspecContent;
60
60
  for (const [from, to] of Object.entries(tokens)) {
@@ -62,20 +62,20 @@ async function generateSupabaseProject(targetDir, options) {
62
62
  }
63
63
  await fs.outputFile(path.join(dir, 'pubspec.yaml'), pubspecReplaced, 'utf8');
64
64
 
65
- // 3. Remover artefatos exclusivos do Firebase
65
+ // 3. Remove Firebase-only artifacts
66
66
  await removeBackendSpecificArtifacts(dir, 'supabase', opts.modules ?? []);
67
67
 
68
- // 4. Escrever overrides de environment com as credenciais Supabase
69
- await writeEnvironnementsOverrides(dir, 'supabase', tokens, {
68
+ // 4. Write environment overrides with the Supabase credentials
69
+ await writeEnvironmentsOverrides(dir, 'supabase', tokens, {
70
70
  supabaseUrl: opts.supabaseUrl,
71
71
  supabaseAnonKey: opts.supabaseAnonKey,
72
72
  });
73
73
 
74
- // 5. Copiar arquivos da infraestrutura Supabase
74
+ // 5. Copy the Supabase infrastructure files
75
75
  const supabaseDir = path.join(dir, 'supabase');
76
76
  await fs.ensureDir(supabaseDir);
77
77
 
78
- // Tokens extras para substituição nas migrations SQL
78
+ // Extra tokens for substitution in the SQL migrations
79
79
  const supabaseTokens = {
80
80
  ...tokens,
81
81
  'supabaseplaceholder.supabase.co': (opts.supabaseUrl || '').replace(/^https?:\/\//, ''),
@@ -0,0 +1,50 @@
1
+ -- AI chat history (one user has many conversations, each with many messages).
2
+
3
+ -- Conversations. The last message is denormalized onto the row so the list can
4
+ -- be rendered with a single query (no need to read each conversation's messages
5
+ -- just to show a preview + timestamp).
6
+ CREATE TABLE IF NOT EXISTS public.ai_conversations (
7
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
8
+ user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
9
+ last_message_role TEXT CHECK (last_message_role IN ('user', 'assistant')),
10
+ last_message_content TEXT,
11
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
12
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
13
+ );
14
+
15
+ -- Row Level Security: users can only access their own conversations.
16
+ ALTER TABLE public.ai_conversations ENABLE ROW LEVEL SECURITY;
17
+
18
+ CREATE POLICY "Users can manage their own ai conversations"
19
+ ON public.ai_conversations
20
+ FOR ALL
21
+ USING (auth.uid() = user_id)
22
+ WITH CHECK (auth.uid() = user_id);
23
+
24
+ -- Index for listing a user's conversations, most recently updated first.
25
+ CREATE INDEX IF NOT EXISTS ai_conversations_user_updated_at_idx
26
+ ON public.ai_conversations (user_id, updated_at DESC);
27
+
28
+ -- Messages. Each row stores one message (user or assistant) inside a
29
+ -- conversation. user_id is kept for a simple RLS policy.
30
+ CREATE TABLE IF NOT EXISTS public.ai_messages (
31
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
32
+ conversation_id UUID NOT NULL REFERENCES public.ai_conversations(id) ON DELETE CASCADE,
33
+ user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
34
+ role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
35
+ content TEXT NOT NULL,
36
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
37
+ );
38
+
39
+ -- Row Level Security: users can only access their own messages.
40
+ ALTER TABLE public.ai_messages ENABLE ROW LEVEL SECURITY;
41
+
42
+ CREATE POLICY "Users can manage their own ai messages"
43
+ ON public.ai_messages
44
+ FOR ALL
45
+ USING (auth.uid() = user_id)
46
+ WITH CHECK (auth.uid() = user_id);
47
+
48
+ -- Index for fast ordered queries within a conversation.
49
+ CREATE INDEX IF NOT EXISTS ai_messages_conversation_created_at_idx
50
+ ON public.ai_messages (conversation_id, created_at ASC);
@@ -0,0 +1,36 @@
1
+ -- Stripe (web) subscriptions support + subscription write hardening.
2
+ --
3
+ -- 1. `stripe_customers` maps a Supabase auth user to its single Stripe customer
4
+ -- id, so the checkout/portal Edge Functions can find-or-create one customer
5
+ -- per user.
6
+ -- 2. Hardens `subscriptions` so ONLY the server (service role, used by the
7
+ -- Stripe and RevenueCat webhooks) can write it. The client can only read its
8
+ -- own row. This prevents a client from forging a premium subscription.
9
+ -- Previously a `FOR ALL` policy let a client insert/update its own row.
10
+
11
+ -- Map auth user -> Stripe customer id. Written only by the server (service role,
12
+ -- which bypasses RLS). Clients may read their own mapping but never write it.
13
+ CREATE TABLE IF NOT EXISTS public.stripe_customers (
14
+ user_id UUID PRIMARY KEY REFERENCES public.users(id) ON DELETE CASCADE,
15
+ customer_id TEXT NOT NULL,
16
+ created_at TIMESTAMPTZ DEFAULT now()
17
+ );
18
+
19
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_stripe_customers_customer_id
20
+ ON public.stripe_customers(customer_id);
21
+
22
+ ALTER TABLE public.stripe_customers ENABLE ROW LEVEL SECURITY;
23
+
24
+ -- Read-only for the owning user; no insert/update/delete policy => clients
25
+ -- cannot write (only the service role, which bypasses RLS, can).
26
+ DROP POLICY IF EXISTS "Stripe customers read own" ON public.stripe_customers;
27
+ CREATE POLICY "Stripe customers read own" ON public.stripe_customers
28
+ FOR SELECT USING (auth.uid() = user_id);
29
+
30
+ -- Harden subscriptions: clients read their own row only; all writes come from
31
+ -- the webhook via the service role. Replaces the previous FOR ALL policy that
32
+ -- allowed a client to insert/update (forge) its own subscription.
33
+ DROP POLICY IF EXISTS "Subscriptions own" ON public.subscriptions;
34
+ DROP POLICY IF EXISTS "Subscriptions read own" ON public.subscriptions;
35
+ CREATE POLICY "Subscriptions read own" ON public.subscriptions
36
+ FOR SELECT USING (auth.uid() = user_id);
@@ -0,0 +1,62 @@
1
+ -- Admin role support
2
+ --
3
+ -- Adds a server-only `role` column to public.users. null/absent = normal user,
4
+ -- 'admin' = administrator (unlocks the admin console's server data). The column
5
+ -- can be written ONLY by the service role (Edge Functions) or the dashboard /
6
+ -- SQL editor — never by the app client, so a user can never promote themselves
7
+ -- to admin. This mirrors the Firebase rule that blocks `role` writes.
8
+
9
+ ALTER TABLE public.users ADD COLUMN IF NOT EXISTS role TEXT;
10
+
11
+ -- Guard trigger: the app client (anon / authenticated) can update its own row
12
+ -- but never the `role` column. Privileged roles pass straight through:
13
+ -- - service_role : used by Edge Functions and the dashboard
14
+ -- - postgres / supabase_admin : migrations and the SQL editor
15
+ -- so an admin can still assign roles by hand.
16
+ CREATE OR REPLACE FUNCTION public.enforce_role_immutable()
17
+ RETURNS TRIGGER
18
+ LANGUAGE plpgsql
19
+ AS $$
20
+ BEGIN
21
+ IF current_user IN ('service_role', 'postgres', 'supabase_admin') THEN
22
+ RETURN NEW; -- privileged caller: allow the change
23
+ END IF;
24
+
25
+ IF TG_OP = 'INSERT' THEN
26
+ NEW.role := NULL; -- clients never set a role on signup
27
+ ELSIF NEW.role IS DISTINCT FROM OLD.role THEN
28
+ RAISE EXCEPTION 'The role field can only be changed by the server';
29
+ END IF;
30
+
31
+ RETURN NEW;
32
+ END;
33
+ $$;
34
+
35
+ DROP TRIGGER IF EXISTS users_enforce_role_immutable ON public.users;
36
+ CREATE TRIGGER users_enforce_role_immutable
37
+ BEFORE INSERT OR UPDATE ON public.users
38
+ FOR EACH ROW EXECUTE FUNCTION public.enforce_role_immutable();
39
+
40
+ -- Admin helper: true when the current caller is an administrator. SECURITY
41
+ -- DEFINER so it reads the role regardless of the caller's own RLS (and avoids
42
+ -- policy recursion). Used by admin-only policies below.
43
+ CREATE OR REPLACE FUNCTION public.is_admin()
44
+ RETURNS BOOLEAN
45
+ LANGUAGE sql
46
+ SECURITY DEFINER
47
+ SET search_path = public
48
+ STABLE
49
+ AS $$
50
+ SELECT EXISTS (
51
+ SELECT 1 FROM public.users
52
+ WHERE id = auth.uid() AND role = 'admin'
53
+ );
54
+ $$;
55
+
56
+ -- Feature requests moderation (admin console "Requests" tab): only admins can
57
+ -- toggle visibility (active) or edit the localized texts. Voting still flows
58
+ -- through the feature_votes triggers, so this admin policy is the ONLY way a
59
+ -- client can UPDATE feature_requests directly.
60
+ DROP POLICY IF EXISTS "Feature requests admin update" ON public.feature_requests;
61
+ CREATE POLICY "Feature requests admin update" ON public.feature_requests
62
+ FOR UPDATE USING (public.is_admin()) WITH CHECK (public.is_admin());