@fufulog/brevomorphic-cms-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,69 @@
1
+ export interface BrevoSender {
2
+ id?: number;
3
+ name: string;
4
+ email: string;
5
+ active?: boolean;
6
+ }
7
+ export interface BrevoTemplate {
8
+ id: number;
9
+ name: string;
10
+ subject: string;
11
+ isActive: boolean;
12
+ htmlContent?: string;
13
+ sender?: BrevoSender;
14
+ }
15
+ export interface LocalMapping {
16
+ id?: number | string;
17
+ template_id: number;
18
+ event_name: string;
19
+ is_active: boolean;
20
+ updated_at?: Date | string;
21
+ }
22
+ export interface CombinedTemplate {
23
+ templateId: number;
24
+ templateName: string;
25
+ subject: string;
26
+ eventName: string;
27
+ isActive: boolean;
28
+ sender?: BrevoSender;
29
+ htmlContent?: string;
30
+ }
31
+ export interface SQLClient {
32
+ query: (sql: string, params: any[]) => Promise<any>;
33
+ }
34
+ export interface FirestoreClient {
35
+ collection: (collectionPath: string) => any;
36
+ }
37
+ export interface SDKConfig {
38
+ brevoApiKey: string;
39
+ defaultSender: {
40
+ name?: string;
41
+ email: string;
42
+ };
43
+ dbType: 'postgres' | 'mysql' | 'firestore';
44
+ /**
45
+ * For 'postgres': an object satisfying { query: (sql, params) => Promise<{ rows: any[] }> } or equivalent
46
+ * For 'mysql': an object satisfying { query: (sql, params) => Promise<[any[], any]> } or equivalent
47
+ * For 'firestore': an instance of Firebase Firestore DB
48
+ */
49
+ dbClient: any;
50
+ /**
51
+ * Table name for postgres/mysql, or Collection path for Firestore.
52
+ * Defaults to 'email_event_templates'
53
+ */
54
+ tableNameOrCollection?: string;
55
+ }
56
+ export interface CreateTemplateInput {
57
+ name?: string;
58
+ subject?: string;
59
+ senderEmail?: string;
60
+ senderName?: string;
61
+ }
62
+ export interface UpdateTemplateInput {
63
+ templateName: string;
64
+ subject: string;
65
+ sender: BrevoSender;
66
+ htmlContent: string;
67
+ eventName: string;
68
+ isActive: boolean;
69
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/env.example ADDED
@@ -0,0 +1,25 @@
1
+ # Brevo API Settings
2
+ BREVO_API_KEY=xkeysib-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxx
3
+ DEFAULT_SENDER_EMAIL=noreply@example.com
4
+ DEFAULT_SENDER_NAME="Brevo CMS Engine"
5
+
6
+ # Database Configuration Strategy
7
+ # Options: postgres | mysql | firestore
8
+ DB_TYPE=postgres
9
+
10
+ # --- SQL Database Connection (For bootstrapping host project) ---
11
+ DB_HOST=localhost
12
+ DB_PORT=5432
13
+ DB_USER=joseph
14
+ DB_PASSWORD=your_secure_password
15
+ DB_NAME=brevo_cms_db
16
+ DATABASE_URL=postgresql://joseph:your_secure_password@localhost:5432/brevo_cms_db
17
+ SQL_TABLE_NAME=email_event_templates
18
+
19
+ # --- NoSQL Firestore Connection Settings ---
20
+ # If DB_TYPE=firestore, supply these OR rely on default Firebase credentials (e.g. Service Account)
21
+ FIREBASE_PROJECT_ID=brevocms-prod
22
+ FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxxxx@brevocms-prod.iam.gserviceaccount.com
23
+ # Use literal \n inside string or place service account credentials JSON in a local configuration file
24
+ FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7...\n-----END PRIVATE KEY-----\n"
25
+ FIRESTORE_COLLECTION_NAME=email_event_templates
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@fufulog/brevomorphic-cms-sdk",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "SDK library to bootstrap Brevo email templates and manage local mappings in Postgres, MySQL, or Firestore",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "test": "tsx tests/sdk.test.ts",
11
+ "prepublishOnly": "npm run build"
12
+ },
13
+ "keywords": [
14
+ "brevo",
15
+ "cms",
16
+ "email",
17
+ "sdk",
18
+ "firestore",
19
+ "postgres",
20
+ "mysql"
21
+ ],
22
+ "author": "Joseph",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "axios": "^1.6.8",
26
+ "dotenv": "^16.4.5"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^20.11.30",
30
+ "@types/express": "^4.17.21",
31
+ "typescript": "^5.4.3",
32
+ "tsx": "^4.7.1",
33
+ "express": "^4.19.2"
34
+ }
35
+ }
package/prd.md ADDED
@@ -0,0 +1,135 @@
1
+ # Product Requirement Document (PRD)
2
+
3
+ ## Project Overview
4
+
5
+ This project aims to build a centralized, internal **Email Template Manager & Event Linker Dashboard**. The application will serve as a gateway dashboard that allows teams to manage, edit, and link Brevo email layouts directly to our application-specific backend events (e.g., `ticket.preapproved`, `user.welcome`).
6
+
7
+ By maintaining a dedicated mapping database locally, the platform isolates core app logic from Brevo's internal identifiers while providing a clean, rich-text editing experience wrapper around Brevo’s API.
8
+
9
+ ---
10
+
11
+ ## 1. System Architecture & Objectives
12
+
13
+ * **Decoupled Sync:** The system operates a two-way synchronization layer—pulling core presentation data from Brevo's API while using a local database as the absolute source of truth for routing events to templates.
14
+ * **Zero Orphaned States:** New templates created via the dashboard will seamlessly spawn required local mapping definitions in an inactive state until configured.
15
+ * **One-Way Outbound Sends:** Application event providers will query the local database map to find the correct active `template_id` and push a flat transaction to Brevo.
16
+
17
+ ---
18
+
19
+ ## 2. Core Functional Requirements
20
+
21
+ ### 2.1 Template List Screen (The Main Ledger)
22
+
23
+ Upon accessing the dashboard, the system must render a comprehensive ledger combining Brevo template properties with local event configurations.
24
+
25
+ * **Data Aggregation Engine:** The backend must fetch all templates from Brevo (`GET /v3/smtp/templates`) and cross-reference them against the local database table (`email_event_templates`).
26
+ * **Just-In-Time (JIT) Local Mapping Generation:** If a template exists on Brevo but does not have a corresponding record in our local mapping database, the application must automatically generate a new mapping object in the local database with a blank `event_name` string and `is_active = false`.
27
+ * **UI Columns Required:**
28
+ * **Internal Template Name** (Read from Brevo metadata)
29
+ * **Subject Line** (Read from Brevo metadata)
30
+ * **Mapped Target Event** (Read from Local DB mapping)
31
+ * **Status Toggle** (Reflects `isActive` state from Brevo / Local DB syncing)
32
+ * **Actions:** Edit Button
33
+
34
+
35
+
36
+ ### 2.2 Template Activation / Deactivation Flow
37
+
38
+ * The Template List screen must display a clear toggle switch or button indicating whether a template is active or disabled.
39
+ * Clicking the button will execute an explicit toggle event that hits both systems:
40
+ 1. Update Brevo remotely (`PUT /v3/smtp/templates/{id}`) setting `isActive` to the new state.
41
+ 2. Update the local database mapping record (`is_active` boolean column).
42
+
43
+
44
+ * **Guardrail:** If a template is marked disabled (`isActive = false`), the event engine must completely ignore it, ensuring it does not process or execute the associated target event if fired by an application.
45
+
46
+ ### 2.3 Creation Workflow ("New Template")
47
+
48
+ When an authorized user clicks the **"New Template"** button, the system must automate the initialization sequence without leaving the current view:
49
+
50
+ 1. **Brevo Initialization:** The backend calls Brevo (`POST /v3/smtp/templates`) passing an initial structural setup name ("New Untitled Template"), placeholder subject line, verified default sender email, and a minimum valid HTML string skeleton (`<html><body><p>Drafting...</p></body></html>`).
51
+ 2. **ID Extraction:** Brevo returns a unique numerical `templateId`.
52
+ 3. **Local Mapping Insertion:** The backend immediately saves a new mapping row in our database linking the new `templateId` with an empty `event_name` and `is_active = false`.
53
+ 4. **Automatic Redirect:** The dashboard must automatically fetch the fresh payload properties and instantly open the **Edit Email Template** view for that specific ID.
54
+
55
+ ### 2.4 Edit Template View (The Screen Layout)
56
+
57
+ This view provides an explicit interface for updating visual parameters and application routing logic, matching our verified structural wireframe design:
58
+
59
+ * **Internal Template Name:** A required input field allowing the user to rename the template file identifier (`templateName` in Brevo).
60
+ * **Subject:** A required input text field mapping to the email subject.
61
+ * **Sender Selection:** A dropdown menu populated by valid sender profiles pulled down directly from Brevo's API (`GET /v3/senders`).
62
+ * **Target Event Field:** A dropdown menu populated by our application’s known structural system events. Modifying this field directly updates the local database mapping row upon submission.
63
+ * **Variable Injector ("Constants"):** A dropdown interface filled with system keys (e.g., `Customer Name`, `Ticket Code`). Clicking **"Insert Variable"** drops the appropriate Brevo Twig expression (e.g., `{{ params.name }}`) directly into the current cursor location inside the editor.
64
+ * **Email Body WYSIWYG Editor:** A rich text editor component.
65
+ * Must support full HTML markup layout architecture.
66
+ * Must feature a **"Source"** toggle view button to allow advanced layout engineering and direct copy/pasting of raw template code blocks.
67
+ * Must possess string rule protections to prevent the WYSIWYG parser from corrupting or encoding raw Twig brackets (`{{ ... }}` or `{% ... %}`).
68
+
69
+
70
+
71
+ ---
72
+
73
+ ## 3. Data Dictionary & Contract Mapping
74
+
75
+ ### 3.1 Local Database Contract (`email_event_templates`)
76
+
77
+ ```sql
78
+ CREATE TABLE email_event_templates (
79
+ id SERIAL PRIMARY KEY,
80
+ event_name VARCHAR(255) UNIQUE, -- Stores user assigned target event string
81
+ template_id INT NOT NULL UNIQUE, -- Bridges directly to Brevo's Template ID
82
+ is_active BOOLEAN DEFAULT false,
83
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
84
+ );
85
+
86
+ ```
87
+
88
+ ### 3.2 Main Dashboard Fields to Brevo API Structural Alignment
89
+
90
+ | Dashboard Form Element | Local DB Target | Brevo API Target Parameter | API Type |
91
+ | --- | --- | --- | --- |
92
+ | **Internal Name Input** | N/A | `templateName` | String |
93
+ | **Subject Input** | N/A | `subject` | String |
94
+ | **Sender Dropdown** | N/A | `sender.email` / `sender.id` | Object |
95
+ | **Target Event Selection** | `event_name` | N/A (Stored locally in DB) | String |
96
+ | **Status Toggle** | `is_active` | `isActive` | Boolean |
97
+ | **WYSIWYG Workspace** | N/A | `htmlContent` | String (Full HTML) |
98
+
99
+ ---
100
+
101
+ ## 4. User Interaction Flows (User Journeys)
102
+
103
+ ### 4.1 Editing and Assigning an Existing Event
104
+
105
+ ```
106
+ [User logs into Dashboard]
107
+
108
+
109
+ [List screen performs concurrent lookups: Local DB mappings + Brevo templates list]
110
+
111
+
112
+ [System matches rows; auto-creates a fallback mapping block in local DB if anomalies exist]
113
+
114
+
115
+ [User clicks "Edit" on a template row]
116
+
117
+
118
+ [Router opens Editor UI populated with current Brevo details and the Local DB Target Event]
119
+
120
+
121
+ [User rewrites HTML body, inputs an internal name, and changes Target Event to 'ticket.preapproved']
122
+
123
+
124
+ [User clicks "Submit"] ───► 1. PUT Payload streams directly to Brevo API
125
+ 2. SQL Upsert saves 'ticket.preapproved' to Local DB Map
126
+
127
+ ```
128
+
129
+ ---
130
+
131
+ ## 5. Non-Functional & Security Requirements
132
+
133
+ * **Content Sanitization Exception:** The rich text editor engine must be strictly configured **not** to escape or scrub symbols like `{`, `%`, or `}`. Doing so breaks Brevo’s templating system processor.
134
+ * **API Network Fault Isolation:** If Brevo's API experiences downstream lag or outages, the local dashboard list should gracefully fail via a readable UI banner message while protecting database integrity.
135
+ * **Payload Boundary Constraint:** The editor form must enforce standard text character validations before sending to Brevo: a template name cannot be blank, and the final extracted HTML content must exceed 10 characters to clear Brevo's server validations.
@@ -0,0 +1,233 @@
1
+ /* BrevoWysiwyg.css — Styles for the React WYSIWYG editor component */
2
+
3
+ /* ── Container ── */
4
+ .brevo-wysiwyg {
5
+ --brevo-bg: #1a1a2e;
6
+ --brevo-surface: #16213e;
7
+ --brevo-border: #2a2a4a;
8
+ --brevo-border-focus: #6366f1;
9
+ --brevo-text: #e2e8f0;
10
+ --brevo-text-muted: #94a3b8;
11
+ --brevo-accent: #6366f1;
12
+ --brevo-accent-hover: #818cf8;
13
+ --brevo-toolbar-bg: #0f1629;
14
+ --brevo-btn-hover: #2a2a4a;
15
+ --brevo-btn-active: #3730a3;
16
+ --brevo-radius: 8px;
17
+ --brevo-font: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
18
+
19
+ font-family: var(--brevo-font);
20
+ border: 1.5px solid var(--brevo-border);
21
+ border-radius: var(--brevo-radius);
22
+ overflow: hidden;
23
+ background: var(--brevo-bg);
24
+ transition: border-color 0.2s ease;
25
+ }
26
+
27
+ .brevo-wysiwyg--focused {
28
+ border-color: var(--brevo-border-focus);
29
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
30
+ }
31
+
32
+ .brevo-wysiwyg--disabled {
33
+ opacity: 0.55;
34
+ pointer-events: none;
35
+ }
36
+
37
+ /* ── Toolbar ── */
38
+ .brevo-toolbar {
39
+ display: flex;
40
+ align-items: center;
41
+ flex-wrap: wrap;
42
+ gap: 2px;
43
+ padding: 6px 8px;
44
+ background: var(--brevo-toolbar-bg);
45
+ border-bottom: 1px solid var(--brevo-border);
46
+ }
47
+
48
+ .brevo-toolbar__group {
49
+ display: flex;
50
+ gap: 2px;
51
+ }
52
+
53
+ .brevo-toolbar__divider {
54
+ width: 1px;
55
+ height: 22px;
56
+ background: var(--brevo-border);
57
+ margin: 0 4px;
58
+ }
59
+
60
+ .brevo-toolbar__spacer {
61
+ flex: 1;
62
+ }
63
+
64
+ /* ── Buttons ── */
65
+ .brevo-btn {
66
+ display: inline-flex;
67
+ align-items: center;
68
+ gap: 4px;
69
+ padding: 5px 10px;
70
+ border: none;
71
+ border-radius: 5px;
72
+ background: transparent;
73
+ color: var(--brevo-text-muted);
74
+ font-family: var(--brevo-font);
75
+ font-size: 13px;
76
+ cursor: pointer;
77
+ transition: all 0.15s ease;
78
+ white-space: nowrap;
79
+ line-height: 1.4;
80
+ }
81
+
82
+ .brevo-btn:hover {
83
+ background: var(--brevo-btn-hover);
84
+ color: var(--brevo-text);
85
+ }
86
+
87
+ .brevo-btn--active {
88
+ background: var(--brevo-btn-active);
89
+ color: #fff;
90
+ }
91
+
92
+ .brevo-btn--accent {
93
+ color: var(--brevo-accent);
94
+ font-weight: 500;
95
+ }
96
+
97
+ .brevo-btn--accent:hover {
98
+ color: var(--brevo-accent-hover);
99
+ background: rgba(99, 102, 241, 0.12);
100
+ }
101
+
102
+ .brevo-btn--source {
103
+ font-size: 12px;
104
+ font-weight: 600;
105
+ letter-spacing: 0.02em;
106
+ }
107
+
108
+ .brevo-btn:disabled {
109
+ opacity: 0.4;
110
+ cursor: not-allowed;
111
+ }
112
+
113
+ /* ── Dropdowns ── */
114
+ .brevo-dropdown {
115
+ position: relative;
116
+ }
117
+
118
+ .brevo-dropdown__menu {
119
+ position: absolute;
120
+ top: calc(100% + 4px);
121
+ left: 0;
122
+ min-width: 160px;
123
+ padding: 4px;
124
+ background: var(--brevo-surface);
125
+ border: 1px solid var(--brevo-border);
126
+ border-radius: 6px;
127
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
128
+ z-index: 100;
129
+ animation: brevo-dropdown-in 0.12s ease-out;
130
+ }
131
+
132
+ .brevo-dropdown__menu--variables {
133
+ min-width: 260px;
134
+ }
135
+
136
+ @keyframes brevo-dropdown-in {
137
+ from {
138
+ opacity: 0;
139
+ transform: translateY(-4px);
140
+ }
141
+ to {
142
+ opacity: 1;
143
+ transform: translateY(0);
144
+ }
145
+ }
146
+
147
+ .brevo-dropdown__menu button {
148
+ display: flex;
149
+ align-items: center;
150
+ justify-content: space-between;
151
+ width: 100%;
152
+ padding: 7px 10px;
153
+ border: none;
154
+ border-radius: 4px;
155
+ background: transparent;
156
+ color: var(--brevo-text);
157
+ font-family: var(--brevo-font);
158
+ font-size: 13px;
159
+ cursor: pointer;
160
+ text-align: left;
161
+ gap: 8px;
162
+ }
163
+
164
+ .brevo-dropdown__menu button:hover {
165
+ background: var(--brevo-btn-hover);
166
+ }
167
+
168
+ .brevo-var-label {
169
+ flex: 1;
170
+ }
171
+
172
+ .brevo-var-key {
173
+ font-size: 11px;
174
+ color: var(--brevo-accent);
175
+ background: rgba(99, 102, 241, 0.1);
176
+ padding: 2px 6px;
177
+ border-radius: 3px;
178
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
179
+ }
180
+
181
+ /* ── Editor ── */
182
+ .brevo-editor-wrapper {
183
+ background: var(--brevo-bg);
184
+ }
185
+
186
+ .brevo-editor {
187
+ min-height: 320px;
188
+ max-height: 600px;
189
+ overflow-y: auto;
190
+ padding: 20px 24px;
191
+ color: var(--brevo-text);
192
+ font-size: 15px;
193
+ line-height: 1.7;
194
+ outline: none;
195
+ word-wrap: break-word;
196
+ overflow-wrap: break-word;
197
+ }
198
+
199
+ .brevo-editor:empty::before {
200
+ content: attr(data-placeholder);
201
+ color: var(--brevo-text-muted);
202
+ opacity: 0.5;
203
+ pointer-events: none;
204
+ }
205
+
206
+ .brevo-editor h1 { font-size: 1.8em; font-weight: 700; margin: 0.4em 0; }
207
+ .brevo-editor h2 { font-size: 1.4em; font-weight: 600; margin: 0.4em 0; }
208
+ .brevo-editor h3 { font-size: 1.15em; font-weight: 600; margin: 0.4em 0; }
209
+ .brevo-editor a { color: var(--brevo-accent-hover); text-decoration: underline; }
210
+ .brevo-editor ul,
211
+ .brevo-editor ol { padding-left: 1.6em; }
212
+
213
+ /* ── Source ── */
214
+ .brevo-source {
215
+ width: 100%;
216
+ min-height: 320px;
217
+ max-height: 600px;
218
+ padding: 16px 20px;
219
+ border: none;
220
+ background: #0d1117;
221
+ color: #c9d1d9;
222
+ font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
223
+ font-size: 13px;
224
+ line-height: 1.65;
225
+ resize: vertical;
226
+ outline: none;
227
+ tab-size: 2;
228
+ box-sizing: border-box;
229
+ }
230
+
231
+ .brevo-source::placeholder {
232
+ color: #484f58;
233
+ }