kasy-cli 1.21.9 → 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 (267) 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 +80 -37
  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/generator.js +5 -5
  31. package/lib/scaffold/backends/firebase/tokens.js +4 -4
  32. package/lib/scaffold/backends/supabase/deploy.js +63 -11
  33. package/lib/scaffold/backends/supabase/edge-functions/admin-list-users/index.ts +149 -0
  34. package/lib/scaffold/backends/supabase/edge-functions/{llm-chat → ai-chat}/index.ts +19 -19
  35. package/lib/scaffold/backends/supabase/edge-functions/revenuecat-webhook/index.ts +2 -0
  36. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-checkout-session/index.ts +123 -0
  37. package/lib/scaffold/backends/supabase/edge-functions/stripe-create-portal-session/index.ts +97 -0
  38. package/lib/scaffold/backends/supabase/edge-functions/stripe-list-prices/index.ts +83 -0
  39. package/lib/scaffold/backends/supabase/edge-functions/stripe-webhook/index.ts +138 -0
  40. package/lib/scaffold/backends/supabase/generator.js +17 -17
  41. package/lib/scaffold/backends/supabase/migrations/20240101000009_ai_messages.sql +50 -0
  42. package/lib/scaffold/backends/supabase/migrations/20240101000012_stripe_customers.sql +36 -0
  43. package/lib/scaffold/backends/supabase/migrations/20240101000013_admin_role.sql +62 -0
  44. package/lib/scaffold/backends/supabase/patch/lib/core/data/entities/user_entity.dart +4 -0
  45. package/lib/scaffold/backends/supabase/patch/lib/{environnements.dart → environments.dart} +3 -13
  46. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_api.dart +95 -0
  47. package/lib/scaffold/backends/supabase/patch/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +52 -0
  48. 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
  49. package/lib/scaffold/backends/supabase/patch/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +63 -35
  50. package/lib/scaffold/backends/supabase/patch/lib/features/authentication/api/authentication_api.dart +1 -1
  51. package/lib/scaffold/backends/supabase/patch/lib/features/feedbacks/api/feature_request_api.dart +46 -0
  52. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/models/user_info.dart +16 -16
  53. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  54. package/lib/scaffold/backends/supabase/patch/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  55. package/lib/scaffold/backends/supabase/patch/lib/features/settings/ui/components/admin/admin_users_api.dart +93 -0
  56. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +13 -0
  57. package/lib/scaffold/backends/supabase/patch/lib/features/subscriptions/api/stripe_backend_api.dart +52 -0
  58. package/lib/scaffold/backends/supabase/patch/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  59. package/lib/scaffold/backends/supabase/pubspec.yaml.tpl +4 -5
  60. package/lib/scaffold/backends/supabase/tokens.js +3 -3
  61. package/lib/scaffold/catalog.js +9 -11
  62. package/lib/scaffold/generate.js +45 -31
  63. package/lib/scaffold/shared/generator-utils.js +188 -81
  64. package/lib/scaffold/shared/sort-imports.js +191 -0
  65. package/lib/scaffold/shared/template-strings.js +3 -3
  66. package/lib/utils/checks.js +2 -2
  67. package/lib/utils/i18n/messages-en.js +50 -35
  68. package/lib/utils/i18n/messages-es.js +50 -35
  69. package/lib/utils/i18n/messages-pt.js +52 -37
  70. package/lib/utils/updates.js +15 -15
  71. package/package.json +1 -1
  72. package/templates/firebase/.env.example +2 -2
  73. package/templates/firebase/android/app/src/main/res/drawable/background.png +0 -0
  74. package/templates/firebase/android/app/src/main/res/drawable-night/background.png +0 -0
  75. package/templates/firebase/android/app/src/main/res/drawable-night-v21/background.png +0 -0
  76. package/templates/firebase/android/app/src/main/res/drawable-v21/background.png +0 -0
  77. package/templates/firebase/android/app/src/main/res/values-night-v31/styles.xml +1 -1
  78. package/templates/firebase/android/app/src/main/res/values-v31/styles.xml +1 -1
  79. package/templates/firebase/assets/images/logo_wordmark_dark.png +0 -0
  80. package/templates/firebase/assets/images/logo_wordmark_light.png +0 -0
  81. package/templates/firebase/docs/revenuecat-setup.es.md +1 -1
  82. package/templates/firebase/docs/revenuecat-setup.pt.md +1 -1
  83. package/templates/firebase/firestore.rules +24 -5
  84. package/templates/firebase/functions/package-lock.json +22 -1
  85. package/templates/firebase/functions/package.json +2 -1
  86. package/templates/firebase/functions/src/admin/functions.ts +113 -0
  87. package/templates/firebase/functions/src/{llm_chat → ai_chat}/index.ts +16 -16
  88. package/templates/firebase/functions/src/index.ts +8 -2
  89. package/templates/firebase/functions/src/notifications/device_triggers.ts +2 -2
  90. package/templates/firebase/functions/src/notifications/triggers.ts +3 -3
  91. package/templates/firebase/functions/src/subscriptions/models/subscription_status.ts +2 -0
  92. package/templates/firebase/functions/src/subscriptions/stripe_functions.ts +222 -0
  93. package/templates/firebase/functions/src/subscriptions/subscriptions_functions.ts +2 -2
  94. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png +0 -0
  95. package/templates/firebase/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png +0 -0
  96. package/templates/firebase/lib/components/components.dart +4 -1
  97. package/templates/firebase/lib/components/kasy_app_bar.dart +22 -7
  98. package/templates/firebase/lib/components/kasy_avatar.dart +7 -6
  99. package/templates/firebase/lib/components/kasy_button.dart +23 -99
  100. package/templates/firebase/lib/components/kasy_dialog.dart +11 -11
  101. package/templates/firebase/lib/components/kasy_image_viewer.dart +84 -0
  102. package/templates/firebase/lib/components/{kasy_sidebar_pro.dart → kasy_sidebar.dart} +692 -425
  103. package/templates/firebase/lib/components/kasy_status_tag.dart +91 -0
  104. package/templates/firebase/lib/components/kasy_text_area.dart +1 -3
  105. package/templates/firebase/lib/components/kasy_text_field.dart +5 -6
  106. package/templates/firebase/lib/components/kasy_text_field_otp.dart +1 -3
  107. package/templates/firebase/lib/components/kasy_toast.dart +2 -2
  108. package/templates/firebase/lib/components/kasy_web_header.dart +209 -0
  109. package/templates/firebase/lib/core/ads/ads_provider.dart +1 -1
  110. package/templates/firebase/lib/core/bottom_menu/bottom_menu.dart +57 -4
  111. package/templates/firebase/lib/core/bottom_menu/bottom_router.dart +19 -91
  112. package/templates/firebase/lib/core/bottom_menu/kasy_bottom_bar_factory.dart +196 -96
  113. package/templates/firebase/lib/core/bottom_menu/web_content_wrapper.dart +41 -0
  114. package/templates/firebase/lib/core/config/app_env.dart +5 -11
  115. package/templates/firebase/lib/core/config/features.dart +5 -4
  116. package/templates/firebase/lib/core/data/api/analytics_api.dart +1 -1
  117. package/templates/firebase/lib/core/data/api/http_client.dart +1 -1
  118. package/templates/firebase/lib/core/data/entities/user_entity.dart +3 -0
  119. package/templates/firebase/lib/core/data/models/entitlement.dart +35 -0
  120. package/templates/firebase/lib/core/data/models/subscription.dart +13 -186
  121. package/templates/firebase/lib/core/data/models/user.dart +11 -0
  122. package/templates/firebase/lib/core/data/repositories/user_repository.dart +1 -1
  123. package/templates/firebase/lib/core/home_widgets/home_widget_background_task.dart +1 -1
  124. package/templates/firebase/lib/core/home_widgets/home_widget_mywidget_service.dart +1 -1
  125. package/templates/firebase/lib/core/icons/kasy_icons.dart +31 -1
  126. package/templates/firebase/lib/core/rating/api/rating_api.dart +2 -2
  127. package/templates/firebase/lib/core/states/logout_action.dart +25 -0
  128. package/templates/firebase/lib/core/states/user_state_notifier.dart +3 -3
  129. package/templates/firebase/lib/core/theme/colors.dart +488 -188
  130. package/templates/firebase/lib/core/theme/radius.dart +22 -11
  131. package/templates/firebase/lib/core/theme/shadows.dart +66 -0
  132. package/templates/firebase/lib/core/theme/texts.dart +75 -41
  133. package/templates/firebase/lib/core/theme/universal_theme.dart +9 -4
  134. package/templates/firebase/lib/core/web_device_preview/web_device_preview.dart +5 -4
  135. package/templates/firebase/lib/core/web_viewport_scale.dart +52 -0
  136. package/templates/firebase/lib/core/widgets/kasy_brand_badge.dart +118 -0
  137. package/templates/firebase/lib/core/widgets/kasy_brand_logo.dart +40 -0
  138. package/templates/firebase/lib/core/widgets/kasy_focus_ring.dart +125 -0
  139. package/templates/firebase/lib/core/widgets/kasy_hover.dart +33 -13
  140. package/templates/firebase/lib/core/widgets/kasy_pressable_depth.dart +18 -13
  141. package/templates/firebase/lib/{environnements.dart → environments.dart} +3 -14
  142. package/templates/firebase/lib/features/ai_chat/ai_chat_page.dart +159 -0
  143. package/templates/firebase/lib/features/ai_chat/api/ai_chat_api.dart +107 -0
  144. package/templates/firebase/lib/features/ai_chat/api/ai_chat_conversation_entity.dart +64 -0
  145. package/templates/firebase/lib/features/{llm_chat/api/llm_chat_message_entity.dart → ai_chat/api/ai_chat_message_entity.dart} +6 -6
  146. package/templates/firebase/lib/features/{llm_chat/providers/llm_chat_notifier.dart → ai_chat/providers/ai_chat_notifier.dart} +73 -38
  147. package/templates/firebase/lib/features/ai_chat/providers/ai_conversations_notifier.dart +103 -0
  148. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_avatars.dart → ai_chat/ui/widgets/ai_chat_avatars.dart} +13 -13
  149. package/templates/firebase/lib/features/{llm_chat/ui/widgets/llm_chat_composer.dart → ai_chat/ui/widgets/ai_chat_composer.dart} +10 -10
  150. package/templates/firebase/lib/features/{llm_chat/llm_chat_page.dart → ai_chat/ui/widgets/ai_chat_conversation_view.dart} +80 -67
  151. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_list.dart +285 -0
  152. package/templates/firebase/lib/features/ai_chat/ui/widgets/ai_conversation_tile.dart +163 -0
  153. package/templates/firebase/lib/features/authentication/api/authentication_api.dart +52 -13
  154. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher.dart +12 -0
  155. package/templates/firebase/lib/features/authentication/api/popup_dismiss_watcher_web.dart +35 -0
  156. package/templates/firebase/lib/features/authentication/ui/recover_password_page.dart +108 -68
  157. package/templates/firebase/lib/features/authentication/ui/signin_page.dart +38 -51
  158. package/templates/firebase/lib/features/authentication/ui/signup_page.dart +38 -51
  159. package/templates/firebase/lib/features/authentication/ui/widgets/auth_card_scaffold.dart +118 -0
  160. package/templates/firebase/lib/features/authentication/ui/widgets/recover_password_result.dart +61 -44
  161. package/templates/firebase/lib/features/feedbacks/api/feature_request_api.dart +32 -0
  162. package/templates/firebase/lib/features/feedbacks/models/feedback_state.dart +5 -5
  163. package/templates/firebase/lib/features/feedbacks/providers/feedback_page_notifier.dart +2 -2
  164. package/templates/firebase/lib/features/home/design_system_page.dart +808 -170
  165. package/templates/firebase/lib/features/home/home_components_page.dart +6 -3
  166. package/templates/firebase/lib/features/home/home_components_preview_page.dart +6 -6
  167. package/templates/firebase/lib/features/home/home_components_preview_registry.dart +325 -186
  168. package/templates/firebase/lib/features/home/home_feed.dart +289 -0
  169. package/templates/firebase/lib/features/home/home_image_grid.dart +355 -0
  170. package/templates/firebase/lib/features/home/home_page.dart +11 -250
  171. package/templates/firebase/lib/features/{local_reminder → local_reminders}/providers/reminder_notifier.dart +1 -1
  172. package/templates/firebase/lib/features/{local_reminder → local_reminders}/ui/reminder_page.dart +2 -2
  173. package/templates/firebase/lib/features/notifications/shared/att_permission.dart +1 -1
  174. package/templates/firebase/lib/features/notifications/ui/request_notification_permission.dart +25 -61
  175. package/templates/firebase/lib/features/notifications/ui/widgets/permission_request_view.dart +117 -0
  176. package/templates/firebase/lib/features/onboarding/models/user_info.dart +16 -16
  177. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_att_setup.dart +4 -3
  178. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_features.dart +7 -9
  179. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_loader.dart +71 -48
  180. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_notifications_setup.dart +3 -2
  181. package/templates/firebase/lib/features/onboarding/ui/components/onboarding_questions.dart +5 -5
  182. package/templates/firebase/lib/features/onboarding/ui/onboarding_page.dart +4 -4
  183. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_background.dart +4 -2
  184. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_feature.dart +39 -121
  185. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_illustration_scaffold.dart +105 -70
  186. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_module_mockups.dart +639 -0
  187. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_progress.dart +62 -50
  188. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_radio_question.dart +38 -28
  189. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_step_header.dart +75 -0
  190. package/templates/firebase/lib/features/onboarding/ui/widgets/onboarding_sticky_footer.dart +16 -5
  191. package/templates/firebase/lib/features/onboarding/ui/widgets/selectable_row_tile.dart +26 -22
  192. package/templates/firebase/lib/features/settings/settings_page.dart +601 -90
  193. package/templates/firebase/lib/features/settings/ui/components/admin/admin_page.dart +1193 -0
  194. package/templates/firebase/lib/features/settings/ui/components/admin/admin_paywalls.dart +1 -1
  195. package/templates/firebase/lib/features/settings/ui/components/admin/admin_routes.dart +2 -3
  196. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_api.dart +86 -0
  197. package/templates/firebase/lib/features/settings/ui/components/admin/admin_users_tab.dart +1215 -0
  198. package/templates/firebase/lib/features/settings/ui/components/admin/send_push_notification_page.dart +236 -0
  199. package/templates/firebase/lib/features/settings/ui/components/avatar_component.dart +3 -3
  200. package/templates/firebase/lib/features/settings/ui/widgets/kasy_user_avatar.dart +77 -0
  201. package/templates/firebase/lib/features/settings/ui/widgets/settings_tile.dart +1 -0
  202. package/templates/firebase/lib/features/{subscription → subscriptions}/api/entities/subscription_entity.dart +17 -0
  203. package/templates/firebase/lib/features/{subscription → subscriptions}/api/inapp_subscription_api.dart +67 -46
  204. package/templates/firebase/lib/features/subscriptions/api/revenuecat_product.dart +189 -0
  205. package/templates/firebase/lib/features/subscriptions/api/stripe_backend_api.dart +55 -0
  206. package/templates/firebase/lib/features/subscriptions/api/stripe_payment_api.dart +82 -0
  207. package/templates/firebase/lib/features/subscriptions/api/stripe_product.dart +178 -0
  208. package/templates/firebase/lib/features/{subscription → subscriptions}/api/subscription_api.dart +1 -1
  209. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api.dart +53 -0
  210. package/templates/firebase/lib/features/subscriptions/api/subscription_payment_api_provider.dart +21 -0
  211. package/templates/firebase/lib/features/{subscription → subscriptions}/providers/premium_page_provider.dart +9 -6
  212. package/templates/firebase/lib/features/{subscription → subscriptions}/repositories/subscription_repository.dart +26 -30
  213. package/{lib/scaffold/backends/supabase/patch/lib/features/subscription → templates/firebase/lib/features/subscriptions}/shared/maybeshow_premium.dart +0 -2
  214. package/templates/firebase/lib/features/subscriptions/shared/subscription_management.dart +45 -0
  215. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/active_premium_content.dart +28 -4
  216. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_minimal.dart +7 -7
  217. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_row.dart +8 -8
  218. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/paywall_with_switch.dart +7 -7
  219. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_content.dart +7 -7
  220. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/component/premium_page_factory.dart +5 -5
  221. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/premium_page.dart +5 -5
  222. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/comparison_table.dart +1 -1
  223. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/paywall_empty_state.dart +4 -4
  224. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_bottom_menu.dart +3 -4
  225. package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_col.dart +1 -1
  226. package/templates/firebase/lib/i18n/en.i18n.json +171 -46
  227. package/templates/firebase/lib/i18n/es.i18n.json +175 -50
  228. package/templates/firebase/lib/i18n/pt.i18n.json +166 -41
  229. package/templates/firebase/lib/main.dart +6 -3
  230. package/templates/firebase/lib/router.dart +15 -23
  231. package/templates/firebase/pubspec.yaml +4 -5
  232. package/templates/firebase/test/core/data/repositories/user_repository_test.dart +3 -3
  233. package/templates/firebase/test/core/states/user_state_notifier_test.dart +4 -4
  234. package/templates/firebase/test/core/widgets/focus_ring_shape_test.dart +55 -0
  235. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_inapp_subscription_api.dart +11 -4
  236. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_revenuecat_product.dart +1 -0
  237. package/templates/firebase/test/features/{subscription → subscriptions}/api/fake_subscription_api.dart +2 -2
  238. package/templates/firebase/test/features/{subscription → subscriptions}/subscription_page_test.dart +4 -4
  239. package/templates/firebase/test/test_utils.dart +6 -6
  240. package/templates/firebase/web/index.html +5 -2
  241. package/lib/scaffold/backends/api/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -58
  242. package/lib/scaffold/backends/api/patch/lib/features/subscription/shared/maybeshow_premium.dart +0 -86
  243. package/lib/scaffold/backends/supabase/migrations/20240101000009_llm_messages.sql +0 -22
  244. package/lib/scaffold/backends/supabase/patch/lib/features/llm_chat/api/llm_chat_api.dart +0 -47
  245. package/templates/firebase/assets/images/onboarding/authentication-login-template.jpg +0 -0
  246. package/templates/firebase/assets/images/onboarding/img2.jpg +0 -0
  247. package/templates/firebase/assets/images/onboarding/img3.jpg +0 -0
  248. package/templates/firebase/assets/images/onboarding/notifications.png +0 -0
  249. package/templates/firebase/assets/images/onboarding/purchase.png +0 -0
  250. package/templates/firebase/lib/core/sidebar/kasy_sidebar.dart +0 -2021
  251. package/templates/firebase/lib/features/authentication/ui/widgets/auth_brand.dart +0 -35
  252. package/templates/firebase/lib/features/home/home_features_page.dart +0 -207
  253. package/templates/firebase/lib/features/llm_chat/api/llm_chat_api.dart +0 -50
  254. package/templates/firebase/lib/features/settings/ui/components/admin/admin_bottom_sheet.dart +0 -316
  255. package/templates/firebase/lib/features/subscription/shared/maybeshow_premium.dart +0 -85
  256. /package/templates/firebase/lib/features/{local_reminder → local_reminders}/repositories/reminder_preferences.dart +0 -0
  257. /package/templates/firebase/lib/features/{subscription → subscriptions}/providers/models/premium_state.dart +0 -0
  258. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/feature_line.dart +0 -0
  259. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background.dart +0 -0
  260. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_background_gradient.dart +0 -0
  261. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_banner.dart +0 -0
  262. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_card.dart +0 -0
  263. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_close_button.dart +0 -0
  264. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/premium_feature.dart +0 -0
  265. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/selectable_row.dart +0 -0
  266. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/trial_switcher.dart +0 -0
  267. /package/templates/firebase/lib/features/{subscription → subscriptions}/ui/widgets/unsubscribe_feedback_popup.dart +0 -0
