hazo_notify 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.
- package/.cursor/rules/db_schema.mdc +0 -0
- package/.cursor/rules/design.mdc +16 -0
- package/.cursor/rules/general.mdc +49 -0
- package/README.md +765 -0
- package/components/emailer-html-editor.tsx +94 -0
- package/components/ui/button.tsx +53 -0
- package/components/ui/card.tsx +78 -0
- package/components/ui/input.tsx +24 -0
- package/components/ui/label.tsx +21 -0
- package/components/ui/sidebar.tsx +121 -0
- package/components/ui/spinner.tsx +54 -0
- package/components/ui/textarea.tsx +23 -0
- package/components/ui/tooltip.tsx +30 -0
- package/components.json +20 -0
- package/hazo_notify_config.ini +153 -0
- package/jest.config.js +27 -0
- package/jest.setup.js +1 -0
- package/next.config.js +22 -0
- package/package.json +72 -0
- package/postcss.config.js +6 -0
- package/src/app/api/hazo_notify/emailer/send/__tests__/route.test.ts +227 -0
- package/src/app/api/hazo_notify/emailer/send/route.ts +537 -0
- package/src/app/editor-00/page.tsx +47 -0
- package/src/app/globals.css +69 -0
- package/src/app/hazo_notify/emailer_test/layout.tsx +53 -0
- package/src/app/hazo_notify/emailer_test/page.tsx +369 -0
- package/src/app/hazo_notify/layout.tsx +77 -0
- package/src/app/hazo_notify/page.tsx +12 -0
- package/src/app/layout.tsx +26 -0
- package/src/app/page.tsx +14 -0
- package/src/components/blocks/editor-00/editor.tsx +61 -0
- package/src/components/blocks/editor-00/nodes.ts +11 -0
- package/src/components/blocks/editor-00/plugins.tsx +36 -0
- package/src/components/editor/editor-ui/content-editable.tsx +34 -0
- package/src/components/editor/themes/editor-theme.css +91 -0
- package/src/components/editor/themes/editor-theme.ts +130 -0
- package/src/components/ui/button.tsx +53 -0
- package/src/components/ui/card.tsx +78 -0
- package/src/components/ui/input.tsx +24 -0
- package/src/components/ui/label.tsx +21 -0
- package/src/components/ui/sidebar.tsx +121 -0
- package/src/components/ui/spinner.tsx +54 -0
- package/src/components/ui/textarea.tsx +23 -0
- package/src/components/ui/tooltip.tsx +30 -0
- package/src/lib/emailer/__tests__/emailer.test.ts +200 -0
- package/src/lib/emailer/emailer.ts +263 -0
- package/src/lib/emailer/index.ts +11 -0
- package/src/lib/emailer/providers/__tests__/zeptomail_provider.test.ts +196 -0
- package/src/lib/emailer/providers/index.ts +33 -0
- package/src/lib/emailer/providers/pop3_provider.ts +30 -0
- package/src/lib/emailer/providers/smtp_provider.ts +30 -0
- package/src/lib/emailer/providers/zeptomail_provider.ts +299 -0
- package/src/lib/emailer/types.ts +119 -0
- package/src/lib/emailer/utils/constants.ts +24 -0
- package/src/lib/emailer/utils/index.ts +9 -0
- package/src/lib/emailer/utils/logger.ts +71 -0
- package/src/lib/emailer/utils/validation.ts +84 -0
- package/src/lib/index.ts +6 -0
- package/src/lib/utils.ts +6 -0
- package/tailwind.config.ts +65 -0
- package/tsconfig.json +27 -0
package/jest.config.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const nextJest = require('next/jest');
|
|
2
|
+
|
|
3
|
+
const createJestConfig = nextJest({
|
|
4
|
+
dir: './',
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
const customJestConfig = {
|
|
8
|
+
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
|
9
|
+
testEnvironment: 'jest-environment-jsdom',
|
|
10
|
+
moduleNameMapper: {
|
|
11
|
+
'^@/(.*)$': '<rootDir>/src/$1',
|
|
12
|
+
'^@/components/(.*)$': '<rootDir>/components/$1',
|
|
13
|
+
},
|
|
14
|
+
collectCoverageFrom: [
|
|
15
|
+
'src/**/*.{js,jsx,ts,tsx}',
|
|
16
|
+
'!src/**/*.d.ts',
|
|
17
|
+
'!src/**/*.stories.{js,jsx,ts,tsx}',
|
|
18
|
+
'!src/**/__tests__/**',
|
|
19
|
+
'!src/app/**',
|
|
20
|
+
],
|
|
21
|
+
testMatch: [
|
|
22
|
+
'**/__tests__/**/*.test.[jt]s?(x)',
|
|
23
|
+
'**/?(*.)+(spec|test).[jt]s?(x)',
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
module.exports = createJestConfig(customJestConfig);
|
package/jest.setup.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
package/next.config.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** @type {import('next').NextConfig} */
|
|
2
|
+
const nextConfig = {
|
|
3
|
+
reactStrictMode: true,
|
|
4
|
+
typescript: {
|
|
5
|
+
ignoreBuildErrors: false,
|
|
6
|
+
},
|
|
7
|
+
eslint: {
|
|
8
|
+
ignoreDuringBuilds: false,
|
|
9
|
+
},
|
|
10
|
+
webpack: (config, { isServer }) => {
|
|
11
|
+
if (isServer) {
|
|
12
|
+
// Externalize isomorphic-dompurify for server-side rendering
|
|
13
|
+
config.externals = config.externals || [];
|
|
14
|
+
config.externals.push({
|
|
15
|
+
'isomorphic-dompurify': 'commonjs isomorphic-dompurify',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return config;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
module.exports = nextConfig;
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hazo_notify",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Email that acts as a notification center as well for schedules too",
|
|
5
|
+
"main": "./src/lib/index.ts",
|
|
6
|
+
"types": "./src/lib/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/lib/index.ts",
|
|
9
|
+
"./emailer": "./src/lib/emailer/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "next dev",
|
|
13
|
+
"build": "next build",
|
|
14
|
+
"start": "next start",
|
|
15
|
+
"lint": "next lint",
|
|
16
|
+
"test": "jest",
|
|
17
|
+
"test:watch": "jest --watch",
|
|
18
|
+
"test:coverage": "jest --coverage"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/pub12/hazo_notify.git"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"emailer",
|
|
26
|
+
"notify",
|
|
27
|
+
"zeptomail",
|
|
28
|
+
"email",
|
|
29
|
+
"notification"
|
|
30
|
+
],
|
|
31
|
+
"author": "Pubs Abayasiri",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/pub12/hazo_notify/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/pub12/hazo_notify#readme",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@lexical/react": "^0.38.2",
|
|
39
|
+
"@lexical/rich-text": "^0.38.2",
|
|
40
|
+
"@lexical/selection": "^0.38.2",
|
|
41
|
+
"@lexical/utils": "^0.38.2",
|
|
42
|
+
"@radix-ui/react-tooltip": "^1.2.8",
|
|
43
|
+
"class-variance-authority": "^0.7.0",
|
|
44
|
+
"clsx": "^2.1.0",
|
|
45
|
+
"hazo_config": "^1.3.0",
|
|
46
|
+
"isomorphic-dompurify": "^2.9.0",
|
|
47
|
+
"lexical": "^0.38.2",
|
|
48
|
+
"lucide-react": "^0.344.0",
|
|
49
|
+
"next": "^14.2.0",
|
|
50
|
+
"react": "^18.3.0",
|
|
51
|
+
"react-dom": "^18.3.0",
|
|
52
|
+
"sonner": "^1.4.0",
|
|
53
|
+
"tailwind-merge": "^2.2.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@testing-library/jest-dom": "^6.2.0",
|
|
57
|
+
"@testing-library/react": "^14.1.0",
|
|
58
|
+
"@testing-library/user-event": "^14.5.0",
|
|
59
|
+
"@types/jest": "^29.5.0",
|
|
60
|
+
"@types/node": "^20.11.0",
|
|
61
|
+
"@types/react": "^18.3.0",
|
|
62
|
+
"@types/react-dom": "^18.3.0",
|
|
63
|
+
"autoprefixer": "^10.4.0",
|
|
64
|
+
"jest": "^29.7.0",
|
|
65
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
66
|
+
"postcss": "^8.4.0",
|
|
67
|
+
"tailwindcss": "^3.4.0",
|
|
68
|
+
"tailwindcss-animate": "^1.0.7",
|
|
69
|
+
"ts-jest": "^29.1.0",
|
|
70
|
+
"typescript": "^5.3.0"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for emailer API route
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { POST } from '../../route';
|
|
6
|
+
import { NextRequest } from 'next/server';
|
|
7
|
+
|
|
8
|
+
// Mock the emailer service
|
|
9
|
+
jest.mock('@/lib/emailer', () => ({
|
|
10
|
+
send_email: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import { send_email } from '@/lib/emailer';
|
|
14
|
+
|
|
15
|
+
describe('POST /api/hazo_notify/emailer/send', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should send email successfully', async () => {
|
|
21
|
+
(send_email as jest.Mock).mockResolvedValueOnce({
|
|
22
|
+
success: true,
|
|
23
|
+
message_id: 'test_message_id',
|
|
24
|
+
message: 'Email sent successfully',
|
|
25
|
+
raw_response: {
|
|
26
|
+
status: 200,
|
|
27
|
+
body: {
|
|
28
|
+
data: {
|
|
29
|
+
message_id: 'test_message_id',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const request_body = {
|
|
36
|
+
to: 'recipient@example.com',
|
|
37
|
+
subject: 'Test Subject',
|
|
38
|
+
content: {
|
|
39
|
+
text: 'Test email content',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Create a mock Request object
|
|
44
|
+
const request = new Request('http://localhost/api/hazo_notify/emailer/send', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
body: JSON.stringify(request_body),
|
|
47
|
+
headers: {
|
|
48
|
+
'Content-Type': 'application/json',
|
|
49
|
+
},
|
|
50
|
+
}) as NextRequest;
|
|
51
|
+
|
|
52
|
+
const response = await POST(request);
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
|
|
55
|
+
expect(response.status).toBe(200);
|
|
56
|
+
expect(data.success).toBe(true);
|
|
57
|
+
expect(data.message_id).toBe('test_message_id');
|
|
58
|
+
expect(send_email).toHaveBeenCalledTimes(1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should validate required fields', async () => {
|
|
62
|
+
const request_body = {
|
|
63
|
+
to: '',
|
|
64
|
+
subject: 'Test Subject',
|
|
65
|
+
content: {
|
|
66
|
+
text: 'Test email content',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Create a mock Request object
|
|
71
|
+
const request = new Request('http://localhost/api/hazo_notify/emailer/send', {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
body: JSON.stringify(request_body),
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
},
|
|
77
|
+
}) as NextRequest;
|
|
78
|
+
|
|
79
|
+
const response = await POST(request);
|
|
80
|
+
const data = await response.json();
|
|
81
|
+
|
|
82
|
+
expect(response.status).toBe(400);
|
|
83
|
+
expect(data.success).toBe(false);
|
|
84
|
+
expect(data.error).toContain('Recipient email address');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should handle email sending failure', async () => {
|
|
88
|
+
(send_email as jest.Mock).mockResolvedValueOnce({
|
|
89
|
+
success: false,
|
|
90
|
+
error: 'Failed to send email',
|
|
91
|
+
message: 'Failed to send email',
|
|
92
|
+
raw_response: {
|
|
93
|
+
status: 500,
|
|
94
|
+
body: {
|
|
95
|
+
error: 'Failed to send email',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const request_body = {
|
|
101
|
+
to: 'recipient@example.com',
|
|
102
|
+
subject: 'Test Subject',
|
|
103
|
+
content: {
|
|
104
|
+
text: 'Test email content',
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Create a mock Request object
|
|
109
|
+
const request = new Request('http://localhost/api/hazo_notify/emailer/send', {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
body: JSON.stringify(request_body),
|
|
112
|
+
headers: {
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
},
|
|
115
|
+
}) as NextRequest;
|
|
116
|
+
|
|
117
|
+
const response = await POST(request);
|
|
118
|
+
const data = await response.json();
|
|
119
|
+
|
|
120
|
+
expect(response.status).toBe(500);
|
|
121
|
+
expect(data.success).toBe(false);
|
|
122
|
+
expect(data.error).toBe('Failed to send email');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should support HTML email content', async () => {
|
|
126
|
+
(send_email as jest.Mock).mockResolvedValueOnce({
|
|
127
|
+
success: true,
|
|
128
|
+
message_id: 'test_message_id',
|
|
129
|
+
message: 'Email sent successfully',
|
|
130
|
+
raw_response: {
|
|
131
|
+
status: 200,
|
|
132
|
+
body: {
|
|
133
|
+
data: {
|
|
134
|
+
message_id: 'test_message_id',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const request_body = {
|
|
141
|
+
to: 'recipient@example.com',
|
|
142
|
+
subject: 'Test Subject',
|
|
143
|
+
content: {
|
|
144
|
+
html: '<h1>Test HTML Email</h1>',
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Create a mock Request object
|
|
149
|
+
const request = new Request('http://localhost/api/hazo_notify/emailer/send', {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
body: JSON.stringify(request_body),
|
|
152
|
+
headers: {
|
|
153
|
+
'Content-Type': 'application/json',
|
|
154
|
+
},
|
|
155
|
+
}) as NextRequest;
|
|
156
|
+
|
|
157
|
+
const response = await POST(request);
|
|
158
|
+
const data = await response.json();
|
|
159
|
+
|
|
160
|
+
expect(response.status).toBe(200);
|
|
161
|
+
expect(data.success).toBe(true);
|
|
162
|
+
expect(send_email).toHaveBeenCalledWith(
|
|
163
|
+
expect.objectContaining({
|
|
164
|
+
content: {
|
|
165
|
+
html: '<h1>Test HTML Email</h1>',
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should support attachments', async () => {
|
|
172
|
+
(send_email as jest.Mock).mockResolvedValueOnce({
|
|
173
|
+
success: true,
|
|
174
|
+
message_id: 'test_message_id',
|
|
175
|
+
message: 'Email sent successfully',
|
|
176
|
+
raw_response: {
|
|
177
|
+
status: 200,
|
|
178
|
+
body: {
|
|
179
|
+
data: {
|
|
180
|
+
message_id: 'test_message_id',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const request_body = {
|
|
187
|
+
to: 'recipient@example.com',
|
|
188
|
+
subject: 'Test Subject',
|
|
189
|
+
content: {
|
|
190
|
+
text: 'Test email content',
|
|
191
|
+
},
|
|
192
|
+
attachments: [
|
|
193
|
+
{
|
|
194
|
+
filename: 'test.txt',
|
|
195
|
+
content: 'dGVzdCBjb250ZW50',
|
|
196
|
+
mime_type: 'text/plain',
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Create a mock Request object
|
|
202
|
+
const request = new Request('http://localhost/api/hazo_notify/emailer/send', {
|
|
203
|
+
method: 'POST',
|
|
204
|
+
body: JSON.stringify(request_body),
|
|
205
|
+
headers: {
|
|
206
|
+
'Content-Type': 'application/json',
|
|
207
|
+
},
|
|
208
|
+
}) as NextRequest;
|
|
209
|
+
|
|
210
|
+
const response = await POST(request);
|
|
211
|
+
const data = await response.json();
|
|
212
|
+
|
|
213
|
+
expect(response.status).toBe(200);
|
|
214
|
+
expect(data.success).toBe(true);
|
|
215
|
+
expect(send_email).toHaveBeenCalledWith(
|
|
216
|
+
expect.objectContaining({
|
|
217
|
+
attachments: [
|
|
218
|
+
{
|
|
219
|
+
filename: 'test.txt',
|
|
220
|
+
content: 'dGVzdCBjb250ZW50',
|
|
221
|
+
mime_type: 'text/plain',
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
});
|