@sonicjs-cms/core 2.0.9 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/chunk-22EFGHAX.cjs +801 -0
  2. package/dist/chunk-22EFGHAX.cjs.map +1 -0
  3. package/dist/{chunk-TMIRVVQ7.cjs → chunk-2MBNRFS5.cjs} +7 -5
  4. package/dist/chunk-2MBNRFS5.cjs.map +1 -0
  5. package/dist/{chunk-VEC5MLT3.js → chunk-5RKQB2JG.js} +10 -228
  6. package/dist/chunk-5RKQB2JG.js.map +1 -0
  7. package/dist/{chunk-DYYAXDXI.cjs → chunk-7EGKU7OO.cjs} +31 -11
  8. package/dist/chunk-7EGKU7OO.cjs.map +1 -0
  9. package/dist/{chunk-6FR25MPC.js → chunk-F4K5QAN6.js} +246 -3
  10. package/dist/chunk-F4K5QAN6.js.map +1 -0
  11. package/dist/{chunk-WRRLB6KG.js → chunk-K5JZ4JI3.js} +7 -5
  12. package/dist/chunk-K5JZ4JI3.js.map +1 -0
  13. package/dist/{chunk-ABYMIXRN.js → chunk-KZ5XDGE6.js} +11891 -10589
  14. package/dist/chunk-KZ5XDGE6.js.map +1 -0
  15. package/dist/{chunk-4I25AGUR.cjs → chunk-LPM3NPAX.cjs} +12083 -10780
  16. package/dist/chunk-LPM3NPAX.cjs.map +1 -0
  17. package/dist/chunk-LWMMMW43.js +787 -0
  18. package/dist/chunk-LWMMMW43.js.map +1 -0
  19. package/dist/chunk-NMVOTNSL.js +61 -0
  20. package/dist/chunk-NMVOTNSL.js.map +1 -0
  21. package/dist/{chunk-OPGDMS7L.js → chunk-QNWYQZ55.js} +3 -3
  22. package/dist/{chunk-OPGDMS7L.js.map → chunk-QNWYQZ55.js.map} +1 -1
  23. package/dist/{chunk-COBUPOMD.js → chunk-T7IYBGGO.cjs} +5 -770
  24. package/dist/chunk-T7IYBGGO.cjs.map +1 -0
  25. package/dist/{chunk-OKPDQO2Y.js → chunk-UJ4K4B23.js} +30 -10
  26. package/dist/chunk-UJ4K4B23.js.map +1 -0
  27. package/dist/chunk-WBX5YMTB.cjs +70 -0
  28. package/dist/chunk-WBX5YMTB.cjs.map +1 -0
  29. package/dist/{chunk-DOR2IU73.cjs → chunk-YP52USGX.cjs} +249 -2
  30. package/dist/chunk-YP52USGX.cjs.map +1 -0
  31. package/dist/{chunk-EYMHWJTW.cjs → chunk-YU6QFFI4.cjs} +9 -228
  32. package/dist/chunk-YU6QFFI4.cjs.map +1 -0
  33. package/dist/{chunk-MABBKINE.cjs → chunk-ZMSYKV62.cjs} +5 -5
  34. package/dist/{chunk-MABBKINE.cjs.map → chunk-ZMSYKV62.cjs.map} +1 -1
  35. package/dist/{chunk-NBDPIRQS.cjs → chunk-ZPMFT2JW.js} +4 -786
  36. package/dist/chunk-ZPMFT2JW.js.map +1 -0
  37. package/dist/index.cjs +520 -147
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.js +390 -13
  40. package/dist/index.js.map +1 -1
  41. package/dist/middleware.cjs +24 -23
  42. package/dist/middleware.js +3 -2
  43. package/dist/migrations-IHERIQVD.js +4 -0
  44. package/dist/migrations-IHERIQVD.js.map +1 -0
  45. package/dist/migrations-POFD5KNG.cjs +13 -0
  46. package/dist/migrations-POFD5KNG.cjs.map +1 -0
  47. package/dist/routes.cjs +31 -29
  48. package/dist/routes.js +8 -6
  49. package/dist/services.cjs +42 -24
  50. package/dist/services.js +4 -2
  51. package/dist/templates.cjs +17 -21
  52. package/dist/templates.js +2 -2
  53. package/dist/utils.cjs +44 -11
  54. package/dist/utils.js +2 -1
  55. package/migrations/001_initial_schema.sql +2 -2
  56. package/migrations/007_demo_login_plugin.sql +1 -1
  57. package/migrations/020_add_email_plugin.sql +22 -0
  58. package/migrations/021_add_magic_link_auth_plugin.sql +42 -0
  59. package/migrations/021_add_otp_login.sql +42 -0
  60. package/migrations/022_add_tinymce_plugin.sql +25 -0
  61. package/migrations/023_add_easy_mdx_plugin.sql +25 -0
  62. package/migrations/024_add_quill_editor_plugin.sql +25 -0
  63. package/migrations/025_rename_mdxeditor_to_easy_mdx.sql +22 -0
  64. package/package.json +5 -3
  65. package/dist/chunk-4I25AGUR.cjs.map +0 -1
  66. package/dist/chunk-6FR25MPC.js.map +0 -1
  67. package/dist/chunk-ABYMIXRN.js.map +0 -1
  68. package/dist/chunk-COBUPOMD.js.map +0 -1
  69. package/dist/chunk-DOR2IU73.cjs.map +0 -1
  70. package/dist/chunk-DYYAXDXI.cjs.map +0 -1
  71. package/dist/chunk-EYMHWJTW.cjs.map +0 -1
  72. package/dist/chunk-NBDPIRQS.cjs.map +0 -1
  73. package/dist/chunk-OKPDQO2Y.js.map +0 -1
  74. package/dist/chunk-TMIRVVQ7.cjs.map +0 -1
  75. package/dist/chunk-VEC5MLT3.js.map +0 -1
  76. package/dist/chunk-WRRLB6KG.js.map +0 -1
  77. package/migrations/002_faq_plugin.sql +0 -86
