@sonicjs-cms/core 2.0.9 → 2.0.11

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