@@ -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());
@@ -16,6 +16,10 @@ sealed class UserEntity with _$UserEntity {
16
16
  @JsonKey(name: 'avatar_url') String? avatarPath,
17
17
  bool? onboarded,
18
18
  String? locale,
19
+ // Access-control role. null/absent = normal user; "admin" unlocks the admin
20
+ // console's server data. Server-only: the `users` guard trigger blocks the
21
+ // client from writing it (set it from the Supabase dashboard / SQL editor).
22
+ String? role,
19
23
  }) = UserEntityData;
20
24
 
21
25
  const UserEntity._();
@@ -6,7 +6,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
6
6
  import 'package:kasy_kit/core/config/app_env.dart';
7
7
  import 'package:kasy_kit/core/states/user_state_notifier.dart';
8
8
 
9
- part 'environnements.freezed.dart';
9
+ part 'environments.freezed.dart';
10
10
 
11
11
  // URLs for terms of service and privacy policy
12
12
  const kTermsUrl = '';
@@ -38,10 +38,6 @@ sealed class Environment with _$Environment {
38
38
  /// (only if you want to use in-app purchases with RevenueCat)
39
39
  String? revenueCatIOSApiKey,
40
40
 
41
- /// RevenueCat Web Billing API key (rcb_xxx or rcb_sb_xxx)
42
- /// (only if you want to use subscriptions on web)
43
- String? revenueCatWebApiKey,
44
-
45
41
  /// this is used to open the app store page of your app for reviews
46
42
  String? appStoreId,
47
43
 
@@ -74,10 +70,6 @@ sealed class Environment with _$Environment {
74
70
  /// (only if you want to use in-app purchases with RevenueCat)
75
71
  String? revenueCatIOSApiKey,
76
72
 
77
- /// RevenueCat Web Billing API key (rcb_xxx or rcb_sb_xxx)
78
- /// (only if you want to use subscriptions on web)
79
- String? revenueCatWebApiKey,
80
-
81
73
  /// only if you want to use ads
82
74
  String? androidInterstitialAdUnitId,
83
75
 
@@ -114,7 +106,6 @@ sealed class Environment with _$Environment {
114
106
  appStoreId: '',
115
107
  revenueCatAndroidApiKey: AppEnv.rcAndroidApiKey,
116
108
  revenueCatIOSApiKey: AppEnv.rcIosApiKey,
117
- revenueCatWebApiKey: AppEnv.rcWebApiKey,
118
109
  mixpanelToken: AppEnv.mixpanelToken,
119
110
  authenticationMode: AuthenticationMode.anonymous,
120
111
  );
@@ -125,7 +116,6 @@ sealed class Environment with _$Environment {
125
116
  appStoreId: AppEnv.appStoreId,
126
117
  revenueCatAndroidApiKey: AppEnv.rcAndroidApiKey,
127
118
  revenueCatIOSApiKey: AppEnv.rcIosApiKey,
128
- revenueCatWebApiKey: AppEnv.rcWebApiKey,
129
119
  sentryDsn: AppEnv.sentryDsn,
130
120
  mixpanelToken: AppEnv.mixpanelToken,
131
121
  authenticationMode: AuthenticationMode.anonymous,
@@ -140,8 +130,8 @@ sealed class Environment with _$Environment {
140
130
  revenueCatIOSApiKey != null && revenueCatIOSApiKey!.isNotEmpty,
141
131
  TargetPlatform.android =>
142
132
  revenueCatAndroidApiKey != null && revenueCatAndroidApiKey!.isNotEmpty,
143
- _ => kIsWeb &&
144
- (revenueCatWebApiKey != null && revenueCatWebApiKey!.isNotEmpty),
133
+ // RevenueCat is mobile-only; web/desktop are never RevenueCat-configured.
134
+ _ => false,
145
135
  };
146
136
  }
147
137
 
@@ -0,0 +1,95 @@
1
+ import 'package:flutter_riverpod/flutter_riverpod.dart';
2
+ import 'package:logger/logger.dart';
3
+ import 'package:kasy_kit/features/ai_chat/api/ai_chat_conversation_entity.dart';
4
+ import 'package:kasy_kit/features/ai_chat/api/ai_chat_message_entity.dart';
5
+ import 'package:supabase_flutter/supabase_flutter.dart';
6
+
7
+ final aiChatApiProvider = Provider<AiChatApi>(
8
+ (ref) => AiChatApi(client: Supabase.instance.client),
9
+ );
10
+
11
+ const _kConversationsTable = 'ai_conversations';
12
+ const _kMessagesTable = 'ai_messages';
13
+
14
+ /// One user has many conversations (public.ai_conversations), each with many
15
+ /// messages (public.ai_messages, linked by conversation_id).
16
+ class AiChatApi {
17
+ final SupabaseClient _client;
18
+ final Logger _logger = Logger();
19
+
20
+ AiChatApi({required SupabaseClient client}) : _client = client;
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Conversations
24
+ // ---------------------------------------------------------------------------
25
+
26
+ /// Returns the user's conversations, most recently updated first.
27
+ Future<List<AiChatConversationEntity>> loadConversations(
28
+ String userId,
29
+ ) async {
30
+ final rows = await _client
31
+ .from(_kConversationsTable)
32
+ .select()
33
+ .eq('user_id', userId)
34
+ .order('updated_at', ascending: false);
35
+ return rows.map(AiChatConversationEntity.fromJson).toList();
36
+ }
37
+
38
+ /// Creates an empty conversation and returns it (with its generated id).
39
+ Future<AiChatConversationEntity> createConversation(String userId) async {
40
+ final row = await _client
41
+ .from(_kConversationsTable)
42
+ .insert({'user_id': userId})
43
+ .select()
44
+ .single();
45
+ return AiChatConversationEntity.fromJson(row);
46
+ }
47
+
48
+ /// Deletes a conversation; its messages cascade via the foreign key.
49
+ Future<void> deleteConversation(String userId, String conversationId) async {
50
+ await _client.from(_kConversationsTable).delete().eq('id', conversationId);
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Messages
55
+ // ---------------------------------------------------------------------------
56
+
57
+ /// Returns all messages in a conversation, oldest first.
58
+ Future<List<AiChatMessageEntity>> loadMessages(
59
+ String userId,
60
+ String conversationId,
61
+ ) async {
62
+ final rows = await _client
63
+ .from(_kMessagesTable)
64
+ .select()
65
+ .eq('conversation_id', conversationId)
66
+ .order('created_at', ascending: true);
67
+ return rows.map(AiChatMessageEntity.fromJson).toList();
68
+ }
69
+
70
+ /// Persists a message and denormalizes it onto the parent conversation
71
+ /// (last message preview + updated_at) so the list stays cheap to render.
72
+ Future<void> saveMessage(
73
+ String userId,
74
+ String conversationId,
75
+ AiChatMessageEntity message,
76
+ ) async {
77
+ try {
78
+ await _client.from(_kMessagesTable).insert({
79
+ 'user_id': userId,
80
+ 'conversation_id': conversationId,
81
+ ...message.toJson(),
82
+ });
83
+ await _client
84
+ .from(_kConversationsTable)
85
+ .update({
86
+ 'updated_at': message.createdAt.toUtc().toIso8601String(),
87
+ 'last_message_role': message.role,
88
+ 'last_message_content': message.content,
89
+ })
90
+ .eq('id', conversationId);
91
+ } catch (e) {
92
+ _logger.e('Failed to persist AI message: $e');
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,52 @@
1
+ /// Supabase row mapping for a single AI chat conversation.
2
+ /// Table: public.ai_conversations
3
+ ///
4
+ /// The last message is denormalized onto the conversation so the list can be
5
+ /// rendered with a single query (no need to read each conversation's messages
6
+ /// just to show a preview + timestamp).
7
+ class AiChatConversationEntity {
8
+ final String id;
9
+ final DateTime createdAt;
10
+ final DateTime updatedAt;
11
+
12
+ /// Role of the most recent message ('user' or 'assistant'), or null when the
13
+ /// conversation has no messages yet.
14
+ final String? lastMessageRole;
15
+
16
+ /// Content of the most recent message, or null when empty.
17
+ final String? lastMessageContent;
18
+
19
+ const AiChatConversationEntity({
20
+ required this.id,
21
+ required this.createdAt,
22
+ required this.updatedAt,
23
+ this.lastMessageRole,
24
+ this.lastMessageContent,
25
+ });
26
+
27
+ bool get isEmpty => lastMessageContent == null;
28
+
29
+ AiChatConversationEntity copyWith({
30
+ DateTime? updatedAt,
31
+ String? lastMessageRole,
32
+ String? lastMessageContent,
33
+ }) {
34
+ return AiChatConversationEntity(
35
+ id: id,
36
+ createdAt: createdAt,
37
+ updatedAt: updatedAt ?? this.updatedAt,
38
+ lastMessageRole: lastMessageRole ?? this.lastMessageRole,
39
+ lastMessageContent: lastMessageContent ?? this.lastMessageContent,
40
+ );
41
+ }
42
+
43
+ factory AiChatConversationEntity.fromJson(Map<String, dynamic> json) {
44
+ return AiChatConversationEntity(
45
+ id: json['id'] as String,
46
+ createdAt: DateTime.parse(json['created_at'] as String),
47
+ updatedAt: DateTime.parse(json['updated_at'] as String),
48
+ lastMessageRole: json['last_message_role'] as String?,
49
+ lastMessageContent: json['last_message_content'] as String?,
50
+ );
51
+ }
52
+ }
@@ -1,20 +1,20 @@
1
- /// Supabase row mapping for a single LLM chat message.
2
- /// Table: public.llm_messages
3
- class LlmChatMessageEntity {
1
+ /// Supabase row mapping for a single AI chat message.
2
+ /// Table: public.ai_messages
3
+ class AiChatMessageEntity {
4
4
  final String? id;
5
5
  final String role; // 'user' or 'assistant'
6
6
  final String content;
7
7
  final DateTime createdAt;
8
8
 
9
- const LlmChatMessageEntity({
9
+ const AiChatMessageEntity({
10
10
  this.id,
11
11
  required this.role,
12
12
  required this.content,
13
13
  required this.createdAt,
14
14
  });
15
15
 
16
- factory LlmChatMessageEntity.fromJson(Map<String, dynamic> json) {
17
- return LlmChatMessageEntity(
16
+ factory AiChatMessageEntity.fromJson(Map<String, dynamic> json) {
17
+ return AiChatMessageEntity(
18
18
  id: json['id'] as String?,
19
19
  role: json['role'] as String,
20
20
  content: json['content'] as String,