package/dist/index.js CHANGED
@@ -1,20 +1,23 @@
1
- import { api_default, api_media_default, api_system_default, admin_api_default, router, adminCollectionsRoutes, adminSettingsRoutes, admin_content_default, adminMediaRoutes, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default } from './chunk-ABYMIXRN.js';
2
- export { ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, admin_code_examples_default as adminCodeExamplesRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminDesignRoutes, admin_faq_default as adminFAQRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_testimonials_default as adminTestimonialsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes } from './chunk-ABYMIXRN.js';
3
- import { schema_exports } from './chunk-6FR25MPC.js';
4
- export { Logger, apiTokens, collections, content, contentVersions, getLogger, initLogger, insertCollectionSchema, insertContentSchema, insertLogConfigSchema, insertMediaSchema, insertPluginActivityLogSchema, insertPluginAssetSchema, insertPluginHookSchema, insertPluginRouteSchema, insertPluginSchema, insertSystemLogSchema, insertUserSchema, insertWorkflowHistorySchema, logConfig, media, pluginActivityLog, pluginAssets, pluginHooks, pluginRoutes, plugins, selectCollectionSchema, selectContentSchema, selectLogConfigSchema, selectMediaSchema, selectPluginActivityLogSchema, selectPluginAssetSchema, selectPluginHookSchema, selectPluginRouteSchema, selectPluginSchema, selectSystemLogSchema, selectUserSchema, selectWorkflowHistorySchema, systemLogs, users, workflowHistory } from './chunk-6FR25MPC.js';
5
- import { metricsMiddleware, bootstrapMiddleware, requireAuth } from './chunk-OKPDQO2Y.js';
6
- export { AuthManager, PermissionManager, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeaders, securityLoggingMiddleware } from './chunk-OKPDQO2Y.js';
7
- export { MigrationService, PluginBootstrapService, PluginService as PluginServiceClass, cleanupRemovedCollections, fullCollectionSync, getAvailableCollectionNames, getManagedCollections, isCollectionManaged, loadCollectionConfig, loadCollectionConfigs, registerCollections, syncCollection, syncCollections, validateCollectionConfig } from './chunk-COBUPOMD.js';
8
- export { renderFilterBar } from './chunk-OPGDMS7L.js';
9
- import { init_admin_layout_catalyst_template, renderAdminLayoutCatalyst } from './chunk-VEC5MLT3.js';
10
- export { getConfirmationDialogScript, renderAlert, renderConfirmationDialog, renderForm, renderFormField, renderPagination, renderTable } from './chunk-VEC5MLT3.js';
1
+ import { PluginBuilder, api_default, api_media_default, api_system_default, admin_api_default, router, adminCollectionsRoutes, adminSettingsRoutes, admin_content_default, adminMediaRoutes, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default, test_cleanup_default } from './chunk-KZ5XDGE6.js';
2
+ export { ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, admin_code_examples_default as adminCodeExamplesRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_testimonials_default as adminTestimonialsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes } from './chunk-KZ5XDGE6.js';
3
+ import { schema_exports } from './chunk-F4K5QAN6.js';
4
+ export { Logger, apiTokens, collections, content, contentVersions, getLogger, initLogger, insertCollectionSchema, insertContentSchema, insertLogConfigSchema, insertMediaSchema, insertPluginActivityLogSchema, insertPluginAssetSchema, insertPluginHookSchema, insertPluginRouteSchema, insertPluginSchema, insertSystemLogSchema, insertUserSchema, insertWorkflowHistorySchema, logConfig, media, pluginActivityLog, pluginAssets, pluginHooks, pluginRoutes, plugins, selectCollectionSchema, selectContentSchema, selectLogConfigSchema, selectMediaSchema, selectPluginActivityLogSchema, selectPluginAssetSchema, selectPluginHookSchema, selectPluginRouteSchema, selectPluginSchema, selectSystemLogSchema, selectUserSchema, selectWorkflowHistorySchema, systemLogs, users, workflowHistory } from './chunk-F4K5QAN6.js';
5
+ import { metricsMiddleware, bootstrapMiddleware, requireAuth } from './chunk-UJ4K4B23.js';
6
+ export { AuthManager, PermissionManager, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeaders, securityLoggingMiddleware } from './chunk-UJ4K4B23.js';
7
+ export { PluginBootstrapService, PluginService as PluginServiceClass, cleanupRemovedCollections, fullCollectionSync, getAvailableCollectionNames, getManagedCollections, isCollectionManaged, loadCollectionConfig, loadCollectionConfigs, registerCollections, syncCollection, syncCollections, validateCollectionConfig } from './chunk-LWMMMW43.js';
8
+ export { MigrationService } from './chunk-ZPMFT2JW.js';
9
+ export { renderFilterBar } from './chunk-QNWYQZ55.js';
10
+ import { init_admin_layout_catalyst_template, renderAdminLayout, renderAdminLayoutCatalyst } from './chunk-5RKQB2JG.js';
11
+ export { getConfirmationDialogScript, renderAlert, renderConfirmationDialog, renderForm, renderFormField, renderPagination, renderTable } from './chunk-5RKQB2JG.js';
11
12
  export { HookSystemImpl, HookUtils, PluginManager as PluginManagerClass, PluginRegistryImpl, PluginValidator as PluginValidatorClass, ScopedHookSystem as ScopedHookSystemClass } from './chunk-HKEK7UNV.js';
