payload-plugin-newsletter 0.3.1 → 0.4.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../src/endpoints/subscribe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,SAAS,CAAA;AACvD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAQtD,eAAO,MAAM,uBAAuB,GAClC,QAAQ,sBAAsB,KAC7B,QAgLF,CAAA"}
1
+ {"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../src/endpoints/subscribe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,SAAS,CAAA;AACvD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAQtD,eAAO,MAAM,uBAAuB,GAClC,QAAQ,sBAAsB,KAC7B,QAyLF,CAAA"}
@@ -369,8 +369,8 @@ export const createNewsletterSettingsCollection = (pluginConfig)=>{
369
369
  try {
370
370
  // TODO: Implement email service reinitialization
371
371
  console.warn('Newsletter settings updated, reinitializing service...');
372
- } catch (error) {
373
- console.error('Failed to reinitialize email service:', error);
372
+ } catch {
373
+ // Failed to reinitialize email service
374
374
  }
375
375
  }
376
376
  return doc;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/collections/NewsletterSettings.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\nimport { adminOnly } from '../utils/access'\n\nexport const createNewsletterSettingsCollection = (\n pluginConfig: NewsletterPluginConfig\n): CollectionConfig => {\n const slug = pluginConfig.settingsSlug || 'newsletter-settings'\n \n return {\n slug,\n labels: {\n singular: 'Newsletter Setting',\n plural: 'Newsletter Settings',\n },\n admin: {\n useAsTitle: 'name',\n defaultColumns: ['name', 'provider', 'active', 'updatedAt'],\n group: 'Newsletter',\n description: 'Configure email provider settings and templates',\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Configuration Name',\n required: true,\n admin: {\n description: 'A descriptive name for this configuration (e.g., \"Production\", \"Development\", \"Marketing Emails\")',\n },\n },\n {\n name: 'active',\n type: 'checkbox',\n label: 'Active',\n defaultValue: false,\n admin: {\n description: 'Only one configuration can be active at a time',\n },\n },\n {\n type: 'tabs',\n tabs: [\n {\n label: 'Provider Settings',\n fields: [\n {\n name: 'provider',\n type: 'select',\n label: 'Email Provider',\n required: true,\n options: [\n { label: 'Resend', value: 'resend' },\n { label: 'Broadcast (Self-Hosted)', value: 'broadcast' },\n ],\n defaultValue: pluginConfig.providers.default,\n admin: {\n description: 'Choose which email service to use',\n },\n },\n {\n name: 'resendSettings',\n type: 'group',\n label: 'Resend Settings',\n admin: {\n condition: (data) => data?.provider === 'resend',\n },\n fields: [\n {\n name: 'apiKey',\n type: 'text',\n label: 'API Key',\n required: true,\n admin: {\n description: 'Your Resend API key',\n },\n },\n {\n name: 'audienceIds',\n type: 'array',\n label: 'Audience IDs by Locale',\n fields: [\n {\n name: 'locale',\n type: 'select',\n label: 'Locale',\n required: true,\n options: pluginConfig.i18n?.locales?.map(locale => ({\n label: locale.toUpperCase(),\n value: locale,\n })) || [\n { label: 'EN', value: 'en' },\n ],\n },\n {\n name: 'production',\n type: 'text',\n label: 'Production Audience ID',\n },\n {\n name: 'development',\n type: 'text',\n label: 'Development Audience ID',\n },\n ],\n },\n ],\n },\n {\n name: 'broadcastSettings',\n type: 'group',\n label: 'Broadcast Settings',\n admin: {\n condition: (data) => data?.provider === 'broadcast',\n },\n fields: [\n {\n name: 'apiUrl',\n type: 'text',\n label: 'API URL',\n required: true,\n admin: {\n description: 'Your Broadcast instance URL',\n },\n },\n {\n name: 'productionToken',\n type: 'text',\n label: 'Production Token',\n admin: {\n description: 'Token for production environment',\n },\n },\n {\n name: 'developmentToken',\n type: 'text',\n label: 'Development Token',\n admin: {\n description: 'Token for development environment',\n },\n },\n ],\n },\n {\n name: 'fromAddress',\n type: 'email',\n label: 'From Address',\n required: true,\n admin: {\n description: 'Default sender email address',\n },\n },\n {\n name: 'fromName',\n type: 'text',\n label: 'From Name',\n required: true,\n admin: {\n description: 'Default sender name',\n },\n },\n {\n name: 'replyTo',\n type: 'email',\n label: 'Reply-To Address',\n admin: {\n description: 'Optional reply-to email address',\n },\n },\n ],\n },\n {\n label: 'Email Templates',\n fields: [\n {\n name: 'emailTemplates',\n type: 'group',\n label: 'Email Templates',\n fields: [\n {\n name: 'welcome',\n type: 'group',\n label: 'Welcome Email',\n fields: [\n {\n name: 'enabled',\n type: 'checkbox',\n label: 'Send Welcome Email',\n defaultValue: true,\n },\n {\n name: 'subject',\n type: 'text',\n label: 'Subject Line',\n defaultValue: 'Welcome to {{fromName}}!',\n admin: {\n condition: (data) => data?.emailTemplates?.welcome?.enabled,\n },\n },\n {\n name: 'preheader',\n type: 'text',\n label: 'Preheader Text',\n admin: {\n condition: (data) => data?.emailTemplates?.welcome?.enabled,\n },\n },\n ],\n },\n {\n name: 'magicLink',\n type: 'group',\n label: 'Magic Link Email',\n fields: [\n {\n name: 'subject',\n type: 'text',\n label: 'Subject Line',\n defaultValue: 'Sign in to {{fromName}}',\n },\n {\n name: 'preheader',\n type: 'text',\n label: 'Preheader Text',\n defaultValue: 'Click the link to access your preferences',\n },\n {\n name: 'expirationTime',\n type: 'select',\n label: 'Link Expiration',\n defaultValue: '7d',\n options: [\n { label: '1 hour', value: '1h' },\n { label: '24 hours', value: '24h' },\n { label: '7 days', value: '7d' },\n { label: '30 days', value: '30d' },\n ],\n },\n ],\n },\n ],\n },\n ],\n },\n {\n label: 'Subscription Settings',\n fields: [\n {\n name: 'subscriptionSettings',\n type: 'group',\n label: 'Subscription Settings',\n fields: [\n {\n name: 'requireDoubleOptIn',\n type: 'checkbox',\n label: 'Require Double Opt-In',\n defaultValue: false,\n admin: {\n description: 'Require email confirmation before activating subscriptions',\n },\n },\n {\n name: 'allowedDomains',\n type: 'array',\n label: 'Allowed Email Domains',\n admin: {\n description: 'Leave empty to allow all domains',\n },\n fields: [\n {\n name: 'domain',\n type: 'text',\n label: 'Domain',\n required: true,\n admin: {\n placeholder: 'example.com',\n },\n },\n ],\n },\n {\n name: 'maxSubscribersPerIP',\n type: 'number',\n label: 'Max Subscribers per IP',\n defaultValue: 10,\n min: 1,\n admin: {\n description: 'Maximum number of subscriptions allowed from a single IP address',\n },\n },\n ],\n },\n ],\n },\n ],\n },\n ],\n hooks: {\n beforeChange: [\n async ({ data, req, operation }) => {\n // Verify admin access for settings changes\n if (!req.user || req.user.collection !== 'users') {\n throw new Error('Only administrators can modify newsletter settings')\n }\n \n // If setting this config as active, deactivate all others\n if (data?.active && operation !== 'create') {\n await req.payload.update({\n collection: slug,\n where: {\n id: {\n not_equals: data.id,\n },\n },\n data: {\n active: false,\n },\n // Keep overrideAccess: true for admin operations after verification\n })\n }\n \n // For new configs, ensure only one is active\n if (operation === 'create' && data?.active) {\n const existingActive = await req.payload.find({\n collection: slug,\n where: {\n active: {\n equals: true,\n },\n },\n // Keep overrideAccess: true for admin operations\n })\n \n if (existingActive.docs.length > 0) {\n // Deactivate existing active configs\n for (const doc of existingActive.docs) {\n await req.payload.update({\n collection: slug,\n id: doc.id,\n data: {\n active: false,\n },\n // Keep overrideAccess: true for admin operations\n })\n }\n }\n }\n \n return data\n },\n ],\n afterChange: [\n async ({ doc, req }) => {\n // Reinitialize email service when settings change\n if ((req.payload as any).newsletterEmailService && doc.active) {\n try {\n // TODO: Implement email service reinitialization\n console.warn('Newsletter settings updated, reinitializing service...')\n } catch (error) {\n console.error('Failed to reinitialize email service:', error)\n }\n }\n \n return doc\n },\n ],\n },\n access: {\n read: () => true, // Settings can be read publicly for validation\n create: adminOnly(pluginConfig),\n update: adminOnly(pluginConfig),\n delete: adminOnly(pluginConfig),\n },\n timestamps: true,\n }\n}"],"names":["adminOnly","createNewsletterSettingsCollection","pluginConfig","slug","settingsSlug","labels","singular","plural","admin","useAsTitle","defaultColumns","group","description","fields","name","type","label","required","defaultValue","tabs","options","value","providers","default","condition","data","provider","i18n","locales","map","locale","toUpperCase","emailTemplates","welcome","enabled","placeholder","min","hooks","beforeChange","req","operation","user","collection","Error","active","payload","update","where","id","not_equals","existingActive","find","equals","docs","length","doc","afterChange","newsletterEmailService","console","warn","error","access","read","create","delete","timestamps"],"mappings":"AAEA,SAASA,SAAS,QAAQ,kBAAiB;AAE3C,OAAO,MAAMC,qCAAqC,CAChDC;IAEA,MAAMC,OAAOD,aAAaE,YAAY,IAAI;IAE1C,OAAO;QACLD;QACAE,QAAQ;YACNC,UAAU;YACVC,QAAQ;QACV;QACAC,OAAO;YACLC,YAAY;YACZC,gBAAgB;gBAAC;gBAAQ;gBAAY;gBAAU;aAAY;YAC3DC,OAAO;YACPC,aAAa;QACf;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNC,OAAO;gBACPC,UAAU;gBACVT,OAAO;oBACLI,aAAa;gBACf;YACF;YACA;gBACEE,MAAM;gBACNC,MAAM;gBACNC,OAAO;gBACPE,cAAc;gBACdV,OAAO;oBACLI,aAAa;gBACf;YACF;YACA;gBACEG,MAAM;gBACNI,MAAM;oBACJ;wBACEH,OAAO;wBACPH,QAAQ;4BACN;gCACEC,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPC,UAAU;gCACVG,SAAS;oCACP;wCAAEJ,OAAO;wCAAUK,OAAO;oCAAS;oCACnC;wCAAEL,OAAO;wCAA2BK,OAAO;oCAAY;iCACxD;gCACDH,cAAchB,aAAaoB,SAAS,CAACC,OAAO;gCAC5Cf,OAAO;oCACLI,aAAa;gCACf;4BACF;4BACA;gCACEE,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPR,OAAO;oCACLgB,WAAW,CAACC,OAASA,MAAMC,aAAa;gCAC1C;gCACAb,QAAQ;oCACN;wCACEC,MAAM;wCACNC,MAAM;wCACNC,OAAO;wCACPC,UAAU;wCACVT,OAAO;4CACLI,aAAa;wCACf;oCACF;oCACA;wCACEE,MAAM;wCACNC,MAAM;wCACNC,OAAO;wCACPH,QAAQ;4CACN;gDACEC,MAAM;gDACNC,MAAM;gDACNC,OAAO;gDACPC,UAAU;gDACVG,SAASlB,aAAayB,IAAI,EAAEC,SAASC,IAAIC,CAAAA,SAAW,CAAA;wDAClDd,OAAOc,OAAOC,WAAW;wDACzBV,OAAOS;oDACT,CAAA,MAAO;oDACL;wDAAEd,OAAO;wDAAMK,OAAO;oDAAK;iDAC5B;4CACH;4CACA;gDACEP,MAAM;gDACNC,MAAM;gDACNC,OAAO;4CACT;4CACA;gDACEF,MAAM;gDACNC,MAAM;gDACNC,OAAO;4CACT;yCACD;oCACH;iCACD;4BACH;4BACA;gCACEF,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPR,OAAO;oCACLgB,WAAW,CAACC,OAASA,MAAMC,aAAa;gCAC1C;gCACAb,QAAQ;oCACN;wCACEC,MAAM;wCACNC,MAAM;wCACNC,OAAO;wCACPC,UAAU;wCACVT,OAAO;4CACLI,aAAa;wCACf;oCACF;oCACA;wCACEE,MAAM;wCACNC,MAAM;wCACNC,OAAO;wCACPR,OAAO;4CACLI,aAAa;wCACf;oCACF;oCACA;wCACEE,MAAM;wCACNC,MAAM;wCACNC,OAAO;wCACPR,OAAO;4CACLI,aAAa;wCACf;oCACF;iCACD;4BACH;4BACA;gCACEE,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPC,UAAU;gCACVT,OAAO;oCACLI,aAAa;gCACf;4BACF;4BACA;gCACEE,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPC,UAAU;gCACVT,OAAO;oCACLI,aAAa;gCACf;4BACF;4BACA;gCACEE,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPR,OAAO;oCACLI,aAAa;gCACf;4BACF;yBACD;oBACH;oBACA;wBACEI,OAAO;wBACPH,QAAQ;4BACN;gCACEC,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPH,QAAQ;oCACN;wCACEC,MAAM;wCACNC,MAAM;wCACNC,OAAO;wCACPH,QAAQ;4CACN;gDACEC,MAAM;gDACNC,MAAM;gDACNC,OAAO;gDACPE,cAAc;4CAChB;4CACA;gDACEJ,MAAM;gDACNC,MAAM;gDACNC,OAAO;gDACPE,cAAc;gDACdV,OAAO;oDACLgB,WAAW,CAACC,OAASA,MAAMO,gBAAgBC,SAASC;gDACtD;4CACF;4CACA;gDACEpB,MAAM;gDACNC,MAAM;gDACNC,OAAO;gDACPR,OAAO;oDACLgB,WAAW,CAACC,OAASA,MAAMO,gBAAgBC,SAASC;gDACtD;4CACF;yCACD;oCACH;oCACA;wCACEpB,MAAM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"}
1
+ {"version":3,"sources":["../../../src/collections/NewsletterSettings.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\nimport { adminOnly } from '../utils/access'\n\nexport const createNewsletterSettingsCollection = (\n pluginConfig: NewsletterPluginConfig\n): CollectionConfig => {\n const slug = pluginConfig.settingsSlug || 'newsletter-settings'\n \n return {\n slug,\n labels: {\n singular: 'Newsletter Setting',\n plural: 'Newsletter Settings',\n },\n admin: {\n useAsTitle: 'name',\n defaultColumns: ['name', 'provider', 'active', 'updatedAt'],\n group: 'Newsletter',\n description: 'Configure email provider settings and templates',\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: 'Configuration Name',\n required: true,\n admin: {\n description: 'A descriptive name for this configuration (e.g., \"Production\", \"Development\", \"Marketing Emails\")',\n },\n },\n {\n name: 'active',\n type: 'checkbox',\n label: 'Active',\n defaultValue: false,\n admin: {\n description: 'Only one configuration can be active at a time',\n },\n },\n {\n type: 'tabs',\n tabs: [\n {\n label: 'Provider Settings',\n fields: [\n {\n name: 'provider',\n type: 'select',\n label: 'Email Provider',\n required: true,\n options: [\n { label: 'Resend', value: 'resend' },\n { label: 'Broadcast (Self-Hosted)', value: 'broadcast' },\n ],\n defaultValue: pluginConfig.providers.default,\n admin: {\n description: 'Choose which email service to use',\n },\n },\n {\n name: 'resendSettings',\n type: 'group',\n label: 'Resend Settings',\n admin: {\n condition: (data) => data?.provider === 'resend',\n },\n fields: [\n {\n name: 'apiKey',\n type: 'text',\n label: 'API Key',\n required: true,\n admin: {\n description: 'Your Resend API key',\n },\n },\n {\n name: 'audienceIds',\n type: 'array',\n label: 'Audience IDs by Locale',\n fields: [\n {\n name: 'locale',\n type: 'select',\n label: 'Locale',\n required: true,\n options: pluginConfig.i18n?.locales?.map(locale => ({\n label: locale.toUpperCase(),\n value: locale,\n })) || [\n { label: 'EN', value: 'en' },\n ],\n },\n {\n name: 'production',\n type: 'text',\n label: 'Production Audience ID',\n },\n {\n name: 'development',\n type: 'text',\n label: 'Development Audience ID',\n },\n ],\n },\n ],\n },\n {\n name: 'broadcastSettings',\n type: 'group',\n label: 'Broadcast Settings',\n admin: {\n condition: (data) => data?.provider === 'broadcast',\n },\n fields: [\n {\n name: 'apiUrl',\n type: 'text',\n label: 'API URL',\n required: true,\n admin: {\n description: 'Your Broadcast instance URL',\n },\n },\n {\n name: 'productionToken',\n type: 'text',\n label: 'Production Token',\n admin: {\n description: 'Token for production environment',\n },\n },\n {\n name: 'developmentToken',\n type: 'text',\n label: 'Development Token',\n admin: {\n description: 'Token for development environment',\n },\n },\n ],\n },\n {\n name: 'fromAddress',\n type: 'email',\n label: 'From Address',\n required: true,\n admin: {\n description: 'Default sender email address',\n },\n },\n {\n name: 'fromName',\n type: 'text',\n label: 'From Name',\n required: true,\n admin: {\n description: 'Default sender name',\n },\n },\n {\n name: 'replyTo',\n type: 'email',\n label: 'Reply-To Address',\n admin: {\n description: 'Optional reply-to email address',\n },\n },\n ],\n },\n {\n label: 'Email Templates',\n fields: [\n {\n name: 'emailTemplates',\n type: 'group',\n label: 'Email Templates',\n fields: [\n {\n name: 'welcome',\n type: 'group',\n label: 'Welcome Email',\n fields: [\n {\n name: 'enabled',\n type: 'checkbox',\n label: 'Send Welcome Email',\n defaultValue: true,\n },\n {\n name: 'subject',\n type: 'text',\n label: 'Subject Line',\n defaultValue: 'Welcome to {{fromName}}!',\n admin: {\n condition: (data) => data?.emailTemplates?.welcome?.enabled,\n },\n },\n {\n name: 'preheader',\n type: 'text',\n label: 'Preheader Text',\n admin: {\n condition: (data) => data?.emailTemplates?.welcome?.enabled,\n },\n },\n ],\n },\n {\n name: 'magicLink',\n type: 'group',\n label: 'Magic Link Email',\n fields: [\n {\n name: 'subject',\n type: 'text',\n label: 'Subject Line',\n defaultValue: 'Sign in to {{fromName}}',\n },\n {\n name: 'preheader',\n type: 'text',\n label: 'Preheader Text',\n defaultValue: 'Click the link to access your preferences',\n },\n {\n name: 'expirationTime',\n type: 'select',\n label: 'Link Expiration',\n defaultValue: '7d',\n options: [\n { label: '1 hour', value: '1h' },\n { label: '24 hours', value: '24h' },\n { label: '7 days', value: '7d' },\n { label: '30 days', value: '30d' },\n ],\n },\n ],\n },\n ],\n },\n ],\n },\n {\n label: 'Subscription Settings',\n fields: [\n {\n name: 'subscriptionSettings',\n type: 'group',\n label: 'Subscription Settings',\n fields: [\n {\n name: 'requireDoubleOptIn',\n type: 'checkbox',\n label: 'Require Double Opt-In',\n defaultValue: false,\n admin: {\n description: 'Require email confirmation before activating subscriptions',\n },\n },\n {\n name: 'allowedDomains',\n type: 'array',\n label: 'Allowed Email Domains',\n admin: {\n description: 'Leave empty to allow all domains',\n },\n fields: [\n {\n name: 'domain',\n type: 'text',\n label: 'Domain',\n required: true,\n admin: {\n placeholder: 'example.com',\n },\n },\n ],\n },\n {\n name: 'maxSubscribersPerIP',\n type: 'number',\n label: 'Max Subscribers per IP',\n defaultValue: 10,\n min: 1,\n admin: {\n description: 'Maximum number of subscriptions allowed from a single IP address',\n },\n },\n ],\n },\n ],\n },\n ],\n },\n ],\n hooks: {\n beforeChange: [\n async ({ data, req, operation }) => {\n // Verify admin access for settings changes\n if (!req.user || req.user.collection !== 'users') {\n throw new Error('Only administrators can modify newsletter settings')\n }\n \n // If setting this config as active, deactivate all others\n if (data?.active && operation !== 'create') {\n await req.payload.update({\n collection: slug,\n where: {\n id: {\n not_equals: data.id,\n },\n },\n data: {\n active: false,\n },\n // Keep overrideAccess: true for admin operations after verification\n })\n }\n \n // For new configs, ensure only one is active\n if (operation === 'create' && data?.active) {\n const existingActive = await req.payload.find({\n collection: slug,\n where: {\n active: {\n equals: true,\n },\n },\n // Keep overrideAccess: true for admin operations\n })\n \n if (existingActive.docs.length > 0) {\n // Deactivate existing active configs\n for (const doc of existingActive.docs) {\n await req.payload.update({\n collection: slug,\n id: doc.id,\n data: {\n active: false,\n },\n // Keep overrideAccess: true for admin operations\n })\n }\n }\n }\n \n return data\n },\n ],\n afterChange: [\n async ({ doc, req }) => {\n // Reinitialize email service when settings change\n if ((req.payload as any).newsletterEmailService && doc.active) {\n try {\n // TODO: Implement email service reinitialization\n console.warn('Newsletter settings updated, reinitializing service...')\n } catch {\n // Failed to reinitialize email service\n }\n }\n \n return doc\n },\n ],\n },\n access: {\n read: () => true, // Settings can be read publicly for validation\n create: adminOnly(pluginConfig),\n update: adminOnly(pluginConfig),\n delete: adminOnly(pluginConfig),\n },\n timestamps: true,\n }\n}"],"names":["adminOnly","createNewsletterSettingsCollection","pluginConfig","slug","settingsSlug","labels","singular","plural","admin","useAsTitle","defaultColumns","group","description","fields","name","type","label","required","defaultValue","tabs","options","value","providers","default","condition","data","provider","i18n","locales","map","locale","toUpperCase","emailTemplates","welcome","enabled","placeholder","min","hooks","beforeChange","req","operation","user","collection","Error","active","payload","update","where","id","not_equals","existingActive","find","equals","docs","length","doc","afterChange","newsletterEmailService","console","warn","access","read","create","delete","timestamps"],"mappings":"AAEA,SAASA,SAAS,QAAQ,kBAAiB;AAE3C,OAAO,MAAMC,qCAAqC,CAChDC;IAEA,MAAMC,OAAOD,aAAaE,YAAY,IAAI;IAE1C,OAAO;QACLD;QACAE,QAAQ;YACNC,UAAU;YACVC,QAAQ;QACV;QACAC,OAAO;YACLC,YAAY;YACZC,gBAAgB;gBAAC;gBAAQ;gBAAY;gBAAU;aAAY;YAC3DC,OAAO;YACPC,aAAa;QACf;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNC,OAAO;gBACPC,UAAU;gBACVT,OAAO;oBACLI,aAAa;gBACf;YACF;YACA;gBACEE,MAAM;gBACNC,MAAM;gBACNC,OAAO;gBACPE,cAAc;gBACdV,OAAO;oBACLI,aAAa;gBACf;YACF;YACA;gBACEG,MAAM;gBACNI,MAAM;oBACJ;wBACEH,OAAO;wBACPH,QAAQ;4BACN;gCACEC,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPC,UAAU;gCACVG,SAAS;oCACP;wCAAEJ,OAAO;wCAAUK,OAAO;oCAAS;oCACnC;wCAAEL,OAAO;wCAA2BK,OAAO;oCAAY;iCACxD;gCACDH,cAAchB,aAAaoB,SAAS,CAACC,OAAO;gCAC5Cf,OAAO;oCACLI,aAAa;gCACf;4BACF;4BACA;gCACEE,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPR,OAAO;oCACLgB,WAAW,CAACC,OAASA,MAAMC,aAAa;gCAC1C;gCACAb,QAAQ;oCACN;wCACEC,MAAM;wCACNC,MAAM;wCACNC,OAAO;wCACPC,UAAU;wCACVT,OAAO;4CACLI,aAAa;wCACf;oCACF;oCACA;wCACEE,MAAM;wCACNC,MAAM;wCACNC,OAAO;wCACPH,QAAQ;4CACN;gDACEC,MAAM;gDACNC,MAAM;gDACNC,OAAO;gDACPC,UAAU;gDACVG,SAASlB,aAAayB,IAAI,EAAEC,SAASC,IAAIC,CAAAA,SAAW,CAAA;wDAClDd,OAAOc,OAAOC,WAAW;wDACzBV,OAAOS;oDACT,CAAA,MAAO;oDACL;wDAAEd,OAAO;wDAAMK,OAAO;oDAAK;iDAC5B;4CACH;4CACA;gDACEP,MAAM;gDACNC,MAAM;gDACNC,OAAO;4CACT;4CACA;gDACEF,MAAM;gDACNC,MAAM;gDACNC,OAAO;4CACT;yCACD;oCACH;iCACD;4BACH;4BACA;gCACEF,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPR,OAAO;oCACLgB,WAAW,CAACC,OAASA,MAAMC,aAAa;gCAC1C;gCACAb,QAAQ;oCACN;wCACEC,MAAM;wCACNC,MAAM;wCACNC,OAAO;wCACPC,UAAU;wCACVT,OAAO;4CACLI,aAAa;wCACf;oCACF;oCACA;wCACEE,MAAM;wCACNC,MAAM;wCACNC,OAAO;wCACPR,OAAO;4CACLI,aAAa;wCACf;oCACF;oCACA;wCACEE,MAAM;wCACNC,MAAM;wCACNC,OAAO;wCACPR,OAAO;4CACLI,aAAa;wCACf;oCACF;iCACD;4BACH;4BACA;gCACEE,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPC,UAAU;gCACVT,OAAO;oCACLI,aAAa;gCACf;4BACF;4BACA;gCACEE,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPC,UAAU;gCACVT,OAAO;oCACLI,aAAa;gCACf;4BACF;4BACA;gCACEE,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPR,OAAO;oCACLI,aAAa;gCACf;4BACF;yBACD;oBACH;oBACA;wBACEI,OAAO;wBACPH,QAAQ;4BACN;gCACEC,MAAM;gCACNC,MAAM;gCACNC,OAAO;gCACPH,QAAQ;oCACN;wCACEC,MAAM;wCACNC,MAAM;wCACNC,OAAO;wCACPH,QAAQ;4CACN;gDACEC,MAAM;gDACNC,MAAM;gDACNC,OAAO;gDACPE,cAAc;4CAChB;4CACA;gDACEJ,MAAM;gDACNC,MAAM;gDACNC,OAAO;gDACPE,cAAc;gDACdV,OAAO;oDACLgB,WAAW,CAACC,OAASA,MAAMO,gBAAgBC,SAASC;gDACtD;4CACF;4CACA;gDACEpB,MAAM;gDACNC,MAAM;gDACNC,OAAO;gDACPR,OAAO;oDACLgB,WAAW,CAACC,OAASA,MAAMO,gBAAgBC,SAASC;gDACtD;4CACF;yCACD;oCACH;oCACA;wCACEpB,MAAM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"}
@@ -231,16 +231,16 @@ export const createSubscribersCollection = (pluginConfig)=>{
231
231
  if (emailService) {
232
232
  try {
233
233
  await emailService.addContact(doc);
234
- } catch (error) {
235
- console.error('Failed to add contact to email service:', error);
234
+ } catch {
235
+ // Failed to add contact to email service
236
236
  }
237
237
  }
238
238
  // Send welcome email if active
239
239
  if (doc.subscriptionStatus === 'active' && emailService) {
240
240
  try {
241
241
  // TODO: Send welcome email
242
- } catch (error) {
243
- console.error('Failed to send welcome email:', error);
242
+ } catch {
243
+ // Failed to send welcome email
244
244
  }
245
245
  }
246
246
  // Custom after subscribe hook
@@ -258,8 +258,8 @@ export const createSubscribersCollection = (pluginConfig)=>{
258
258
  if (doc.subscriptionStatus !== previousDoc.subscriptionStatus && emailService) {
259
259
  try {
260
260
  await emailService.updateContact(doc);
261
- } catch (error) {
262
- console.error('Failed to update contact in email service:', error);
261
+ } catch {
262
+ // Failed to update contact in email service
263
263
  }
264
264
  }
265
265
  // Handle unsubscribe
@@ -288,8 +288,8 @@ export const createSubscribersCollection = (pluginConfig)=>{
288
288
  id
289
289
  });
290
290
  await emailService.removeContact(doc.email);
291
- } catch (error) {
292
- console.error('Failed to remove contact from email service:', error);
291
+ } catch {
292
+ // Failed to remove contact from email service
293
293
  }
294
294
  }
295
295
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/collections/Subscribers.ts"],"sourcesContent":["import type { CollectionConfig, Field, CollectionAfterChangeHook, CollectionBeforeDeleteHook } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\nimport { adminOnly, adminOrSelf } from '../utils/access'\n\nexport const createSubscribersCollection = (\n pluginConfig: NewsletterPluginConfig\n): CollectionConfig => {\n const slug = pluginConfig.subscribersSlug || 'subscribers'\n \n // Default fields for the subscribers collection\n const defaultFields: Field[] = [\n // Core fields\n {\n name: 'email',\n type: 'email',\n required: true,\n unique: true,\n admin: {\n description: 'Subscriber email address',\n },\n },\n {\n name: 'name',\n type: 'text',\n admin: {\n description: 'Subscriber full name',\n },\n },\n {\n name: 'locale',\n type: 'select',\n options: pluginConfig.i18n?.locales?.map(locale => ({\n label: locale.toUpperCase(),\n value: locale,\n })) || [\n { label: 'EN', value: 'en' },\n ],\n defaultValue: pluginConfig.i18n?.defaultLocale || 'en',\n admin: {\n description: 'Preferred language for communications',\n },\n },\n \n // Authentication fields (hidden from admin UI)\n {\n name: 'magicLinkToken',\n type: 'text',\n hidden: true,\n },\n {\n name: 'magicLinkTokenExpiry',\n type: 'date',\n hidden: true,\n },\n \n // Subscription status\n {\n name: 'subscriptionStatus',\n type: 'select',\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Unsubscribed', value: 'unsubscribed' },\n { label: 'Pending', value: 'pending' },\n ],\n defaultValue: 'pending',\n required: true,\n admin: {\n description: 'Current subscription status',\n },\n },\n {\n name: 'unsubscribedAt',\n type: 'date',\n admin: {\n condition: (data) => data?.subscriptionStatus === 'unsubscribed',\n description: 'When the user unsubscribed',\n readOnly: true,\n },\n },\n \n // Email preferences\n {\n name: 'emailPreferences',\n type: 'group',\n fields: [\n {\n name: 'newsletter',\n type: 'checkbox',\n defaultValue: true,\n label: 'Newsletter',\n admin: {\n description: 'Receive regular newsletter updates',\n },\n },\n {\n name: 'announcements',\n type: 'checkbox',\n defaultValue: true,\n label: 'Announcements',\n admin: {\n description: 'Receive important announcements',\n },\n },\n ],\n admin: {\n description: 'Email communication preferences',\n },\n },\n \n // Source tracking\n {\n name: 'source',\n type: 'text',\n admin: {\n description: 'Where the subscriber signed up from',\n },\n },\n ]\n\n // Add UTM tracking fields if enabled\n if (pluginConfig.features?.utmTracking?.enabled) {\n const utmFields = pluginConfig.features.utmTracking.fields || [\n 'source',\n 'medium',\n 'campaign',\n 'content',\n 'term',\n ]\n \n defaultFields.push({\n name: 'utmParameters',\n type: 'group',\n fields: utmFields.map(field => ({\n name: field,\n type: 'text',\n admin: {\n description: `UTM ${field} parameter`,\n },\n })),\n admin: {\n description: 'UTM tracking parameters',\n },\n })\n }\n\n // Add signup metadata\n defaultFields.push({\n name: 'signupMetadata',\n type: 'group',\n fields: [\n {\n name: 'ipAddress',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'userAgent',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'referrer',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'signupPage',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n ],\n admin: {\n description: 'Technical information about signup',\n },\n })\n\n // Add lead magnet field if enabled\n if (pluginConfig.features?.leadMagnets?.enabled) {\n defaultFields.push({\n name: 'leadMagnet',\n type: 'relationship',\n relationTo: pluginConfig.features.leadMagnets.collection || 'media',\n admin: {\n description: 'Lead magnet downloaded at signup',\n },\n })\n }\n\n // Allow field customization\n let fields = defaultFields\n if (pluginConfig.fields?.overrides) {\n fields = pluginConfig.fields.overrides({ defaultFields })\n }\n if (pluginConfig.fields?.additional) {\n fields = [...fields, ...pluginConfig.fields.additional]\n }\n\n const subscribersCollection: CollectionConfig = {\n slug,\n labels: {\n singular: 'Subscriber',\n plural: 'Subscribers',\n },\n admin: {\n useAsTitle: 'email',\n defaultColumns: ['email', 'name', 'subscriptionStatus', 'createdAt'],\n group: 'Newsletter',\n },\n fields,\n hooks: {\n afterChange: [\n async ({ doc, req, operation, previousDoc }) => {\n // After create logic\n if (operation === 'create') {\n // Add to email service\n const emailService = (req.payload as any).newsletterEmailService\n if (emailService) {\n try {\n await emailService.addContact(doc)\n } catch (error) {\n console.error('Failed to add contact to email service:', error)\n }\n }\n\n // Send welcome email if active\n if (doc.subscriptionStatus === 'active' && emailService) {\n try {\n // TODO: Send welcome email\n } catch (error) {\n console.error('Failed to send welcome email:', error)\n }\n }\n\n // Custom after subscribe hook\n if (pluginConfig.hooks?.afterSubscribe) {\n await pluginConfig.hooks.afterSubscribe({ doc, req })\n }\n }\n \n // After update logic\n if (operation === 'update' && previousDoc) {\n // Update email service if status changed\n const emailService = (req.payload as any).newsletterEmailService\n if (\n doc.subscriptionStatus !== previousDoc.subscriptionStatus &&\n emailService\n ) {\n try {\n await emailService.updateContact(doc)\n } catch (error) {\n console.error('Failed to update contact in email service:', error)\n }\n }\n\n // Handle unsubscribe\n if (\n doc.subscriptionStatus === 'unsubscribed' &&\n previousDoc.subscriptionStatus !== 'unsubscribed'\n ) {\n // Set unsubscribed timestamp\n doc.unsubscribedAt = new Date().toISOString()\n \n // Custom after unsubscribe hook\n if (pluginConfig.hooks?.afterUnsubscribe) {\n await pluginConfig.hooks.afterUnsubscribe({ doc, req })\n }\n }\n }\n },\n ] as CollectionAfterChangeHook[],\n beforeDelete: [\n async ({ id, req }) => {\n // Remove from email service\n const emailService = (req.payload as any).newsletterEmailService\n if (emailService) {\n try {\n const doc = await req.payload.findByID({\n collection: slug,\n id,\n })\n await emailService.removeContact(doc.email)\n } catch (error) {\n console.error('Failed to remove contact from email service:', error)\n }\n }\n },\n ] as CollectionBeforeDeleteHook[],\n },\n access: {\n create: () => true, // Public can subscribe\n read: adminOrSelf(pluginConfig),\n update: adminOrSelf(pluginConfig),\n delete: adminOnly(pluginConfig),\n },\n timestamps: true,\n }\n\n return subscribersCollection\n}"],"names":["adminOnly","adminOrSelf","createSubscribersCollection","pluginConfig","slug","subscribersSlug","defaultFields","name","type","required","unique","admin","description","options","i18n","locales","map","locale","label","toUpperCase","value","defaultValue","defaultLocale","hidden","condition","data","subscriptionStatus","readOnly","fields","features","utmTracking","enabled","utmFields","push","field","leadMagnets","relationTo","collection","overrides","additional","subscribersCollection","labels","singular","plural","useAsTitle","defaultColumns","group","hooks","afterChange","doc","req","operation","previousDoc","emailService","payload","newsletterEmailService","addContact","error","console","afterSubscribe","updateContact","unsubscribedAt","Date","toISOString","afterUnsubscribe","beforeDelete","id","findByID","removeContact","email","access","create","read","update","delete","timestamps"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/collections/Subscribers.ts"],"sourcesContent":["import type { CollectionConfig, Field, CollectionAfterChangeHook, CollectionBeforeDeleteHook } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\nimport { adminOnly, adminOrSelf } from '../utils/access'\n\nexport const createSubscribersCollection = (\n pluginConfig: NewsletterPluginConfig\n): CollectionConfig => {\n const slug = pluginConfig.subscribersSlug || 'subscribers'\n \n // Default fields for the subscribers collection\n const defaultFields: Field[] = [\n // Core fields\n {\n name: 'email',\n type: 'email',\n required: true,\n unique: true,\n admin: {\n description: 'Subscriber email address',\n },\n },\n {\n name: 'name',\n type: 'text',\n admin: {\n description: 'Subscriber full name',\n },\n },\n {\n name: 'locale',\n type: 'select',\n options: pluginConfig.i18n?.locales?.map(locale => ({\n label: locale.toUpperCase(),\n value: locale,\n })) || [\n { label: 'EN', value: 'en' },\n ],\n defaultValue: pluginConfig.i18n?.defaultLocale || 'en',\n admin: {\n description: 'Preferred language for communications',\n },\n },\n \n // Authentication fields (hidden from admin UI)\n {\n name: 'magicLinkToken',\n type: 'text',\n hidden: true,\n },\n {\n name: 'magicLinkTokenExpiry',\n type: 'date',\n hidden: true,\n },\n \n // Subscription status\n {\n name: 'subscriptionStatus',\n type: 'select',\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Unsubscribed', value: 'unsubscribed' },\n { label: 'Pending', value: 'pending' },\n ],\n defaultValue: 'pending',\n required: true,\n admin: {\n description: 'Current subscription status',\n },\n },\n {\n name: 'unsubscribedAt',\n type: 'date',\n admin: {\n condition: (data) => data?.subscriptionStatus === 'unsubscribed',\n description: 'When the user unsubscribed',\n readOnly: true,\n },\n },\n \n // Email preferences\n {\n name: 'emailPreferences',\n type: 'group',\n fields: [\n {\n name: 'newsletter',\n type: 'checkbox',\n defaultValue: true,\n label: 'Newsletter',\n admin: {\n description: 'Receive regular newsletter updates',\n },\n },\n {\n name: 'announcements',\n type: 'checkbox',\n defaultValue: true,\n label: 'Announcements',\n admin: {\n description: 'Receive important announcements',\n },\n },\n ],\n admin: {\n description: 'Email communication preferences',\n },\n },\n \n // Source tracking\n {\n name: 'source',\n type: 'text',\n admin: {\n description: 'Where the subscriber signed up from',\n },\n },\n ]\n\n // Add UTM tracking fields if enabled\n if (pluginConfig.features?.utmTracking?.enabled) {\n const utmFields = pluginConfig.features.utmTracking.fields || [\n 'source',\n 'medium',\n 'campaign',\n 'content',\n 'term',\n ]\n \n defaultFields.push({\n name: 'utmParameters',\n type: 'group',\n fields: utmFields.map(field => ({\n name: field,\n type: 'text',\n admin: {\n description: `UTM ${field} parameter`,\n },\n })),\n admin: {\n description: 'UTM tracking parameters',\n },\n })\n }\n\n // Add signup metadata\n defaultFields.push({\n name: 'signupMetadata',\n type: 'group',\n fields: [\n {\n name: 'ipAddress',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'userAgent',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'referrer',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'signupPage',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n ],\n admin: {\n description: 'Technical information about signup',\n },\n })\n\n // Add lead magnet field if enabled\n if (pluginConfig.features?.leadMagnets?.enabled) {\n defaultFields.push({\n name: 'leadMagnet',\n type: 'relationship',\n relationTo: pluginConfig.features.leadMagnets.collection || 'media',\n admin: {\n description: 'Lead magnet downloaded at signup',\n },\n })\n }\n\n // Allow field customization\n let fields = defaultFields\n if (pluginConfig.fields?.overrides) {\n fields = pluginConfig.fields.overrides({ defaultFields })\n }\n if (pluginConfig.fields?.additional) {\n fields = [...fields, ...pluginConfig.fields.additional]\n }\n\n const subscribersCollection: CollectionConfig = {\n slug,\n labels: {\n singular: 'Subscriber',\n plural: 'Subscribers',\n },\n admin: {\n useAsTitle: 'email',\n defaultColumns: ['email', 'name', 'subscriptionStatus', 'createdAt'],\n group: 'Newsletter',\n },\n fields,\n hooks: {\n afterChange: [\n async ({ doc, req, operation, previousDoc }) => {\n // After create logic\n if (operation === 'create') {\n // Add to email service\n const emailService = (req.payload as any).newsletterEmailService\n if (emailService) {\n try {\n await emailService.addContact(doc)\n } catch {\n // Failed to add contact to email service\n }\n }\n\n // Send welcome email if active\n if (doc.subscriptionStatus === 'active' && emailService) {\n try {\n // TODO: Send welcome email\n } catch {\n // Failed to send welcome email\n }\n }\n\n // Custom after subscribe hook\n if (pluginConfig.hooks?.afterSubscribe) {\n await pluginConfig.hooks.afterSubscribe({ doc, req })\n }\n }\n \n // After update logic\n if (operation === 'update' && previousDoc) {\n // Update email service if status changed\n const emailService = (req.payload as any).newsletterEmailService\n if (\n doc.subscriptionStatus !== previousDoc.subscriptionStatus &&\n emailService\n ) {\n try {\n await emailService.updateContact(doc)\n } catch {\n // Failed to update contact in email service\n }\n }\n\n // Handle unsubscribe\n if (\n doc.subscriptionStatus === 'unsubscribed' &&\n previousDoc.subscriptionStatus !== 'unsubscribed'\n ) {\n // Set unsubscribed timestamp\n doc.unsubscribedAt = new Date().toISOString()\n \n // Custom after unsubscribe hook\n if (pluginConfig.hooks?.afterUnsubscribe) {\n await pluginConfig.hooks.afterUnsubscribe({ doc, req })\n }\n }\n }\n },\n ] as CollectionAfterChangeHook[],\n beforeDelete: [\n async ({ id, req }) => {\n // Remove from email service\n const emailService = (req.payload as any).newsletterEmailService\n if (emailService) {\n try {\n const doc = await req.payload.findByID({\n collection: slug,\n id,\n })\n await emailService.removeContact(doc.email)\n } catch {\n // Failed to remove contact from email service\n }\n }\n },\n ] as CollectionBeforeDeleteHook[],\n },\n access: {\n create: () => true, // Public can subscribe\n read: adminOrSelf(pluginConfig),\n update: adminOrSelf(pluginConfig),\n delete: adminOnly(pluginConfig),\n },\n timestamps: true,\n }\n\n return subscribersCollection\n}"],"names":["adminOnly","adminOrSelf","createSubscribersCollection","pluginConfig","slug","subscribersSlug","defaultFields","name","type","required","unique","admin","description","options","i18n","locales","map","locale","label","toUpperCase","value","defaultValue","defaultLocale","hidden","condition","data","subscriptionStatus","readOnly","fields","features","utmTracking","enabled","utmFields","push","field","leadMagnets","relationTo","collection","overrides","additional","subscribersCollection","labels","singular","plural","useAsTitle","defaultColumns","group","hooks","afterChange","doc","req","operation","previousDoc","emailService","payload","newsletterEmailService","addContact","afterSubscribe","updateContact","unsubscribedAt","Date","toISOString","afterUnsubscribe","beforeDelete","id","findByID","removeContact","email","access","create","read","update","delete","timestamps"],"mappings":"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"}
@@ -6,9 +6,11 @@ export const createSubscribeEndpoint = (config)=>{
6
6
  handler: async (req, res)=>{
7
7
  try {
8
8
  const { email, name, source, preferences, leadMagnet, surveyResponses, metadata = {} } = req.body;
9
+ // Trim email before validation
10
+ const trimmedEmail = email?.trim();
9
11
  // Validate input
10
12
  const validation = validateSubscriberData({
11
- email,
13
+ email: trimmedEmail,
12
14
  name,
13
15
  source
14
16
  });
@@ -31,8 +33,8 @@ export const createSubscribeEndpoint = (config)=>{
31
33
  overrideAccess: false
32
34
  });
33
35
  const settings = settingsResult.docs[0];
34
- const allowedDomains = settings?.allowedDomains?.map((d)=>d.domain) || [];
35
- if (!isDomainAllowed(email, allowedDomains)) {
36
+ const allowedDomains = settings?.subscriptionSettings?.allowedDomains?.map((d)=>d.domain) || [];
37
+ if (!isDomainAllowed(trimmedEmail, allowedDomains)) {
36
38
  return res.status(400).json({
37
39
  success: false,
38
40
  error: 'Email domain not allowed'
@@ -44,9 +46,10 @@ export const createSubscribeEndpoint = (config)=>{
44
46
  collection: config.subscribersSlug || 'subscribers',
45
47
  where: {
46
48
  email: {
47
- equals: email.toLowerCase()
49
+ equals: trimmedEmail.toLowerCase()
48
50
  }
49
- }
51
+ },
52
+ overrideAccess: true
50
53
  });
51
54
  if (existing.docs.length > 0) {
52
55
  const subscriber = existing.docs[0];
@@ -69,14 +72,15 @@ export const createSubscribeEndpoint = (config)=>{
69
72
  }
70
73
  // Check IP rate limiting
71
74
  const ipAddress = req.ip || req.connection.remoteAddress;
72
- const maxPerIP = settings?.maxSubscribersPerIP || 10;
75
+ const maxPerIP = settings?.subscriptionSettings?.maxSubscribersPerIP || 10;
73
76
  const ipSubscribers = await req.payload.find({
74
77
  collection: config.subscribersSlug || 'subscribers',
75
78
  where: {
76
79
  'signupMetadata.ipAddress': {
77
80
  equals: ipAddress
78
81
  }
79
- }
82
+ },
83
+ overrideAccess: true
80
84
  });
81
85
  if (ipSubscribers.docs.length >= maxPerIP) {
82
86
  return res.status(429).json({
@@ -86,13 +90,20 @@ export const createSubscribeEndpoint = (config)=>{
86
90
  }
87
91
  // Extract UTM parameters
88
92
  const referer = req.headers.referer || req.headers.referrer || '';
89
- const utmParams = extractUTMParams(new URL(referer).searchParams);
93
+ let utmParams = {};
94
+ if (referer) {
95
+ try {
96
+ utmParams = extractUTMParams(new URL(referer).searchParams);
97
+ } catch {
98
+ // Invalid URL, ignore UTM params
99
+ }
100
+ }
90
101
  // Prepare subscriber data
91
102
  const subscriberData = {
92
- email: email.toLowerCase(),
103
+ email: trimmedEmail.toLowerCase(),
93
104
  name: name ? sanitizeInput(name) : undefined,
94
105
  locale: metadata.locale || config.i18n?.defaultLocale || 'en',
95
- subscriptionStatus: settings?.requireDoubleOptIn ? 'pending' : 'active',
106
+ subscriptionStatus: settings?.subscriptionSettings?.requireDoubleOptIn ? 'pending' : 'active',
96
107
  source: source || 'api',
97
108
  emailPreferences: {
98
109
  newsletter: true,
@@ -118,14 +129,15 @@ export const createSubscribeEndpoint = (config)=>{
118
129
  // Public endpoint needs to create subscribers
119
130
  const subscriber = await req.payload.create({
120
131
  collection: config.subscribersSlug || 'subscribers',
121
- data: subscriberData
132
+ data: subscriberData,
133
+ overrideAccess: true
122
134
  });
123
135
  // Handle survey responses if provided
124
136
  if (config.features?.surveys?.enabled && surveyResponses) {
125
137
  // TODO: Store survey responses
126
138
  }
127
139
  // Send confirmation email if double opt-in
128
- if (settings?.requireDoubleOptIn) {
140
+ if (settings?.subscriptionSettings?.requireDoubleOptIn) {
129
141
  // TODO: Send confirmation email with magic link
130
142
  }
131
143
  res.json({
@@ -135,10 +147,9 @@ export const createSubscribeEndpoint = (config)=>{
135
147
  email: subscriber.email,
136
148
  subscriptionStatus: subscriber.subscriptionStatus
137
149
  },
138
- message: settings?.requireDoubleOptIn ? 'Please check your email to confirm your subscription' : 'Successfully subscribed'
150
+ message: settings?.subscriptionSettings?.requireDoubleOptIn ? 'Please check your email to confirm your subscription' : 'Successfully subscribed'
139
151
  });
140
- } catch (error) {
141
- console.error('Subscribe endpoint error:', error);
152
+ } catch {
142
153
  res.status(500).json({
143
154
  success: false,
144
155
  error: 'Failed to subscribe. Please try again.'
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/endpoints/subscribe.ts"],"sourcesContent":["import type { Endpoint, PayloadHandler } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\nimport { \n isDomainAllowed, \n sanitizeInput, \n validateSubscriberData,\n extractUTMParams \n} from '../utils/validation'\n\nexport const createSubscribeEndpoint = (\n config: NewsletterPluginConfig\n): Endpoint => {\n return {\n path: '/newsletter/subscribe',\n method: 'post',\n handler: (async (req: any, res: any) => {\n try {\n const { \n email, \n name, \n source,\n preferences,\n leadMagnet,\n surveyResponses,\n metadata = {}\n } = req.body\n\n // Validate input\n const validation = validateSubscriberData({ email, name, source })\n if (!validation.valid) {\n return res.status(400).json({\n success: false,\n errors: validation.errors,\n })\n }\n\n // Check domain restrictions from active settings\n // Settings are public info needed for validation, but we can still respect access control\n const settingsResult = await req.payload.find({\n collection: config.settingsSlug || 'newsletter-settings',\n where: {\n active: {\n equals: true,\n },\n },\n limit: 1,\n overrideAccess: false,\n // No user context for public endpoint\n })\n \n const settings = settingsResult.docs[0]\n\n const allowedDomains = settings?.allowedDomains?.map((d: any) => d.domain) || []\n if (!isDomainAllowed(email, allowedDomains)) {\n return res.status(400).json({\n success: false,\n error: 'Email domain not allowed',\n })\n }\n\n // Check if already subscribed\n // This needs admin access to check for existing email\n const existing = await req.payload.find({\n collection: config.subscribersSlug || 'subscribers',\n where: {\n email: {\n equals: email.toLowerCase(),\n },\n },\n // Keep overrideAccess: true for public subscription check\n })\n\n if (existing.docs.length > 0) {\n const subscriber = existing.docs[0]\n \n // If unsubscribed, don't allow resubscription via API\n if (subscriber.subscriptionStatus === 'unsubscribed') {\n return res.status(400).json({\n success: false,\n error: 'This email has been unsubscribed. Please contact support to resubscribe.',\n })\n }\n\n return res.status(400).json({\n success: false,\n error: 'Already subscribed',\n subscriber: {\n id: subscriber.id,\n email: subscriber.email,\n subscriptionStatus: subscriber.subscriptionStatus,\n },\n })\n }\n\n // Check IP rate limiting\n const ipAddress = req.ip || req.connection.remoteAddress\n const maxPerIP = settings?.maxSubscribersPerIP || 10\n\n const ipSubscribers = await req.payload.find({\n collection: config.subscribersSlug || 'subscribers',\n where: {\n 'signupMetadata.ipAddress': {\n equals: ipAddress,\n },\n },\n // Keep overrideAccess: true for rate limiting check\n })\n\n if (ipSubscribers.docs.length >= maxPerIP) {\n return res.status(429).json({\n success: false,\n error: 'Too many subscriptions from this IP address',\n })\n }\n\n // Extract UTM parameters\n const referer = req.headers.referer || req.headers.referrer || ''\n const utmParams = extractUTMParams(new URL(referer).searchParams)\n\n // Prepare subscriber data\n const subscriberData: any = {\n email: email.toLowerCase(),\n name: name ? sanitizeInput(name) : undefined,\n locale: metadata.locale || config.i18n?.defaultLocale || 'en',\n subscriptionStatus: settings?.requireDoubleOptIn ? 'pending' : 'active',\n source: source || 'api',\n emailPreferences: {\n newsletter: true,\n announcements: true,\n ...(preferences || {}),\n },\n signupMetadata: {\n ipAddress,\n userAgent: req.headers['user-agent'],\n referrer: referer,\n signupPage: metadata.signupPage || referer,\n },\n }\n\n // Add UTM parameters if tracking is enabled\n if (config.features?.utmTracking?.enabled && Object.keys(utmParams).length > 0) {\n subscriberData.utmParameters = utmParams\n }\n\n // Add lead magnet if provided\n if (config.features?.leadMagnets?.enabled && leadMagnet) {\n subscriberData.leadMagnet = leadMagnet\n }\n\n // Create subscriber\n // Public endpoint needs to create subscribers\n const subscriber = await req.payload.create({\n collection: config.subscribersSlug || 'subscribers',\n data: subscriberData,\n // Keep overrideAccess: true for public subscription\n })\n\n // Handle survey responses if provided\n if (config.features?.surveys?.enabled && surveyResponses) {\n // TODO: Store survey responses\n }\n\n // Send confirmation email if double opt-in\n if (settings?.requireDoubleOptIn) {\n // TODO: Send confirmation email with magic link\n }\n\n res.json({\n success: true,\n subscriber: {\n id: subscriber.id,\n email: subscriber.email,\n subscriptionStatus: subscriber.subscriptionStatus,\n },\n message: settings?.requireDoubleOptIn \n ? 'Please check your email to confirm your subscription'\n : 'Successfully subscribed',\n })\n } catch (error) {\n console.error('Subscribe endpoint error:', error)\n res.status(500).json({\n success: false,\n error: 'Failed to subscribe. Please try again.',\n })\n }\n }) as PayloadHandler,\n }\n}"],"names":["isDomainAllowed","sanitizeInput","validateSubscriberData","extractUTMParams","createSubscribeEndpoint","config","path","method","handler","req","res","email","name","source","preferences","leadMagnet","surveyResponses","metadata","body","validation","valid","status","json","success","errors","settingsResult","payload","find","collection","settingsSlug","where","active","equals","limit","overrideAccess","settings","docs","allowedDomains","map","d","domain","error","existing","subscribersSlug","toLowerCase","length","subscriber","subscriptionStatus","id","ipAddress","ip","connection","remoteAddress","maxPerIP","maxSubscribersPerIP","ipSubscribers","referer","headers","referrer","utmParams","URL","searchParams","subscriberData","undefined","locale","i18n","defaultLocale","requireDoubleOptIn","emailPreferences","newsletter","announcements","signupMetadata","userAgent","signupPage","features","utmTracking","enabled","Object","keys","utmParameters","leadMagnets","create","data","surveys","message","console"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/endpoints/subscribe.ts"],"sourcesContent":["import type { Endpoint, PayloadHandler } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\nimport { \n isDomainAllowed, \n sanitizeInput, \n validateSubscriberData,\n extractUTMParams \n} from '../utils/validation'\n\nexport const createSubscribeEndpoint = (\n config: NewsletterPluginConfig\n): Endpoint => {\n return {\n path: '/newsletter/subscribe',\n method: 'post',\n handler: (async (req: any, res: any) => {\n try {\n const { \n email, \n name, \n source,\n preferences,\n leadMagnet,\n surveyResponses,\n metadata = {}\n } = req.body\n\n // Trim email before validation\n const trimmedEmail = email?.trim()\n\n // Validate input\n const validation = validateSubscriberData({ email: trimmedEmail, name, source })\n if (!validation.valid) {\n return res.status(400).json({\n success: false,\n errors: validation.errors,\n })\n }\n\n // Check domain restrictions from active settings\n // Settings are public info needed for validation, but we can still respect access control\n const settingsResult = await req.payload.find({\n collection: config.settingsSlug || 'newsletter-settings',\n where: {\n active: {\n equals: true,\n },\n },\n limit: 1,\n overrideAccess: false,\n // No user context for public endpoint\n })\n \n const settings = settingsResult.docs[0]\n\n const allowedDomains = settings?.subscriptionSettings?.allowedDomains?.map((d: any) => d.domain) || []\n if (!isDomainAllowed(trimmedEmail, allowedDomains)) {\n return res.status(400).json({\n success: false,\n error: 'Email domain not allowed',\n })\n }\n\n // Check if already subscribed\n // This needs admin access to check for existing email\n const existing = await req.payload.find({\n collection: config.subscribersSlug || 'subscribers',\n where: {\n email: {\n equals: trimmedEmail.toLowerCase(),\n },\n },\n overrideAccess: true, // Need to check for duplicates in public endpoint\n })\n\n if (existing.docs.length > 0) {\n const subscriber = existing.docs[0]\n \n // If unsubscribed, don't allow resubscription via API\n if (subscriber.subscriptionStatus === 'unsubscribed') {\n return res.status(400).json({\n success: false,\n error: 'This email has been unsubscribed. Please contact support to resubscribe.',\n })\n }\n\n return res.status(400).json({\n success: false,\n error: 'Already subscribed',\n subscriber: {\n id: subscriber.id,\n email: subscriber.email,\n subscriptionStatus: subscriber.subscriptionStatus,\n },\n })\n }\n\n // Check IP rate limiting\n const ipAddress = req.ip || req.connection.remoteAddress\n const maxPerIP = settings?.subscriptionSettings?.maxSubscribersPerIP || 10\n\n const ipSubscribers = await req.payload.find({\n collection: config.subscribersSlug || 'subscribers',\n where: {\n 'signupMetadata.ipAddress': {\n equals: ipAddress,\n },\n },\n overrideAccess: true, // Need to check IP limits in public endpoint\n })\n\n if (ipSubscribers.docs.length >= maxPerIP) {\n return res.status(429).json({\n success: false,\n error: 'Too many subscriptions from this IP address',\n })\n }\n\n // Extract UTM parameters\n const referer = req.headers.referer || req.headers.referrer || ''\n let utmParams = {}\n if (referer) {\n try {\n utmParams = extractUTMParams(new URL(referer).searchParams)\n } catch {\n // Invalid URL, ignore UTM params\n }\n }\n\n // Prepare subscriber data\n const subscriberData: any = {\n email: trimmedEmail.toLowerCase(),\n name: name ? sanitizeInput(name) : undefined,\n locale: metadata.locale || config.i18n?.defaultLocale || 'en',\n subscriptionStatus: settings?.subscriptionSettings?.requireDoubleOptIn ? 'pending' : 'active',\n source: source || 'api',\n emailPreferences: {\n newsletter: true,\n announcements: true,\n ...(preferences || {}),\n },\n signupMetadata: {\n ipAddress,\n userAgent: req.headers['user-agent'],\n referrer: referer,\n signupPage: metadata.signupPage || referer,\n },\n }\n\n // Add UTM parameters if tracking is enabled\n if (config.features?.utmTracking?.enabled && Object.keys(utmParams).length > 0) {\n subscriberData.utmParameters = utmParams\n }\n\n // Add lead magnet if provided\n if (config.features?.leadMagnets?.enabled && leadMagnet) {\n subscriberData.leadMagnet = leadMagnet\n }\n\n // Create subscriber\n // Public endpoint needs to create subscribers\n const subscriber = await req.payload.create({\n collection: config.subscribersSlug || 'subscribers',\n data: subscriberData,\n overrideAccess: true, // Public endpoint needs to create subscribers\n })\n\n // Handle survey responses if provided\n if (config.features?.surveys?.enabled && surveyResponses) {\n // TODO: Store survey responses\n }\n\n // Send confirmation email if double opt-in\n if (settings?.subscriptionSettings?.requireDoubleOptIn) {\n // TODO: Send confirmation email with magic link\n }\n\n res.json({\n success: true,\n subscriber: {\n id: subscriber.id,\n email: subscriber.email,\n subscriptionStatus: subscriber.subscriptionStatus,\n },\n message: settings?.subscriptionSettings?.requireDoubleOptIn \n ? 'Please check your email to confirm your subscription'\n : 'Successfully subscribed',\n })\n } catch {\n res.status(500).json({\n success: false,\n error: 'Failed to subscribe. Please try again.',\n })\n }\n }) as PayloadHandler,\n }\n}"],"names":["isDomainAllowed","sanitizeInput","validateSubscriberData","extractUTMParams","createSubscribeEndpoint","config","path","method","handler","req","res","email","name","source","preferences","leadMagnet","surveyResponses","metadata","body","trimmedEmail","trim","validation","valid","status","json","success","errors","settingsResult","payload","find","collection","settingsSlug","where","active","equals","limit","overrideAccess","settings","docs","allowedDomains","subscriptionSettings","map","d","domain","error","existing","subscribersSlug","toLowerCase","length","subscriber","subscriptionStatus","id","ipAddress","ip","connection","remoteAddress","maxPerIP","maxSubscribersPerIP","ipSubscribers","referer","headers","referrer","utmParams","URL","searchParams","subscriberData","undefined","locale","i18n","defaultLocale","requireDoubleOptIn","emailPreferences","newsletter","announcements","signupMetadata","userAgent","signupPage","features","utmTracking","enabled","Object","keys","utmParameters","leadMagnets","create","data","surveys","message"],"mappings":"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"}
@@ -174,8 +174,7 @@ export function createNewsletterSchedulingFields(config) {
174
174
  return convertLexicalToMarkdown({
175
175
  data: data[config.richTextField]
176
176
  });
177
- } catch (error) {
178
- console.error('Failed to convert rich text to markdown:', error);
177
+ } catch {
179
178
  return '';
180
179
  }
181
180
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/fields/newsletterScheduling.ts"],"sourcesContent":["import type { Field } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\n\nexport function createNewsletterSchedulingFields(\n config: NewsletterPluginConfig\n): Field[] {\n const groupName = config.features?.newsletterScheduling?.fields?.groupName || 'newsletterScheduling'\n const contentField = config.features?.newsletterScheduling?.fields?.contentField || 'content'\n const createMarkdownField = config.features?.newsletterScheduling?.fields?.createMarkdownField !== false\n\n const fields: Field[] = [\n {\n name: groupName,\n type: 'group',\n label: 'Newsletter Scheduling',\n admin: {\n condition: (data, { user }) => user?.collection === 'users', // Only show for admin users\n },\n fields: [\n {\n name: 'scheduled',\n type: 'checkbox',\n label: 'Schedule for Newsletter',\n defaultValue: false,\n admin: {\n description: 'Schedule this content to be sent as a newsletter',\n },\n },\n {\n name: 'scheduledDate',\n type: 'date',\n label: 'Send Date',\n required: true,\n admin: {\n date: {\n pickerAppearance: 'dayAndTime',\n },\n condition: (data) => data?.[groupName]?.scheduled,\n description: 'When to send this newsletter',\n },\n },\n {\n name: 'sentDate',\n type: 'date',\n label: 'Sent Date',\n admin: {\n readOnly: true,\n condition: (data) => data?.[groupName]?.sendStatus === 'sent',\n description: 'When this newsletter was sent',\n },\n },\n {\n name: 'sendStatus',\n type: 'select',\n label: 'Status',\n options: [\n { label: 'Draft', value: 'draft' },\n { label: 'Scheduled', value: 'scheduled' },\n { label: 'Sending', value: 'sending' },\n { label: 'Sent', value: 'sent' },\n { label: 'Failed', value: 'failed' },\n ],\n defaultValue: 'draft',\n admin: {\n readOnly: true,\n description: 'Current send status',\n },\n },\n {\n name: 'emailSubject',\n type: 'text',\n label: 'Email Subject',\n required: true,\n admin: {\n condition: (data) => data?.[groupName]?.scheduled,\n description: 'Subject line for the newsletter email',\n },\n },\n {\n name: 'preheader',\n type: 'text',\n label: 'Email Preheader',\n admin: {\n condition: (data) => data?.[groupName]?.scheduled,\n description: 'Preview text that appears after the subject line',\n },\n },\n {\n name: 'segments',\n type: 'select',\n label: 'Target Segments',\n hasMany: true,\n options: [\n { label: 'All Subscribers', value: 'all' },\n ...(config.i18n?.locales?.map(locale => ({\n label: `${locale.toUpperCase()} Subscribers`,\n value: locale,\n })) || []),\n ],\n defaultValue: ['all'],\n admin: {\n condition: (data) => data?.[groupName]?.scheduled,\n description: 'Which subscriber segments to send to',\n },\n },\n {\n name: 'testEmails',\n type: 'array',\n label: 'Test Email Recipients',\n admin: {\n condition: (data) => data?.[groupName]?.scheduled && data?.[groupName]?.sendStatus === 'draft',\n description: 'Send test emails before scheduling',\n },\n fields: [\n {\n name: 'email',\n type: 'email',\n required: true,\n },\n ],\n },\n ],\n },\n ]\n\n // Add markdown companion field if requested\n if (createMarkdownField) {\n fields.push(createMarkdownFieldInternal({\n name: `${contentField}Markdown`,\n richTextField: contentField,\n label: 'Email Content (Markdown)',\n admin: {\n position: 'sidebar',\n condition: (data: any) => Boolean(data?.[contentField] && data?.[groupName]?.scheduled),\n description: 'Markdown version for email rendering',\n readOnly: true,\n },\n }))\n }\n\n return fields\n}\n\n/**\n * Create a markdown companion field for rich text\n * This creates a virtual field that converts rich text to markdown\n */\nfunction createMarkdownFieldInternal(config: {\n name: string\n richTextField: string\n label?: string\n admin?: any\n}): Field {\n return {\n name: config.name,\n type: 'textarea',\n label: config.label || 'Markdown',\n admin: {\n ...config.admin,\n description: config.admin?.description || 'Auto-generated from rich text content',\n },\n hooks: {\n afterRead: [\n async ({ data }) => {\n // Convert rich text to markdown on read\n if (data?.[config.richTextField]) {\n try {\n const { convertLexicalToMarkdown } = await import('@payloadcms/richtext-lexical')\n return convertLexicalToMarkdown({\n data: data[config.richTextField],\n } as any)\n } catch (error) {\n console.error('Failed to convert rich text to markdown:', error)\n return ''\n }\n }\n return ''\n },\n ],\n beforeChange: [\n () => {\n // Don't save markdown to database\n return null\n },\n ],\n },\n }\n}"],"names":["createNewsletterSchedulingFields","config","groupName","features","newsletterScheduling","fields","contentField","createMarkdownField","name","type","label","admin","condition","data","user","collection","defaultValue","description","required","date","pickerAppearance","scheduled","readOnly","sendStatus","options","value","hasMany","i18n","locales","map","locale","toUpperCase","push","createMarkdownFieldInternal","richTextField","position","Boolean","hooks","afterRead","convertLexicalToMarkdown","error","console","beforeChange"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/fields/newsletterScheduling.ts"],"sourcesContent":["import type { Field } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\n\nexport function createNewsletterSchedulingFields(\n config: NewsletterPluginConfig\n): Field[] {\n const groupName = config.features?.newsletterScheduling?.fields?.groupName || 'newsletterScheduling'\n const contentField = config.features?.newsletterScheduling?.fields?.contentField || 'content'\n const createMarkdownField = config.features?.newsletterScheduling?.fields?.createMarkdownField !== false\n\n const fields: Field[] = [\n {\n name: groupName,\n type: 'group',\n label: 'Newsletter Scheduling',\n admin: {\n condition: (data, { user }) => user?.collection === 'users', // Only show for admin users\n },\n fields: [\n {\n name: 'scheduled',\n type: 'checkbox',\n label: 'Schedule for Newsletter',\n defaultValue: false,\n admin: {\n description: 'Schedule this content to be sent as a newsletter',\n },\n },\n {\n name: 'scheduledDate',\n type: 'date',\n label: 'Send Date',\n required: true,\n admin: {\n date: {\n pickerAppearance: 'dayAndTime',\n },\n condition: (data) => data?.[groupName]?.scheduled,\n description: 'When to send this newsletter',\n },\n },\n {\n name: 'sentDate',\n type: 'date',\n label: 'Sent Date',\n admin: {\n readOnly: true,\n condition: (data) => data?.[groupName]?.sendStatus === 'sent',\n description: 'When this newsletter was sent',\n },\n },\n {\n name: 'sendStatus',\n type: 'select',\n label: 'Status',\n options: [\n { label: 'Draft', value: 'draft' },\n { label: 'Scheduled', value: 'scheduled' },\n { label: 'Sending', value: 'sending' },\n { label: 'Sent', value: 'sent' },\n { label: 'Failed', value: 'failed' },\n ],\n defaultValue: 'draft',\n admin: {\n readOnly: true,\n description: 'Current send status',\n },\n },\n {\n name: 'emailSubject',\n type: 'text',\n label: 'Email Subject',\n required: true,\n admin: {\n condition: (data) => data?.[groupName]?.scheduled,\n description: 'Subject line for the newsletter email',\n },\n },\n {\n name: 'preheader',\n type: 'text',\n label: 'Email Preheader',\n admin: {\n condition: (data) => data?.[groupName]?.scheduled,\n description: 'Preview text that appears after the subject line',\n },\n },\n {\n name: 'segments',\n type: 'select',\n label: 'Target Segments',\n hasMany: true,\n options: [\n { label: 'All Subscribers', value: 'all' },\n ...(config.i18n?.locales?.map(locale => ({\n label: `${locale.toUpperCase()} Subscribers`,\n value: locale,\n })) || []),\n ],\n defaultValue: ['all'],\n admin: {\n condition: (data) => data?.[groupName]?.scheduled,\n description: 'Which subscriber segments to send to',\n },\n },\n {\n name: 'testEmails',\n type: 'array',\n label: 'Test Email Recipients',\n admin: {\n condition: (data) => data?.[groupName]?.scheduled && data?.[groupName]?.sendStatus === 'draft',\n description: 'Send test emails before scheduling',\n },\n fields: [\n {\n name: 'email',\n type: 'email',\n required: true,\n },\n ],\n },\n ],\n },\n ]\n\n // Add markdown companion field if requested\n if (createMarkdownField) {\n fields.push(createMarkdownFieldInternal({\n name: `${contentField}Markdown`,\n richTextField: contentField,\n label: 'Email Content (Markdown)',\n admin: {\n position: 'sidebar',\n condition: (data: any) => Boolean(data?.[contentField] && data?.[groupName]?.scheduled),\n description: 'Markdown version for email rendering',\n readOnly: true,\n },\n }))\n }\n\n return fields\n}\n\n/**\n * Create a markdown companion field for rich text\n * This creates a virtual field that converts rich text to markdown\n */\nfunction createMarkdownFieldInternal(config: {\n name: string\n richTextField: string\n label?: string\n admin?: any\n}): Field {\n return {\n name: config.name,\n type: 'textarea',\n label: config.label || 'Markdown',\n admin: {\n ...config.admin,\n description: config.admin?.description || 'Auto-generated from rich text content',\n },\n hooks: {\n afterRead: [\n async ({ data }) => {\n // Convert rich text to markdown on read\n if (data?.[config.richTextField]) {\n try {\n const { convertLexicalToMarkdown } = await import('@payloadcms/richtext-lexical')\n return convertLexicalToMarkdown({\n data: data[config.richTextField],\n } as any)\n } catch {\n return ''\n }\n }\n return ''\n },\n ],\n beforeChange: [\n () => {\n // Don't save markdown to database\n return null\n },\n ],\n },\n }\n}"],"names":["createNewsletterSchedulingFields","config","groupName","features","newsletterScheduling","fields","contentField","createMarkdownField","name","type","label","admin","condition","data","user","collection","defaultValue","description","required","date","pickerAppearance","scheduled","readOnly","sendStatus","options","value","hasMany","i18n","locales","map","locale","toUpperCase","push","createMarkdownFieldInternal","richTextField","position","Boolean","hooks","afterRead","convertLexicalToMarkdown","beforeChange"],"mappings":"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"}
@@ -37,16 +37,40 @@
37
37
  * Create admin or owner access control
38
38
  */ export const adminOrSelf = (config)=>({ req, id })=>{
39
39
  const user = req.user;
40
+ // No user = no access
41
+ if (!user) {
42
+ // For list operations without ID, return impossible condition
43
+ if (!id) {
44
+ return {
45
+ id: {
46
+ equals: 'unauthorized-no-access'
47
+ }
48
+ };
49
+ }
50
+ return false;
51
+ }
40
52
  // Admins can access everything
41
53
  if (isAdmin(user, config)) {
42
54
  return true;
43
55
  }
44
- // Magic link authenticated subscribers can access their own data
45
- const subscriberId = req.user?.subscriberId;
46
- if (subscriberId && id) {
56
+ // Synthetic users (subscribers from magic link) can access their own data
57
+ if (user.collection === 'subscribers') {
58
+ // For list operations, scope to their own data
59
+ if (!id) {
60
+ return {
61
+ id: {
62
+ equals: user.id
63
+ }
64
+ };
65
+ }
66
+ // For specific document access, check if it's their own
67
+ return id === user.id;
68
+ }
69
+ // Regular users cannot access subscriber data
70
+ if (!id) {
47
71
  return {
48
72
  id: {
49
- equals: subscriberId
73
+ equals: 'unauthorized-no-access'
50
74
  }
51
75
  };
52
76
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/access.ts"],"sourcesContent":["import type { Access, AccessArgs } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\n\n/**\n * Check if a user is an admin based on the plugin configuration\n */\nexport const isAdmin = (user: any, config?: NewsletterPluginConfig): boolean => {\n if (!user || user.collection !== 'users') {\n return false\n }\n\n // If custom admin check is provided, use it\n if (config?.access?.isAdmin) {\n return config.access.isAdmin(user)\n }\n\n // Default checks for common admin patterns\n // 1. Check for admin role\n if (user.roles?.includes('admin')) {\n return true\n }\n\n // 2. Check for isAdmin boolean field\n if (user.isAdmin === true) {\n return true\n }\n\n // 3. Check for role field with admin value\n if (user.role === 'admin') {\n return true\n }\n\n // 4. Check for admin collection relationship\n if (user.admin === true) {\n return true\n }\n\n return false\n}\n\n/**\n * Create admin-only access control\n */\nexport const adminOnly = (config?: NewsletterPluginConfig): Access => \n ({ req }: AccessArgs) => {\n const user = req.user\n return isAdmin(user, config)\n }\n\n/**\n * Create admin or owner access control\n */\nexport const adminOrSelf = (config?: NewsletterPluginConfig): Access => \n ({ req, id }: AccessArgs) => {\n const user = req.user\n \n // Admins can access everything\n if (isAdmin(user, config)) {\n return true\n }\n \n // Magic link authenticated subscribers can access their own data\n const subscriberId = (req as any).user?.subscriberId\n if (subscriberId && id) {\n return {\n id: {\n equals: subscriberId,\n },\n }\n }\n \n return false\n }"],"names":["isAdmin","user","config","collection","access","roles","includes","role","admin","adminOnly","req","adminOrSelf","id","subscriberId","equals"],"mappings":"AAGA;;CAEC,GACD,OAAO,MAAMA,UAAU,CAACC,MAAWC;IACjC,IAAI,CAACD,QAAQA,KAAKE,UAAU,KAAK,SAAS;QACxC,OAAO;IACT;IAEA,4CAA4C;IAC5C,IAAID,QAAQE,QAAQJ,SAAS;QAC3B,OAAOE,OAAOE,MAAM,CAACJ,OAAO,CAACC;IAC/B;IAEA,2CAA2C;IAC3C,0BAA0B;IAC1B,IAAIA,KAAKI,KAAK,EAAEC,SAAS,UAAU;QACjC,OAAO;IACT;IAEA,qCAAqC;IACrC,IAAIL,KAAKD,OAAO,KAAK,MAAM;QACzB,OAAO;IACT;IAEA,2CAA2C;IAC3C,IAAIC,KAAKM,IAAI,KAAK,SAAS;QACzB,OAAO;IACT;IAEA,6CAA6C;IAC7C,IAAIN,KAAKO,KAAK,KAAK,MAAM;QACvB,OAAO;IACT;IAEA,OAAO;AACT,EAAC;AAED;;CAEC,GACD,OAAO,MAAMC,YAAY,CAACP,SACxB,CAAC,EAAEQ,GAAG,EAAc;QAClB,MAAMT,OAAOS,IAAIT,IAAI;QACrB,OAAOD,QAAQC,MAAMC;IACvB,EAAC;AAEH;;CAEC,GACD,OAAO,MAAMS,cAAc,CAACT,SAC1B,CAAC,EAAEQ,GAAG,EAAEE,EAAE,EAAc;QACtB,MAAMX,OAAOS,IAAIT,IAAI;QAErB,+BAA+B;QAC/B,IAAID,QAAQC,MAAMC,SAAS;YACzB,OAAO;QACT;QAEA,iEAAiE;QACjE,MAAMW,eAAe,AAACH,IAAYT,IAAI,EAAEY;QACxC,IAAIA,gBAAgBD,IAAI;YACtB,OAAO;gBACLA,IAAI;oBACFE,QAAQD;gBACV;YACF;QACF;QAEA,OAAO;IACT,EAAC"}
1
+ {"version":3,"sources":["../../../src/utils/access.ts"],"sourcesContent":["import type { Access, AccessArgs } from 'payload'\nimport type { NewsletterPluginConfig } from '../types'\n\n/**\n * Check if a user is an admin based on the plugin configuration\n */\nexport const isAdmin = (user: any, config?: NewsletterPluginConfig): boolean => {\n if (!user || user.collection !== 'users') {\n return false\n }\n\n // If custom admin check is provided, use it\n if (config?.access?.isAdmin) {\n return config.access.isAdmin(user)\n }\n\n // Default checks for common admin patterns\n // 1. Check for admin role\n if (user.roles?.includes('admin')) {\n return true\n }\n\n // 2. Check for isAdmin boolean field\n if (user.isAdmin === true) {\n return true\n }\n\n // 3. Check for role field with admin value\n if (user.role === 'admin') {\n return true\n }\n\n // 4. Check for admin collection relationship\n if (user.admin === true) {\n return true\n }\n\n return false\n}\n\n/**\n * Create admin-only access control\n */\nexport const adminOnly = (config?: NewsletterPluginConfig): Access => \n ({ req }: AccessArgs) => {\n const user = req.user\n return isAdmin(user, config)\n }\n\n/**\n * Create admin or owner access control\n */\nexport const adminOrSelf = (config?: NewsletterPluginConfig): Access => \n ({ req, id }: AccessArgs) => {\n const user = req.user\n \n // No user = no access\n if (!user) {\n // For list operations without ID, return impossible condition\n if (!id) {\n return {\n id: {\n equals: 'unauthorized-no-access',\n },\n }\n }\n return false\n }\n \n // Admins can access everything\n if (isAdmin(user, config)) {\n return true\n }\n \n // Synthetic users (subscribers from magic link) can access their own data\n if (user.collection === 'subscribers') {\n // For list operations, scope to their own data\n if (!id) {\n return {\n id: {\n equals: user.id,\n },\n }\n }\n // For specific document access, check if it's their own\n return id === user.id\n }\n \n // Regular users cannot access subscriber data\n if (!id) {\n return {\n id: {\n equals: 'unauthorized-no-access',\n },\n }\n }\n return false\n }"],"names":["isAdmin","user","config","collection","access","roles","includes","role","admin","adminOnly","req","adminOrSelf","id","equals"],"mappings":"AAGA;;CAEC,GACD,OAAO,MAAMA,UAAU,CAACC,MAAWC;IACjC,IAAI,CAACD,QAAQA,KAAKE,UAAU,KAAK,SAAS;QACxC,OAAO;IACT;IAEA,4CAA4C;IAC5C,IAAID,QAAQE,QAAQJ,SAAS;QAC3B,OAAOE,OAAOE,MAAM,CAACJ,OAAO,CAACC;IAC/B;IAEA,2CAA2C;IAC3C,0BAA0B;IAC1B,IAAIA,KAAKI,KAAK,EAAEC,SAAS,UAAU;QACjC,OAAO;IACT;IAEA,qCAAqC;IACrC,IAAIL,KAAKD,OAAO,KAAK,MAAM;QACzB,OAAO;IACT;IAEA,2CAA2C;IAC3C,IAAIC,KAAKM,IAAI,KAAK,SAAS;QACzB,OAAO;IACT;IAEA,6CAA6C;IAC7C,IAAIN,KAAKO,KAAK,KAAK,MAAM;QACvB,OAAO;IACT;IAEA,OAAO;AACT,EAAC;AAED;;CAEC,GACD,OAAO,MAAMC,YAAY,CAACP,SACxB,CAAC,EAAEQ,GAAG,EAAc;QAClB,MAAMT,OAAOS,IAAIT,IAAI;QACrB,OAAOD,QAAQC,MAAMC;IACvB,EAAC;AAEH;;CAEC,GACD,OAAO,MAAMS,cAAc,CAACT,SAC1B,CAAC,EAAEQ,GAAG,EAAEE,EAAE,EAAc;QACtB,MAAMX,OAAOS,IAAIT,IAAI;QAErB,sBAAsB;QACtB,IAAI,CAACA,MAAM;YACT,8DAA8D;YAC9D,IAAI,CAACW,IAAI;gBACP,OAAO;oBACLA,IAAI;wBACFC,QAAQ;oBACV;gBACF;YACF;YACA,OAAO;QACT;QAEA,+BAA+B;QAC/B,IAAIb,QAAQC,MAAMC,SAAS;YACzB,OAAO;QACT;QAEA,0EAA0E;QAC1E,IAAID,KAAKE,UAAU,KAAK,eAAe;YACrC,+CAA+C;YAC/C,IAAI,CAACS,IAAI;gBACP,OAAO;oBACLA,IAAI;wBACFC,QAAQZ,KAAKW,EAAE;oBACjB;gBACF;YACF;YACA,wDAAwD;YACxD,OAAOA,OAAOX,KAAKW,EAAE;QACvB;QAEA,8CAA8C;QAC9C,IAAI,CAACA,IAAI;YACP,OAAO;gBACLA,IAAI;oBACFC,QAAQ;gBACV;YACF;QACF;QACA,OAAO;IACT,EAAC"}
@@ -0,0 +1,43 @@
1
+ export class RateLimiter {
2
+ attempts = new Map();
3
+ options;
4
+ constructor(options){
5
+ this.options = options;
6
+ }
7
+ async checkLimit(key) {
8
+ const now = Date.now();
9
+ const record = this.attempts.get(key);
10
+ if (!record || record.resetTime < now) {
11
+ this.attempts.set(key, {
12
+ count: 1,
13
+ resetTime: now + this.options.windowMs
14
+ });
15
+ return true;
16
+ }
17
+ if (record.count >= this.options.maxAttempts) {
18
+ return false;
19
+ }
20
+ record.count++;
21
+ return true;
22
+ }
23
+ async incrementAttempt(key) {
24
+ const now = Date.now();
25
+ const record = this.attempts.get(key);
26
+ if (!record || record.resetTime < now) {
27
+ this.attempts.set(key, {
28
+ count: 1,
29
+ resetTime: now + this.options.windowMs
30
+ });
31
+ } else {
32
+ record.count++;
33
+ }
34
+ }
35
+ async reset(key) {
36
+ this.attempts.delete(key);
37
+ }
38
+ async resetAll() {
39
+ this.attempts.clear();
40
+ }
41
+ }
42
+
43
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/utils/rate-limiter.ts"],"sourcesContent":["export interface RateLimiterOptions {\n maxAttempts: number\n windowMs: number\n prefix?: string\n}\n\nexport class RateLimiter {\n private attempts: Map<string, { count: number; resetTime: number }> = new Map()\n private options: RateLimiterOptions\n\n constructor(options: RateLimiterOptions) {\n this.options = options\n }\n\n async checkLimit(key: string): Promise<boolean> {\n const now = Date.now()\n const record = this.attempts.get(key)\n\n if (!record || record.resetTime < now) {\n this.attempts.set(key, {\n count: 1,\n resetTime: now + this.options.windowMs\n })\n return true\n }\n\n if (record.count >= this.options.maxAttempts) {\n return false\n }\n\n record.count++\n return true\n }\n\n async incrementAttempt(key: string): Promise<void> {\n const now = Date.now()\n const record = this.attempts.get(key)\n\n if (!record || record.resetTime < now) {\n this.attempts.set(key, {\n count: 1,\n resetTime: now + this.options.windowMs\n })\n } else {\n record.count++\n }\n }\n\n async reset(key: string): Promise<void> {\n this.attempts.delete(key)\n }\n\n async resetAll(): Promise<void> {\n this.attempts.clear()\n }\n}"],"names":["RateLimiter","attempts","Map","options","checkLimit","key","now","Date","record","get","resetTime","set","count","windowMs","maxAttempts","incrementAttempt","reset","delete","resetAll","clear"],"mappings":"AAMA,OAAO,MAAMA;IACHC,WAA8D,IAAIC,MAAK;IACvEC,QAA2B;IAEnC,YAAYA,OAA2B,CAAE;QACvC,IAAI,CAACA,OAAO,GAAGA;IACjB;IAEA,MAAMC,WAAWC,GAAW,EAAoB;QAC9C,MAAMC,MAAMC,KAAKD,GAAG;QACpB,MAAME,SAAS,IAAI,CAACP,QAAQ,CAACQ,GAAG,CAACJ;QAEjC,IAAI,CAACG,UAAUA,OAAOE,SAAS,GAAGJ,KAAK;YACrC,IAAI,CAACL,QAAQ,CAACU,GAAG,CAACN,KAAK;gBACrBO,OAAO;gBACPF,WAAWJ,MAAM,IAAI,CAACH,OAAO,CAACU,QAAQ;YACxC;YACA,OAAO;QACT;QAEA,IAAIL,OAAOI,KAAK,IAAI,IAAI,CAACT,OAAO,CAACW,WAAW,EAAE;YAC5C,OAAO;QACT;QAEAN,OAAOI,KAAK;QACZ,OAAO;IACT;IAEA,MAAMG,iBAAiBV,GAAW,EAAiB;QACjD,MAAMC,MAAMC,KAAKD,GAAG;QACpB,MAAME,SAAS,IAAI,CAACP,QAAQ,CAACQ,GAAG,CAACJ;QAEjC,IAAI,CAACG,UAAUA,OAAOE,SAAS,GAAGJ,KAAK;YACrC,IAAI,CAACL,QAAQ,CAACU,GAAG,CAACN,KAAK;gBACrBO,OAAO;gBACPF,WAAWJ,MAAM,IAAI,CAACH,OAAO,CAACU,QAAQ;YACxC;QACF,OAAO;YACLL,OAAOI,KAAK;QACd;IACF;IAEA,MAAMI,MAAMX,GAAW,EAAiB;QACtC,IAAI,CAACJ,QAAQ,CAACgB,MAAM,CAACZ;IACvB;IAEA,MAAMa,WAA0B;QAC9B,IAAI,CAACjB,QAAQ,CAACkB,KAAK;IACrB;AACF"}