12
- import { package_default, getCoreVersion } from './chunk-WRRLB6KG.js';
13
- export { QueryFilterBuilder, SONICJS_VERSION, TemplateRenderer, buildQuery, escapeHtml, getCoreVersion, renderTemplate, sanitizeInput, sanitizeObject, templateRenderer } from './chunk-WRRLB6KG.js';
13
+ import { package_default, getCoreVersion } from './chunk-K5JZ4JI3.js';
14
+ export { QueryFilterBuilder, SONICJS_VERSION, TemplateRenderer, buildQuery, escapeHtml, getCoreVersion, renderTemplate, sanitizeInput, sanitizeObject, templateRenderer } from './chunk-K5JZ4JI3.js';
15
+ import './chunk-NMVOTNSL.js';
14
16
  export { metricsTracker } from './chunk-FICTAGD4.js';
15
17
  export { HOOKS } from './chunk-LOUJRBXV.js';
16
18
  import './chunk-V4OQ3NZ2.js';
17
19
  import { Hono } from 'hono';
20
+ import { html } from 'hono/html';
18
21
  import { drizzle } from 'drizzle-orm/d1';
19
22
 
20
23
  // src/plugins/core-plugins/database-tools-plugin/services/database-service.ts
@@ -736,6 +739,374 @@ function createDatabaseToolsAdminRoutes() {
736
739
  });
737
740
  return router2;
738
741
  }
742
+ function createEmailPlugin() {
743
+ const builder = PluginBuilder.create({
744
+ name: "email",
745
+ version: "1.0.0-beta.1",
746
+ description: "Send transactional emails using Resend"
747
+ });
748
+ builder.metadata({
749
+ author: {
750
+ name: "SonicJS Team",
751
+ email: "team@sonicjs.com"
752
+ },
753
+ license: "MIT",
754
+ compatibility: "^2.0.0"
755
+ });
756
+ const emailRoutes = new Hono();
757
+ emailRoutes.get("/settings", async (c) => {
758
+ const user = c.get("user");
759
+ const db = c.env.DB;
760
+ const plugin = await db.prepare(`
761
+ SELECT settings FROM plugins WHERE id = 'email'
762
+ `).first();
763
+ const settings = plugin?.settings ? JSON.parse(plugin.settings) : {};
764
+ const contentHTML = html`
765
+ <div class="p-8">
766
+ <!-- Header -->
767
+ <div class="mb-8">
768
+ <h1 class="text-3xl font-bold text-zinc-950 dark:text-white mb-2">Email Settings</h1>
769
+ <p class="text-zinc-600 dark:text-zinc-400">Configure Resend API for sending transactional emails</p>
770
+ </div>
771
+
772
+ <!-- Settings Form -->
773
+ <div class="max-w-3xl">
774
+ <!-- Main Settings Card -->
775
+ <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-6 mb-6">
776
+ <h2 class="text-xl font-semibold text-zinc-950 dark:text-white mb-4">Resend Configuration</h2>
777
+
778
+ <form id="emailSettingsForm" class="space-y-6">
779
+ <!-- API Key -->
780
+ <div>
781
+ <label for="apiKey" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
782
+ Resend API Key <span class="text-red-500">*</span>
783
+ </label>
784
+ <input
785
+ type="password"
786
+ id="apiKey"
787
+ name="apiKey"
788
+ value="${settings.apiKey || ""}"
789
+ class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
790
+ placeholder="re_..."
791
+ required
792
+ />
793
+ <p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">
794
+ Get your API key from <a href="https://resend.com/api-keys" target="_blank" class="text-indigo-600 dark:text-indigo-400 hover:underline">resend.com/api-keys</a>
795
+ </p>
796
+ </div>
797
+
798
+ <!-- From Email -->
799
+ <div>
800
+ <label for="fromEmail" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
801
+ From Email <span class="text-red-500">*</span>
802
+ </label>
803
+ <input
804
+ type="email"
805
+ id="fromEmail"
806
+ name="fromEmail"
807
+ value="${settings.fromEmail || ""}"
808
+ class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
809
+ placeholder="noreply@yourdomain.com"
810
+ required
811
+ />
812
+ <p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">
813
+ Must be a verified domain in Resend
814
+ </p>
815
+ </div>
816
+
817
+ <!-- From Name -->
818
+ <div>
819
+ <label for="fromName" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
820
+ From Name <span class="text-red-500">*</span>
821
+ </label>
822
+ <input
823
+ type="text"
824
+ id="fromName"
825
+ name="fromName"
826
+ value="${settings.fromName || ""}"
827
+ class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
828
+ placeholder="Your App Name"
829
+ required
830
+ />
831
+ </div>
832
+
833
+ <!-- Reply To -->
834
+ <div>
835
+ <label for="replyTo" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
836
+ Reply-To Email
837
+ </label>
838
+ <input
839
+ type="email"
840
+ id="replyTo"
841
+ name="replyTo"
842
+ value="${settings.replyTo || ""}"
843
+ class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
844
+ placeholder="support@yourdomain.com"
845
+ />
846
+ </div>
847
+
848
+ <!-- Logo URL -->
849
+ <div>
850
+ <label for="logoUrl" class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">
851
+ Logo URL
852
+ </label>
853
+ <input
854
+ type="url"
855
+ id="logoUrl"
856
+ name="logoUrl"
857
+ value="${settings.logoUrl || ""}"
858
+ class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-500 dark:placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-indigo-500 dark:focus:ring-indigo-400"
859
+ placeholder="https://yourdomain.com/logo.png"
860
+ />
861
+ <p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">
862
+ Logo to display in email templates
863
+ </p>
864
+ </div>
865
+
866
+ <!-- Action Buttons -->
867
+ <div class="flex gap-3 pt-4">
868
+ <button
869
+ type="submit"
870
+ class="inline-flex items-center justify-center rounded-lg bg-zinc-950 dark:bg-white px-3.5 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors shadow-sm"
871
+ >
872
+ Save Settings
873
+ </button>
874
+ <button
875
+ type="button"
876
+ id="testEmailBtn"
877
+ class="inline-flex items-center justify-center rounded-lg bg-white dark:bg-zinc-800 px-3.5 py-2.5 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors shadow-sm"
878
+ >
879
+ Send Test Email
880
+ </button>
881
+ <button
882
+ type="button"
883
+ id="resetBtn"
884
+ class="inline-flex items-center justify-center rounded-lg bg-white dark:bg-zinc-800 px-3.5 py-2.5 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors shadow-sm"
885
+ >
886
+ Reset
887
+ </button>
888
+ </div>
889
+ </form>
890
+ </div>
891
+
892
+ <!-- Status Message -->
893
+ <div id="statusMessage" class="hidden rounded-xl p-4 mb-6"></div>
894
+
895
+ <!-- Info Card -->
896
+ <div class="rounded-xl bg-indigo-50 dark:bg-indigo-950/30 ring-1 ring-indigo-100 dark:ring-indigo-900/50 p-6">
897
+ <h3 class="font-semibold text-indigo-900 dark:text-indigo-300 mb-3">
898
+ 📧 Email Templates Included
899
+ </h3>
900
+ <ul class="text-sm text-indigo-800 dark:text-indigo-200 space-y-2">
901
+ <li>✓ Registration confirmation</li>
902
+ <li>✓ Email verification</li>
903
+ <li>✓ Password reset</li>
904
+ <li>✓ One-time code (2FA)</li>
905
+ </ul>
906
+ <p class="text-xs text-indigo-700 dark:text-indigo-300 mt-4">
907
+ Templates are code-based and can be customized by editing the plugin files.
908
+ </p>
909
+ </div>
910
+ </div>
911
+ </div>
912
+
913
+ <script>
914
+ // Form submission handler
915
+ document.getElementById('emailSettingsForm').addEventListener('submit', async (e) => {
916
+ e.preventDefault()
917
+ const formData = new FormData(e.target)
918
+ const data = Object.fromEntries(formData.entries())
919
+
920
+ const statusEl = document.getElementById('statusMessage')
921
+
922
+ try {
923
+ const response = await fetch('/admin/plugins/email/settings', {
924
+ method: 'POST',
925
+ headers: { 'Content-Type': 'application/json' },
926
+ body: JSON.stringify(data)
927
+ })
928
+
929
+ if (response.ok) {
930
+ statusEl.className = 'rounded-xl bg-green-50 dark:bg-green-950/30 ring-1 ring-green-100 dark:ring-green-900/50 p-4 mb-6 text-green-900 dark:text-green-200'
931
+ statusEl.innerHTML = '✅ Settings saved successfully!'
932
+ statusEl.classList.remove('hidden')
933
+ setTimeout(() => statusEl.classList.add('hidden'), 3000)
934
+ } else {
935
+ throw new Error('Failed to save settings')
936
+ }
937
+ } catch (error) {
938
+ statusEl.className = 'rounded-xl bg-red-50 dark:bg-red-950/30 ring-1 ring-red-100 dark:ring-red-900/50 p-4 mb-6 text-red-900 dark:text-red-200'
939
+ statusEl.innerHTML = '❌ Failed to save settings. Please try again.'
940
+ statusEl.classList.remove('hidden')
941
+ }
942
+ })
943
+
944
+ // Test email handler
945
+ document.getElementById('testEmailBtn').addEventListener('click', async () => {
946
+ // Prompt for destination email
947
+ const toEmail = prompt('Enter destination email address for test:')
948
+ if (!toEmail) return
949
+
950
+ // Basic email validation
951
+ if (!toEmail.match(/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/)) {
952
+ alert('Please enter a valid email address')
953
+ return
954
+ }
955
+
956
+ const statusEl = document.getElementById('statusMessage')
957
+
958
+ statusEl.className = 'rounded-xl bg-indigo-50 dark:bg-indigo-950/30 ring-1 ring-indigo-100 dark:ring-indigo-900/50 p-4 mb-6 text-indigo-900 dark:text-indigo-200'
959
+ statusEl.innerHTML = \`📧 Sending test email to \${toEmail}...\`
960
+ statusEl.classList.remove('hidden')
961
+
962
+ try {
963
+ const response = await fetch('/admin/plugins/email/test', {
964
+ method: 'POST',
965
+ headers: { 'Content-Type': 'application/json' },
966
+ body: JSON.stringify({ toEmail })
967
+ })
968
+
969
+ const data = await response.json()
970
+
971
+ if (response.ok) {
972
+ statusEl.className = 'rounded-xl bg-green-50 dark:bg-green-950/30 ring-1 ring-green-100 dark:ring-green-900/50 p-4 mb-6 text-green-900 dark:text-green-200'
973
+ statusEl.innerHTML = \`✅ \${data.message || 'Test email sent! Check your inbox.'}\`
974
+ } else {
975
+ statusEl.className = 'rounded-xl bg-red-50 dark:bg-red-950/30 ring-1 ring-red-100 dark:ring-red-900/50 p-4 mb-6 text-red-900 dark:text-red-200'
976
+ statusEl.innerHTML = \`❌ \${data.error || 'Failed to send test email. Check your settings.'}\`
977
+ }
978
+ } catch (error) {
979
+ statusEl.className = 'rounded-xl bg-red-50 dark:bg-red-950/30 ring-1 ring-red-100 dark:ring-red-900/50 p-4 mb-6 text-red-900 dark:text-red-200'
980
+ statusEl.innerHTML = '❌ Network error. Please try again.'
981
+ }
982
+ })
983
+
984
+ // Reset button handler
985
+ document.getElementById('resetBtn').addEventListener('click', () => {
986
+ document.getElementById('emailSettingsForm').reset()
987
+ })
988
+ </script>
989
+ `;
990
+ return c.html(
991
+ renderAdminLayout({
992
+ title: "Email Settings",
993
+ content: contentHTML,
994
+ user,
995
+ currentPath: "/admin/plugins/email/settings"
996
+ })
997
+ );
998
+ });
999
+ emailRoutes.post("/settings", async (c) => {
1000
+ try {
1001
+ const body = await c.req.json();
1002
+ const db = c.env.DB;
1003
+ await db.prepare(`
1004
+ UPDATE plugins
1005
+ SET settings = ?,
1006
+ updated_at = unixepoch()
1007
+ WHERE id = 'email'
1008
+ `).bind(JSON.stringify(body)).run();
1009
+ return c.json({ success: true });
1010
+ } catch (error) {
1011
+ console.error("Error saving email settings:", error);
1012
+ return c.json({ success: false, error: "Failed to save settings" }, 500);
1013
+ }
1014
+ });
1015
+ emailRoutes.post("/test", async (c) => {
1016
+ try {
1017
+ const db = c.env.DB;
1018
+ const body = await c.req.json();
1019
+ const plugin = await db.prepare(`
1020
+ SELECT settings FROM plugins WHERE id = 'email'
1021
+ `).first();
1022
+ if (!plugin?.settings) {
1023
+ return c.json({
1024
+ success: false,
1025
+ error: "Email settings not configured. Please save your settings first."
1026
+ }, 400);
1027
+ }
1028
+ const settings = JSON.parse(plugin.settings);
1029
+ if (!settings.apiKey || !settings.fromEmail || !settings.fromName) {
1030
+ return c.json({
1031
+ success: false,
1032
+ error: "Missing required settings. Please configure API Key, From Email, and From Name."
1033
+ }, 400);
1034
+ }
1035
+ const toEmail = body.toEmail || settings.fromEmail;
1036
+ if (!toEmail.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
1037
+ return c.json({
1038
+ success: false,
1039
+ error: "Invalid email address format"
1040
+ }, 400);
1041
+ }
1042
+ const response = await fetch("https://api.resend.com/emails", {
1043
+ method: "POST",
1044
+ headers: {
1045
+ "Authorization": `Bearer ${settings.apiKey}`,
1046
+ "Content-Type": "application/json"
1047
+ },
1048
+ body: JSON.stringify({
1049
+ from: `${settings.fromName} <${settings.fromEmail}>`,
1050
+ to: [toEmail],
1051
+ subject: "Test Email from SonicJS",
1052
+ html: `
1053
+ <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
1054
+ <h1 style="color: #667eea;">Test Email Successful! \u{1F389}</h1>
1055
+ <p>This is a test email from your SonicJS Email plugin.</p>
1056
+ <p><strong>Configuration:</strong></p>
1057
+ <ul>
1058
+ <li>From: ${settings.fromName} &lt;${settings.fromEmail}&gt;</li>
1059
+ <li>Reply-To: ${settings.replyTo || "Not set"}</li>
1060
+ <li>Sent at: ${(/* @__PURE__ */ new Date()).toISOString()}</li>
1061
+ </ul>
1062
+ <p>Your email settings are working correctly!</p>
1063
+ </div>
1064
+ `,
1065
+ reply_to: settings.replyTo || settings.fromEmail
1066
+ })
1067
+ });
1068
+ const data = await response.json();
1069
+ if (!response.ok) {
1070
+ console.error("Resend API error:", data);
1071
+ return c.json({
1072
+ success: false,
1073
+ error: data.message || "Failed to send test email. Check your API key and domain verification."
1074
+ }, response.status);
1075
+ }
1076
+ return c.json({
1077
+ success: true,
1078
+ message: `Test email sent successfully to ${toEmail}`,
1079
+ emailId: data.id
1080
+ });
1081
+ } catch (error) {
1082
+ console.error("Test email error:", error);
1083
+ return c.json({
1084
+ success: false,
1085
+ error: error.message || "An error occurred while sending test email"
1086
+ }, 500);
1087
+ }
1088
+ });
1089
+ builder.addRoute("/admin/plugins/email", emailRoutes, {
1090
+ description: "Email plugin settings",
1091
+ requiresAuth: true,
1092
+ priority: 80
1093
+ });
1094
+ builder.addMenuItem("Email", "/admin/plugins/email/settings", {
1095
+ icon: "envelope",
1096
+ order: 80,
1097
+ permissions: ["email:manage"]
1098
+ });
1099
+ builder.lifecycle({
1100
+ activate: async () => {
1101
+ console.info("\u2705 Email plugin activated");
1102
+ },
1103
+ deactivate: async () => {
1104
+ console.info("\u274C Email plugin deactivated");
1105
+ }
1106
+ });
1107
+ return builder.build();
1108
+ }
1109
+ var emailPlugin = createEmailPlugin();
739
1110
 
740
1111
  // src/app.ts
741
1112
  function createSonicJSApp(config = {}) {
@@ -747,7 +1118,7 @@ function createSonicJSApp(config = {}) {
747
1118
  await next();
748
1119
  });
749
1120
  app.use("*", metricsMiddleware());
750
- app.use("*", bootstrapMiddleware());
1121
+ app.use("*", bootstrapMiddleware(config));
751
1122
  if (config.middleware?.beforeAuth) {
752
1123
  for (const middleware of config.middleware.beforeAuth) {
753
1124
  app.use("*", middleware);
@@ -778,6 +1149,12 @@ function createSonicJSApp(config = {}) {
778
1149
  app.route("/admin/logs", adminLogsRoutes);
779
1150
  app.route("/admin", userRoutes);
780
1151
  app.route("/auth", auth_default);
1152
+ app.route("/", test_cleanup_default);
1153
+ if (emailPlugin.routes && emailPlugin.routes.length > 0) {
1154
+ for (const route of emailPlugin.routes) {
1155
+ app.route(route.path, route.handler);
1156
+ }
1157
+ }
781
1158
  app.get("/files/*", async (c) => {
782
1159
  try {
783
1160
  const url = new URL(c.req.url);