fix: edit profile in user and admin page
This commit is contained in:
parent
8e92a58e49
commit
6d87d8d0a1
|
@ -8,5 +8,6 @@ EMAIL_HOST=
|
||||||
EMAIL_PORT=
|
EMAIL_PORT=
|
||||||
EMAIL_USER=
|
EMAIL_USER=
|
||||||
EMAIL_PASS=
|
EMAIL_PASS=
|
||||||
|
SENDGRID_SENDER_NAME=
|
||||||
SENDGRID_API_KEY=
|
SENDGRID_API_KEY=
|
||||||
EMAIL_FROM=
|
EMAIL_FROM=
|
|
@ -3,6 +3,8 @@ const argon2 = require('argon2');
|
||||||
const randomstring = require('randomstring');
|
const randomstring = require('randomstring');
|
||||||
const {User} = require('../models'); // Pastikan sesuai dengan struktur project
|
const {User} = require('../models'); // Pastikan sesuai dengan struktur project
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
const nodemailer = require('nodemailer');
|
||||||
|
const sgMail = require('@sendgrid/mail');
|
||||||
|
|
||||||
// Fungsi untuk membuat token JWT
|
// Fungsi untuk membuat token JWT
|
||||||
const generateToken = (user) => {
|
const generateToken = (user) => {
|
||||||
|
@ -78,30 +80,198 @@ exports.login = async (req, res) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.forgotPassword = async (req, res) => {
|
// Buat transporter Nodemailer dengan Gmail
|
||||||
|
const createGmailTransporter = () => {
|
||||||
|
return nodemailer.createTransport({
|
||||||
|
service: 'gmail',
|
||||||
|
auth: {
|
||||||
|
user: process.env.EMAIL_FROM, // sibayam52@gmail.com dari .env yang sudah ada
|
||||||
|
pass: process.env.EMAIL_PASS, // Gunakan App Password, bukan password biasa!
|
||||||
|
},
|
||||||
|
// Pool koneksi untuk menghindari rate limiting
|
||||||
|
pool: true,
|
||||||
|
maxConnections: 3,
|
||||||
|
maxMessages: 50,
|
||||||
|
rateDelta: 1500,
|
||||||
|
rateLimit: 3
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.sendResetCodeWithGmail = async (req, res) => {
|
||||||
|
const { email } = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { email, password } = req.body; // Gunakan 'password' sebagai field yang dikirim
|
// Validasi email
|
||||||
|
if (!email || !email.includes('@')) {
|
||||||
// 🔹 Validasi input
|
return res.status(400).json({ message: 'Email tidak valid' });
|
||||||
if (!email || !password) {
|
|
||||||
return res.status(400).json({ message: "Email dan password baru harus diisi" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔹 Cek apakah user dengan email tersebut ada
|
|
||||||
const user = await User.findOne({ where: { email } });
|
const user = await User.findOne({ where: { email } });
|
||||||
if (!user) {
|
if (!user) return res.status(404).json({ message: 'User tidak ditemukan' });
|
||||||
return res.status(404).json({ message: "User tidak ditemukan" });
|
|
||||||
|
// Generate 6 digit random code
|
||||||
|
const code = Math.floor(100000 + Math.random() * 900000).toString();
|
||||||
|
const expiresAt = new Date(Date.now() + 10 * 60 * 1000);
|
||||||
|
|
||||||
|
await user.update({
|
||||||
|
resetToken: code,
|
||||||
|
resetTokenExpiry: expiresAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Nama aplikasi yang konsisten
|
||||||
|
const appName = process.env.SENDGRID_SENDER_NAME || 'SistemPakar SIBAYAM';
|
||||||
|
|
||||||
|
// Coba buat transporter setiap kali untuk menghindari koneksi mati
|
||||||
|
const transporter = createGmailTransporter();
|
||||||
|
|
||||||
|
// Email sangat sederhana tetapi efektif
|
||||||
|
const mailOptions = {
|
||||||
|
from: `"${appName}" <${process.env.EMAIL_FROM}>`, // Menggunakan EMAIL_FROM dari .env
|
||||||
|
to: email,
|
||||||
|
subject: `[${code}] Kode Verifikasi ${appName}`, // Tanda kode di subject membantu visibilitas
|
||||||
|
html: `
|
||||||
|
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #eee; border-radius: 5px;">
|
||||||
|
<h2 style="color: #333; margin-bottom: 20px;">Reset Password</h2>
|
||||||
|
<p>Halo ${user.name || 'Pengguna'},</p>
|
||||||
|
<p style="color: #666; margin-bottom: 15px;">Anda telah meminta untuk mereset password akun SIBAYAM Anda.</p>
|
||||||
|
<div style="background: #f9f9f9; padding: 15px; border-radius: 5px; margin: 20px 0;">
|
||||||
|
<p style="font-size: 16px; margin: 0;">Kode verifikasi Anda:</p>
|
||||||
|
<h1 style="color: #4CAF50; margin: 10px 0; letter-spacing: 5px;">${code}</h1>
|
||||||
|
</div>
|
||||||
|
<p style="color: #666; margin-bottom: 15px;">Kode ini akan kadaluarsa dalam 10 menit.</p>
|
||||||
|
<p style="color: #999; font-size: 12px; margin-top: 30px;">Jika Anda tidak meminta reset password, abaikan email ini.</p>
|
||||||
|
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;"/>
|
||||||
|
<p style="color: #999; font-size: 12px;">Email ini dikirim oleh sistem SIBAYAM. Mohon jangan membalas email ini.</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tambahkan debugging
|
||||||
|
console.log('Mengirim email ke:', email);
|
||||||
|
console.log('Menggunakan akun:', process.env.EMAIL_FROM);
|
||||||
|
console.log('Dengan transporter:', transporter ? 'Berhasil dibuat' : 'Gagal dibuat');
|
||||||
|
|
||||||
|
// Kirim email dengan try-catch terpisah untuk debugging
|
||||||
|
try {
|
||||||
|
const info = await transporter.sendMail(mailOptions);
|
||||||
|
console.log('Email reset password berhasil dikirim via Gmail:', info.messageId);
|
||||||
|
console.log('Preview URL:', nodemailer.getTestMessageUrl(info));
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
message: 'Kode verifikasi telah dikirim ke email Anda.',
|
||||||
|
expiresIn: '10 menit',
|
||||||
|
});
|
||||||
|
} catch (emailError) {
|
||||||
|
console.error('Error saat mengirim email:', emailError);
|
||||||
|
return res.status(500).json({
|
||||||
|
message: 'Gagal mengirim kode verifikasi via email',
|
||||||
|
error: process.env.NODE_ENV === 'development' ? emailError.toString() : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in sendResetCodeWithGmail:', error);
|
||||||
|
return res.status(500).json({
|
||||||
|
message: 'Gagal memproses permintaan reset password',
|
||||||
|
error: process.env.NODE_ENV === 'development' ? error.toString() : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
|
||||||
|
|
||||||
|
exports.resetPasswordWithCode = async (req, res) => {
|
||||||
|
const { code, password } = req.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validasi input
|
||||||
|
if (!code || !password) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Kode dan password baru wajib diisi"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi password yang lebih ketat
|
||||||
|
if (password.length < 8) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Password harus minimal 8 karakter"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.findOne({
|
||||||
|
where: {
|
||||||
|
resetToken: code
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Kode verifikasi salah"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if code is expired
|
||||||
|
const now = new Date();
|
||||||
|
if (now > user.resetTokenExpiry) {
|
||||||
|
return res.status(400).json({
|
||||||
|
message: "Kode verifikasi sudah kadaluarsa. Silakan minta kode baru."
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔹 Hash password baru dengan Argon2
|
|
||||||
const hashedPassword = await argon2.hash(password);
|
const hashedPassword = await argon2.hash(password);
|
||||||
|
|
||||||
// 🔹 Update password user di database
|
await user.update({
|
||||||
await user.update({ password: hashedPassword });
|
password: hashedPassword,
|
||||||
|
resetToken: null,
|
||||||
|
resetTokenExpiry: null
|
||||||
|
});
|
||||||
|
|
||||||
|
// Kirim email konfirmasi menggunakan Gmail
|
||||||
|
try {
|
||||||
|
const appName = process.env.SENDGRID_SENDER_NAME || 'SistemPakar SIBAYAM';
|
||||||
|
const transporter = createGmailTransporter();
|
||||||
|
|
||||||
|
const mailOptions = {
|
||||||
|
from: `"${appName}" <${process.env.EMAIL_FROM}>`,
|
||||||
|
to: user.email,
|
||||||
|
subject: `Password Berhasil Diubah - ${appName}`,
|
||||||
|
html: `
|
||||||
|
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #eee; border-radius: 5px;">
|
||||||
|
<h2 style="color: #333; margin-bottom: 20px;">Password Berhasil Diubah</h2>
|
||||||
|
<p>Halo ${user.name || 'Pengguna'},</p>
|
||||||
|
<p style="color: #666; margin-bottom: 15px;">Password akun SIBAYAM Anda telah berhasil diubah.</p>
|
||||||
|
<div style="background: #f9f9f9; padding: 15px; border-radius: 5px; margin: 20px 0;">
|
||||||
|
<p style="font-size: 16px; margin: 0; color: #4CAF50;">✓ Password telah diperbarui</p>
|
||||||
|
</div>
|
||||||
|
<p style="color: #666; margin-bottom: 15px;">Jika Anda tidak melakukan perubahan ini, segera hubungi tim dukungan kami.</p>
|
||||||
|
<hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;"/>
|
||||||
|
<p style="color: #999; font-size: 12px;">Email ini dikirim oleh sistem SIBAYAM. Mohon jangan membalas email ini.</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tambahkan debugging
|
||||||
|
console.log('Mengirim email konfirmasi ke:', user.email);
|
||||||
|
console.log('Menggunakan akun:', process.env.EMAIL_FROM);
|
||||||
|
|
||||||
|
const info = await transporter.sendMail(mailOptions);
|
||||||
|
console.log('Email konfirmasi berhasil dikirim via Gmail:', info.messageId);
|
||||||
|
console.log('Preview URL:', nodemailer.getTestMessageUrl(info));
|
||||||
|
|
||||||
|
} catch (emailError) {
|
||||||
|
console.error('Error saat mengirim email konfirmasi:', emailError);
|
||||||
|
// Tidak menghentikan proses meski email konfirmasi gagal
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
message: "Password berhasil direset",
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
|
||||||
res.status(200).json({ message: "Password berhasil diperbarui" });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error pada forgotPassword:", error);
|
console.error("Error in resetPasswordWithCode:", error);
|
||||||
res.status(500).json({ message: "Terjadi kesalahan", error: error.message });
|
return res.status(500).json({
|
||||||
|
message: "Terjadi kesalahan pada server",
|
||||||
|
error: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -1,9 +1,80 @@
|
||||||
const { Rule_penyakit, Rule_hama, Gejala, Penyakit, Hama, Histori } = require('../models');
|
const { Rule_penyakit, Rule_hama, Gejala, Penyakit, Hama, Histori } = require('../models');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
|
||||||
|
// Helper function to calculate Bayes probability
|
||||||
|
function calculateBayesProbability(rules, entityType) {
|
||||||
|
if (!rules || rules.length === 0) return null;
|
||||||
|
|
||||||
|
const entityData = rules[0][entityType];
|
||||||
|
const entityName = entityData.nama;
|
||||||
|
const entityId = entityType === 'penyakit' ? rules[0].id_penyakit : rules[0].id_hama;
|
||||||
|
|
||||||
|
// LANGKAH 1: Mencari nilai semesta P(E|Hi) untuk setiap gejala
|
||||||
|
let nilai_semesta = 0;
|
||||||
|
const gejalaValues = {};
|
||||||
|
|
||||||
|
for (const rule of rules) {
|
||||||
|
gejalaValues[rule.id_gejala] = rule.nilai_pakar;
|
||||||
|
nilai_semesta += rule.nilai_pakar;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LANGKAH 2: Mencari hasil bobot P(Hi) untuk setiap gejala
|
||||||
|
const bobotGejala = {};
|
||||||
|
for (const [idGejala, nilai] of Object.entries(gejalaValues)) {
|
||||||
|
bobotGejala[idGejala] = nilai / nilai_semesta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LANGKAH 3: Hitung probabilitas H tanpa memandang Evidence P(E|Hi) × P(Hi)
|
||||||
|
const probTanpaEvidence = {};
|
||||||
|
for (const [idGejala, nilai] of Object.entries(gejalaValues)) {
|
||||||
|
probTanpaEvidence[idGejala] = nilai * bobotGejala[idGejala];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hitung total untuk digunakan di langkah 4
|
||||||
|
let totalProbTanpaEvidence = 0;
|
||||||
|
for (const nilai of Object.values(probTanpaEvidence)) {
|
||||||
|
totalProbTanpaEvidence += nilai;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LANGKAH 4: Hitung probabilitas H dengan memandang Evidence P(Hi|E)
|
||||||
|
const probDenganEvidence = {};
|
||||||
|
for (const [idGejala, nilai] of Object.entries(probTanpaEvidence)) {
|
||||||
|
probDenganEvidence[idGejala] = nilai / totalProbTanpaEvidence;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LANGKAH 5: Hitung Nilai Bayes ∑bayes = ∑(P(E|Hi) × P(Hi|E))
|
||||||
|
let nilaiBayes = 0;
|
||||||
|
const detailBayes = [];
|
||||||
|
|
||||||
|
for (const [idGejala, nilai] of Object.entries(gejalaValues)) {
|
||||||
|
const bayes = nilai * probDenganEvidence[idGejala];
|
||||||
|
nilaiBayes += bayes;
|
||||||
|
|
||||||
|
detailBayes.push({
|
||||||
|
id_gejala: parseInt(idGejala),
|
||||||
|
P_E_given_Hi: nilai,
|
||||||
|
P_Hi: bobotGejala[idGejala],
|
||||||
|
P_E_Hi_x_P_Hi: probTanpaEvidence[idGejala],
|
||||||
|
P_Hi_given_E: probDenganEvidence[idGejala],
|
||||||
|
bayes_value: bayes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hasil akhir
|
||||||
|
const idField = entityType === 'penyakit' ? 'id_penyakit' : 'id_hama';
|
||||||
|
return {
|
||||||
|
[idField]: entityId,
|
||||||
|
nama: entityName,
|
||||||
|
nilai_semesta: nilai_semesta,
|
||||||
|
detail_perhitungan: detailBayes,
|
||||||
|
nilai_bayes: nilaiBayes,
|
||||||
|
probabilitas_persen: nilaiBayes * 100
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
exports.diagnosa = async (req, res) => {
|
exports.diagnosa = async (req, res) => {
|
||||||
const { gejala } = req.body; // array of id_gejala
|
const { gejala } = req.body;
|
||||||
const userId = req.user?.id; // Use optional chaining to avoid errors if req.user is undefined
|
const userId = req.user?.id;
|
||||||
const tanggal_diagnosa = moment().format('YYYY-MM-DD');
|
const tanggal_diagnosa = moment().format('YYYY-MM-DD');
|
||||||
|
|
||||||
if (!gejala || !Array.isArray(gejala)) {
|
if (!gejala || !Array.isArray(gejala)) {
|
||||||
|
@ -16,116 +87,21 @@ exports.diagnosa = async (req, res) => {
|
||||||
where: { id: gejala }
|
where: { id: gejala }
|
||||||
});
|
});
|
||||||
|
|
||||||
// ========== HITUNG TOTAL P(E) UNTUK SEMUA GEJALA ==========
|
|
||||||
// P(E) seharusnya sama untuk semua penyakit dan hama yang memiliki gejala yang sama
|
|
||||||
|
|
||||||
// Object untuk menyimpan P(E) untuk setiap gejala
|
|
||||||
const evidenceProbabilities = {};
|
|
||||||
|
|
||||||
// Hitung P(E) untuk PENYAKIT
|
|
||||||
for (const idGejala of gejala) {
|
|
||||||
// Dapatkan semua rule untuk gejala ini di semua penyakit
|
|
||||||
const penyakitRulesForGejala = await Rule_penyakit.findAll({
|
|
||||||
where: { id_gejala: idGejala },
|
|
||||||
include: [{ model: Penyakit, as: 'penyakit' }]
|
|
||||||
});
|
|
||||||
|
|
||||||
let evidenceProbForGejala = 0;
|
|
||||||
|
|
||||||
// Hitung P(E) = Σ [P(E|Hi) * P(Hi)] untuk penyakit
|
|
||||||
for (const rule of penyakitRulesForGejala) {
|
|
||||||
const pHi = rule.penyakit.nilai_pakar; // P(Hi)
|
|
||||||
const pEgivenHi = rule.nilai_pakar; // P(E|Hi)
|
|
||||||
|
|
||||||
evidenceProbForGejala += pEgivenHi * pHi;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dapatkan semua rule untuk gejala ini di semua hama
|
|
||||||
const hamaRulesForGejala = await Rule_hama.findAll({
|
|
||||||
where: { id_gejala: idGejala },
|
|
||||||
include: [{ model: Hama, as: 'hama' }]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Hitung P(E) = Σ [P(E|Hi) * P(Hi)] untuk hama
|
|
||||||
for (const rule of hamaRulesForGejala) {
|
|
||||||
const pHi = rule.hama.nilai_pakar; // P(Hi)
|
|
||||||
const pEgivenHi = rule.nilai_pakar; // P(E|Hi)
|
|
||||||
|
|
||||||
evidenceProbForGejala += pEgivenHi * pHi;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simpan P(E) untuk gejala ini
|
|
||||||
evidenceProbabilities[idGejala] = evidenceProbForGejala;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hitung total P(E) untuk semua gejala yang diinput
|
|
||||||
let totalEvidenceProbability = 0;
|
|
||||||
for (const idGejala of gejala) {
|
|
||||||
totalEvidenceProbability += evidenceProbabilities[idGejala] || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pastikan total P(E) tidak nol untuk menghindari division by zero
|
|
||||||
if (totalEvidenceProbability === 0) {
|
|
||||||
totalEvidenceProbability = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== PENYAKIT ==========
|
// ========== PENYAKIT ==========
|
||||||
const allPenyakitRules = await Rule_penyakit.findAll({
|
const allPenyakitRules = await Rule_penyakit.findAll({
|
||||||
where: { id_gejala: gejala },
|
where: { id_gejala: gejala },
|
||||||
include: [{ model: Penyakit, as: 'penyakit' }]
|
include: [{ model: Penyakit, as: 'penyakit' }]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mendapatkan semua penyakit unik yang memiliki gejala yang dipilih
|
|
||||||
const uniquePenyakitIds = [...new Set(allPenyakitRules.map(rule => rule.id_penyakit))];
|
const uniquePenyakitIds = [...new Set(allPenyakitRules.map(rule => rule.id_penyakit))];
|
||||||
|
|
||||||
// Hasil perhitungan untuk setiap penyakit
|
|
||||||
const hasilPenyakit = [];
|
const hasilPenyakit = [];
|
||||||
|
|
||||||
// Hitung untuk setiap penyakit
|
// Hitung untuk setiap penyakit
|
||||||
for (const idPenyakit of uniquePenyakitIds) {
|
for (const idPenyakit of uniquePenyakitIds) {
|
||||||
// Filter rules yang berhubungan dengan penyakit ini
|
|
||||||
const penyakitRules = allPenyakitRules.filter(rule => rule.id_penyakit === idPenyakit);
|
const penyakitRules = allPenyakitRules.filter(rule => rule.id_penyakit === idPenyakit);
|
||||||
|
const hasil = calculateBayesProbability(penyakitRules, 'penyakit');
|
||||||
if (penyakitRules.length > 0) {
|
if (hasil) {
|
||||||
const dataPenyakit = penyakitRules[0].penyakit;
|
hasilPenyakit.push(hasil);
|
||||||
const namaPenyakit = dataPenyakit.nama;
|
|
||||||
const priorProbability = dataPenyakit.nilai_pakar; // P(H) - prior probability penyakit
|
|
||||||
|
|
||||||
// Menghitung P(E|H) untuk setiap gejala
|
|
||||||
const evidenceGivenHypothesis = {};
|
|
||||||
for (const rule of penyakitRules) {
|
|
||||||
evidenceGivenHypothesis[rule.id_gejala] = rule.nilai_pakar; // P(E|H) untuk setiap gejala
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menghitung P(H|E) = [P(E|H) * P(H)] / P(E) untuk semua gejala
|
|
||||||
let posteriorNumerator = priorProbability; // Inisialisasi dengan P(H)
|
|
||||||
const evidencesUsed = [];
|
|
||||||
|
|
||||||
// Mengalikan dengan nilai P(E|H) untuk setiap gejala yang ada
|
|
||||||
for (const idGejala of gejala) {
|
|
||||||
if (evidenceGivenHypothesis[idGejala]) {
|
|
||||||
posteriorNumerator *= evidenceGivenHypothesis[idGejala];
|
|
||||||
evidencesUsed.push({
|
|
||||||
id_gejala: parseInt(idGejala),
|
|
||||||
P_E_given_H: evidenceGivenHypothesis[idGejala],
|
|
||||||
nilai_P_E: evidenceProbabilities[idGejala] || 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Posterior probability adalah P(H|E)
|
|
||||||
const posteriorProbability = posteriorNumerator / totalEvidenceProbability;
|
|
||||||
|
|
||||||
hasilPenyakit.push({
|
|
||||||
id_penyakit: idPenyakit,
|
|
||||||
nama: namaPenyakit,
|
|
||||||
P_H: priorProbability,
|
|
||||||
P_E: totalEvidenceProbability,
|
|
||||||
evidences: evidencesUsed,
|
|
||||||
posterior_numerator: posteriorNumerator,
|
|
||||||
posterior_probability: posteriorProbability,
|
|
||||||
probabilitas: posteriorProbability
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,88 +111,46 @@ exports.diagnosa = async (req, res) => {
|
||||||
include: [{ model: Hama, as: 'hama' }]
|
include: [{ model: Hama, as: 'hama' }]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mendapatkan semua hama unik yang memiliki gejala yang dipilih
|
|
||||||
const uniqueHamaIds = [...new Set(allHamaRules.map(rule => rule.id_hama))];
|
const uniqueHamaIds = [...new Set(allHamaRules.map(rule => rule.id_hama))];
|
||||||
|
|
||||||
// Hasil perhitungan untuk setiap hama
|
|
||||||
const hasilHama = [];
|
const hasilHama = [];
|
||||||
|
|
||||||
// Hitung untuk setiap hama
|
// Hitung untuk setiap hama
|
||||||
for (const idHama of uniqueHamaIds) {
|
for (const idHama of uniqueHamaIds) {
|
||||||
// Filter rules yang berhubungan dengan hama ini
|
|
||||||
const hamaRules = allHamaRules.filter(rule => rule.id_hama === idHama);
|
const hamaRules = allHamaRules.filter(rule => rule.id_hama === idHama);
|
||||||
|
const hasil = calculateBayesProbability(hamaRules, 'hama');
|
||||||
if (hamaRules.length > 0) {
|
if (hasil) {
|
||||||
const dataHama = hamaRules[0].hama;
|
hasilHama.push(hasil);
|
||||||
const namaHama = dataHama.nama;
|
|
||||||
const priorProbability = dataHama.nilai_pakar; // P(H) - prior probability hama
|
|
||||||
|
|
||||||
// Menghitung P(E|H) untuk setiap gejala
|
|
||||||
const evidenceGivenHypothesis = {};
|
|
||||||
for (const rule of hamaRules) {
|
|
||||||
evidenceGivenHypothesis[rule.id_gejala] = rule.nilai_pakar; // P(E|H) untuk setiap gejala
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menghitung P(H|E) = [P(E|H) * P(H)] / P(E) untuk semua gejala
|
|
||||||
let posteriorNumerator = priorProbability; // Inisialisasi dengan P(H)
|
|
||||||
const evidencesUsed = [];
|
|
||||||
|
|
||||||
// Mengalikan dengan nilai P(E|H) untuk setiap gejala yang ada
|
|
||||||
for (const idGejala of gejala) {
|
|
||||||
if (evidenceGivenHypothesis[idGejala]) {
|
|
||||||
posteriorNumerator *= evidenceGivenHypothesis[idGejala];
|
|
||||||
evidencesUsed.push({
|
|
||||||
id_gejala: parseInt(idGejala),
|
|
||||||
P_E_given_H: evidenceGivenHypothesis[idGejala],
|
|
||||||
nilai_P_E: evidenceProbabilities[idGejala] || 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Posterior probability adalah P(H|E)
|
|
||||||
const posteriorProbability = posteriorNumerator / totalEvidenceProbability;
|
|
||||||
|
|
||||||
hasilHama.push({
|
|
||||||
id_hama: idHama,
|
|
||||||
nama: namaHama,
|
|
||||||
P_H: priorProbability,
|
|
||||||
P_E: totalEvidenceProbability,
|
|
||||||
evidences: evidencesUsed,
|
|
||||||
posterior_numerator: posteriorNumerator,
|
|
||||||
posterior_probability: posteriorProbability,
|
|
||||||
probabilitas: posteriorProbability
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Urutkan hasil berdasarkan probabilitas
|
// Urutkan hasil berdasarkan probabilitas
|
||||||
const sortedPenyakit = hasilPenyakit.sort((a, b) => b.probabilitas - a.probabilitas);
|
const sortedPenyakit = hasilPenyakit.sort((a, b) => b.probabilitas_persen - a.probabilitas_persen);
|
||||||
const sortedHama = hasilHama.sort((a, b) => b.probabilitas - a.probabilitas);
|
const sortedHama = hasilHama.sort((a, b) => b.probabilitas_persen - a.probabilitas_persen);
|
||||||
|
|
||||||
// Buat ringkasan gejala yang dimasukkan
|
// Gabung hasil dan ambil yang tertinggi (bisa penyakit atau hama)
|
||||||
const gejalaSummary = await Gejala.findAll({
|
const allResults = [
|
||||||
where: { id: gejala },
|
...sortedPenyakit.map(p => ({ type: 'penyakit', ...p })),
|
||||||
attributes: ['id', 'kode', 'nama']
|
...sortedHama.map(h => ({ type: 'hama', ...h }))
|
||||||
});
|
].sort((a, b) => b.probabilitas_persen - a.probabilitas_persen);
|
||||||
|
|
||||||
|
// Simpan histori diagnosa jika ada user yang login dan ada hasil diagnosa
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
console.error('ID user tidak ditemukan pada request. Histori tidak dapat disimpan.');
|
console.error('ID user tidak ditemukan. Histori tidak dapat disimpan.');
|
||||||
} else {
|
} else {
|
||||||
const idGejalaDipilih = gejala;
|
|
||||||
const semuaHasil = [...hasilPenyakit, ...hasilHama];
|
const semuaHasil = [...hasilPenyakit, ...hasilHama];
|
||||||
|
|
||||||
if (semuaHasil.length > 0) {
|
if (semuaHasil.length > 0) {
|
||||||
const hasilTerbesar = semuaHasil.reduce((max, current) => {
|
const hasilTerbesar = semuaHasil.reduce((max, current) => {
|
||||||
return current.probabilitas > max.probabilitas ? current : max;
|
return current.probabilitas_persen > max.probabilitas_persen ? current : max;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Base histori data without id_gejala
|
|
||||||
const baseHistoriData = {
|
const baseHistoriData = {
|
||||||
userId: userId,
|
userId: userId, // harus ada
|
||||||
tanggal_diagnosa: tanggal_diagnosa,
|
tanggal_diagnosa: tanggal_diagnosa, // harus ada
|
||||||
hasil: hasilTerbesar.probabilitas
|
hasil: hasilTerbesar.nilai_bayes, // harus ada, harus tipe FLOAT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Tambahkan id_penyakit / id_hama jika ada
|
||||||
if (hasilTerbesar.id_penyakit) {
|
if (hasilTerbesar.id_penyakit) {
|
||||||
baseHistoriData.id_penyakit = hasilTerbesar.id_penyakit;
|
baseHistoriData.id_penyakit = hasilTerbesar.id_penyakit;
|
||||||
} else if (hasilTerbesar.id_hama) {
|
} else if (hasilTerbesar.id_hama) {
|
||||||
|
@ -224,60 +158,40 @@ exports.diagnosa = async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Option 1: Store only the first gejala ID if we're limited to one record
|
const historiPromises = gejala.map(gejalaId => {
|
||||||
// if (idGejalaDipilih.length > 0) {
|
|
||||||
// await Histori.create({
|
|
||||||
// ...baseHistoriData,
|
|
||||||
// id_gejala: parseInt(idGejalaDipilih[0]) // Store as integer
|
|
||||||
// }, { timestamps: false });
|
|
||||||
// console.log('Histori berhasil disimpan dengan gejala utama');
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Option 2 (Uncomment if you want to create multiple records):
|
|
||||||
|
|
||||||
// Create multiple records - one for each gejala
|
|
||||||
const historiPromises = idGejalaDipilih.map(gejalaId => {
|
|
||||||
return Histori.create({
|
return Histori.create({
|
||||||
...baseHistoriData,
|
...baseHistoriData,
|
||||||
id_gejala: parseInt(gejalaId) // Store as integer
|
id_gejala: parseInt(gejalaId)
|
||||||
}, { timestamps: false });
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(historiPromises);
|
await Promise.all(historiPromises);
|
||||||
console.log(`Histori berhasil disimpan untuk ${idGejalaDipilih.length} gejala`);
|
console.log(`Histori berhasil disimpan untuk ${gejala.length} gejala.`);
|
||||||
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Gagal menyimpan histori:', error.message);
|
console.error('Gagal menyimpan histori:', error.message);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('Tidak ada hasil untuk disimpan ke histori.');
|
console.log('Tidak ada hasil diagnosa untuk disimpan.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Kirim hasil perhitungan sebagai respons
|
return res.status(200).json({
|
||||||
res.json({
|
success: true,
|
||||||
input_gejala: gejalaSummary,
|
message: 'Berhasil melakukan diagnosa',
|
||||||
total_evidence_probability: totalEvidenceProbability,
|
data: {
|
||||||
evidence_per_gejala: evidenceProbabilities,
|
|
||||||
penyakit: sortedPenyakit,
|
penyakit: sortedPenyakit,
|
||||||
hama: sortedHama,
|
hama: sortedHama,
|
||||||
detail_perhitungan: {
|
gejala_input: gejala.map(id => parseInt(id)),
|
||||||
keterangan: "Menggunakan teorema Bayes: P(H|E) = [P(E|H) * P(H)] / P(E)",
|
hasil_tertinggi: allResults.length > 0 ? allResults[0] : null
|
||||||
formula: {
|
|
||||||
P_H: "Prior probability (nilai pakar untuk penyakit/hama)",
|
|
||||||
P_E_given_H: "Likelihood (nilai pakar untuk gejala terhadap penyakit/hama)",
|
|
||||||
P_E: "Evidence probability = Σ [P(E|Hi) * P(Hi)] untuk semua hipotesis",
|
|
||||||
P_H_given_E: "Posterior probability (hasil akhir)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error dalam perhitungan Bayes:', error);
|
console.error('Error diagnosa:', error);
|
||||||
res.status(500).json({
|
return res.status(500).json({
|
||||||
message: 'Terjadi kesalahan dalam proses diagnosa',
|
success: false,
|
||||||
|
message: 'Gagal melakukan diagnosa',
|
||||||
error: error.message
|
error: error.message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,39 +66,70 @@ exports.updateUser = async (req, res) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mengupdate berdasarkan email
|
// Mengupdate berdasarkan ID
|
||||||
exports.updateUserEmail = async (req, res) => {
|
exports.updateUserEmail = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { email, name, alamat, nomorTelepon, newPassword } = req.body;
|
const { id } = req.params;
|
||||||
|
const { name, email, alamat, nomorTelepon, password } = req.body;
|
||||||
|
|
||||||
if (!email) {
|
if (!id) {
|
||||||
return res.status(400).json({ message: "Email harus disertakan" });
|
return res.status(400).json({ message: "ID harus disertakan" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await User.findOne({ where: { email } });
|
const user = await User.findByPk(id);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(404).json({ message: "User tidak ditemukan" });
|
return res.status(404).json({ message: "User tidak ditemukan" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check email uniqueness
|
||||||
|
if (email && email !== user.email) {
|
||||||
|
const existingUser = await User.findOne({ where: { email } });
|
||||||
|
if (existingUser) {
|
||||||
|
return res.status(400).json({ message: "Email sudah digunakan" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password if provided
|
||||||
let hashedPassword = user.password;
|
let hashedPassword = user.password;
|
||||||
if (newPassword) {
|
if (password) {
|
||||||
hashedPassword = await argon2.hash(newPassword);
|
hashedPassword = await argon2.hash(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
await user.update({
|
await user.update({
|
||||||
name: name || user.name,
|
name: name || user.name,
|
||||||
|
email: email || user.email,
|
||||||
alamat: alamat || user.alamat,
|
alamat: alamat || user.alamat,
|
||||||
nomorTelepon: nomorTelepon || user.nomorTelepon,
|
nomorTelepon: nomorTelepon || user.nomorTelepon,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(200).json({ message: "User berhasil diperbarui", user });
|
// Updated response object
|
||||||
} catch (error) {
|
const userResponse = {
|
||||||
res.status(500).json({ message: "Terjadi kesalahan pada server", error });
|
id: user.id,
|
||||||
}
|
name: user.name,
|
||||||
|
email: user.email,
|
||||||
|
alamat: user.alamat,
|
||||||
|
nomorTelepon: user.nomorTelepon,
|
||||||
|
role: user.role,
|
||||||
|
createdAt: user.createdAt,
|
||||||
|
updatedAt: user.updatedAt,
|
||||||
|
passwordUpdated: Boolean(password) // Menggunakan Boolean untuk mengkonversi ke true/false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
message: "User berhasil diperbarui",
|
||||||
|
user: userResponse
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Update user error:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
message: "Terjadi kesalahan pada server",
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Menghapus user berdasarkan ID (soft delete)
|
// Menghapus user berdasarkan ID (soft delete)
|
||||||
exports.deleteUser = async (req, res) => {
|
exports.deleteUser = async (req, res) => {
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
async up(queryInterface, Sequelize) {
|
||||||
|
await queryInterface.addColumn('Users', 'resetToken', {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
after: 'password'
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryInterface.addColumn('Users', 'resetTokenExpiry', {
|
||||||
|
type: Sequelize.DATE,
|
||||||
|
allowNull: true,
|
||||||
|
after: 'resetToken'
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async down(queryInterface, Sequelize) {
|
||||||
|
await queryInterface.removeColumn('Users', 'resetToken');
|
||||||
|
await queryInterface.removeColumn('Users', 'resetTokenExpiry');
|
||||||
|
}
|
||||||
|
};
|
|
@ -40,6 +40,14 @@ module.exports = (sequelize) => {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
|
resetToken: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
resetTokenExpiry: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequelize,
|
sequelize,
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
||||||
|
|
||||||
case `uname` in
|
|
||||||
*CYGWIN*|*MINGW*|*MSYS*)
|
|
||||||
if command -v cygpath > /dev/null 2>&1; then
|
|
||||||
basedir=`cygpath -w "$basedir"`
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -x "$basedir/node" ]; then
|
|
||||||
exec "$basedir/node" "$basedir/../mkdirp/bin/cmd.js" "$@"
|
|
||||||
else
|
|
||||||
exec node "$basedir/../mkdirp/bin/cmd.js" "$@"
|
|
||||||
fi
|
|
|
@ -1,17 +0,0 @@
|
||||||
@ECHO off
|
|
||||||
GOTO start
|
|
||||||
:find_dp0
|
|
||||||
SET dp0=%~dp0
|
|
||||||
EXIT /b
|
|
||||||
:start
|
|
||||||
SETLOCAL
|
|
||||||
CALL :find_dp0
|
|
||||||
|
|
||||||
IF EXIST "%dp0%\node.exe" (
|
|
||||||
SET "_prog=%dp0%\node.exe"
|
|
||||||
) ELSE (
|
|
||||||
SET "_prog=node"
|
|
||||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
|
||||||
)
|
|
||||||
|
|
||||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mkdirp\bin\cmd.js" %*
|
|
|
@ -1,28 +0,0 @@
|
||||||
#!/usr/bin/env pwsh
|
|
||||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
|
||||||
|
|
||||||
$exe=""
|
|
||||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
|
||||||
# Fix case when both the Windows and Linux builds of Node
|
|
||||||
# are installed in the same directory
|
|
||||||
$exe=".exe"
|
|
||||||
}
|
|
||||||
$ret=0
|
|
||||||
if (Test-Path "$basedir/node$exe") {
|
|
||||||
# Support pipeline input
|
|
||||||
if ($MyInvocation.ExpectingInput) {
|
|
||||||
$input | & "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
|
|
||||||
} else {
|
|
||||||
& "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
|
|
||||||
}
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
} else {
|
|
||||||
# Support pipeline input
|
|
||||||
if ($MyInvocation.ExpectingInput) {
|
|
||||||
$input | & "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
|
|
||||||
} else {
|
|
||||||
& "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
|
|
||||||
}
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
}
|
|
||||||
exit $ret
|
|
|
@ -106,6 +106,44 @@
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/@sendgrid/client": {
|
||||||
|
"version": "8.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.5.tgz",
|
||||||
|
"integrity": "sha512-Jqt8aAuGIpWGa15ZorTWI46q9gbaIdQFA21HIPQQl60rCjzAko75l3D1z7EyjFrNr4MfQ0StusivWh8Rjh10Cg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sendgrid/helpers": "^8.0.0",
|
||||||
|
"axios": "^1.8.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sendgrid/helpers": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"deepmerge": "^4.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sendgrid/mail": {
|
||||||
|
"version": "8.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.1.5.tgz",
|
||||||
|
"integrity": "sha512-W+YuMnkVs4+HA/bgfto4VHKcPKLc7NiZ50/NH2pzO6UHCCFuq8/GNB98YJlLEr/ESDyzAaDr7lVE7hoBwFTT3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@sendgrid/client": "^8.1.5",
|
||||||
|
"@sendgrid/helpers": "^8.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/body-parser": {
|
"node_modules/@types/body-parser": {
|
||||||
"version": "1.19.5",
|
"version": "1.19.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
||||||
|
@ -323,6 +361,12 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/at-least-node": {
|
"node_modules/at-least-node": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||||
|
@ -342,6 +386,17 @@
|
||||||
"node": ">= 6.0.0"
|
"node": ">= 6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
@ -561,6 +616,18 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "10.0.1",
|
"version": "10.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
|
||||||
|
@ -665,6 +732,24 @@
|
||||||
"ms": "2.0.0"
|
"ms": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/deepmerge": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/denque": {
|
"node_modules/denque": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
|
@ -808,6 +893,21 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es-set-tostringtag": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.6",
|
||||||
|
"has-tostringtag": "^1.0.2",
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es5-ext": {
|
"node_modules/es5-ext": {
|
||||||
"version": "0.10.64",
|
"version": "0.10.64",
|
||||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
|
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
|
||||||
|
@ -993,6 +1093,26 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
|
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/foreground-child": {
|
"node_modules/foreground-child": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||||
|
@ -1010,6 +1130,21 @@
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/forwarded": {
|
"node_modules/forwarded": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -1169,6 +1304,21 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/has-tostringtag": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-symbols": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -1721,6 +1871,15 @@
|
||||||
"node-gyp-build-test": "build-test.js"
|
"node-gyp-build-test": "build-test.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nodemailer": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==",
|
||||||
|
"license": "MIT-0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nopt": {
|
"node_modules/nopt": {
|
||||||
"version": "7.2.1",
|
"version": "7.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
|
||||||
|
@ -1874,6 +2033,12 @@
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.13.0",
|
"version": "6.13.0",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
|
|
43
backend/node_modules/isarray/README.md → backend/node_modules/@sendgrid/client/LICENSE
generated
vendored
43
backend/node_modules/isarray/README.md → backend/node_modules/@sendgrid/client/LICENSE
generated
vendored
|
@ -1,45 +1,6 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
# isarray
|
Copyright (C) 2025, Twilio SendGrid, Inc. <help@twilio.com>
|
||||||
|
|
||||||
`Array#isArray` for older browsers.
|
|
||||||
|
|
||||||
[](http://travis-ci.org/juliangruber/isarray)
|
|
||||||
[](https://www.npmjs.org/package/isarray)
|
|
||||||
|
|
||||||
[
|
|
||||||
](https://ci.testling.com/juliangruber/isarray)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```js
|
|
||||||
var isArray = require('isarray');
|
|
||||||
|
|
||||||
console.log(isArray([])); // => true
|
|
||||||
console.log(isArray({})); // => false
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
With [npm](http://npmjs.org) do
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ npm install isarray
|
|
||||||
```
|
|
||||||
|
|
||||||
Then bundle for the browser with
|
|
||||||
[browserify](https://github.com/substack/browserify).
|
|
||||||
|
|
||||||
With [component](http://component.io) do
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ component install juliangruber/isarray
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
(MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
this software and associated documentation files (the "Software"), to deal in
|
|
@ -0,0 +1,123 @@
|
||||||
|
[](https://travis-ci.com/sendgrid/sendgrid-nodejs)
|
||||||
|
[](https://www.npmjs.com/org/sendgrid)
|
||||||
|
|
||||||
|
**This package is part of a monorepo, please see [this README](../../README.md) for details.**
|
||||||
|
|
||||||
|
# Client for the SendGrid v3 Web API
|
||||||
|
This client library is used by the other [Twilio SendGrid service packages](https://www.npmjs.com/org/sendgrid) to make requests to the [Twilio SendGrid v3 Web API](https://sendgrid.com/docs/api-reference/). You can also use it independently to make custom requests to the Twilio SendGrid v3 Web API and other HTTP APIs.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Node.js version 6, 8 or >=10
|
||||||
|
- A Twilio SendGrid account, [sign up for free](https://sendgrid.com/free?source=sendgrid-nodejs) to send up to 40,000 emails for the first 30 days or check out [our pricing](https://sendgrid.com/pricing?source=sendgrid-nodejs).
|
||||||
|
|
||||||
|
## Obtain an API Key
|
||||||
|
|
||||||
|
Grab your API Key from the [Twilio SendGrid UI](https://app.sendgrid.com/settings/api_keys).
|
||||||
|
|
||||||
|
## Setup Environment Variables
|
||||||
|
|
||||||
|
Do not hardcode your [Twilio SendGrid API Key](https://app.sendgrid.com/settings/api_keys) into your code. Instead, use an environment variable or some other secure means of protecting your Twilio SendGrid API Key. Following is an example of using an environment variable.
|
||||||
|
|
||||||
|
Update the development environment with your [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys), for example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env
|
||||||
|
echo "sendgrid.env" >> .gitignore
|
||||||
|
source ./sendgrid.env
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install Package
|
||||||
|
|
||||||
|
The following recommended installation requires [npm](https://npmjs.org/). If you are unfamiliar with npm, see the [npm docs](https://npmjs.org/doc/). Npm comes installed with Node.js since node version 0.8.x, therefore, you likely already have it.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install --save @sendgrid/client
|
||||||
|
```
|
||||||
|
|
||||||
|
You may also use [yarn](https://yarnpkg.com/en/) to install.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @sendgrid/client
|
||||||
|
```
|
||||||
|
|
||||||
|
<a name="general"></a>
|
||||||
|
## General v3 Web API Usage Example
|
||||||
|
|
||||||
|
Please see [USAGE.md](USAGE.md) for all endpoint examples for the [Twilio SendGrid v3 Web API](https://sendgrid.com/docs/API_Reference/api_v3.html).
|
||||||
|
|
||||||
|
```js
|
||||||
|
const client = require('@sendgrid/client');
|
||||||
|
client.setApiKey(process.env.SENDGRID_API_KEY);
|
||||||
|
const request = {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/v3/api_keys'
|
||||||
|
};
|
||||||
|
client.request(request)
|
||||||
|
.then(([response, body]) => {
|
||||||
|
console.log(response.statusCode);
|
||||||
|
console.log(body);
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add a Custom Default Header
|
||||||
|
```js
|
||||||
|
client.setDefaultHeader('User-Agent', 'Some user agent string');
|
||||||
|
// or
|
||||||
|
client.setDefaultHeader({'User-Agent': 'Some user agent string'});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Change Request Defaults
|
||||||
|
```js
|
||||||
|
client.setDefaultRequest('baseUrl', 'https://api.sendgrid.com/');
|
||||||
|
// or
|
||||||
|
client.setDefaultRequest({baseUrl: 'https://api.sendgrid.com/'});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overwrite Promise Implementation
|
||||||
|
You can overwrite the promise implementation you want the client to use. Defaults to the ES6 `Promise`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
global.Promise = require('bluebird');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instantiate Client Instances Manually
|
||||||
|
```js
|
||||||
|
const {Client} = require('@sendgrid/client');
|
||||||
|
const sgClient1 = new Client();
|
||||||
|
const sgClient2 = new Client();
|
||||||
|
sgClient1.setApiKey('KEY1');
|
||||||
|
sgClient2.setApiKey('KEY2');
|
||||||
|
```
|
||||||
|
|
||||||
|
<a name="announcements"></a>
|
||||||
|
# Announcements
|
||||||
|
|
||||||
|
All updates to this library are documented in our [CHANGELOG](../../CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-nodejs/releases).
|
||||||
|
|
||||||
|
<a name="contribute"></a>
|
||||||
|
# How to Contribute
|
||||||
|
|
||||||
|
We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING](../../CONTRIBUTING.md) guide for details.
|
||||||
|
|
||||||
|
* [Feature Request](../../CONTRIBUTING.md#feature-request)
|
||||||
|
* [Bug Reports](../../CONTRIBUTING.md#submit-a-bug-report)
|
||||||
|
* [Improvements to the Codebase](../../CONTRIBUTING.md#improvements-to-the-codebase)
|
||||||
|
|
||||||
|
<a name="troubleshooting"></a>
|
||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
Please see our [troubleshooting guide](https://github.com/sendgrid/sendgrid-nodejs/blob/main/TROUBLESHOOTING.md) for common library issues.
|
||||||
|
|
||||||
|
<a name="about"></a>
|
||||||
|
# About
|
||||||
|
|
||||||
|
@sendgrid/client is maintained and funded by Twilio SendGrid, Inc. The names and logos for @sendgrid/client are trademarks of Twilio SendGrid, Inc.
|
||||||
|
|
||||||
|
If you need help installing or using the library, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com).
|
||||||
|
|
||||||
|
If you've instead found a bug in the library or would like new features added, go ahead and open issues or pull requests against this repo!
|
||||||
|
|
||||||
|

|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,29 @@
|
||||||
|
This document provides examples for specific Twilio SendGrid v3 API non-mail/send use cases. Please [open an issue](https://github.com/sendgrid/sendgrid-nodejs/issues) or make a pull request for any use cases you would like us to document here. Thank you!
|
||||||
|
|
||||||
|
# Table of Contents
|
||||||
|
|
||||||
|
- [How to Setup a Domain Authentication](#domain-authentication)
|
||||||
|
- [How to View Email Statistics](#how-to-view-email-statistics)
|
||||||
|
- [How to use the Email Activity Feed](#how-to-use-the-email-activity-feed)
|
||||||
|
|
||||||
|
|
||||||
|
<a name="domain-authentication"></a>
|
||||||
|
# How to Setup a Domain Authentication
|
||||||
|
|
||||||
|
You can find documentation for how to setup a domain authentication via the UI [here](https://sendgrid.com/docs/ui/account-and-settings/how-to-set-up-domain-authentication/) and via API [here](USAGE.md#sender-authentication).
|
||||||
|
|
||||||
|
Find more information about all of Twilio SendGrid's authentication related documentation [here](https://sendgrid.com/docs/ui/account-and-settings/).
|
||||||
|
|
||||||
|
<a name="email-stats"></a>
|
||||||
|
# How to View Email Statistics
|
||||||
|
|
||||||
|
You can find documentation for how to view your email statistics via the UI [here](https://app.sendgrid.com/statistics) and via API [here](USAGE.md#stats).
|
||||||
|
|
||||||
|
Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://sendgrid.com/docs/API_Reference/Webhooks/event.html) about events that occur as Twilio SendGrid processes your email.
|
||||||
|
|
||||||
|
<a name="email-activity-feed">
|
||||||
|
# How to use the Email Activity Feed
|
||||||
|
|
||||||
|
You can find documentation for how to use the Email Activity Feed via the UI [here](https://sendgrid.com/docs/ui/analytics-and-reporting/email-activity-feed/) and via API [here](USAGE.md#messages).
|
||||||
|
|
||||||
|
Find more information about getting started with the Email Activity Feed API [here](https://sendgrid.com/docs/API_Reference/Web_API_v3/Tutorials/getting_started_email_activity_api.html).
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Client = require("@sendgrid/client/src/client");
|
||||||
|
|
||||||
|
export = Client;
|
|
@ -0,0 +1,7 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const client = require('./src/client');
|
||||||
|
const Client = require('./src/classes/client');
|
||||||
|
|
||||||
|
module.exports = client;
|
||||||
|
module.exports.Client = Client;
|
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"name": "@sendgrid/client",
|
||||||
|
"description": "Twilio SendGrid NodeJS API client",
|
||||||
|
"version": "8.1.5",
|
||||||
|
"author": "Twilio SendGrid <help@twilio.com> (sendgrid.com)",
|
||||||
|
"contributors": [
|
||||||
|
"Kyle Partridge <kyle.partridge@sendgrid.com>",
|
||||||
|
"David Tomberlin <david.tomberlin@sendgrid.com>",
|
||||||
|
"Swift <swift@sendgrid.com>",
|
||||||
|
"Brandon West <brandon.west@sendgrid.com>",
|
||||||
|
"Scott Motte <scott.motte@sendgrid.com>",
|
||||||
|
"Robert Acosta <robert.acosta@sendgrid.com>",
|
||||||
|
"Elmer Thomas <ethomas@twilio.com>",
|
||||||
|
"Adam Reis <adam@reis.nz>"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://sendgrid.com",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/sendgrid/sendgrid-nodejs.git"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"main": "index.js",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@sendgrid/helpers": "^8.0.0",
|
||||||
|
"axios": "^1.8.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"chai": "4.2.0",
|
||||||
|
"nock": "^10.0.6"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"chai": "4.2.0"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"http",
|
||||||
|
"rest",
|
||||||
|
"api",
|
||||||
|
"mail",
|
||||||
|
"sendgrid"
|
||||||
|
],
|
||||||
|
"gitHead": "2bac86884f71be3fb19f96a10c02a1fb616b81fc"
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
'use strict';
|
||||||
|
const axios = require('axios');
|
||||||
|
const pkg = require('../../package.json');
|
||||||
|
const {
|
||||||
|
helpers: {
|
||||||
|
mergeData,
|
||||||
|
},
|
||||||
|
classes: {
|
||||||
|
Response,
|
||||||
|
ResponseError,
|
||||||
|
},
|
||||||
|
} = require('@sendgrid/helpers');
|
||||||
|
|
||||||
|
const API_KEY_PREFIX = 'SG.';
|
||||||
|
const SENDGRID_BASE_URL = 'https://api.sendgrid.com/';
|
||||||
|
const TWILIO_BASE_URL = 'https://email.twilio.com/';
|
||||||
|
const SENDGRID_REGION = 'global';
|
||||||
|
// Initialize the allowed regions and their corresponding hosts
|
||||||
|
const REGION_HOST_MAP = {
|
||||||
|
eu: 'https://api.eu.sendgrid.com/',
|
||||||
|
global: 'https://api.sendgrid.com/',
|
||||||
|
};
|
||||||
|
class Client {
|
||||||
|
constructor() {
|
||||||
|
this.auth = '';
|
||||||
|
this.impersonateSubuser = '';
|
||||||
|
this.sendgrid_region = SENDGRID_REGION;
|
||||||
|
|
||||||
|
this.defaultHeaders = {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'User-Agent': 'sendgrid/' + pkg.version + ';nodejs',
|
||||||
|
};
|
||||||
|
|
||||||
|
this.defaultRequest = {
|
||||||
|
baseUrl: SENDGRID_BASE_URL,
|
||||||
|
url: '',
|
||||||
|
method: 'GET',
|
||||||
|
headers: {},
|
||||||
|
maxContentLength: Infinity, // Don't limit the content length.
|
||||||
|
maxBodyLength: Infinity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setApiKey(apiKey) {
|
||||||
|
this.auth = 'Bearer ' + apiKey;
|
||||||
|
this.setDefaultRequest('baseUrl', REGION_HOST_MAP[this.sendgrid_region]);
|
||||||
|
|
||||||
|
if (!this.isValidApiKey(apiKey)) {
|
||||||
|
console.warn(`API key does not start with "${API_KEY_PREFIX}".`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTwilioEmailAuth(username, password) {
|
||||||
|
const b64Auth = Buffer.from(username + ':' + password).toString('base64');
|
||||||
|
this.auth = 'Basic ' + b64Auth;
|
||||||
|
this.setDefaultRequest('baseUrl', TWILIO_BASE_URL);
|
||||||
|
|
||||||
|
if (!this.isValidTwilioAuth(username, password)) {
|
||||||
|
console.warn('Twilio Email credentials must be non-empty strings.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidApiKey(apiKey) {
|
||||||
|
return this.isString(apiKey) && apiKey.trim().startsWith(API_KEY_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidTwilioAuth(username, password) {
|
||||||
|
return this.isString(username) && username
|
||||||
|
&& this.isString(password) && password;
|
||||||
|
}
|
||||||
|
|
||||||
|
isString(value) {
|
||||||
|
return typeof value === 'string' || value instanceof String;
|
||||||
|
}
|
||||||
|
|
||||||
|
setImpersonateSubuser(subuser) {
|
||||||
|
this.impersonateSubuser = subuser;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaultHeader(key, value) {
|
||||||
|
if (key !== null && typeof key === 'object') {
|
||||||
|
// key is an object
|
||||||
|
Object.assign(this.defaultHeaders, key);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.defaultHeaders[key] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaultRequest(key, value) {
|
||||||
|
if (key !== null && typeof key === 'object') {
|
||||||
|
// key is an object
|
||||||
|
Object.assign(this.defaultRequest, key);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.defaultRequest[key] = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global is the default residency (or region)
|
||||||
|
* Global region means the message will be sent through https://api.sendgrid.com
|
||||||
|
* EU region means the message will be sent through https://api.eu.sendgrid.com
|
||||||
|
**/
|
||||||
|
setDataResidency(region) {
|
||||||
|
if (!REGION_HOST_MAP.hasOwnProperty(region)) {
|
||||||
|
console.warn('Region can only be "global" or "eu".');
|
||||||
|
} else {
|
||||||
|
this.sendgrid_region = region;
|
||||||
|
this.setDefaultRequest('baseUrl', REGION_HOST_MAP[region]);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
createHeaders(data) {
|
||||||
|
// Merge data with default headers.
|
||||||
|
const headers = mergeData(this.defaultHeaders, data);
|
||||||
|
|
||||||
|
// Add auth, but don't overwrite if header already set.
|
||||||
|
if (typeof headers.Authorization === 'undefined' && this.auth) {
|
||||||
|
headers.Authorization = this.auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.impersonateSubuser) {
|
||||||
|
headers['On-Behalf-Of'] = this.impersonateSubuser;
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
createRequest(data) {
|
||||||
|
let options = {
|
||||||
|
url: data.uri || data.url,
|
||||||
|
baseUrl: data.baseUrl,
|
||||||
|
method: data.method,
|
||||||
|
data: data.body,
|
||||||
|
params: data.qs,
|
||||||
|
headers: data.headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Merge data with default request.
|
||||||
|
options = mergeData(this.defaultRequest, options);
|
||||||
|
options.headers = this.createHeaders(options.headers);
|
||||||
|
options.baseURL = options.baseUrl;
|
||||||
|
delete options.baseUrl;
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
request(data, cb) {
|
||||||
|
data = this.createRequest(data);
|
||||||
|
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
axios(data)
|
||||||
|
.then(response => {
|
||||||
|
return resolve([
|
||||||
|
new Response(response.status, response.data, response.headers),
|
||||||
|
response.data,
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (error.response) {
|
||||||
|
if (error.response.status >= 400) {
|
||||||
|
return reject(new ResponseError(error.response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Throw an error in case a callback function was not passed.
|
||||||
|
if (cb && typeof cb !== 'function') {
|
||||||
|
throw new Error('Callback passed is not a function.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cb) {
|
||||||
|
return promise
|
||||||
|
.then(result => cb(null, result))
|
||||||
|
.catch(error => cb(error, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Client;
|
|
@ -0,0 +1,58 @@
|
||||||
|
import {ResponseError} from "@sendgrid/helpers/classes";
|
||||||
|
import {ClientRequest} from "@sendgrid/client/src/request";
|
||||||
|
import {ClientResponse} from "@sendgrid/client/src/response";
|
||||||
|
|
||||||
|
declare class Client {
|
||||||
|
constructor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the SendGrid API key.
|
||||||
|
*/
|
||||||
|
setApiKey(apiKey: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Twilio Email credentials.
|
||||||
|
*/
|
||||||
|
setTwilioEmailAuth(username: string, password: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set client requests to impersonate a subuser
|
||||||
|
*/
|
||||||
|
setImpersonateSubuser(subuser: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set default header
|
||||||
|
*/
|
||||||
|
setDefaultHeader(key: string | { [s: string]: string }, value ?: string): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set default request
|
||||||
|
*/
|
||||||
|
setDefaultRequest<K extends keyof ClientRequest>(key: K | ClientRequest, value ?: ClientRequest[K]): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data residency as per region provided
|
||||||
|
*/
|
||||||
|
setDataResidency(region: string): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create headers for request
|
||||||
|
*/
|
||||||
|
createHeaders(data: { [key: string]: string }): { [key: string]: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create request
|
||||||
|
*/
|
||||||
|
createRequest(data: ClientRequest): ClientRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do a request
|
||||||
|
*/
|
||||||
|
request(data: ClientRequest, cb?: (err: ResponseError, response: [ClientResponse, any]) => void): Promise<[ClientResponse, any]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const client: Client;
|
||||||
|
// @ts-ignore
|
||||||
|
export = client
|
||||||
|
|
||||||
|
export {Client};
|
|
@ -0,0 +1,9 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Client = require('./classes/client');
|
||||||
|
|
||||||
|
//Export singleton instance
|
||||||
|
module.exports = new Client();
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,3 @@
|
||||||
|
import RequestOptions from "@sendgrid/helpers/classes/request";
|
||||||
|
|
||||||
|
export type ClientRequest = RequestOptions;
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Response from "@sendgrid/helpers/classes/response";
|
||||||
|
|
||||||
|
export type ClientResponse = Response;
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (C) 2023, Twilio SendGrid, Inc. <help@twilio.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,45 @@
|
||||||
|
[](https://travis-ci.com/sendgrid/sendgrid-nodejs)
|
||||||
|
[](https://www.npmjs.com/org/sendgrid)
|
||||||
|
|
||||||
|
**This package is part of a monorepo, please see [this README](../../README.md) for details.**
|
||||||
|
|
||||||
|
# Support classes and helpers for the SendGrid NodeJS libraries
|
||||||
|
This is a collection of classes and helpers used internally by the
|
||||||
|
[SendGrid NodeJS libraries](https://www.npmjs.com/org/sendgrid).
|
||||||
|
|
||||||
|
Note that not all objects represented in the SendGrid API have helper classes assigned to them because it is not expected that developers will use these classes themselves. They are primarily for internal use and developers are expected to use the publicly exposed API in the [various endpoint services](https://www.npmjs.com/org/sendgrid).
|
||||||
|
|
||||||
|
## Mail class
|
||||||
|
Used to compose a `Mail` object that converts itself to proper JSON for use with the [SendGrid v3 API](https://sendgrid.com/docs/api-reference/). This class supports a slightly different API to make sending emails easier in many cases by not having to deal with personalization arrays, instead offering a more straightforward interface for composing emails.
|
||||||
|
|
||||||
|
## Attachment class
|
||||||
|
Used by the inbound mail parser to compose `Attachment` objects.
|
||||||
|
|
||||||
|
## Personalization class
|
||||||
|
Used by the Mail class to compose `Personalization` objects.
|
||||||
|
|
||||||
|
## Email address
|
||||||
|
`Helper` class to represent an email address with name/email. Used by both the `Mail` and `Personalization` classes to deal with email addresses of various formats.
|
||||||
|
|
||||||
|
## Helpers
|
||||||
|
Internal helpers that mostly speak for themselves.
|
||||||
|
|
||||||
|
<a name="contribute"></a>
|
||||||
|
# How to Contribute
|
||||||
|
|
||||||
|
We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING](https://github.com/sendgrid/sendgrid-nodejs/blob/HEAD/CONTRIBUTING.md) guide for details.
|
||||||
|
|
||||||
|
* [Feature Request](../../CONTRIBUTING.md#feature-request)
|
||||||
|
* [Bug Reports](../../CONTRIBUTING.md#submit-a-bug-report)
|
||||||
|
* [Improvements to the Codebase](../../CONTRIBUTING.md#improvements-to-the-codebase)
|
||||||
|
|
||||||
|
<a name="about"></a>
|
||||||
|
# About
|
||||||
|
|
||||||
|
@sendgrid/helpers are maintained and funded by Twilio SendGrid, Inc. The names and logos for @sendgrid/helpers are trademarks of Twilio SendGrid, Inc.
|
||||||
|
|
||||||
|
If you need help installing or using the library, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com).
|
||||||
|
|
||||||
|
If you've instead found a bug in the library or would like new features added, go ahead and open issues or pull requests against this repo!
|
||||||
|
|
||||||
|

|
|
@ -0,0 +1 @@
|
||||||
|
Just a little file for testing attachments.
|
|
@ -0,0 +1,33 @@
|
||||||
|
export interface AttachmentData {
|
||||||
|
content: string;
|
||||||
|
filename: string;
|
||||||
|
type?: string;
|
||||||
|
disposition?: string;
|
||||||
|
contentId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttachmentJSON {
|
||||||
|
content: string;
|
||||||
|
filename: string;
|
||||||
|
type?: string;
|
||||||
|
disposition?: string;
|
||||||
|
content_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Attachment implements AttachmentData {
|
||||||
|
content: string;
|
||||||
|
filename: string;
|
||||||
|
type?: string;
|
||||||
|
disposition?: string;
|
||||||
|
contentId?: string;
|
||||||
|
|
||||||
|
constructor(data?: AttachmentData);
|
||||||
|
|
||||||
|
fromData(data: AttachmentData): void;
|
||||||
|
setContent(content: string): void;
|
||||||
|
setFilename(filename: string): void;
|
||||||
|
setType(type: string): void;
|
||||||
|
setDisposition(disposition: string): void;
|
||||||
|
setContentId(contentId: string): void;
|
||||||
|
toJSON(): AttachmentJSON;
|
||||||
|
}
|
|
@ -0,0 +1,187 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const toCamelCase = require('../helpers/to-camel-case');
|
||||||
|
const toSnakeCase = require('../helpers/to-snake-case');
|
||||||
|
const deepClone = require('../helpers/deep-clone');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attachment class
|
||||||
|
*/
|
||||||
|
class Attachment {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
constructor(data) {
|
||||||
|
|
||||||
|
//Create from data
|
||||||
|
if (data) {
|
||||||
|
this.fromData(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From data
|
||||||
|
*/
|
||||||
|
fromData(data) {
|
||||||
|
|
||||||
|
//Expecting object
|
||||||
|
if (typeof data !== 'object') {
|
||||||
|
throw new Error('Expecting object for Mail data');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert to camel case to make it workable, making a copy to prevent
|
||||||
|
//changes to the original objects
|
||||||
|
data = deepClone(data);
|
||||||
|
data = toCamelCase(data);
|
||||||
|
|
||||||
|
//Extract properties from data
|
||||||
|
const {
|
||||||
|
content,
|
||||||
|
filename,
|
||||||
|
type,
|
||||||
|
disposition,
|
||||||
|
contentId,
|
||||||
|
filePath,
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
if ((typeof content !== 'undefined') && (typeof filePath !== 'undefined')) {
|
||||||
|
throw new Error(
|
||||||
|
"The props 'content' and 'filePath' cannot be used together."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set data
|
||||||
|
this.setFilename(filename);
|
||||||
|
this.setType(type);
|
||||||
|
this.setDisposition(disposition);
|
||||||
|
this.setContentId(contentId);
|
||||||
|
this.setContent(filePath ? this.readFile(filePath) : content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a file and return its content as base64
|
||||||
|
*/
|
||||||
|
readFile(filePath) {
|
||||||
|
return fs.readFileSync(path.resolve(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set content
|
||||||
|
*/
|
||||||
|
setContent(content) {
|
||||||
|
//Duck type check toString on content if it's a Buffer as that's the method that will be called.
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
this.content = content;
|
||||||
|
return;
|
||||||
|
} else if (content instanceof Buffer && content.toString !== undefined) {
|
||||||
|
this.content = content.toString();
|
||||||
|
|
||||||
|
if (this.disposition === 'attachment') {
|
||||||
|
this.content = content.toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('`content` expected to be either Buffer or string');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set content
|
||||||
|
*/
|
||||||
|
setFileContent(content) {
|
||||||
|
if (content instanceof Buffer && content.toString !== undefined) {
|
||||||
|
this.content = content.toString('base64');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('`content` expected to be Buffer');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set filename
|
||||||
|
*/
|
||||||
|
setFilename(filename) {
|
||||||
|
if (typeof filename === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (filename && typeof filename !== 'string') {
|
||||||
|
throw new Error('String expected for `filename`');
|
||||||
|
}
|
||||||
|
this.filename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set type
|
||||||
|
*/
|
||||||
|
setType(type) {
|
||||||
|
if (typeof type === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof type !== 'string') {
|
||||||
|
throw new Error('String expected for `type`');
|
||||||
|
}
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set disposition
|
||||||
|
*/
|
||||||
|
setDisposition(disposition) {
|
||||||
|
if (typeof disposition === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof disposition !== 'string') {
|
||||||
|
throw new Error('String expected for `disposition`');
|
||||||
|
}
|
||||||
|
this.disposition = disposition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set content ID
|
||||||
|
*/
|
||||||
|
setContentId(contentId) {
|
||||||
|
if (typeof contentId === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof contentId !== 'string') {
|
||||||
|
throw new Error('String expected for `contentId`');
|
||||||
|
}
|
||||||
|
this.contentId = contentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To JSON
|
||||||
|
*/
|
||||||
|
toJSON() {
|
||||||
|
|
||||||
|
//Extract properties from self
|
||||||
|
const {content, filename, type, disposition, contentId} = this;
|
||||||
|
|
||||||
|
//Initialize with mandatory properties
|
||||||
|
const json = {content, filename};
|
||||||
|
|
||||||
|
//Add whatever else we have
|
||||||
|
if (typeof type !== 'undefined') {
|
||||||
|
json.type = type;
|
||||||
|
}
|
||||||
|
if (typeof disposition !== 'undefined') {
|
||||||
|
json.disposition = disposition;
|
||||||
|
}
|
||||||
|
if (typeof contentId !== 'undefined') {
|
||||||
|
json.contentId = contentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return
|
||||||
|
return toSnakeCase(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Export class
|
||||||
|
module.exports = Attachment;
|
|
@ -0,0 +1,95 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Attachment = require('./attachment');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Attachment', function() {
|
||||||
|
let attachment;
|
||||||
|
beforeEach(function() {
|
||||||
|
attachment = new Attachment();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set content as string
|
||||||
|
describe('setContent(), string', function() {
|
||||||
|
it('should set string as content', function() {
|
||||||
|
attachment.setContent('Just a string.');
|
||||||
|
expect(attachment.content).to.equal('Just a string.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set content as stream
|
||||||
|
describe('setContent(), stream', function() {
|
||||||
|
it('should convert stream to string and set as content', function() {
|
||||||
|
const fileData = fs.readFileSync('./packages/helpers/attachment.txt');
|
||||||
|
attachment.setContent(fileData);
|
||||||
|
expect(attachment.content).to.equal('Just a little file for testing attachments.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set content as wrong type
|
||||||
|
describe('setContent(), wrong type', function() {
|
||||||
|
it('should not allow setting content of wrong type', function() {
|
||||||
|
expect(() => attachment.setContent(null)).to.throw('`content` expected to be either Buffer or string');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Constructor
|
||||||
|
describe('constructor(data)', function() {
|
||||||
|
it('should not accept both content and filePath', function() {
|
||||||
|
expect(function() {
|
||||||
|
attachment = new Attachment({
|
||||||
|
filename: 'attachment.txt',
|
||||||
|
type: 'plain/text',
|
||||||
|
disposition: 'attachment',
|
||||||
|
contentId: 'myattachment',
|
||||||
|
content: '',
|
||||||
|
filePath: '',
|
||||||
|
});
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set content
|
||||||
|
describe('setContent()', function() {
|
||||||
|
let attachment;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
attachment = new Attachment({
|
||||||
|
filename: 'attachment.txt',
|
||||||
|
type: 'plain/text',
|
||||||
|
disposition: 'attachment',
|
||||||
|
contentId: 'myattachment',
|
||||||
|
content: 'SGVsbG8gV29ybGQK',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the given value', function() {
|
||||||
|
expect(attachment.content).to.equal('SGVsbG8gV29ybGQK');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept a buffer', function() {
|
||||||
|
attachment.setContent(new Buffer('Hello World\n'));
|
||||||
|
expect(attachment.content).to.equal('SGVsbG8gV29ybGQK');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept a raw file', function() {
|
||||||
|
attachment = new Attachment({
|
||||||
|
filename: 'attachment.txt',
|
||||||
|
type: 'plain/text',
|
||||||
|
disposition: 'attachment',
|
||||||
|
contentId: 'myattachment',
|
||||||
|
filePath: path.join(__dirname, '/attachment.js'),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(attachment.content).to.be.a('string');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,24 @@
|
||||||
|
export type EmailData = string|{ name?: string; email: string; }
|
||||||
|
|
||||||
|
export type EmailJSON = { name?: string; email: string }
|
||||||
|
|
||||||
|
export default class EmailAddress {
|
||||||
|
constructor(data?: EmailData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From data
|
||||||
|
*/
|
||||||
|
fromData(data: EmailData): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set name
|
||||||
|
*/
|
||||||
|
setName(name: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set email (mandatory)
|
||||||
|
*/
|
||||||
|
setEmail(email: string): void;
|
||||||
|
|
||||||
|
toJSON(): EmailJSON;
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const splitNameEmail = require('../helpers/split-name-email');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Email address class
|
||||||
|
*/
|
||||||
|
class EmailAddress {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
constructor(data) {
|
||||||
|
|
||||||
|
//Construct from data
|
||||||
|
if (data) {
|
||||||
|
this.fromData(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From data
|
||||||
|
*/
|
||||||
|
fromData(data) {
|
||||||
|
|
||||||
|
//String given
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
const [name, email] = splitNameEmail(data);
|
||||||
|
data = {name, email};
|
||||||
|
}
|
||||||
|
|
||||||
|
//Expecting object
|
||||||
|
if (typeof data !== 'object') {
|
||||||
|
throw new Error('Expecting object or string for EmailAddress data');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Extract name and email
|
||||||
|
const {name, email} = data;
|
||||||
|
|
||||||
|
//Set
|
||||||
|
this.setEmail(email);
|
||||||
|
this.setName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set name
|
||||||
|
*/
|
||||||
|
setName(name) {
|
||||||
|
if (typeof name === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof name !== 'string') {
|
||||||
|
throw new Error('String expected for `name`');
|
||||||
|
}
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set email (mandatory)
|
||||||
|
*/
|
||||||
|
setEmail(email) {
|
||||||
|
if (typeof email === 'undefined') {
|
||||||
|
throw new Error('Must provide `email`');
|
||||||
|
}
|
||||||
|
if (typeof email !== 'string') {
|
||||||
|
throw new Error('String expected for `email`');
|
||||||
|
}
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To JSON
|
||||||
|
*/
|
||||||
|
toJSON() {
|
||||||
|
|
||||||
|
//Get properties
|
||||||
|
const {email, name} = this;
|
||||||
|
|
||||||
|
//Initialize with mandatory properties
|
||||||
|
const json = {email};
|
||||||
|
|
||||||
|
//Add name if present
|
||||||
|
if (name !== '') {
|
||||||
|
json.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************
|
||||||
|
* Static helpers
|
||||||
|
***/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an EmailAddress instance from given data
|
||||||
|
*/
|
||||||
|
static create(data) {
|
||||||
|
|
||||||
|
//Array?
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data
|
||||||
|
.filter(item => !!item)
|
||||||
|
.map(item => this.create(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Already instance of EmailAddress class?
|
||||||
|
if (data instanceof EmailAddress) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create instance
|
||||||
|
return new EmailAddress(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Export class
|
||||||
|
module.exports = EmailAddress;
|
175
backend/node_modules/@sendgrid/helpers/classes/email-address.spec.js
generated
vendored
Normal file
175
backend/node_modules/@sendgrid/helpers/classes/email-address.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const EmailAddress = require('./email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('EmailAddress', function() {
|
||||||
|
|
||||||
|
//Test data
|
||||||
|
const data = [
|
||||||
|
'test@example.org',
|
||||||
|
'Test <test@example.org>',
|
||||||
|
{name: 'Test', email: 'test@example.org'},
|
||||||
|
];
|
||||||
|
|
||||||
|
//Set email
|
||||||
|
describe('setEmail()', function() {
|
||||||
|
let email;
|
||||||
|
beforeEach(function() {
|
||||||
|
email = new EmailAddress();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the email address', function() {
|
||||||
|
email.setEmail('test@example.org');
|
||||||
|
expect(email.email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
email.setEmail(5);
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
email.setEmail(null);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should throw an error for no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
email.setEmail();
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set name
|
||||||
|
describe('setName()', function() {
|
||||||
|
let email;
|
||||||
|
beforeEach(function() {
|
||||||
|
email = new EmailAddress();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the name', function() {
|
||||||
|
email.setName('Test');
|
||||||
|
expect(email.name).to.equal('Test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not wrap name in quotes if a comma is present', function() {
|
||||||
|
email.setName('Doe, John');
|
||||||
|
expect(email.name).to.equal('Doe, John');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not double wrap in quotes', function() {
|
||||||
|
email.setName('\"Doe, John\"');
|
||||||
|
expect(email.name).to.equal('\"Doe, John\"');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
email.setName(5);
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
email.setName(null);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
email.setName();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//To JSON
|
||||||
|
describe('toJSON()', function() {
|
||||||
|
let emails;
|
||||||
|
beforeEach(function() {
|
||||||
|
emails = data.map(email => EmailAddress.create(email));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should always have the email field', function() {
|
||||||
|
emails.forEach(email => {
|
||||||
|
const json = email.toJSON();
|
||||||
|
expect(json).to.have.property('email');
|
||||||
|
expect(json.email).to.equal(email.email);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should have the name field if given', function() {
|
||||||
|
emails.filter(email => email.name !== '').forEach(email => {
|
||||||
|
const json = email.toJSON();
|
||||||
|
expect(json).to.have.property('name');
|
||||||
|
expect(json.name).to.equal(email.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should not have the name field if not given', function() {
|
||||||
|
emails.filter(email => email.name === '').forEach(email => {
|
||||||
|
const json = email.toJSON();
|
||||||
|
expect(json).not.to.have.property('name');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//From data
|
||||||
|
describe('fromData()', function() {
|
||||||
|
let email;
|
||||||
|
beforeEach(function() {
|
||||||
|
email = new EmailAddress();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle email address strings', function() {
|
||||||
|
email.fromData(data[0]);
|
||||||
|
expect(email.email).to.equal('test@example.org');
|
||||||
|
expect(email.name).to.equal('');
|
||||||
|
});
|
||||||
|
it('should handle name and email address strings', function() {
|
||||||
|
email.fromData(data[1]);
|
||||||
|
expect(email.email).to.equal('test@example.org');
|
||||||
|
expect(email.name).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should handle objects', function() {
|
||||||
|
email.fromData(data[2]);
|
||||||
|
expect(email.email).to.equal('test@example.org');
|
||||||
|
expect(email.name).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
email.fromData(5);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Static create method
|
||||||
|
describe('create()', function() {
|
||||||
|
let emails;
|
||||||
|
beforeEach(function() {
|
||||||
|
emails = data.map(email => EmailAddress.create(email));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create email address instances from given data', function() {
|
||||||
|
emails.forEach(email => {
|
||||||
|
expect(email).to.be.an.instanceof(EmailAddress);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should have the expected properties for each email', function() {
|
||||||
|
emails.forEach(email => {
|
||||||
|
expect(email).to.have.property('email');
|
||||||
|
expect(email).to.have.property('name');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should handle arrays', function() {
|
||||||
|
const emailsArr = EmailAddress.create(data);
|
||||||
|
expect(emailsArr).to.be.an.instanceof(Array);
|
||||||
|
expect(emailsArr).to.have.lengthOf(3);
|
||||||
|
emailsArr.forEach(email => {
|
||||||
|
expect(email).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(email).to.have.property('email');
|
||||||
|
expect(email).to.have.property('name');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should handle instances of EmailAddress', function() {
|
||||||
|
const email1 = new EmailAddress({email: 'test@example.org'});
|
||||||
|
const email2 = EmailAddress.create(email1);
|
||||||
|
expect(email2).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(email2.email).to.equal(email1.email);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
import Attachment from "@sendgrid/helpers/classes/attachment";
|
||||||
|
import EmailAddress from "@sendgrid/helpers/classes/email-address";
|
||||||
|
import Mail from "@sendgrid/helpers/classes/mail"
|
||||||
|
import Personalization from "@sendgrid/helpers/classes/personalization";
|
||||||
|
import Response from "@sendgrid/helpers/classes/response";
|
||||||
|
import ResponseError from "@sendgrid/helpers/classes/response-error";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Attachment,
|
||||||
|
EmailAddress,
|
||||||
|
Mail,
|
||||||
|
Personalization,
|
||||||
|
Response,
|
||||||
|
ResponseError,
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose classes
|
||||||
|
*/
|
||||||
|
const Attachment = require('./attachment');
|
||||||
|
const EmailAddress = require('./email-address');
|
||||||
|
const Mail = require('./mail');
|
||||||
|
const Personalization = require('./personalization');
|
||||||
|
const Response = require('./response');
|
||||||
|
const ResponseError = require('./response-error');
|
||||||
|
const Statistics = require('./statistics');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
Attachment,
|
||||||
|
EmailAddress,
|
||||||
|
Mail,
|
||||||
|
Personalization,
|
||||||
|
Response,
|
||||||
|
ResponseError,
|
||||||
|
Statistics,
|
||||||
|
};
|
|
@ -0,0 +1,373 @@
|
||||||
|
import {AttachmentData, AttachmentJSON} from "./attachment";
|
||||||
|
import {EmailData, EmailJSON} from "./email-address";
|
||||||
|
import Personalization from "./personalization";
|
||||||
|
import {PersonalizationData, PersonalizationJSON} from "./personalization";
|
||||||
|
|
||||||
|
export interface MailContent {
|
||||||
|
type: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ASMOptions {
|
||||||
|
groupId: number;
|
||||||
|
groupsToDisplay?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ASMOptionsJSON {
|
||||||
|
group_id: number;
|
||||||
|
groups_to_display?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MailSettings {
|
||||||
|
bcc?: {
|
||||||
|
enable?: boolean;
|
||||||
|
email?: string;
|
||||||
|
};
|
||||||
|
bypassListManagement?: {
|
||||||
|
enable?: boolean;
|
||||||
|
};
|
||||||
|
bypassSpamManagement?: {
|
||||||
|
enable?: boolean;
|
||||||
|
};
|
||||||
|
bypassBounceManagement?: {
|
||||||
|
enable?: boolean;
|
||||||
|
};
|
||||||
|
bypassUnsubscribeManagement?: {
|
||||||
|
enable?: boolean;
|
||||||
|
};
|
||||||
|
footer?: {
|
||||||
|
enable?: boolean;
|
||||||
|
text?: string;
|
||||||
|
html?: string;
|
||||||
|
};
|
||||||
|
sandboxMode?: {
|
||||||
|
enable?: boolean;
|
||||||
|
};
|
||||||
|
spamCheck?: {
|
||||||
|
enable?: boolean;
|
||||||
|
threshold?: number;
|
||||||
|
postToUrl?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MailSettingsJSON {
|
||||||
|
bcc?: {
|
||||||
|
enable?: boolean;
|
||||||
|
email?: string;
|
||||||
|
};
|
||||||
|
bypass_list_management?: {
|
||||||
|
enable?: boolean;
|
||||||
|
};
|
||||||
|
footer?: {
|
||||||
|
enable?: boolean;
|
||||||
|
text?: string;
|
||||||
|
html?: string;
|
||||||
|
};
|
||||||
|
sandbox_mode?: {
|
||||||
|
enable?: boolean;
|
||||||
|
};
|
||||||
|
spam_check?: {
|
||||||
|
enable?: boolean;
|
||||||
|
threshold?: number;
|
||||||
|
post_to_url?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrackingSettings {
|
||||||
|
clickTracking?: {
|
||||||
|
enable?: boolean;
|
||||||
|
enableText?: boolean;
|
||||||
|
};
|
||||||
|
openTracking?: {
|
||||||
|
enable?: boolean;
|
||||||
|
substitutionTag?: string;
|
||||||
|
};
|
||||||
|
subscriptionTracking?: {
|
||||||
|
enable?: boolean;
|
||||||
|
text?: string;
|
||||||
|
html?: string;
|
||||||
|
substitutionTag?: string;
|
||||||
|
};
|
||||||
|
ganalytics?: {
|
||||||
|
enable?: boolean;
|
||||||
|
utmSource?: string;
|
||||||
|
utmMedium?: string;
|
||||||
|
utmTerm?: string;
|
||||||
|
utmContent?: string;
|
||||||
|
utmCampaign?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrackingSettingsJSON {
|
||||||
|
click_tracking?: {
|
||||||
|
enable?: boolean;
|
||||||
|
enable_text?: boolean;
|
||||||
|
};
|
||||||
|
open_tracking?: {
|
||||||
|
enable?: boolean;
|
||||||
|
substitution_tag?: string;
|
||||||
|
};
|
||||||
|
subscription_tracking?: {
|
||||||
|
enable?: boolean;
|
||||||
|
text?: string;
|
||||||
|
html?: string;
|
||||||
|
substitution_tag?: string;
|
||||||
|
};
|
||||||
|
ganalytics?: {
|
||||||
|
enable?: boolean;
|
||||||
|
utm_source?: string;
|
||||||
|
utm_medium?: string;
|
||||||
|
utm_term?: string;
|
||||||
|
utm_content?: string;
|
||||||
|
utm_campaign?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MailData {
|
||||||
|
to?: EmailData|EmailData[],
|
||||||
|
cc?: EmailData|EmailData[],
|
||||||
|
bcc?: EmailData|EmailData[],
|
||||||
|
|
||||||
|
from: EmailData,
|
||||||
|
replyTo?: EmailData,
|
||||||
|
|
||||||
|
sendAt?: number,
|
||||||
|
|
||||||
|
subject?: string,
|
||||||
|
text?: string,
|
||||||
|
html?: string,
|
||||||
|
content?: MailContent[],
|
||||||
|
templateId?: string,
|
||||||
|
|
||||||
|
personalizations?: PersonalizationData[],
|
||||||
|
attachments?: AttachmentData[],
|
||||||
|
|
||||||
|
ipPoolName?: string,
|
||||||
|
batchId?: string,
|
||||||
|
|
||||||
|
sections?: { [key: string]: string },
|
||||||
|
headers?: { [key: string]: string },
|
||||||
|
|
||||||
|
categories?: string[],
|
||||||
|
category?: string,
|
||||||
|
|
||||||
|
customArgs?: { [key: string]: any },
|
||||||
|
asm?: ASMOptions,
|
||||||
|
|
||||||
|
mailSettings?: MailSettings,
|
||||||
|
trackingSettings?: TrackingSettings,
|
||||||
|
|
||||||
|
substitutions?: { [key: string]: string },
|
||||||
|
substitutionWrappers?: string[],
|
||||||
|
|
||||||
|
isMultiple?: boolean,
|
||||||
|
dynamicTemplateData?: { [key: string]: any },
|
||||||
|
|
||||||
|
hideWarnings?: boolean,
|
||||||
|
|
||||||
|
replyToList?: EmailJSON | EmailJSON[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MailDataRequired = MailData & (
|
||||||
|
{ text: string } | { html: string } | { templateId: string } | { content: MailContent[] & { 0: MailContent } });
|
||||||
|
|
||||||
|
export interface MailJSON {
|
||||||
|
from: EmailJSON;
|
||||||
|
subject: string;
|
||||||
|
content: MailContent[];
|
||||||
|
personalizations: PersonalizationJSON[];
|
||||||
|
attachments?: AttachmentJSON[];
|
||||||
|
categories?: string[];
|
||||||
|
headers?: { [key: string]: string };
|
||||||
|
mail_settings?: MailSettingsJSON;
|
||||||
|
tracking_settings?: TrackingSettingsJSON;
|
||||||
|
custom_args?: { [key: string]: string };
|
||||||
|
sections?: { [key: string]: string };
|
||||||
|
asm?: ASMOptionsJSON;
|
||||||
|
|
||||||
|
reply_to?: EmailJSON;
|
||||||
|
send_at?: number;
|
||||||
|
batch_id?: string;
|
||||||
|
template_id?: string;
|
||||||
|
ip_pool_name?: string;
|
||||||
|
reply_to_list?: EmailJSON[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Mail {
|
||||||
|
constructor(data?: MailData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build from data
|
||||||
|
*/
|
||||||
|
fromData(data: MailData): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set from email
|
||||||
|
*/
|
||||||
|
setFrom(from: EmailData): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set reply to
|
||||||
|
*/
|
||||||
|
setReplyTo(replyTo: EmailData): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set subject
|
||||||
|
*/
|
||||||
|
setSubject(subject: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set send at
|
||||||
|
*/
|
||||||
|
setSendAt(sendAt: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set template ID
|
||||||
|
*/
|
||||||
|
setTemplateId(templateId: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set batch ID
|
||||||
|
*/
|
||||||
|
setBatchId(batchId: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set IP pool name
|
||||||
|
*/
|
||||||
|
setIpPoolName(ipPoolName: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set ASM
|
||||||
|
*/
|
||||||
|
setAsm(asm: ASMOptions): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set personalizations
|
||||||
|
*/
|
||||||
|
setPersonalizations(personalizations: PersonalizationData[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add personalization
|
||||||
|
*/
|
||||||
|
addPersonalization(personalization: PersonalizationData): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method for quickly creating personalizations
|
||||||
|
*/
|
||||||
|
addTo(to: EmailData|EmailData[], cc: EmailData|EmailData[], bcc: EmailData|EmailData[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set substitutions
|
||||||
|
*/
|
||||||
|
setSubstitutions(substitutions: { [key: string]: string }): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set substitution wrappers
|
||||||
|
*/
|
||||||
|
setSubstitutionWrappers(wrappers: string[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper which applies globally set substitutions to personalizations
|
||||||
|
*/
|
||||||
|
applySubstitutions(personalization: Personalization): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set content
|
||||||
|
*/
|
||||||
|
setContent(content: MailContent[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add content
|
||||||
|
*/
|
||||||
|
addContent(content: MailContent): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add text content
|
||||||
|
*/
|
||||||
|
addTextContent(text: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add HTML content
|
||||||
|
*/
|
||||||
|
addHtmlContent(html: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set attachments
|
||||||
|
*/
|
||||||
|
setAttachments(attachments: AttachmentData[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add attachment
|
||||||
|
*/
|
||||||
|
addAttachment(attachment: AttachmentData): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set categories
|
||||||
|
*/
|
||||||
|
setCategories(categories: string[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add category
|
||||||
|
*/
|
||||||
|
addCategory(category: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set headers
|
||||||
|
*/
|
||||||
|
setHeaders(headers: { [key: string]: string }): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a header
|
||||||
|
*/
|
||||||
|
addHeader(key: string, value: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set sections
|
||||||
|
*/
|
||||||
|
setSections(sections: { [key: string]: string }): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set custom args
|
||||||
|
*/
|
||||||
|
setCustomArgs(customArgs: { [key: string]: string }): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set tracking settings
|
||||||
|
*/
|
||||||
|
setTrackingSettings(settings: TrackingSettings): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set mail settings
|
||||||
|
*/
|
||||||
|
setMailSettings(settings: MailSettings): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set hide warnings
|
||||||
|
*/
|
||||||
|
setHideWarnings(hide: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To JSON
|
||||||
|
*/
|
||||||
|
toJSON(): MailJSON;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Mail instance from given data
|
||||||
|
*/
|
||||||
|
static create(data: MailData): Mail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Mail instance from given data
|
||||||
|
*/
|
||||||
|
static create(data: Mail): Mail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Mail instance from given data
|
||||||
|
*/
|
||||||
|
static create(data: MailData[]): Mail[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set reply_to_list header from given data
|
||||||
|
*/
|
||||||
|
setReplyToList(replyToList: EmailJSON[]): void;
|
||||||
|
}
|
|
@ -0,0 +1,689 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const EmailAddress = require('./email-address');
|
||||||
|
const Personalization = require('./personalization');
|
||||||
|
const toCamelCase = require('../helpers/to-camel-case');
|
||||||
|
const toSnakeCase = require('../helpers/to-snake-case');
|
||||||
|
const deepClone = require('../helpers/deep-clone');
|
||||||
|
const arrayToJSON = require('../helpers/array-to-json');
|
||||||
|
const { DYNAMIC_TEMPLATE_CHAR_WARNING } = require('../constants');
|
||||||
|
const {validateMailSettings, validateTrackingSettings} = require('../helpers/validate-settings');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mail class
|
||||||
|
*/
|
||||||
|
class Mail {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
constructor(data) {
|
||||||
|
|
||||||
|
//Initialize array and object properties
|
||||||
|
this.isDynamic = false;
|
||||||
|
this.hideWarnings = false;
|
||||||
|
this.personalizations = [];
|
||||||
|
this.attachments = [];
|
||||||
|
this.content = [];
|
||||||
|
this.categories = [];
|
||||||
|
this.headers = {};
|
||||||
|
this.sections = {};
|
||||||
|
this.customArgs = {};
|
||||||
|
this.trackingSettings = {};
|
||||||
|
this.mailSettings = {};
|
||||||
|
this.asm = {};
|
||||||
|
|
||||||
|
//Helper properties
|
||||||
|
this.substitutions = null;
|
||||||
|
this.substitutionWrappers = null;
|
||||||
|
this.dynamicTemplateData = null;
|
||||||
|
|
||||||
|
//Process data if given
|
||||||
|
if (data) {
|
||||||
|
this.fromData(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build from data
|
||||||
|
*/
|
||||||
|
fromData(data) {
|
||||||
|
|
||||||
|
//Expecting object
|
||||||
|
if (typeof data !== 'object') {
|
||||||
|
throw new Error('Expecting object for Mail data');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert to camel case to make it workable, making a copy to prevent
|
||||||
|
//changes to the original objects
|
||||||
|
data = deepClone(data);
|
||||||
|
data = toCamelCase(data, ['substitutions', 'dynamicTemplateData', 'customArgs', 'headers', 'sections']);
|
||||||
|
|
||||||
|
//Extract properties from data
|
||||||
|
const {
|
||||||
|
to, from, replyTo, cc, bcc, sendAt, subject, text, html, content,
|
||||||
|
templateId, personalizations, attachments, ipPoolName, batchId,
|
||||||
|
sections, headers, categories, category, customArgs, asm, mailSettings,
|
||||||
|
trackingSettings, substitutions, substitutionWrappers, dynamicTemplateData, isMultiple,
|
||||||
|
hideWarnings, replyToList,
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
//Set data
|
||||||
|
this.setFrom(from);
|
||||||
|
this.setReplyTo(replyTo);
|
||||||
|
this.setSubject(subject);
|
||||||
|
this.setSendAt(sendAt);
|
||||||
|
this.setTemplateId(templateId);
|
||||||
|
this.setBatchId(batchId);
|
||||||
|
this.setIpPoolName(ipPoolName);
|
||||||
|
this.setAttachments(attachments);
|
||||||
|
this.setContent(content);
|
||||||
|
this.setSections(sections);
|
||||||
|
this.setHeaders(headers);
|
||||||
|
this.setCategories(category);
|
||||||
|
this.setCategories(categories);
|
||||||
|
this.setCustomArgs(customArgs);
|
||||||
|
this.setAsm(asm);
|
||||||
|
this.setMailSettings(mailSettings);
|
||||||
|
this.setTrackingSettings(trackingSettings);
|
||||||
|
this.setHideWarnings(hideWarnings);
|
||||||
|
this.setReplyToList(replyToList);
|
||||||
|
|
||||||
|
if (this.isDynamic) {
|
||||||
|
this.setDynamicTemplateData(dynamicTemplateData);
|
||||||
|
} else {
|
||||||
|
this.setSubstitutions(substitutions);
|
||||||
|
this.setSubstitutionWrappers(substitutionWrappers);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add contents from text/html properties
|
||||||
|
this.addTextContent(text);
|
||||||
|
this.addHtmlContent(html);
|
||||||
|
|
||||||
|
//Using "to" property for personalizations
|
||||||
|
if (personalizations) {
|
||||||
|
this.setPersonalizations(personalizations);
|
||||||
|
} else if (isMultiple && Array.isArray(to)) {
|
||||||
|
//Multiple individual emails
|
||||||
|
to.forEach(to => this.addTo(to, cc, bcc));
|
||||||
|
} else {
|
||||||
|
//Single email (possibly with multiple recipients in the to field)
|
||||||
|
this.addTo(to, cc, bcc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set from email
|
||||||
|
*/
|
||||||
|
setFrom(from) {
|
||||||
|
if (this._checkProperty('from', from, [this._checkUndefined])) {
|
||||||
|
if (typeof from !== 'string' && typeof from.email !== 'string') {
|
||||||
|
throw new Error('String or address object expected for `from`');
|
||||||
|
}
|
||||||
|
this.from = EmailAddress.create(from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set reply to
|
||||||
|
*/
|
||||||
|
setReplyTo(replyTo) {
|
||||||
|
if (this._checkProperty('replyTo', replyTo, [this._checkUndefined])) {
|
||||||
|
if (typeof replyTo !== 'string' && typeof replyTo.email !== 'string') {
|
||||||
|
throw new Error('String or address object expected for `replyTo`');
|
||||||
|
}
|
||||||
|
this.replyTo = EmailAddress.create(replyTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set subject
|
||||||
|
*/
|
||||||
|
setSubject(subject) {
|
||||||
|
this._setProperty('subject', subject, 'string');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set send at
|
||||||
|
*/
|
||||||
|
setSendAt(sendAt) {
|
||||||
|
if (this._checkProperty('sendAt', sendAt, [this._checkUndefined, this._createCheckThatThrows(Number.isInteger, 'Integer expected for `sendAt`')])) {
|
||||||
|
this.sendAt = sendAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set template ID, also checks if the template is dynamic or legacy
|
||||||
|
*/
|
||||||
|
setTemplateId(templateId) {
|
||||||
|
if (this._setProperty('templateId', templateId, 'string')) {
|
||||||
|
if (templateId.indexOf('d-') === 0) {
|
||||||
|
this.isDynamic = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set batch ID
|
||||||
|
*/
|
||||||
|
setBatchId(batchId) {
|
||||||
|
this._setProperty('batchId', batchId, 'string');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set IP pool name
|
||||||
|
*/
|
||||||
|
setIpPoolName(ipPoolName) {
|
||||||
|
this._setProperty('ipPoolName', ipPoolName, 'string');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set ASM
|
||||||
|
*/
|
||||||
|
setAsm(asm) {
|
||||||
|
if (this._checkProperty('asm', asm, [this._checkUndefined, this._createTypeCheck('object')])) {
|
||||||
|
if (typeof asm.groupId !== 'number') {
|
||||||
|
throw new Error('Expected `asm` to include an integer in its `groupId` field');
|
||||||
|
}
|
||||||
|
if (asm.groupsToDisplay &&
|
||||||
|
(!Array.isArray(asm.groupsToDisplay) || !asm.groupsToDisplay.every(group => typeof group === 'number'))) {
|
||||||
|
throw new Error('Array of integers expected for `asm.groupsToDisplay`');
|
||||||
|
}
|
||||||
|
this.asm = asm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set personalizations
|
||||||
|
*/
|
||||||
|
setPersonalizations(personalizations) {
|
||||||
|
if (!this._doArrayCheck('personalizations', personalizations)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!personalizations.every(personalization => typeof personalization === 'object')) {
|
||||||
|
throw new Error('Array of objects expected for `personalizations`');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Clear and use add helper to add one by one
|
||||||
|
this.personalizations = [];
|
||||||
|
personalizations
|
||||||
|
.forEach(personalization => this.addPersonalization(personalization));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add personalization
|
||||||
|
*/
|
||||||
|
addPersonalization(personalization) {
|
||||||
|
//We should either send substitutions or dynamicTemplateData
|
||||||
|
//depending on the templateId
|
||||||
|
if (this.isDynamic && personalization.substitutions) {
|
||||||
|
delete personalization.substitutions;
|
||||||
|
} else if (!this.isDynamic && personalization.dynamicTemplateData) {
|
||||||
|
delete personalization.dynamicTemplateData;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert to class if needed
|
||||||
|
if (!(personalization instanceof Personalization)) {
|
||||||
|
personalization = new Personalization(personalization);
|
||||||
|
}
|
||||||
|
|
||||||
|
//If this is dynamic, set dynamicTemplateData, or set substitutions
|
||||||
|
if (this.isDynamic) {
|
||||||
|
this.applyDynamicTemplateData(personalization);
|
||||||
|
} else {
|
||||||
|
this.applySubstitutions(personalization);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Push personalization to array
|
||||||
|
this.personalizations.push(personalization);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method for quickly creating personalizations
|
||||||
|
*/
|
||||||
|
addTo(to, cc, bcc) {
|
||||||
|
if (
|
||||||
|
typeof to === 'undefined' &&
|
||||||
|
typeof cc === 'undefined' &&
|
||||||
|
typeof bcc === 'undefined'
|
||||||
|
) {
|
||||||
|
throw new Error('Provide at least one of to, cc or bcc');
|
||||||
|
}
|
||||||
|
this.addPersonalization(new Personalization({to, cc, bcc}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set substitutions
|
||||||
|
*/
|
||||||
|
setSubstitutions(substitutions) {
|
||||||
|
this._setProperty('substitutions', substitutions, 'object');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set substitution wrappers
|
||||||
|
*/
|
||||||
|
setSubstitutionWrappers(substitutionWrappers) {
|
||||||
|
let lengthCheck = (propertyName, value) => {
|
||||||
|
if (!Array.isArray(value) || value.length !== 2) {
|
||||||
|
throw new Error('Array expected with two elements for `' + propertyName + '`');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this._checkProperty('substitutionWrappers', substitutionWrappers, [this._checkUndefined, lengthCheck])) {
|
||||||
|
this.substitutionWrappers = substitutionWrappers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper which applies globally set substitutions to personalizations
|
||||||
|
*/
|
||||||
|
applySubstitutions(personalization) {
|
||||||
|
if (personalization instanceof Personalization) {
|
||||||
|
personalization.reverseMergeSubstitutions(this.substitutions);
|
||||||
|
personalization.setSubstitutionWrappers(this.substitutionWrappers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper which applies globally set dynamic_template_data to personalizations
|
||||||
|
*/
|
||||||
|
applyDynamicTemplateData(personalization) {
|
||||||
|
if (personalization instanceof Personalization) {
|
||||||
|
personalization.deepMergeDynamicTemplateData(this.dynamicTemplateData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set dynamicTemplateData
|
||||||
|
*/
|
||||||
|
setDynamicTemplateData(dynamicTemplateData) {
|
||||||
|
if (typeof dynamicTemplateData === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof dynamicTemplateData !== 'object') {
|
||||||
|
throw new Error('Object expected for `dynamicTemplateData`');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check dynamic template for non-escaped characters and warn if found
|
||||||
|
if (!this.hideWarnings) {
|
||||||
|
Object.values(dynamicTemplateData).forEach(value => {
|
||||||
|
if (/['"&]/.test(value)) {
|
||||||
|
console.warn(DYNAMIC_TEMPLATE_CHAR_WARNING);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dynamicTemplateData = dynamicTemplateData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set content
|
||||||
|
*/
|
||||||
|
setContent(content) {
|
||||||
|
if (this._doArrayCheck('content', content)) {
|
||||||
|
if (!content.every(contentField => typeof contentField === 'object')) {
|
||||||
|
throw new Error('Expected each entry in `content` to be an object');
|
||||||
|
}
|
||||||
|
if (!content.every(contentField => typeof contentField.type === 'string')) {
|
||||||
|
throw new Error('Expected each `content` entry to contain a `type` string');
|
||||||
|
}
|
||||||
|
if (!content.every(contentField => typeof contentField.value === 'string')) {
|
||||||
|
throw new Error('Expected each `content` entry to contain a `value` string');
|
||||||
|
}
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add content
|
||||||
|
*/
|
||||||
|
addContent(content) {
|
||||||
|
if (this._checkProperty('content', content, [this._createTypeCheck('object')])) {
|
||||||
|
this.content.push(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add text content
|
||||||
|
*/
|
||||||
|
addTextContent(text) {
|
||||||
|
if (this._checkProperty('text', text, [this._checkUndefined, this._createTypeCheck('string')])) {
|
||||||
|
this.addContent({
|
||||||
|
value: text,
|
||||||
|
type: 'text/plain',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add HTML content
|
||||||
|
*/
|
||||||
|
addHtmlContent(html) {
|
||||||
|
if (this._checkProperty('html', html, [this._checkUndefined, this._createTypeCheck('string')])) {
|
||||||
|
this.addContent({
|
||||||
|
value: html,
|
||||||
|
type: 'text/html',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set attachments
|
||||||
|
*/
|
||||||
|
setAttachments(attachments) {
|
||||||
|
if (this._doArrayCheck('attachments', attachments)) {
|
||||||
|
if (!attachments.every(attachment => typeof attachment.content === 'string')) {
|
||||||
|
throw new Error('Expected each attachment to contain a `content` string');
|
||||||
|
}
|
||||||
|
if (!attachments.every(attachment => typeof attachment.filename === 'string')) {
|
||||||
|
throw new Error('Expected each attachment to contain a `filename` string');
|
||||||
|
}
|
||||||
|
if (!attachments.every(attachment => !attachment.type || typeof attachment.type === 'string')) {
|
||||||
|
throw new Error('Expected the attachment\'s `type` field to be a string');
|
||||||
|
}
|
||||||
|
if (!attachments.every(attachment => !attachment.disposition || typeof attachment.disposition === 'string')) {
|
||||||
|
throw new Error('Expected the attachment\'s `disposition` field to be a string');
|
||||||
|
}
|
||||||
|
this.attachments = attachments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add attachment
|
||||||
|
*/
|
||||||
|
addAttachment(attachment) {
|
||||||
|
if (this._checkProperty('attachment', attachment, [this._checkUndefined, this._createTypeCheck('object')])) {
|
||||||
|
this.attachments.push(attachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set categories
|
||||||
|
*/
|
||||||
|
setCategories(categories) {
|
||||||
|
let allElementsAreStrings = (propertyName, value) => {
|
||||||
|
if (!Array.isArray(value) || !value.every(item => typeof item === 'string')) {
|
||||||
|
throw new Error('Array of strings expected for `' + propertyName + '`');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof categories === 'string') {
|
||||||
|
categories = [categories];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._checkProperty('categories', categories, [this._checkUndefined, allElementsAreStrings])) {
|
||||||
|
this.categories = categories;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add category
|
||||||
|
*/
|
||||||
|
addCategory(category) {
|
||||||
|
if (this._checkProperty('category', category, [this._createTypeCheck('string')])) {
|
||||||
|
this.categories.push(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set headers
|
||||||
|
*/
|
||||||
|
setHeaders(headers) {
|
||||||
|
this._setProperty('headers', headers, 'object');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a header
|
||||||
|
*/
|
||||||
|
addHeader(key, value) {
|
||||||
|
if (this._checkProperty('key', key, [this._createTypeCheck('string')])
|
||||||
|
&& this._checkProperty('value', value, [this._createTypeCheck('string')])) {
|
||||||
|
this.headers[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set sections
|
||||||
|
*/
|
||||||
|
setSections(sections) {
|
||||||
|
this._setProperty('sections', sections, 'object');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set custom args
|
||||||
|
*/
|
||||||
|
setCustomArgs(customArgs) {
|
||||||
|
this._setProperty('customArgs', customArgs, 'object');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set tracking settings
|
||||||
|
*/
|
||||||
|
setTrackingSettings(settings) {
|
||||||
|
if (typeof settings === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
validateTrackingSettings(settings);
|
||||||
|
this.trackingSettings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set mail settings
|
||||||
|
*/
|
||||||
|
setMailSettings(settings) {
|
||||||
|
if (typeof settings === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
validateMailSettings(settings);
|
||||||
|
this.mailSettings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set hide warnings
|
||||||
|
*/
|
||||||
|
setHideWarnings(hide) {
|
||||||
|
if (typeof hide === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof hide !== 'boolean') {
|
||||||
|
throw new Error('Boolean expected for `hideWarnings`');
|
||||||
|
}
|
||||||
|
this.hideWarnings = hide;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To JSON
|
||||||
|
*/
|
||||||
|
toJSON() {
|
||||||
|
|
||||||
|
//Extract properties from self
|
||||||
|
const {
|
||||||
|
from, replyTo, sendAt, subject, content, templateId,
|
||||||
|
personalizations, attachments, ipPoolName, batchId, asm,
|
||||||
|
sections, headers, categories, customArgs, mailSettings,
|
||||||
|
trackingSettings, replyToList,
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
//Initialize with mandatory values
|
||||||
|
const json = {
|
||||||
|
from, subject,
|
||||||
|
personalizations: arrayToJSON(personalizations),
|
||||||
|
};
|
||||||
|
|
||||||
|
//Array properties
|
||||||
|
if (Array.isArray(attachments) && attachments.length > 0) {
|
||||||
|
json.attachments = arrayToJSON(attachments);
|
||||||
|
}
|
||||||
|
if (Array.isArray(categories) && categories.length > 0) {
|
||||||
|
json.categories = categories.filter(cat => cat !== '');
|
||||||
|
}
|
||||||
|
if (Array.isArray(content) && content.length > 0) {
|
||||||
|
json.content = arrayToJSON(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Object properties
|
||||||
|
if (Object.keys(headers).length > 0) {
|
||||||
|
json.headers = headers;
|
||||||
|
}
|
||||||
|
if (Object.keys(mailSettings).length > 0) {
|
||||||
|
json.mailSettings = mailSettings;
|
||||||
|
}
|
||||||
|
if (Object.keys(trackingSettings).length > 0) {
|
||||||
|
json.trackingSettings = trackingSettings;
|
||||||
|
}
|
||||||
|
if (Object.keys(customArgs).length > 0) {
|
||||||
|
json.customArgs = customArgs;
|
||||||
|
}
|
||||||
|
if (Object.keys(sections).length > 0) {
|
||||||
|
json.sections = sections;
|
||||||
|
}
|
||||||
|
if (Object.keys(asm).length > 0) {
|
||||||
|
json.asm = asm;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Simple properties
|
||||||
|
if (typeof replyTo !== 'undefined') {
|
||||||
|
json.replyTo = replyTo;
|
||||||
|
}
|
||||||
|
if (typeof sendAt !== 'undefined') {
|
||||||
|
json.sendAt = sendAt;
|
||||||
|
}
|
||||||
|
if (typeof batchId !== 'undefined') {
|
||||||
|
json.batchId = batchId;
|
||||||
|
}
|
||||||
|
if (typeof templateId !== 'undefined') {
|
||||||
|
json.templateId = templateId;
|
||||||
|
}
|
||||||
|
if (typeof ipPoolName !== 'undefined') {
|
||||||
|
json.ipPoolName = ipPoolName;
|
||||||
|
}
|
||||||
|
if(typeof replyToList !== 'undefined') {
|
||||||
|
json.replyToList = replyToList;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return as snake cased object
|
||||||
|
return toSnakeCase(json, ['substitutions', 'dynamicTemplateData', 'customArgs', 'headers', 'sections']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************
|
||||||
|
* Static helpers
|
||||||
|
***/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Mail instance from given data
|
||||||
|
*/
|
||||||
|
static create(data) {
|
||||||
|
|
||||||
|
//Array?
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
return data
|
||||||
|
.filter(item => !!item)
|
||||||
|
.map(item => this.create(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Already instance of Mail class?
|
||||||
|
if (data instanceof Mail) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create instance
|
||||||
|
return new Mail(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************
|
||||||
|
* helpers for property-setting checks
|
||||||
|
***/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a set of checks on the new property value. Returns true if all
|
||||||
|
* checks complete successfully without throwing errors or returning true.
|
||||||
|
*/
|
||||||
|
_checkProperty(propertyName, value, checks) {
|
||||||
|
return !checks.some((e) => e(propertyName, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a property with normal undefined and type-checks
|
||||||
|
*/
|
||||||
|
_setProperty(propertyName, value, propertyType) {
|
||||||
|
let propertyChecksPassed = this._checkProperty(
|
||||||
|
propertyName,
|
||||||
|
value,
|
||||||
|
[this._checkUndefined, this._createTypeCheck(propertyType)]);
|
||||||
|
|
||||||
|
if (propertyChecksPassed) {
|
||||||
|
this[propertyName] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return propertyChecksPassed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fail if the value is undefined.
|
||||||
|
*/
|
||||||
|
_checkUndefined(propertyName, value) {
|
||||||
|
return typeof value === 'undefined';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and return a function that checks for a given type
|
||||||
|
*/
|
||||||
|
_createTypeCheck(propertyType) {
|
||||||
|
return (propertyName, value) => {
|
||||||
|
if (typeof value !== propertyType) {
|
||||||
|
throw new Error(propertyType + ' expected for `' + propertyName + '`');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a check out of a callback. If the callback
|
||||||
|
* returns false, the check will throw an error.
|
||||||
|
*/
|
||||||
|
_createCheckThatThrows(check, errorString) {
|
||||||
|
return (propertyName, value) => {
|
||||||
|
if (!check(value)) {
|
||||||
|
throw new Error(errorString);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set an array property after checking that the new value is an
|
||||||
|
* array.
|
||||||
|
*/
|
||||||
|
_setArrayProperty(propertyName, value) {
|
||||||
|
if (this._doArrayCheck(propertyName, value)) {
|
||||||
|
this[propertyName] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that a value isn't undefined and is an array.
|
||||||
|
*/
|
||||||
|
_doArrayCheck(propertyName, value) {
|
||||||
|
return this._checkProperty(
|
||||||
|
propertyName,
|
||||||
|
value,
|
||||||
|
[this._checkUndefined, this._createCheckThatThrows(Array.isArray, 'Array expected for`' + propertyName + '`')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the replyToList from email body
|
||||||
|
*/
|
||||||
|
setReplyToList(replyToList) {
|
||||||
|
if (this._doArrayCheck('replyToList', replyToList) && replyToList.length) {
|
||||||
|
if (!replyToList.every(replyTo => replyTo && typeof replyTo.email === 'string')) {
|
||||||
|
throw new Error('Expected each replyTo to contain an `email` string');
|
||||||
|
}
|
||||||
|
this.replyToList = replyToList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Export class
|
||||||
|
module.exports = Mail;
|
|
@ -0,0 +1,307 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Mail = require('./mail');
|
||||||
|
const { DYNAMIC_TEMPLATE_CHAR_WARNING } = require('../constants');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Mail', function() {
|
||||||
|
describe('construct', function() {
|
||||||
|
it('shouldn\'t convert the headers to camel/snake case', function() {
|
||||||
|
const mail = new Mail({
|
||||||
|
personalizations: [{
|
||||||
|
to: 'test@example.com',
|
||||||
|
headers: {
|
||||||
|
'test-header': 'test',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
from: {
|
||||||
|
email: 'test@example.com',
|
||||||
|
},
|
||||||
|
subject: 'test',
|
||||||
|
content: [{
|
||||||
|
type: 'text/plain',
|
||||||
|
value: 'test',
|
||||||
|
}],
|
||||||
|
category: 'test',
|
||||||
|
headers: {
|
||||||
|
'List-Unsubscribe': '<mailto:test@test.com>',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mail.headers['List-Unsubscribe']).to
|
||||||
|
.equal('<mailto:test@test.com>');
|
||||||
|
|
||||||
|
expect(mail.toJSON().headers['List-Unsubscribe']).to
|
||||||
|
.equal('<mailto:test@test.com>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect dynamic template id', function() {
|
||||||
|
const mail = new Mail({
|
||||||
|
personalizations: [{
|
||||||
|
to: 'test@example.com',
|
||||||
|
headers: {
|
||||||
|
'test-header': 'test',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
from: {
|
||||||
|
email: 'test@example.com',
|
||||||
|
},
|
||||||
|
templateId: 'd-df80613cccc6441ea5cd7c95377bc1ef',
|
||||||
|
subject: 'test',
|
||||||
|
content: [{
|
||||||
|
type: 'text/plain',
|
||||||
|
value: 'test',
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
expect(mail.isDynamic).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect legacy template id', function() {
|
||||||
|
const mail = new Mail({
|
||||||
|
personalizations: [{
|
||||||
|
to: 'test@example.com',
|
||||||
|
headers: {
|
||||||
|
'test-header': 'test',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
from: {
|
||||||
|
email: 'test@example.com',
|
||||||
|
},
|
||||||
|
templateId: 'df80613cccc6441ea5cd7c95377bc1ef',
|
||||||
|
subject: 'test',
|
||||||
|
content: [{
|
||||||
|
type: 'text/plain',
|
||||||
|
value: 'test',
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
expect(mail.isDynamic).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore substitutions if templateId is dynamic', function() {
|
||||||
|
const mail = new Mail({
|
||||||
|
personalizations: [{
|
||||||
|
to: 'test@example.com',
|
||||||
|
headers: {
|
||||||
|
'test-header': 'test',
|
||||||
|
},
|
||||||
|
substitutions: {
|
||||||
|
test2: 'Test2',
|
||||||
|
},
|
||||||
|
dynamicTemplateData: {
|
||||||
|
test2: 'Testy 2',
|
||||||
|
test3: 'Testy 3',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
dynamicTemplateData: {
|
||||||
|
test1: 'Test 1',
|
||||||
|
test2: 'Test 2',
|
||||||
|
},
|
||||||
|
substitutions: {
|
||||||
|
test1: 'Test1',
|
||||||
|
},
|
||||||
|
from: {
|
||||||
|
email: 'test@example.com',
|
||||||
|
},
|
||||||
|
templateId: 'd-df80613cccc6441ea5cd7c95377bc1ef',
|
||||||
|
subject: 'test',
|
||||||
|
content: [{
|
||||||
|
type: 'text/plain',
|
||||||
|
value: 'test',
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
expect(mail.substitutions).to.equal(null);
|
||||||
|
expect(mail.personalizations[0].substitutions).to.deep.equal({});
|
||||||
|
|
||||||
|
expect(mail.dynamicTemplateData).to.deep.equal({ test1: 'Test 1', test2: 'Test 2' });
|
||||||
|
expect(mail.personalizations[0].dynamicTemplateData).to.deep.equal({ test1: 'Test 1', test2: 'Testy 2', test3: 'Testy 3' });
|
||||||
|
|
||||||
|
expect(mail.toJSON()).to.deep.equal({
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text/plain',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
from: {
|
||||||
|
email: 'test@example.com',
|
||||||
|
},
|
||||||
|
personalizations: [
|
||||||
|
{
|
||||||
|
dynamic_template_data: {
|
||||||
|
test1: 'Test 1',
|
||||||
|
test2: 'Testy 2',
|
||||||
|
test3: 'Testy 3',
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'test-header': 'test',
|
||||||
|
},
|
||||||
|
to: [
|
||||||
|
{
|
||||||
|
email: 'test@example.com',
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
subject: 'test',
|
||||||
|
template_id: 'd-df80613cccc6441ea5cd7c95377bc1ef',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('attachments', () => {
|
||||||
|
it('handles multiple attachments', () => {
|
||||||
|
const mail = new Mail({
|
||||||
|
to: 'recipient@example.org',
|
||||||
|
attachments: [{
|
||||||
|
content: 'test-content',
|
||||||
|
filename: 'name-that-file',
|
||||||
|
type: 'file-type',
|
||||||
|
}, {
|
||||||
|
content: 'other-content',
|
||||||
|
filename: 'name-this-file',
|
||||||
|
disposition: 'inline',
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
expect(mail.toJSON()['attachments']).to.have.a.lengthOf(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('requires content', () => {
|
||||||
|
expect(() => new Mail({
|
||||||
|
attachments: [{
|
||||||
|
filename: 'missing content',
|
||||||
|
}],
|
||||||
|
})).to.throw('content');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('requires filename', () => {
|
||||||
|
expect(() => new Mail({
|
||||||
|
attachments: [{
|
||||||
|
content: 'missing filename',
|
||||||
|
}],
|
||||||
|
})).to.throw('filename');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('dynamic template handlebars substitutions', () => {
|
||||||
|
let logSpy; let data;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
logSpy = sinon.spy(console, 'warn');
|
||||||
|
data = {
|
||||||
|
to: 'recipient@example.org',
|
||||||
|
from: 'sender@example.org',
|
||||||
|
subject: 'Hello world',
|
||||||
|
text: 'Hello plain world!',
|
||||||
|
html: '<p>Hello HTML world!</p>',
|
||||||
|
templateId: 'd-df80613cccc6441ea5cd7c95377bc1ef',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
console.warn.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log an error if template subject line contains improperly escaped "\'" character', () => {
|
||||||
|
data = Object.assign(data, {
|
||||||
|
dynamicTemplateData: {
|
||||||
|
subject: 'Testing Templates and \'Stuff\'',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mail = new Mail(data);
|
||||||
|
|
||||||
|
expect(logSpy.calledOnce).to.equal(true);
|
||||||
|
expect(logSpy.calledWith(DYNAMIC_TEMPLATE_CHAR_WARNING)).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log an error if template subject line contains improperly escaped """ character', () => {
|
||||||
|
data = Object.assign(data, {
|
||||||
|
dynamicTemplateData: {
|
||||||
|
subject: '"Testing Templates" and Stuff',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mail = new Mail(data);
|
||||||
|
|
||||||
|
expect(logSpy.calledOnce).to.equal(true);
|
||||||
|
expect(logSpy.calledWith(DYNAMIC_TEMPLATE_CHAR_WARNING)).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should log an error if template subject line contains improperly escaped "&" character', () => {
|
||||||
|
data = Object.assign(data, {
|
||||||
|
dynamicTemplateData: {
|
||||||
|
subject: 'Testing Templates & Stuff',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mail = new Mail(data);
|
||||||
|
|
||||||
|
expect(logSpy.calledOnce).to.equal(true);
|
||||||
|
expect(logSpy.calledWith(DYNAMIC_TEMPLATE_CHAR_WARNING)).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('set replyToList to set multiple reply-to', () => {
|
||||||
|
let data;
|
||||||
|
|
||||||
|
this.beforeEach(() => {
|
||||||
|
data = {
|
||||||
|
to: 'send-to@example.org',
|
||||||
|
from: 'sender@example.org',
|
||||||
|
subject: 'test replyToList',
|
||||||
|
category: 'test',
|
||||||
|
text: 'Testing replyToList settings',
|
||||||
|
html: '<p>Testing replyToList settings</p>',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the replyToList', () => {
|
||||||
|
let replyToList = [
|
||||||
|
{
|
||||||
|
'name': 'Test User1',
|
||||||
|
'email': 'test_user1@example.org'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'email': 'test_user2@example.org'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
data.replyToList = replyToList;
|
||||||
|
|
||||||
|
const mail = new Mail(data);
|
||||||
|
|
||||||
|
expect(mail.replyToList)
|
||||||
|
.to.be.deep.equal(replyToList);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for incorrect replyToList format', () => {
|
||||||
|
let replyToList = [
|
||||||
|
{
|
||||||
|
'name': 'Test User1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'email_data': 'test_user2@example.org'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
data.replyToList = replyToList;
|
||||||
|
|
||||||
|
expect(() => new Mail(data))
|
||||||
|
.to.throw('Expected each replyTo to contain an `email` string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for as replyToList is not an array', () => {
|
||||||
|
let replyToList = {
|
||||||
|
'name': 'Test User1',
|
||||||
|
'email': 'test_user1@example.org'
|
||||||
|
};
|
||||||
|
data.replyToList = replyToList;
|
||||||
|
expect(() => new Mail(data))
|
||||||
|
.to.throw('Array expected for`replyToList`');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
128
backend/node_modules/@sendgrid/helpers/classes/personalization.d.ts
generated
vendored
Normal file
128
backend/node_modules/@sendgrid/helpers/classes/personalization.d.ts
generated
vendored
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import { EmailData, EmailJSON } from "./email-address";
|
||||||
|
|
||||||
|
export interface PersonalizationData {
|
||||||
|
to: EmailData | EmailData[],
|
||||||
|
from?: EmailData,
|
||||||
|
cc?: EmailData | EmailData[],
|
||||||
|
bcc?: EmailData | EmailData[],
|
||||||
|
subject?: string;
|
||||||
|
headers?: { [key: string]: string };
|
||||||
|
substitutions?: { [key: string]: string };
|
||||||
|
dynamicTemplateData?: { [key: string]: any; };
|
||||||
|
customArgs?: { [key: string]: string };
|
||||||
|
sendAt?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PersonalizationJSON {
|
||||||
|
to: EmailJSON | EmailJSON[];
|
||||||
|
from?: EmailJSON;
|
||||||
|
cc?: EmailJSON[];
|
||||||
|
bcc?: EmailJSON[];
|
||||||
|
headers?: { [key: string]: string; };
|
||||||
|
substitutions?: { [key: string]: string; };
|
||||||
|
dynamic_template_data?: { [key: string]: string; };
|
||||||
|
custom_args?: { [key: string]: string; };
|
||||||
|
subject?: string;
|
||||||
|
send_at?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Personalization {
|
||||||
|
constructor(data?: PersonalizationData);
|
||||||
|
|
||||||
|
fromData(data: PersonalizationData): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set subject
|
||||||
|
*/
|
||||||
|
setSubject(subject: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set send at
|
||||||
|
*/
|
||||||
|
setSendAt(sendAt: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to
|
||||||
|
*/
|
||||||
|
setTo(to: EmailData | EmailData[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set from
|
||||||
|
*/
|
||||||
|
setFrom(from: EmailData): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a single to
|
||||||
|
*/
|
||||||
|
addTo(to: EmailData): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set cc
|
||||||
|
*/
|
||||||
|
setCc(cc: EmailData | EmailData[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a single cc
|
||||||
|
*/
|
||||||
|
addCc(cc: EmailData): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set bcc
|
||||||
|
*/
|
||||||
|
setBcc(bcc: EmailData | EmailData[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a single bcc
|
||||||
|
*/
|
||||||
|
addBcc(bcc: EmailData): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set headers
|
||||||
|
*/
|
||||||
|
setHeaders(headers: { [key: string]: string }): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a header
|
||||||
|
*/
|
||||||
|
addHeader(key: string, value: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set custom args
|
||||||
|
*/
|
||||||
|
setCustomArgs(customArgs: { [key: string]: string }): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a custom arg
|
||||||
|
*/
|
||||||
|
addCustomArg(key: string, value: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set substitutions
|
||||||
|
*/
|
||||||
|
setSubstitutions(substitutions: { [key: string]: string }): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a substitution
|
||||||
|
*/
|
||||||
|
addSubstitution(key: string, value: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse merge substitutions, preserving existing ones
|
||||||
|
*/
|
||||||
|
reverseMergeSubstitutions(substitutions: { [key: string]: string }): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set substitution wrappers
|
||||||
|
*/
|
||||||
|
setSubstitutionWrappers(wrappers: string[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set dynamic template data
|
||||||
|
*/
|
||||||
|
setDynamicTemplateData(dynamicTemplateData: { [key: string]: any }): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To JSON
|
||||||
|
*/
|
||||||
|
toJSON(): PersonalizationJSON;
|
||||||
|
}
|
371
backend/node_modules/@sendgrid/helpers/classes/personalization.js
generated
vendored
Normal file
371
backend/node_modules/@sendgrid/helpers/classes/personalization.js
generated
vendored
Normal file
|
@ -0,0 +1,371 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const EmailAddress = require('./email-address');
|
||||||
|
const toCamelCase = require('../helpers/to-camel-case');
|
||||||
|
const toSnakeCase = require('../helpers/to-snake-case');
|
||||||
|
const deepClone = require('../helpers/deep-clone');
|
||||||
|
const deepMerge = require('deepmerge');
|
||||||
|
const wrapSubstitutions = require('../helpers/wrap-substitutions');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Personalization class
|
||||||
|
*/
|
||||||
|
class Personalization {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
constructor(data) {
|
||||||
|
|
||||||
|
//Init array and object placeholders
|
||||||
|
this.to = [];
|
||||||
|
this.cc = [];
|
||||||
|
this.bcc = [];
|
||||||
|
this.headers = {};
|
||||||
|
this.customArgs = {};
|
||||||
|
this.substitutions = {};
|
||||||
|
this.substitutionWrappers = ['{{', '}}'];
|
||||||
|
this.dynamicTemplateData = {};
|
||||||
|
|
||||||
|
//Build from data if given
|
||||||
|
if (data) {
|
||||||
|
this.fromData(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From data
|
||||||
|
*/
|
||||||
|
fromData(data) {
|
||||||
|
|
||||||
|
//Expecting object
|
||||||
|
if (typeof data !== 'object') {
|
||||||
|
throw new Error('Expecting object for Mail data');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert to camel case to make it workable, making a copy to prevent
|
||||||
|
//changes to the original objects
|
||||||
|
data = deepClone(data);
|
||||||
|
data = toCamelCase(data, ['substitutions', 'dynamicTemplateData', 'customArgs', 'headers']);
|
||||||
|
|
||||||
|
//Extract properties from data
|
||||||
|
const {
|
||||||
|
to, from, cc, bcc, subject, headers, customArgs, sendAt,
|
||||||
|
substitutions, substitutionWrappers, dynamicTemplateData,
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
//Set data
|
||||||
|
this.setTo(to);
|
||||||
|
this.setFrom(from);
|
||||||
|
this.setCc(cc);
|
||||||
|
this.setBcc(bcc);
|
||||||
|
this.setSubject(subject);
|
||||||
|
this.setHeaders(headers);
|
||||||
|
this.setSubstitutions(substitutions);
|
||||||
|
this.setSubstitutionWrappers(substitutionWrappers);
|
||||||
|
this.setCustomArgs(customArgs);
|
||||||
|
this.setDynamicTemplateData(dynamicTemplateData);
|
||||||
|
this.setSendAt(sendAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set subject
|
||||||
|
*/
|
||||||
|
setSubject(subject) {
|
||||||
|
if (typeof subject === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof subject !== 'string') {
|
||||||
|
throw new Error('String expected for `subject`');
|
||||||
|
}
|
||||||
|
this.subject = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set send at
|
||||||
|
*/
|
||||||
|
setSendAt(sendAt) {
|
||||||
|
if (typeof sendAt === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Number.isInteger(sendAt)) {
|
||||||
|
throw new Error('Integer expected for `sendAt`');
|
||||||
|
}
|
||||||
|
this.sendAt = sendAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to
|
||||||
|
*/
|
||||||
|
setTo(to) {
|
||||||
|
if (typeof to === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(to)) {
|
||||||
|
to = [to];
|
||||||
|
}
|
||||||
|
this.to = EmailAddress.create(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set from
|
||||||
|
* */
|
||||||
|
setFrom(from) {
|
||||||
|
if (typeof from === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.from = EmailAddress.create(from);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a single to
|
||||||
|
*/
|
||||||
|
addTo(to) {
|
||||||
|
if (typeof to === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.to.push(EmailAddress.create(to));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set cc
|
||||||
|
*/
|
||||||
|
setCc(cc) {
|
||||||
|
if (typeof cc === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(cc)) {
|
||||||
|
cc = [cc];
|
||||||
|
}
|
||||||
|
this.cc = EmailAddress.create(cc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a single cc
|
||||||
|
*/
|
||||||
|
addCc(cc) {
|
||||||
|
if (typeof cc === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.cc.push(EmailAddress.create(cc));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set bcc
|
||||||
|
*/
|
||||||
|
setBcc(bcc) {
|
||||||
|
if (typeof bcc === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(bcc)) {
|
||||||
|
bcc = [bcc];
|
||||||
|
}
|
||||||
|
this.bcc = EmailAddress.create(bcc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a single bcc
|
||||||
|
*/
|
||||||
|
addBcc(bcc) {
|
||||||
|
if (typeof bcc === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.bcc.push(EmailAddress.create(bcc));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set headers
|
||||||
|
*/
|
||||||
|
setHeaders(headers) {
|
||||||
|
if (typeof headers === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof headers !== 'object' || headers === null) {
|
||||||
|
throw new Error('Object expected for `headers`');
|
||||||
|
}
|
||||||
|
this.headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a header
|
||||||
|
*/
|
||||||
|
addHeader(key, value) {
|
||||||
|
if (typeof key !== 'string') {
|
||||||
|
throw new Error('String expected for header key');
|
||||||
|
}
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
throw new Error('String expected for header value');
|
||||||
|
}
|
||||||
|
this.headers[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set custom args
|
||||||
|
*/
|
||||||
|
setCustomArgs(customArgs) {
|
||||||
|
if (typeof customArgs === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof customArgs !== 'object' || customArgs === null) {
|
||||||
|
throw new Error('Object expected for `customArgs`');
|
||||||
|
}
|
||||||
|
this.customArgs = customArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a custom arg
|
||||||
|
*/
|
||||||
|
addCustomArg(key, value) {
|
||||||
|
if (typeof key !== 'string') {
|
||||||
|
throw new Error('String expected for custom arg key');
|
||||||
|
}
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
throw new Error('String expected for custom arg value');
|
||||||
|
}
|
||||||
|
this.customArgs[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set substitutions
|
||||||
|
*/
|
||||||
|
setSubstitutions(substitutions) {
|
||||||
|
if (typeof substitutions === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof substitutions !== 'object') {
|
||||||
|
throw new Error('Object expected for `substitutions`');
|
||||||
|
}
|
||||||
|
this.substitutions = substitutions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a substitution
|
||||||
|
*/
|
||||||
|
addSubstitution(key, value) {
|
||||||
|
if (typeof key !== 'string') {
|
||||||
|
throw new Error('String expected for substitution key');
|
||||||
|
}
|
||||||
|
if (typeof value !== 'string' && typeof value !== 'number') {
|
||||||
|
throw new Error('String or Number expected for substitution value');
|
||||||
|
}
|
||||||
|
this.substitutions[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse merge substitutions, preserving existing ones
|
||||||
|
*/
|
||||||
|
reverseMergeSubstitutions(substitutions) {
|
||||||
|
if (typeof substitutions === 'undefined' || substitutions === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof substitutions !== 'object') {
|
||||||
|
throw new Error(
|
||||||
|
'Object expected for `substitutions` in reverseMergeSubstitutions'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.substitutions = Object.assign({}, substitutions, this.substitutions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set substitution wrappers
|
||||||
|
*/
|
||||||
|
setSubstitutionWrappers(wrappers) {
|
||||||
|
if (typeof wrappers === 'undefined' || wrappers === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(wrappers) || wrappers.length !== 2) {
|
||||||
|
throw new Error(
|
||||||
|
'Array expected with two elements for `substitutionWrappers`'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.substitutionWrappers = wrappers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse merge dynamic template data, preserving existing ones
|
||||||
|
*/
|
||||||
|
deepMergeDynamicTemplateData(dynamicTemplateData) {
|
||||||
|
if (typeof dynamicTemplateData === 'undefined' || dynamicTemplateData === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof dynamicTemplateData !== 'object') {
|
||||||
|
throw new Error(
|
||||||
|
'Object expected for `dynamicTemplateData` in deepMergeDynamicTemplateData'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.dynamicTemplateData = deepMerge(dynamicTemplateData, this.dynamicTemplateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set dynamic template data
|
||||||
|
*/
|
||||||
|
setDynamicTemplateData(dynamicTemplateData) {
|
||||||
|
if (typeof dynamicTemplateData === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof dynamicTemplateData !== 'object') {
|
||||||
|
throw new Error('Object expected for `dynamicTemplateData`');
|
||||||
|
}
|
||||||
|
this.dynamicTemplateData = dynamicTemplateData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To JSON
|
||||||
|
*/
|
||||||
|
toJSON() {
|
||||||
|
|
||||||
|
//Get data from self
|
||||||
|
const {
|
||||||
|
to, from, cc, bcc, subject, headers, customArgs, sendAt,
|
||||||
|
substitutions, substitutionWrappers, dynamicTemplateData,
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
//Initialize with mandatory values
|
||||||
|
const json = {to};
|
||||||
|
|
||||||
|
//Arrays
|
||||||
|
if (Array.isArray(cc) && cc.length > 0) {
|
||||||
|
json.cc = cc;
|
||||||
|
}
|
||||||
|
if (Array.isArray(bcc) && bcc.length > 0) {
|
||||||
|
json.bcc = bcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Objects
|
||||||
|
if (Object.keys(headers).length > 0) {
|
||||||
|
json.headers = headers;
|
||||||
|
}
|
||||||
|
if (substitutions && Object.keys(substitutions).length > 0) {
|
||||||
|
const [left, right] = substitutionWrappers;
|
||||||
|
json.substitutions = wrapSubstitutions(substitutions, left, right);
|
||||||
|
}
|
||||||
|
if (Object.keys(customArgs).length > 0) {
|
||||||
|
json.customArgs = customArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dynamicTemplateData && Object.keys(dynamicTemplateData).length > 0) {
|
||||||
|
json.dynamicTemplateData = dynamicTemplateData;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Simple properties
|
||||||
|
if (typeof subject !== 'undefined') {
|
||||||
|
json.subject = subject;
|
||||||
|
}
|
||||||
|
if (typeof sendAt !== 'undefined') {
|
||||||
|
json.sendAt = sendAt;
|
||||||
|
}
|
||||||
|
if (typeof from !== 'undefined') {
|
||||||
|
json.from = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return as snake cased object
|
||||||
|
return toSnakeCase(json, ['substitutions', 'dynamicTemplateData', 'customArgs', 'headers']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Export class
|
||||||
|
module.exports = Personalization;
|
36
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/527-camel-case-headers.spec.js
generated
vendored
Normal file
36
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/527-camel-case-headers.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
// camel case headers test
|
||||||
|
describe('#527', function() {
|
||||||
|
it('shouldn\'t convert the headers to camel/snake case', function() {
|
||||||
|
const p = new Personalization({
|
||||||
|
to: 'test@example.com',
|
||||||
|
headers: {
|
||||||
|
'List-Unsubscribe': '<mailto:test@test.com>',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(p.headers['List-Unsubscribe']).to.equal('<mailto:test@test.com>');
|
||||||
|
|
||||||
|
expect(p.toJSON().headers['List-Unsubscribe']).to
|
||||||
|
.equal('<mailto:test@test.com>');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
23
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/PERSONALIZATION-SPECS-README.md
generated
vendored
Normal file
23
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/PERSONALIZATION-SPECS-README.md
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#### Personalization helper specs
|
||||||
|
|
||||||
|
- setSubject() - set-subject.spec.js
|
||||||
|
- setSendAt() - set-send-at.spec.js
|
||||||
|
- setTo() - set-to.spec.js
|
||||||
|
- setFrom() - set-from.spec.js
|
||||||
|
- addTo() - add-to.spec.js
|
||||||
|
- setCc() - set-cc.spec.js
|
||||||
|
- addCc() - add-cc.spec.js
|
||||||
|
- setBcc() - set-bcc.spec.js
|
||||||
|
- addBcc() - add-bcc.spec.js
|
||||||
|
- setHeaders() - set-headers.spec.js
|
||||||
|
- addHeader() - add-headers.spec.js
|
||||||
|
- setCustomArgs() - set-custom-args.spec.js
|
||||||
|
- addCustomArg() - add-custom-args.spec.js
|
||||||
|
- setSubstitutions() - set-substitutions.spec.js
|
||||||
|
- addSubstitution() - add-substitutions.spec.js
|
||||||
|
- reverseMergeSubstitutions() - reverse-merge-substitutions.spec.js
|
||||||
|
- setSubstitutionWrappers() - set-substitutions-wrappers.spec.js
|
||||||
|
- deepMergeDynamicTemplateData() - reverse-merge-dynamic_template_data.spec.js
|
||||||
|
- toJSON() - to-json.spec.js
|
||||||
|
- fromData() - from-data.spec.js
|
||||||
|
- #527 - 527-camel-case-headers.spec.js
|
53
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/add-bcc.spec.js
generated
vendored
Normal file
53
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/add-bcc.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Add bcc
|
||||||
|
describe('addBcc()', function() {
|
||||||
|
it('should add the item', function() {
|
||||||
|
p.addBcc('test@example.org');
|
||||||
|
expect(p.bcc).to.be.an.instanceof(Array);
|
||||||
|
expect(p.bcc).to.have.a.lengthOf(1);
|
||||||
|
expect(p.bcc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.bcc[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
it('should handle multiple values', function() {
|
||||||
|
p.addBcc('test1@example.org');
|
||||||
|
p.addBcc('test2@example.org');
|
||||||
|
expect(p.bcc).to.be.an.instanceof(Array);
|
||||||
|
expect(p.bcc).to.have.a.lengthOf(2);
|
||||||
|
expect(p.bcc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.bcc[0].email).to.equal('test1@example.org');
|
||||||
|
expect(p.bcc[1]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.bcc[1].email).to.equal('test2@example.org');
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.addBcc();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite with no input', function() {
|
||||||
|
p.addBcc('test@example.org');
|
||||||
|
p.addBcc();
|
||||||
|
expect(p.bcc).to.be.an.instanceof(Array);
|
||||||
|
expect(p.bcc).to.have.a.lengthOf(1);
|
||||||
|
expect(p.bcc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.bcc[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
53
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/add-cc.spec.js
generated
vendored
Normal file
53
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/add-cc.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Add cc
|
||||||
|
describe('addCc()', function() {
|
||||||
|
it('should add the item', function() {
|
||||||
|
p.addCc('test@example.org');
|
||||||
|
expect(p.cc).to.be.an.instanceof(Array);
|
||||||
|
expect(p.cc).to.have.a.lengthOf(1);
|
||||||
|
expect(p.cc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.cc[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
it('should handle multiple values', function() {
|
||||||
|
p.addCc('test1@example.org');
|
||||||
|
p.addCc('test2@example.org');
|
||||||
|
expect(p.cc).to.be.an.instanceof(Array);
|
||||||
|
expect(p.cc).to.have.a.lengthOf(2);
|
||||||
|
expect(p.cc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.cc[0].email).to.equal('test1@example.org');
|
||||||
|
expect(p.cc[1]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.cc[1].email).to.equal('test2@example.org');
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.addCc();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite with no input', function() {
|
||||||
|
p.addCc('test@example.org');
|
||||||
|
p.addCc();
|
||||||
|
expect(p.cc).to.be.an.instanceof(Array);
|
||||||
|
expect(p.cc).to.have.a.lengthOf(1);
|
||||||
|
expect(p.cc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.cc[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
48
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/add-custom-args.spec.js
generated
vendored
Normal file
48
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/add-custom-args.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Add custom arg
|
||||||
|
describe('addCustomArg()', function() {
|
||||||
|
it('should set the given value', function() {
|
||||||
|
p.addCustomArg('test', 'Test');
|
||||||
|
expect(p.customArgs).to.be.an.instanceof(Object);
|
||||||
|
expect(p.customArgs).to.have.a.property('test');
|
||||||
|
expect(p.customArgs.test).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should add multiple values', function() {
|
||||||
|
p.addCustomArg('test1', 'Test1');
|
||||||
|
p.addCustomArg('test2', 'Test2');
|
||||||
|
expect(p.customArgs).to.have.a.property('test1');
|
||||||
|
expect(p.customArgs).to.have.a.property('test2');
|
||||||
|
expect(p.customArgs.test1).to.equal('Test1');
|
||||||
|
expect(p.customArgs.test2).to.equal('Test2');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.addCustomArg('test');
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.addCustomArg(null, 'test');
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.addCustomArg(3, 5);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
48
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/add-headers.spec.js
generated
vendored
Normal file
48
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/add-headers.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Add header
|
||||||
|
describe('addHeader()', function() {
|
||||||
|
it('should set the given value', function() {
|
||||||
|
p.addHeader('test', 'Test');
|
||||||
|
expect(p.headers).to.be.an.instanceof(Object);
|
||||||
|
expect(p.headers).to.have.a.property('test');
|
||||||
|
expect(p.headers.test).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should add multiple values', function() {
|
||||||
|
p.addHeader('test1', 'Test1');
|
||||||
|
p.addHeader('test2', 'Test2');
|
||||||
|
expect(p.headers).to.have.a.property('test1');
|
||||||
|
expect(p.headers).to.have.a.property('test2');
|
||||||
|
expect(p.headers.test1).to.equal('Test1');
|
||||||
|
expect(p.headers.test2).to.equal('Test2');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.addHeader('test');
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.addHeader(null, 'test');
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.addHeader(3, 5);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
48
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/add-substitutions.spec.js
generated
vendored
Normal file
48
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/add-substitutions.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Add substitution
|
||||||
|
describe('addSubstitution()', function() {
|
||||||
|
it('should set the given value', function() {
|
||||||
|
p.addSubstitution('test', 'Test');
|
||||||
|
expect(p.substitutions).to.be.an.instanceof(Object);
|
||||||
|
expect(p.substitutions).to.have.a.property('test');
|
||||||
|
expect(p.substitutions.test).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should add multiple values', function() {
|
||||||
|
p.addSubstitution('test1', 'Test1');
|
||||||
|
p.addSubstitution('test2', 2);
|
||||||
|
expect(p.substitutions).to.have.a.property('test1');
|
||||||
|
expect(p.substitutions).to.have.a.property('test2');
|
||||||
|
expect(p.substitutions.test1).to.equal('Test1');
|
||||||
|
expect(p.substitutions.test2).to.equal(2);
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.addSubstitution('test');
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.addSubstitution(null, 'test');
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.addSubstitution(3, false);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
89
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/from-data.spec.js
generated
vendored
Normal file
89
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/from-data.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//From data
|
||||||
|
describe('fromData()', function() {
|
||||||
|
|
||||||
|
//Data
|
||||||
|
const data = {
|
||||||
|
to: 'to@example.org',
|
||||||
|
from: 'from@example.org',
|
||||||
|
cc: ['cc1@example.org', 'cc2@example.org'],
|
||||||
|
bcc: ['bcc1@example.org', 'bcc2@example.org'],
|
||||||
|
subject: 'Test',
|
||||||
|
sendAt: 1000,
|
||||||
|
headers: {test: 'Test'},
|
||||||
|
customArgs: {snake_case: 'Test', T_EST: 'Test', camelCase: 'Test'},
|
||||||
|
substitutions: {snake_case: 'Test', T_EST: 'Test', camelCase: 'Test'},
|
||||||
|
substitutionWrappers: ['[', ']'],
|
||||||
|
};
|
||||||
|
|
||||||
|
//Tests
|
||||||
|
it('should call fromData() from the constructor', () => {
|
||||||
|
p = new Personalization(data);
|
||||||
|
expect(p.to[0].email).to.equal('to@example.org');
|
||||||
|
expect(p.subject).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', () => {
|
||||||
|
expect(function() {
|
||||||
|
p.fromData(5);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should have set all properties', () => {
|
||||||
|
p.fromData(data);
|
||||||
|
expect(p.to[0].email).to.equal('to@example.org');
|
||||||
|
expect(p.from.email).to.equal('from@example.org');
|
||||||
|
expect(p.cc[0].email).to.equal('cc1@example.org');
|
||||||
|
expect(p.cc[1].email).to.equal('cc2@example.org');
|
||||||
|
expect(p.bcc[0].email).to.equal('bcc1@example.org');
|
||||||
|
expect(p.bcc[1].email).to.equal('bcc2@example.org');
|
||||||
|
expect(p.subject).to.equal('Test');
|
||||||
|
expect(p.sendAt).to.equal(1000);
|
||||||
|
expect(p.headers.test).to.equal('Test');
|
||||||
|
expect(p.customArgs.snake_case).to.equal('Test');
|
||||||
|
expect(p.substitutions.snake_case).to.equal('Test');
|
||||||
|
expect(p.substitutionWrappers).to.have.members(['[', ']']);
|
||||||
|
});
|
||||||
|
it('should not modify the keys of substitutions and custom args', () => {
|
||||||
|
p.fromData(data);
|
||||||
|
expect(p.substitutions.T_EST).to.equal('Test');
|
||||||
|
expect(p.substitutions.camelCase).to.equal('Test');
|
||||||
|
expect(p.substitutions.snake_case).to.equal('Test');
|
||||||
|
expect(p.customArgs.T_EST).to.equal('Test');
|
||||||
|
expect(p.customArgs.camelCase).to.equal('Test');
|
||||||
|
expect(p.customArgs.snake_case).to.equal('Test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#527', function() {
|
||||||
|
it('shouldn\'t convert the headers to camel/snake case', function() {
|
||||||
|
const p = new Personalization({
|
||||||
|
to: 'test@example.com',
|
||||||
|
headers: {
|
||||||
|
'List-Unsubscribe': '<mailto:test@test.com>',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(p.headers['List-Unsubscribe']).to.equal('<mailto:test@test.com>');
|
||||||
|
|
||||||
|
expect(p.toJSON().headers['List-Unsubscribe']).to
|
||||||
|
.equal('<mailto:test@test.com>');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
54
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/reverse-merge-dynamic_teplate_data.spec.js
generated
vendored
Normal file
54
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/reverse-merge-dynamic_teplate_data.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Reverse merge substitutions
|
||||||
|
describe('deepMergeDynamicTemplateData()', function() {
|
||||||
|
it('should reverse merge dynamicTemplateData', function() {
|
||||||
|
p.setDynamicTemplateData({ test1: 'Test1' });
|
||||||
|
p.deepMergeDynamicTemplateData({ test2: 'Test2' });
|
||||||
|
expect(p.dynamicTemplateData).to.have.a.property('test1');
|
||||||
|
expect(p.dynamicTemplateData).to.have.a.property('test2');
|
||||||
|
expect(p.dynamicTemplateData.test1).to.equal('Test1');
|
||||||
|
expect(p.dynamicTemplateData.test2).to.equal('Test2');
|
||||||
|
});
|
||||||
|
it('should not overwrite existing keys', function() {
|
||||||
|
p.setDynamicTemplateData({ test1: 'Test1' });
|
||||||
|
p.deepMergeDynamicTemplateData({ test1: 'Test3', test2: 'Test2' });
|
||||||
|
expect(p.dynamicTemplateData).to.have.a.property('test1');
|
||||||
|
expect(p.dynamicTemplateData).to.have.a.property('test2');
|
||||||
|
expect(p.dynamicTemplateData.test1).to.equal('Test1');
|
||||||
|
expect(p.dynamicTemplateData.test2).to.equal('Test2');
|
||||||
|
});
|
||||||
|
it('should work without prior dynamicTemplateData', function() {
|
||||||
|
p.deepMergeDynamicTemplateData({ test2: 'Test2' });
|
||||||
|
expect(p.dynamicTemplateData).to.have.a.property('test2');
|
||||||
|
expect(p.dynamicTemplateData.test2).to.equal('Test2');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.deepMergeDynamicTemplateData(3);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.deepMergeDynamicTemplateData();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
54
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/reverse-merge-substitutions.spec.js
generated
vendored
Normal file
54
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/reverse-merge-substitutions.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Reverse merge substitutions
|
||||||
|
describe('reverseMergeSubstitutions()', function() {
|
||||||
|
it('should reverse merge substitutions', function() {
|
||||||
|
p.setSubstitutions({test1: 'Test1'});
|
||||||
|
p.reverseMergeSubstitutions({test2: 'Test2'});
|
||||||
|
expect(p.substitutions).to.have.a.property('test1');
|
||||||
|
expect(p.substitutions).to.have.a.property('test2');
|
||||||
|
expect(p.substitutions.test1).to.equal('Test1');
|
||||||
|
expect(p.substitutions.test2).to.equal('Test2');
|
||||||
|
});
|
||||||
|
it('should not overwrite existing keys', function() {
|
||||||
|
p.setSubstitutions({test1: 'Test1'});
|
||||||
|
p.reverseMergeSubstitutions({test1: 'Test3', test2: 'Test2'});
|
||||||
|
expect(p.substitutions).to.have.a.property('test1');
|
||||||
|
expect(p.substitutions).to.have.a.property('test2');
|
||||||
|
expect(p.substitutions.test1).to.equal('Test1');
|
||||||
|
expect(p.substitutions.test2).to.equal('Test2');
|
||||||
|
});
|
||||||
|
it('should work without prior substitutions', function() {
|
||||||
|
p.reverseMergeSubstitutions({test2: 'Test2'});
|
||||||
|
expect(p.substitutions).to.have.a.property('test2');
|
||||||
|
expect(p.substitutions.test2).to.equal('Test2');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.reverseMergeSubstitutions(3);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.reverseMergeSubstitutions();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
53
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-add-to.spec.js
generated
vendored
Normal file
53
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-add-to.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Add to
|
||||||
|
describe('addTo()', function() {
|
||||||
|
it('should add the item', function() {
|
||||||
|
p.addTo('test@example.org');
|
||||||
|
expect(p.to).to.be.an.instanceof(Array);
|
||||||
|
expect(p.to).to.have.a.lengthOf(1);
|
||||||
|
expect(p.to[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.to[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
it('should handle multiple values', function() {
|
||||||
|
p.addTo('test1@example.org');
|
||||||
|
p.addTo('test2@example.org');
|
||||||
|
expect(p.to).to.be.an.instanceof(Array);
|
||||||
|
expect(p.to).to.have.a.lengthOf(2);
|
||||||
|
expect(p.to[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.to[0].email).to.equal('test1@example.org');
|
||||||
|
expect(p.to[1]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.to[1].email).to.equal('test2@example.org');
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.addTo();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite with no input', function() {
|
||||||
|
p.addTo('test@example.org');
|
||||||
|
p.addTo();
|
||||||
|
expect(p.to).to.be.an.instanceof(Array);
|
||||||
|
expect(p.to).to.have.a.lengthOf(1);
|
||||||
|
expect(p.to[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.to[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
56
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-bcc.spec.js
generated
vendored
Normal file
56
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-bcc.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set bcc
|
||||||
|
describe('setBcc()', function() {
|
||||||
|
it('should handle array values', function() {
|
||||||
|
p.setBcc(['test@example.org']);
|
||||||
|
expect(p.bcc).to.be.an.instanceof(Array);
|
||||||
|
expect(p.bcc).to.have.a.lengthOf(1);
|
||||||
|
expect(p.bcc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.bcc[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
it('should handle string values', function() {
|
||||||
|
p.setBcc('test@example.org');
|
||||||
|
expect(p.bcc).to.be.an.instanceof(Array);
|
||||||
|
expect(p.bcc).to.have.a.lengthOf(1);
|
||||||
|
expect(p.bcc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.bcc[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
it('should handle multiple values', function() {
|
||||||
|
p.setBcc(['test1@example.org', 'test2@example.org']);
|
||||||
|
expect(p.bcc).to.be.an.instanceof(Array);
|
||||||
|
expect(p.bcc).to.have.a.lengthOf(2);
|
||||||
|
expect(p.bcc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.bcc[0].email).to.equal('test1@example.org');
|
||||||
|
expect(p.bcc[1]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.bcc[1].email).to.equal('test2@example.org');
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setBcc();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite with no input', function() {
|
||||||
|
p.setBcc('test@example.org');
|
||||||
|
p.setBcc();
|
||||||
|
expect(p.bcc[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
56
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-cc.spec.js
generated
vendored
Normal file
56
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-cc.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set cc
|
||||||
|
describe('setCc()', function() {
|
||||||
|
it('should handle array values', function() {
|
||||||
|
p.setCc(['test@example.org']);
|
||||||
|
expect(p.cc).to.be.an.instanceof(Array);
|
||||||
|
expect(p.cc).to.have.a.lengthOf(1);
|
||||||
|
expect(p.cc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.cc[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
it('should handle string values', function() {
|
||||||
|
p.setCc('test@example.org');
|
||||||
|
expect(p.cc).to.be.an.instanceof(Array);
|
||||||
|
expect(p.cc).to.have.a.lengthOf(1);
|
||||||
|
expect(p.cc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.cc[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
it('should handle multiple values', function() {
|
||||||
|
p.setCc(['test1@example.org', 'test2@example.org']);
|
||||||
|
expect(p.cc).to.be.an.instanceof(Array);
|
||||||
|
expect(p.cc).to.have.a.lengthOf(2);
|
||||||
|
expect(p.cc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.cc[0].email).to.equal('test1@example.org');
|
||||||
|
expect(p.cc[1]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.cc[1].email).to.equal('test2@example.org');
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setCc();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite with no input', function() {
|
||||||
|
p.setCc('test@example.org');
|
||||||
|
p.setCc();
|
||||||
|
expect(p.cc[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
47
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-custom-args.spec.js
generated
vendored
Normal file
47
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-custom-args.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set custom args
|
||||||
|
describe('setCustomArgs()', function() {
|
||||||
|
it('should set the given value', function() {
|
||||||
|
p.setCustomArgs({test: 'Test'});
|
||||||
|
expect(p.customArgs).to.be.an.instanceof(Object);
|
||||||
|
expect(p.customArgs).to.have.a.property('test');
|
||||||
|
expect(p.customArgs.test).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setCustomArgs('Invalid');
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.setCustomArgs(null);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setCustomArgs();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite with no input', function() {
|
||||||
|
p.setCustomArgs({test: 'Test'});
|
||||||
|
p.setCustomArgs();
|
||||||
|
expect(p.customArgs.test).to.equal('Test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
45
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-from.spec.js
generated
vendored
Normal file
45
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-from.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set from
|
||||||
|
describe('setFrom()', function() {
|
||||||
|
it('should accept string values', function() {
|
||||||
|
p.setFrom('test@example.org');
|
||||||
|
expect(p.from).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.from.email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
it('should properly update from value', function() {
|
||||||
|
p.setFrom('test1@example.com');
|
||||||
|
p.setFrom('test2@example.com');
|
||||||
|
p.setFrom('test3@example.com');
|
||||||
|
p.setFrom('test4@example.com');
|
||||||
|
expect(p.from.email).to.equal('test4@example.com');
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setFrom();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite value with no input', function() {
|
||||||
|
p.setFrom('test@example.org');
|
||||||
|
p.setFrom();
|
||||||
|
expect(p.from.email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
47
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-headers.spec.js
generated
vendored
Normal file
47
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-headers.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set headers
|
||||||
|
describe('setHeaders()', function() {
|
||||||
|
it('should set the given value', function() {
|
||||||
|
p.setHeaders({test: 'Test'});
|
||||||
|
expect(p.headers).to.be.an.instanceof(Object);
|
||||||
|
expect(p.headers).to.have.a.property('test');
|
||||||
|
expect(p.headers.test).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setHeaders('Invalid');
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.setHeaders(null);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setHeaders();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite with no input', function() {
|
||||||
|
p.setHeaders({test: 'Test'});
|
||||||
|
p.setHeaders();
|
||||||
|
expect(p.headers.test).to.equal('Test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
45
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-send-at.spec.js
generated
vendored
Normal file
45
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-send-at.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set send at
|
||||||
|
describe('setSendAt()', function() {
|
||||||
|
it('should set the given value', function() {
|
||||||
|
p.setSendAt(1500077141);
|
||||||
|
expect(p.sendAt).to.equal(1500077141);
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setSendAt('Invalid');
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.setSendAt(null);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setSendAt();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite with no input', function() {
|
||||||
|
p.setSendAt(1500077141);
|
||||||
|
p.setSendAt();
|
||||||
|
expect(p.sendAt).to.equal(1500077141);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
45
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-subject.spec.js
generated
vendored
Normal file
45
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-subject.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set subject
|
||||||
|
describe('setSubject()', function() {
|
||||||
|
it('should set the given value', function() {
|
||||||
|
p.setSubject('Test');
|
||||||
|
expect(p.subject).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setSubject(5);
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.setSubject(null);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setSubject();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite with no input', function() {
|
||||||
|
p.setSubject('Test');
|
||||||
|
p.setSubject();
|
||||||
|
expect(p.subject).to.equal('Test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
47
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-substitutions.spec.js
generated
vendored
Normal file
47
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-substitutions.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set substitutions
|
||||||
|
describe('setSubstitutions()', function() {
|
||||||
|
it('should set the given value', function() {
|
||||||
|
p.setSubstitutions({test: 'Test'});
|
||||||
|
expect(p.substitutions).to.be.an.instanceof(Object);
|
||||||
|
expect(p.substitutions).to.have.a.property('test');
|
||||||
|
expect(p.substitutions.test).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setSubstitutions('Invalid');
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.setSubstitutions(3);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setSubstitutions();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite with no input', function() {
|
||||||
|
p.setSubstitutions({test: 'Test'});
|
||||||
|
p.setSubstitutions();
|
||||||
|
expect(p.substitutions.test).to.equal('Test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
56
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-to.spec.js
generated
vendored
Normal file
56
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set-to.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set to
|
||||||
|
describe('setTo()', function() {
|
||||||
|
it('should handle array values', function() {
|
||||||
|
p.setTo(['test@example.org']);
|
||||||
|
expect(p.to).to.be.an.instanceof(Array);
|
||||||
|
expect(p.to).to.have.a.lengthOf(1);
|
||||||
|
expect(p.to[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.to[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
it('should handle string values', function() {
|
||||||
|
p.setTo('test@example.org');
|
||||||
|
expect(p.to).to.be.an.instanceof(Array);
|
||||||
|
expect(p.to).to.have.a.lengthOf(1);
|
||||||
|
expect(p.to[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.to[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
it('should handle multiple values', function() {
|
||||||
|
p.setTo(['test1@example.org', 'test2@example.org']);
|
||||||
|
expect(p.to).to.be.an.instanceof(Array);
|
||||||
|
expect(p.to).to.have.a.lengthOf(2);
|
||||||
|
expect(p.to[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.to[0].email).to.equal('test1@example.org');
|
||||||
|
expect(p.to[1]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(p.to[1].email).to.equal('test2@example.org');
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setTo();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite with no input', function() {
|
||||||
|
p.setTo('test@example.org');
|
||||||
|
p.setTo();
|
||||||
|
expect(p.to[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
47
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set_dynamic_template_data.spec.js
generated
vendored
Normal file
47
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/set_dynamic_template_data.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set substitutions
|
||||||
|
describe('setDynamicTemplateData()', function() {
|
||||||
|
it('should set the given value', function() {
|
||||||
|
p.setDynamicTemplateData({ test: 'Test' });
|
||||||
|
expect(p.dynamicTemplateData).to.be.an.instanceof(Object);
|
||||||
|
expect(p.dynamicTemplateData).to.have.a.property('test');
|
||||||
|
expect(p.dynamicTemplateData.test).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setDynamicTemplateData('Invalid');
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.setDynamicTemplateData(3);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setDynamicTemplateData();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite with no input', function() {
|
||||||
|
p.setDynamicTemplateData({ test: 'Test' });
|
||||||
|
p.setDynamicTemplateData();
|
||||||
|
expect(p.dynamicTemplateData.test).to.equal('Test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
58
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/substitutions-wrappers.spec.js
generated
vendored
Normal file
58
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/substitutions-wrappers.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Set substitutions wrappers
|
||||||
|
describe('setSubstitutionWrappers()', function() {
|
||||||
|
it('should have defaults', function() {
|
||||||
|
expect(p.substitutionWrappers).to.be.an.instanceof(Array);
|
||||||
|
expect(p.substitutionWrappers).to.have.a.lengthOf(2);
|
||||||
|
expect(p.substitutionWrappers[0]).to.equal('{{');
|
||||||
|
expect(p.substitutionWrappers[1]).to.equal('}}');
|
||||||
|
});
|
||||||
|
it('should set the given value', function() {
|
||||||
|
p.setSubstitutionWrappers(['a', 'b']);
|
||||||
|
expect(p.substitutionWrappers).to.be.an.instanceof(Array);
|
||||||
|
expect(p.substitutionWrappers).to.have.a.lengthOf(2);
|
||||||
|
expect(p.substitutionWrappers[0]).to.equal('a');
|
||||||
|
expect(p.substitutionWrappers[1]).to.equal('b');
|
||||||
|
});
|
||||||
|
it('should throw an error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setSubstitutionWrappers('Invalid');
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.setSubstitutionWrappers(['a']);
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
p.setSubstitutionWrappers(['a', 'b', 'c']);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should accept no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
p.setSubstitutionWrappers();
|
||||||
|
}).not.to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should not overwrite with no input', function() {
|
||||||
|
p.setSubstitutionWrappers(['a', 'b']);
|
||||||
|
p.setSubstitutionWrappers();
|
||||||
|
expect(p.substitutionWrappers[0]).to.equal('a');
|
||||||
|
expect(p.substitutionWrappers[1]).to.equal('b');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
144
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/to-json.spec.js
generated
vendored
Normal file
144
backend/node_modules/@sendgrid/helpers/classes/personalization_specs/to-json.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Personalization = require('../personalization');
|
||||||
|
const EmailAddress = require('../email-address');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Personalization', function() {
|
||||||
|
|
||||||
|
//Create new personalization before each test
|
||||||
|
let p;
|
||||||
|
beforeEach(function() {
|
||||||
|
p = new Personalization();
|
||||||
|
});
|
||||||
|
|
||||||
|
//JSON conversion
|
||||||
|
describe('toJSON()', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
p.setTo('test@example.org');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should always have the to field', function() {
|
||||||
|
const json = p.toJSON();
|
||||||
|
expect(json).to.have.property('to');
|
||||||
|
expect(json.to).to.be.an.instanceof(Array);
|
||||||
|
expect(json.to).to.have.a.lengthOf(1);
|
||||||
|
expect(json.to[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(json.to[0].email).to.equal('test@example.org');
|
||||||
|
});
|
||||||
|
it('should set the from field', function() {
|
||||||
|
p.setFrom('testfrom@example.org');
|
||||||
|
const json = p.toJSON();
|
||||||
|
expect(json).to.have.property('from');
|
||||||
|
expect(json.from).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(json.from.email).to.equal('testfrom@example.org');
|
||||||
|
});
|
||||||
|
it('should set the cc field', function() {
|
||||||
|
p.setCc('testcc@example.org');
|
||||||
|
const json = p.toJSON();
|
||||||
|
expect(json).to.have.property('cc');
|
||||||
|
expect(json.cc).to.be.an.instanceof(Array);
|
||||||
|
expect(json.cc).to.have.a.lengthOf(1);
|
||||||
|
expect(json.cc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(json.cc[0].email).to.equal('testcc@example.org');
|
||||||
|
});
|
||||||
|
it('should set the bcc field', function() {
|
||||||
|
p.setBcc('testbcc@example.org');
|
||||||
|
const json = p.toJSON();
|
||||||
|
expect(json).to.have.property('bcc');
|
||||||
|
expect(json.bcc).to.be.an.instanceof(Array);
|
||||||
|
expect(json.bcc).to.have.a.lengthOf(1);
|
||||||
|
expect(json.bcc[0]).to.be.an.instanceof(EmailAddress);
|
||||||
|
expect(json.bcc[0].email).to.equal('testbcc@example.org');
|
||||||
|
});
|
||||||
|
it('should set the headers field', function() {
|
||||||
|
p.setHeaders({ test: 'Test' });
|
||||||
|
const json = p.toJSON();
|
||||||
|
expect(json).to.have.property('headers');
|
||||||
|
expect(json.headers).to.be.an.instanceof(Object);
|
||||||
|
expect(json.headers.test).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should set the custom_args field', function() {
|
||||||
|
p.setCustomArgs({ test: 'Test' });
|
||||||
|
const json = p.toJSON();
|
||||||
|
expect(json).to.have.property('custom_args');
|
||||||
|
expect(json.custom_args).to.be.an.instanceof(Object);
|
||||||
|
expect(json.custom_args.test).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should set the substitutions field', function() {
|
||||||
|
p.setSubstitutions({ test: 'Test' });
|
||||||
|
const json = p.toJSON();
|
||||||
|
expect(json).to.have.property('substitutions');
|
||||||
|
expect(json.substitutions).to.be.an.instanceof(Object);
|
||||||
|
});
|
||||||
|
it('should apply wrappers to the substitutions', function() {
|
||||||
|
p.setSubstitutions({ test: 'Test', otherTest2: 'Test2' });
|
||||||
|
p.setSubstitutionWrappers(['{{', '}}']);
|
||||||
|
const json = p.toJSON();
|
||||||
|
expect(json.substitutions).to.have.property('{{test}}');
|
||||||
|
expect(json.substitutions).to.have.property('{{otherTest2}}');
|
||||||
|
expect(json.substitutions['{{test}}']).to.equal('Test');
|
||||||
|
expect(json.substitutions['{{otherTest2}}']).to.equal('Test2');
|
||||||
|
expect(json.substitutions).not.to.have.property('test');
|
||||||
|
expect(json.substitutions).not.to.have.property('otherTest2');
|
||||||
|
});
|
||||||
|
it('should set the dynamicTemplateData field', function() {
|
||||||
|
p.setDynamicTemplateData({ test: 'Test' });
|
||||||
|
const json = p.toJSON();
|
||||||
|
expect(json).to.have.property('dynamic_template_data');
|
||||||
|
expect(json.dynamic_template_data).to.be.an.instanceof(Object);
|
||||||
|
});
|
||||||
|
it('should set the subject field', function() {
|
||||||
|
p.setSubject('Test');
|
||||||
|
const json = p.toJSON();
|
||||||
|
expect(json).to.have.property('subject');
|
||||||
|
expect(json.subject).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should set the send_at field', function() {
|
||||||
|
p.setSendAt(555);
|
||||||
|
const json = p.toJSON();
|
||||||
|
expect(json).to.have.property('send_at');
|
||||||
|
expect(json.send_at).to.equal(555);
|
||||||
|
});
|
||||||
|
it('should not modify the keys of substitutions and custom args', () => {
|
||||||
|
const data = {
|
||||||
|
to: 'to@example.org',
|
||||||
|
customArgs: { snake_case: 'Test', T_EST: 'Test', camelCase: 'Test' },
|
||||||
|
substitutions: { snake_case: 'Test', T_EST: 'Test', camelCase: 'Test' },
|
||||||
|
};
|
||||||
|
p.fromData(data);
|
||||||
|
const json = p.toJSON();
|
||||||
|
expect(json.substitutions).to.have.property('{{T_EST}}');
|
||||||
|
expect(json.substitutions).to.have.property('{{camelCase}}');
|
||||||
|
expect(json.substitutions).to.have.property('{{snake_case}}');
|
||||||
|
expect(json.substitutions['{{T_EST}}']).to.equal('Test');
|
||||||
|
expect(json.substitutions['{{camelCase}}']).to.equal('Test');
|
||||||
|
expect(json.substitutions['{{snake_case}}']).to.equal('Test');
|
||||||
|
expect(json.custom_args).to.have.property('T_EST');
|
||||||
|
expect(json.custom_args).to.have.property('camelCase');
|
||||||
|
expect(json.custom_args).to.have.property('snake_case');
|
||||||
|
expect(json.custom_args.T_EST).to.equal('Test');
|
||||||
|
expect(json.custom_args.camelCase).to.equal('Test');
|
||||||
|
expect(json.custom_args.snake_case).to.equal('Test');
|
||||||
|
});
|
||||||
|
it('should not modify the keys of dynamic template data', () => {
|
||||||
|
const data = {
|
||||||
|
to: 'to@example.org',
|
||||||
|
dynamicTemplateData: { snake_case: 'Test', T_EST: 'Test', camelCase: 'Test' },
|
||||||
|
};
|
||||||
|
p.fromData(data);
|
||||||
|
const json = p.toJSON();
|
||||||
|
expect(json.dynamic_template_data).to.have.property('T_EST');
|
||||||
|
expect(json.dynamic_template_data).to.have.property('camelCase');
|
||||||
|
expect(json.dynamic_template_data).to.have.property('snake_case');
|
||||||
|
expect(json.dynamic_template_data.T_EST).to.equal('Test');
|
||||||
|
expect(json.dynamic_template_data.camelCase).to.equal('Test');
|
||||||
|
expect(json.dynamic_template_data.snake_case).to.equal('Test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
import * as https from 'https';
|
||||||
|
|
||||||
|
type HttpMethod = 'get'|'GET'|'post'|'POST'|'put'|'PUT'|'patch'|'PATCH'|'delete'|'DELETE';
|
||||||
|
|
||||||
|
export default interface RequestOptions<TData = any, TParams = object> {
|
||||||
|
url: string;
|
||||||
|
method?: HttpMethod;
|
||||||
|
baseUrl?: string;
|
||||||
|
qs?: TParams;
|
||||||
|
body?: TData;
|
||||||
|
headers?: object;
|
||||||
|
httpsAgent?: https.Agent;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
export default class ResponseError extends Error {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
response: {
|
||||||
|
headers: { [key: string]: string; };
|
||||||
|
body: string;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response error class
|
||||||
|
*/
|
||||||
|
class ResponseError extends Error {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
constructor(response) {
|
||||||
|
|
||||||
|
//Super
|
||||||
|
super();
|
||||||
|
|
||||||
|
//Extract data from response
|
||||||
|
const { headers, status, statusText, data } = response;
|
||||||
|
|
||||||
|
//Set data
|
||||||
|
this.code = status;
|
||||||
|
this.message = statusText;
|
||||||
|
this.response = { headers, body: data };
|
||||||
|
|
||||||
|
//Capture stack trace
|
||||||
|
if (!this.stack) {
|
||||||
|
Error.captureStackTrace(this, this.constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Clean up stack trace
|
||||||
|
const regex = new RegExp(process.cwd() + '/', 'gi');
|
||||||
|
this.stack = this.stack.replace(regex, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert to string
|
||||||
|
*/
|
||||||
|
toString() {
|
||||||
|
const { body } = this.response;
|
||||||
|
let err = `${this.message} (${this.code})`;
|
||||||
|
if (body && Array.isArray(body.errors)) {
|
||||||
|
body.errors.forEach(error => {
|
||||||
|
const message = error.message;
|
||||||
|
const field = error.field;
|
||||||
|
const help = error.help;
|
||||||
|
err += `\n ${message}\n ${field}\n ${help}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert to simple object for JSON responses
|
||||||
|
*/
|
||||||
|
toJSON() {
|
||||||
|
const { message, code, response } = this;
|
||||||
|
return { message, code, response };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Export
|
||||||
|
module.exports = ResponseError;
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default class Response<TPayload = object> {
|
||||||
|
statusCode: number;
|
||||||
|
body: TPayload;
|
||||||
|
headers: any;
|
||||||
|
constructor(statusCode: number, body: TPayload, headers?: any);
|
||||||
|
toString(): string;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
class Response {
|
||||||
|
constructor(statusCode, body, headers) {
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.body = body;
|
||||||
|
this.headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return 'HTTP ' + this.statusCode + ' ' + this.body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Response;
|
|
@ -0,0 +1,56 @@
|
||||||
|
export class Stats {
|
||||||
|
startDate: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
aggregatedBy?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Statistics {
|
||||||
|
constructor(data?: Stats);
|
||||||
|
|
||||||
|
fromData(data: Stats): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To JSON
|
||||||
|
*/
|
||||||
|
toJSON(): Stats;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Advanced Statistics
|
||||||
|
*/
|
||||||
|
getAdvanced();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Category Statistics
|
||||||
|
*/
|
||||||
|
getCategory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Global Statistics
|
||||||
|
*/
|
||||||
|
getGlobal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Parse Statistics
|
||||||
|
*/
|
||||||
|
getParse();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Subuser Statistics
|
||||||
|
*/
|
||||||
|
getSubuser();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set StartDate
|
||||||
|
*/
|
||||||
|
setStartDate(startDate: Date): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set EndDate
|
||||||
|
*/
|
||||||
|
setEndDate(endDate: Date): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set AggregatedBy
|
||||||
|
*/
|
||||||
|
setAggregatedBy(aggregatedBy: string): void;
|
||||||
|
}
|
|
@ -0,0 +1,283 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const toCamelCase = require('../helpers/to-camel-case');
|
||||||
|
const deepClone = require('../helpers/deep-clone');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options
|
||||||
|
*/
|
||||||
|
const AggregatedByOptions = ['day', 'week', 'month'];
|
||||||
|
const CountryOptions = ['us', 'ca'];
|
||||||
|
const SortByDirection = ['desc', 'asc'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statistics class
|
||||||
|
*/
|
||||||
|
class Statistics {
|
||||||
|
constructor(data) {
|
||||||
|
this.startDate = null;
|
||||||
|
this.endDate = null;
|
||||||
|
this.aggregatedBy = null;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
this.fromData(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build from data
|
||||||
|
*/
|
||||||
|
fromData(data) {
|
||||||
|
|
||||||
|
//Expecting object
|
||||||
|
if (typeof data !== 'object') {
|
||||||
|
throw new Error('Expecting object for Statistics data');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert to camel case to make it workable, making a copy to prevent
|
||||||
|
//changes to the original objects
|
||||||
|
data = deepClone(data);
|
||||||
|
data = toCamelCase(data, ['substitutions', 'customArgs']);
|
||||||
|
|
||||||
|
const { startDate,
|
||||||
|
endDate,
|
||||||
|
aggregatedBy,
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
this.setStartDate(startDate);
|
||||||
|
this.setEndDate(endDate);
|
||||||
|
this.setAggregatedBy(aggregatedBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set startDate
|
||||||
|
*/
|
||||||
|
setStartDate(startDate) {
|
||||||
|
if (typeof startDate === 'undefined') {
|
||||||
|
throw new Error('Date expected for `startDate`');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((new Date(startDate) === 'Invalid Date') ||
|
||||||
|
isNaN(new Date(startDate))) {
|
||||||
|
throw new Error('Date expected for `startDate`');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(startDate);
|
||||||
|
|
||||||
|
this.startDate = new Date(startDate).toISOString().slice(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set endDate
|
||||||
|
*/
|
||||||
|
setEndDate(endDate) {
|
||||||
|
if (typeof endDate === 'undefined') {
|
||||||
|
this.endDate = new Date().toISOString().slice(0, 10);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new Date(endDate) === 'Invalid Date' || isNaN(new Date(endDate))) {
|
||||||
|
throw new Error('Date expected for `endDate`');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.endDate = new Date(endDate).toISOString().slice(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set aggregatedBy
|
||||||
|
*/
|
||||||
|
setAggregatedBy(aggregatedBy) {
|
||||||
|
if (typeof aggregatedBy === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof aggregatedBy === 'string' &&
|
||||||
|
AggregatedByOptions.includes(aggregatedBy.toLowerCase())) {
|
||||||
|
this.aggregatedBy = aggregatedBy;
|
||||||
|
} else {
|
||||||
|
throw new Error('Incorrect value for `aggregatedBy`');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Global
|
||||||
|
*/
|
||||||
|
getGlobal() {
|
||||||
|
const { startDate, endDate, aggregatedBy } = this;
|
||||||
|
|
||||||
|
return { startDate, endDate, aggregatedBy };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Advanced
|
||||||
|
*/
|
||||||
|
getAdvanced(country) {
|
||||||
|
const json = this.getGlobal();
|
||||||
|
|
||||||
|
if (typeof country === 'undefined') {
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof country === 'string' &&
|
||||||
|
CountryOptions.includes(country.toLowerCase())) {
|
||||||
|
json.country = country;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Advanced Mailbox Providers
|
||||||
|
*/
|
||||||
|
getAdvancedMailboxProviders(mailBoxProviders) {
|
||||||
|
const json = this.getGlobal();
|
||||||
|
|
||||||
|
if (typeof mailBoxProviders === 'undefined') {
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(mailBoxProviders) &&
|
||||||
|
mailBoxProviders.some(x => typeof x !== 'string')) {
|
||||||
|
throw new Error('Array of strings expected for `mailboxProviders`');
|
||||||
|
}
|
||||||
|
|
||||||
|
json.mailBoxProviders = mailBoxProviders;
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Advanced Browsers
|
||||||
|
*/
|
||||||
|
getAdvancedBrowsers(browsers) {
|
||||||
|
const json = this.getGlobal();
|
||||||
|
|
||||||
|
if (typeof browsers === 'undefined') {
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(browsers) && browsers.some(x => typeof x !== 'string')) {
|
||||||
|
throw new Error('Array of strings expected for `browsers`');
|
||||||
|
}
|
||||||
|
|
||||||
|
json.browsers = browsers;
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Categories
|
||||||
|
*/
|
||||||
|
getCategories(categories) {
|
||||||
|
if (typeof categories === 'undefined') {
|
||||||
|
throw new Error('Array of strings expected for `categories`');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._isValidArrayOfStrings(categories)) {
|
||||||
|
throw new Error('Array of strings expected for `categories`');
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = this.getGlobal();
|
||||||
|
json.categories = categories;
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Subuser
|
||||||
|
*/
|
||||||
|
getSubuser(subusers) {
|
||||||
|
if (typeof subusers === 'undefined') {
|
||||||
|
throw new Error('Array of strings expected for `subusers`');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._isValidArrayOfStrings(subusers)) {
|
||||||
|
throw new Error('Array of strings expected for `subusers`');
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = this.getGlobal();
|
||||||
|
json.subusers = subusers;
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Subuser Sum
|
||||||
|
*/
|
||||||
|
getSubuserSum(sortByMetric = 'delivered',
|
||||||
|
sortByDirection = SortByDirection[0], limit = 5, offset = 0) {
|
||||||
|
if (typeof sortByMetric !== 'string') {
|
||||||
|
throw new Error('string expected for `sortByMetric`');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SortByDirection.includes(sortByDirection.toLowerCase())) {
|
||||||
|
throw new Error('desc or asc expected for `sortByDirection`');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof limit !== 'number') {
|
||||||
|
throw new Error('number expected for `limit`');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof offset !== 'number') {
|
||||||
|
throw new Error('number expected for `offset`');
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = this.getGlobal();
|
||||||
|
|
||||||
|
json.sortByMetric = sortByMetric;
|
||||||
|
json.sortByDirection = sortByDirection;
|
||||||
|
json.limit = limit;
|
||||||
|
json.offset = offset;
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Subuser Monthly
|
||||||
|
*/
|
||||||
|
getSubuserMonthly(sortByMetric = 'delivered',
|
||||||
|
sortByDirection = SortByDirection[0], limit = 5, offset = 0) {
|
||||||
|
if (typeof sortByMetric !== 'string') {
|
||||||
|
throw new Error('string expected for `sortByMetric`');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SortByDirection.includes(sortByDirection.toLowerCase())) {
|
||||||
|
throw new Error('desc or asc expected for `sortByDirection`');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof limit !== 'number') {
|
||||||
|
throw new Error('number expected for `limit`');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof offset !== 'number') {
|
||||||
|
throw new Error('number expected for `offset`');
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = this.getGlobal();
|
||||||
|
|
||||||
|
json.sortByMetric = sortByMetric;
|
||||||
|
json.sortByDirection = sortByDirection;
|
||||||
|
json.limit = limit;
|
||||||
|
json.offset = offset;
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isValidArrayOfStrings(arr) {
|
||||||
|
if (!Array.isArray(arr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arr.length < 1 || arr.some(x => typeof x !== 'string')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Export class
|
||||||
|
module.exports = Statistics;
|
192
backend/node_modules/@sendgrid/helpers/classes/statistics.spec.js
generated
vendored
Normal file
192
backend/node_modules/@sendgrid/helpers/classes/statistics.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const Statistics = require('./statistics');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('Statistics', function() {
|
||||||
|
|
||||||
|
//Test data
|
||||||
|
const data = {
|
||||||
|
startDate: new Date(),
|
||||||
|
endDate: new Date('2017-10-21'),
|
||||||
|
aggregatedBy: 'week',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('setStartDate()', function() {
|
||||||
|
let stats;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
stats = new Statistics(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the startDate', function() {
|
||||||
|
stats.setStartDate(new Date('2017-10-22'));
|
||||||
|
expect(stats.startDate).equal('2017-10-22');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
stats.setStartDate('');
|
||||||
|
}).to.throw(Error);
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
stats.setStartDate({});
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for no input', function() {
|
||||||
|
expect(function() {
|
||||||
|
stats.setStartDate();
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('endDate()', function() {
|
||||||
|
let stats;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
stats = new Statistics(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set endDate', function() {
|
||||||
|
stats.setEndDate(new Date('2017-10-22'));
|
||||||
|
expect(stats.endDate).equal('2017-10-22');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
stats.setEndDate('');
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setAggregatedBy()', function() {
|
||||||
|
let stats;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
stats = new Statistics(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set aggregatedBy', function() {
|
||||||
|
stats.setAggregatedBy('week');
|
||||||
|
expect(stats.aggregatedBy).equal('week');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
stats.setAggregatedBy('');
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
stats.setAggregatedBy([1]);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAdvanced()', function() {
|
||||||
|
let stats;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
stats = new Statistics(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get advanced', function() {
|
||||||
|
const advanced = stats.getAdvanced('US');
|
||||||
|
expect(advanced.country).equal('US');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAdvancedMailboxProviders()', function() {
|
||||||
|
let stats;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
stats = new Statistics(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get advanced mailbox providers', function() {
|
||||||
|
const arr = ['something'];
|
||||||
|
const advanced = stats.getAdvancedMailboxProviders(arr);
|
||||||
|
expect(advanced.mailBoxProviders).equal(arr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAdvancedBrowsers()', function() {
|
||||||
|
let stats;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
stats = new Statistics(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get advanced browsers', function() {
|
||||||
|
const arr = ['something'];
|
||||||
|
const advanced = stats.getAdvancedBrowsers(arr);
|
||||||
|
expect(advanced.browsers).equal(arr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCategories()', function() {
|
||||||
|
let stats;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
stats = new Statistics(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get categories', function() {
|
||||||
|
const arr = ['something'];
|
||||||
|
const categories = stats.getCategories(arr);
|
||||||
|
expect(categories.categories).equal(arr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSubuser()', function() {
|
||||||
|
let stats;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
stats = new Statistics(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get subusers', function() {
|
||||||
|
const arr = ['something'];
|
||||||
|
const advanced = stats.getSubuser(arr);
|
||||||
|
expect(advanced.subusers).equal(arr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSubuserSum()', function() {
|
||||||
|
let stats;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
stats = new Statistics(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get subusersum', function() {
|
||||||
|
const subuser = stats.getSubuserSum('delivered', 'asc', 10, 0);
|
||||||
|
|
||||||
|
expect(subuser.sortByMetric).equal('delivered');
|
||||||
|
expect(subuser.sortByDirection).equal('asc');
|
||||||
|
expect(subuser.limit).equal(10);
|
||||||
|
expect(subuser.offset).equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSubuserMonthly()', function() {
|
||||||
|
let stats;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
stats = new Statistics(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get subusersmonthly', function() {
|
||||||
|
const subuser = stats.getSubuserMonthly('delivered', 'asc', 10, 0);
|
||||||
|
|
||||||
|
expect(subuser.sortByMetric).equal('delivered');
|
||||||
|
expect(subuser.sortByDirection).equal('asc');
|
||||||
|
expect(subuser.limit).equal(10);
|
||||||
|
expect(subuser.offset).equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
const DYNAMIC_TEMPLATE_CHAR_WARNING = `
|
||||||
|
Content with characters ', " or & may need to be escaped with three brackets
|
||||||
|
{{{ content }}}
|
||||||
|
See https://sendgrid.com/docs/for-developers/sending-email/using-handlebars/ for more information.`;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
DYNAMIC_TEMPLATE_CHAR_WARNING,
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Helper to convert an array of objects to JSON
|
||||||
|
*/
|
||||||
|
declare function arrayToJSON(arr: any[]): any[];
|
||||||
|
|
||||||
|
export = arrayToJSON;
|
|
@ -0,0 +1,13 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to convert an array of objects to JSON
|
||||||
|
*/
|
||||||
|
module.exports = function arrayToJSON(arr) {
|
||||||
|
return arr.map(item => {
|
||||||
|
if (typeof item === 'object' && item !== null && typeof item.toJSON === 'function') {
|
||||||
|
return item.toJSON();
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
};
|
41
backend/node_modules/@sendgrid/helpers/helpers/array-to-json.spec.js
generated
vendored
Normal file
41
backend/node_modules/@sendgrid/helpers/helpers/array-to-json.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const arrayToJSON = require('./array-to-json');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('arrayToJSON', function() {
|
||||||
|
|
||||||
|
//Test object with toJSON function
|
||||||
|
const obj1 = {
|
||||||
|
toJSON() {
|
||||||
|
return {a: 1, b: 2};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
//Test plain object
|
||||||
|
const obj2 = {c: 3, d: 4};
|
||||||
|
|
||||||
|
//Create mixed array
|
||||||
|
const test = [obj1, obj2, null, obj2, obj1, 2, 'test'];
|
||||||
|
const json = arrayToJSON(test);
|
||||||
|
|
||||||
|
//Tests
|
||||||
|
it('should leave non object values as is', function() {
|
||||||
|
expect(json[2]).to.be.null();
|
||||||
|
expect(json[5]).to.equal(2);
|
||||||
|
expect(json[6]).to.equal('test');
|
||||||
|
});
|
||||||
|
it('should leave plain objects as they are', function() {
|
||||||
|
expect(json[1]).to.have.property('c');
|
||||||
|
expect(json[3]).to.have.property('d');
|
||||||
|
});
|
||||||
|
it('should use the toJSON() handler if specified', function() {
|
||||||
|
expect(json[0]).to.have.property('a');
|
||||||
|
expect(json[4]).to.have.property('b');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Helper to convert an object's keys
|
||||||
|
*/
|
||||||
|
declare function convertKeys<T extends {}, S extends {}>(obj: T, converter: (key: string) => string, ignored?: string[]): S;
|
||||||
|
|
||||||
|
export = convertKeys;
|
|
@ -0,0 +1,49 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to convert an object's keys
|
||||||
|
*/
|
||||||
|
module.exports = function convertKeys(obj, converter, ignored) {
|
||||||
|
|
||||||
|
//Validate
|
||||||
|
if (typeof obj !== 'object' || obj === null) {
|
||||||
|
throw new Error('Non object passed to convertKeys: ' + obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ignore arrays
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Ensure array for ignored values
|
||||||
|
if (!Array.isArray(ignored)) {
|
||||||
|
ignored = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
//Process all properties
|
||||||
|
for (const key in obj) {
|
||||||
|
//istanbul ignore else
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
|
||||||
|
//Convert key to snake case
|
||||||
|
const converted = converter(key);
|
||||||
|
|
||||||
|
//Recursive for child objects, unless ignored
|
||||||
|
//The ignored check checks both variants of the key
|
||||||
|
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||||
|
if (!ignored.includes(key) && !ignored.includes(converted)) {
|
||||||
|
obj[key] = convertKeys(obj[key], converter, ignored);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert key to snake case and set if needed
|
||||||
|
if (converted !== key) {
|
||||||
|
obj[converted] = obj[key];
|
||||||
|
delete obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return object
|
||||||
|
return obj;
|
||||||
|
};
|
83
backend/node_modules/@sendgrid/helpers/helpers/convert-keys.spec.js
generated
vendored
Normal file
83
backend/node_modules/@sendgrid/helpers/helpers/convert-keys.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const convertKeys = require('./convert-keys');
|
||||||
|
const deepClone = require('./deep-clone');
|
||||||
|
const strToCamelCase = require('./str-to-camel-case');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('convertKeys', function() {
|
||||||
|
|
||||||
|
//Test object
|
||||||
|
const obj = {
|
||||||
|
a: 1,
|
||||||
|
snake_case: 2,
|
||||||
|
camelCase: 3,
|
||||||
|
nested_snake_case: {
|
||||||
|
a: 1,
|
||||||
|
snake_case: 2,
|
||||||
|
camelCase: 3,
|
||||||
|
},
|
||||||
|
nestedCamelCase: {
|
||||||
|
a: 1,
|
||||||
|
snake_case: 2,
|
||||||
|
camelCase: 3,
|
||||||
|
},
|
||||||
|
arr: ['a', 'b'],
|
||||||
|
};
|
||||||
|
|
||||||
|
//Create copy of the object
|
||||||
|
const objCopy = deepClone(obj);
|
||||||
|
|
||||||
|
//Convert keys
|
||||||
|
convertKeys(obj, strToCamelCase);
|
||||||
|
|
||||||
|
//Tests
|
||||||
|
it('should convert top level keys properly', function() {
|
||||||
|
expect(obj).to.have.property('a');
|
||||||
|
expect(obj).to.have.property('snakeCase');
|
||||||
|
expect(obj).to.have.property('camelCase');
|
||||||
|
expect(obj).to.have.property('nestedSnakeCase');
|
||||||
|
expect(obj).to.have.property('nestedCamelCase');
|
||||||
|
expect(obj).not.to.have.property('snake_case');
|
||||||
|
expect(obj).not.to.have.property('nested_snake_case');
|
||||||
|
});
|
||||||
|
it('should convert nested keys properly', function() {
|
||||||
|
expect(obj.nestedSnakeCase).to.have.property('a');
|
||||||
|
expect(obj.nestedSnakeCase).to.have.property('snakeCase');
|
||||||
|
expect(obj.nestedSnakeCase).to.have.property('camelCase');
|
||||||
|
expect(obj.nestedSnakeCase).not.to.have.property('snake_case');
|
||||||
|
expect(obj.nestedCamelCase).to.have.property('a');
|
||||||
|
expect(obj.nestedCamelCase).to.have.property('snakeCase');
|
||||||
|
expect(obj.nestedCamelCase).to.have.property('camelCase');
|
||||||
|
expect(obj.nestedCamelCase).not.to.have.property('snake_case');
|
||||||
|
});
|
||||||
|
it('should handle arrays properly', function() {
|
||||||
|
expect(obj.arr).to.be.an.instanceof(Array);
|
||||||
|
expect(obj.arr).to.have.lengthOf(2);
|
||||||
|
expect(obj.arr).to.have.members(['a', 'b']);
|
||||||
|
});
|
||||||
|
it('should not converted nested objects if ignored', function() {
|
||||||
|
convertKeys(objCopy, strToCamelCase, ['nestedSnakeCase']);
|
||||||
|
expect(objCopy.nestedCamelCase).to.have.property('a');
|
||||||
|
expect(objCopy.nestedCamelCase).to.have.property('snakeCase');
|
||||||
|
expect(objCopy.nestedCamelCase).to.have.property('camelCase');
|
||||||
|
expect(objCopy.nestedCamelCase).not.to.have.property('snake_case');
|
||||||
|
expect(objCopy.nestedSnakeCase).to.have.property('a');
|
||||||
|
expect(objCopy.nestedSnakeCase).to.have.property('camelCase');
|
||||||
|
expect(objCopy.nestedSnakeCase).to.have.property('snake_case');
|
||||||
|
expect(objCopy.nestedSnakeCase).not.to.have.property('snakeCase');
|
||||||
|
});
|
||||||
|
it('should throw an error for non object input', function() {
|
||||||
|
expect(function() {
|
||||||
|
convertKeys(null);
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
convertKeys(5);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Deep cloning helper for objects
|
||||||
|
*/
|
||||||
|
declare function deepClone<T>(obj: T): T;
|
||||||
|
|
||||||
|
export = deepClone;
|
|
@ -0,0 +1,8 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep cloning helper for objects
|
||||||
|
*/
|
||||||
|
module.exports = function deepClone(obj) {
|
||||||
|
return JSON.parse(JSON.stringify(obj));
|
||||||
|
};
|
|
@ -0,0 +1,43 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const deepClone = require('./deep-clone');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('deepClone', function() {
|
||||||
|
|
||||||
|
//Test object
|
||||||
|
const obj = {
|
||||||
|
nested: {a: 1, b: 2, c: {d: 4}},
|
||||||
|
e: 5,
|
||||||
|
arr: ['a', 'b'],
|
||||||
|
};
|
||||||
|
|
||||||
|
//Create clone
|
||||||
|
const clone = deepClone(obj);
|
||||||
|
|
||||||
|
//Tests
|
||||||
|
it('should equal the objects to themselves', function() {
|
||||||
|
expect(obj).to.equal(obj);
|
||||||
|
expect(clone).to.equal(clone);
|
||||||
|
});
|
||||||
|
it('should make a copy of the object', function() {
|
||||||
|
expect(obj).to.not.equal(clone);
|
||||||
|
expect(obj).to.deep.equal(clone);
|
||||||
|
});
|
||||||
|
it('should make a copy of nested objects', function() {
|
||||||
|
expect(obj.nested).to.not.equal(clone.nested);
|
||||||
|
expect(obj.nested).to.deep.equal(clone.nested);
|
||||||
|
expect(obj.nested.c).to.not.equal(clone.nested.c);
|
||||||
|
expect(obj.nested.c).to.deep.equal(clone.nested.c);
|
||||||
|
});
|
||||||
|
it('should handle arrays properly', function() {
|
||||||
|
expect(clone.arr).to.be.an.instanceof(Array);
|
||||||
|
expect(clone.arr).to.have.lengthOf(2);
|
||||||
|
expect(clone.arr).to.have.members(['a', 'b']);
|
||||||
|
});
|
||||||
|
});
|
10
backend/node_modules/@sendgrid/helpers/helpers/html-to-plain-text.js
generated
vendored
Normal file
10
backend/node_modules/@sendgrid/helpers/helpers/html-to-plain-text.js
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to convert an HTML string to a plain text string
|
||||||
|
*/
|
||||||
|
module.exports = function convertHTML2PlainString(html) {
|
||||||
|
let text = html.replace(/(<([^>]+)>)/g, '');
|
||||||
|
text = text.replace(/\s+/g, ' ');
|
||||||
|
return text;
|
||||||
|
};
|
32
backend/node_modules/@sendgrid/helpers/helpers/html-to-plain-text.spec.js
generated
vendored
Normal file
32
backend/node_modules/@sendgrid/helpers/helpers/html-to-plain-text.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const convertHTML2PlainString = require('./html-to-plain-text');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('convertHTML2PlainString', function() {
|
||||||
|
|
||||||
|
//Test string with one html tag
|
||||||
|
const html1 = '<p>Hello world</p>';
|
||||||
|
|
||||||
|
//Test string with nested html tags
|
||||||
|
const html2 = '<div><p>Hello <b>World!</b></p></div>';
|
||||||
|
|
||||||
|
//Test string with html tag with attributes
|
||||||
|
const html3 = '<div class="test-class">Hello World!</div>';
|
||||||
|
|
||||||
|
//Tests
|
||||||
|
it('should strip out html tags', function() {
|
||||||
|
expect(convertHTML2PlainString(html1)).to.be.equal('Hello world');
|
||||||
|
});
|
||||||
|
it('should strip out nested html tags', function() {
|
||||||
|
expect(convertHTML2PlainString(html2)).to.be.equal('Hello World!');
|
||||||
|
});
|
||||||
|
it('should strip out html tags with attributes', function() {
|
||||||
|
expect(convertHTML2PlainString(html3)).to.be.equal('Hello World!');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,19 @@
|
||||||
|
import arrayToJSON = require("@sendgrid/helpers/helpers/array-to-json");
|
||||||
|
import convertKeys = require("@sendgrid/helpers/helpers/convert-keys");
|
||||||
|
import deepClone = require("@sendgrid/helpers/helpers/deep-clone");
|
||||||
|
import mergeData = require("@sendgrid/helpers/helpers/merge-data");
|
||||||
|
import splitNameEmail = require("@sendgrid/helpers/helpers/split-name-email");
|
||||||
|
import toCamelCase = require("@sendgrid/helpers/helpers/to-camel-case");
|
||||||
|
import toSnakeCase = require("@sendgrid/helpers/helpers/to-snake-case");
|
||||||
|
import wrapSubstitutions = require("@sendgrid/helpers/helpers/wrap-substitutions");
|
||||||
|
|
||||||
|
export {
|
||||||
|
arrayToJSON,
|
||||||
|
convertKeys,
|
||||||
|
deepClone,
|
||||||
|
mergeData,
|
||||||
|
splitNameEmail,
|
||||||
|
toCamelCase,
|
||||||
|
toSnakeCase,
|
||||||
|
wrapSubstitutions,
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expose helpers
|
||||||
|
*/
|
||||||
|
const arrayToJSON = require('./array-to-json');
|
||||||
|
const convertKeys = require('./convert-keys');
|
||||||
|
const deepClone = require('./deep-clone');
|
||||||
|
const mergeData = require('./merge-data');
|
||||||
|
const splitNameEmail = require('./split-name-email');
|
||||||
|
const toCamelCase = require('./to-camel-case');
|
||||||
|
const toSnakeCase = require('./to-snake-case');
|
||||||
|
const wrapSubstitutions = require('./wrap-substitutions');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
arrayToJSON,
|
||||||
|
convertKeys,
|
||||||
|
deepClone,
|
||||||
|
mergeData,
|
||||||
|
splitNameEmail,
|
||||||
|
toCamelCase,
|
||||||
|
toSnakeCase,
|
||||||
|
wrapSubstitutions,
|
||||||
|
};
|
6
backend/node_modules/@sendgrid/helpers/helpers/merge-data-deep.d.ts
generated
vendored
Normal file
6
backend/node_modules/@sendgrid/helpers/helpers/merge-data-deep.d.ts
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Merge data deep helper
|
||||||
|
*/
|
||||||
|
declare function mergeDataDeep<T, S>(base: T, data: S): T & S;
|
||||||
|
|
||||||
|
export = mergeDataDeep;
|
|
@ -0,0 +1,34 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge data deep helper
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isObject(item) {
|
||||||
|
return (item && typeof item === 'object' && !Array.isArray(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function mergeDeep(base, data) {
|
||||||
|
//Validate data
|
||||||
|
if (typeof base !== 'object' || base === null) {
|
||||||
|
throw new Error('Not an object provided for base');
|
||||||
|
}
|
||||||
|
if (typeof data !== 'object' || data === null) {
|
||||||
|
throw new Error('Not an object provided for data');
|
||||||
|
}
|
||||||
|
let output = Object.assign({}, base);
|
||||||
|
if (isObject(base) && isObject(data)) {
|
||||||
|
Object.keys(data).forEach(key => {
|
||||||
|
if (isObject(data[key])) {
|
||||||
|
if (!(key in base)) {
|
||||||
|
Object.assign(output, { [key]: data[key] });
|
||||||
|
} else {
|
||||||
|
output[key] = mergeDeep(base[key], data[key]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Object.assign(output, { [key]: data[key] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
56
backend/node_modules/@sendgrid/helpers/helpers/merge-data-deep.spec.js
generated
vendored
Normal file
56
backend/node_modules/@sendgrid/helpers/helpers/merge-data-deep.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const mergeDataDeep = require('./merge-data-deep');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('mergeDataDeep', function() {
|
||||||
|
|
||||||
|
//Test objects
|
||||||
|
const obj1 = {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
e: { g: 9 },
|
||||||
|
arr: ['a', 'b'],
|
||||||
|
};
|
||||||
|
const obj2 = {
|
||||||
|
a: 3,
|
||||||
|
c: 3,
|
||||||
|
d: 4,
|
||||||
|
e: { f: 6 },
|
||||||
|
arr: ['c'],
|
||||||
|
};
|
||||||
|
|
||||||
|
//Merge
|
||||||
|
const merged = mergeDataDeep(obj1, obj2);
|
||||||
|
|
||||||
|
//Tests
|
||||||
|
it('should merge the two objects', function() {
|
||||||
|
expect(merged).to.have.property('a');
|
||||||
|
expect(merged).to.have.property('b');
|
||||||
|
expect(merged).to.have.property('c');
|
||||||
|
expect(merged).to.have.property('d');
|
||||||
|
expect(merged).to.have.property('e');
|
||||||
|
expect(merged.a).to.equal(3);
|
||||||
|
expect(merged.e).to.have.property('f');
|
||||||
|
expect(merged.e).to.have.property('g');
|
||||||
|
});
|
||||||
|
it('should throw on invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
mergeDataDeep(null, obj2);
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
mergeDataDeep(obj1, 4);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should overwrite arrays', function() {
|
||||||
|
expect(merged).to.have.property('arr');
|
||||||
|
expect(merged.arr).to.be.an.instanceof(Array);
|
||||||
|
expect(merged.arr).to.have.lengthOf(1);
|
||||||
|
expect(merged.arr[0]).to.equal('c');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Merge data helper
|
||||||
|
*/
|
||||||
|
declare function mergeData<T, S>(base: T, data: S): T&S;
|
||||||
|
|
||||||
|
export = mergeData;
|
|
@ -0,0 +1,35 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge data helper
|
||||||
|
*/
|
||||||
|
module.exports = function mergeData(base, data) {
|
||||||
|
|
||||||
|
//Validate data
|
||||||
|
if (typeof base !== 'object' || base === null) {
|
||||||
|
throw new Error('Not an object provided for base');
|
||||||
|
}
|
||||||
|
if (typeof data !== 'object' || data === null) {
|
||||||
|
throw new Error('Not an object provided for data');
|
||||||
|
}
|
||||||
|
|
||||||
|
//Copy base
|
||||||
|
const merged = Object.assign({}, base);
|
||||||
|
|
||||||
|
//Add data
|
||||||
|
for (const key in data) {
|
||||||
|
//istanbul ignore else
|
||||||
|
if (data.hasOwnProperty(key)) {
|
||||||
|
if (data[key] && Array.isArray(data[key])) {
|
||||||
|
merged[key] = data[key];
|
||||||
|
} else if (data[key] && typeof data[key] === 'object') {
|
||||||
|
merged[key] = Object.assign({}, data[key]);
|
||||||
|
} else if (data[key]) {
|
||||||
|
merged[key] = data[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Return
|
||||||
|
return merged;
|
||||||
|
};
|
|
@ -0,0 +1,52 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const mergeData = require('./merge-data');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('mergeData', function() {
|
||||||
|
|
||||||
|
//Test objects
|
||||||
|
const obj1 = {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
arr: ['a', 'b'],
|
||||||
|
};
|
||||||
|
const obj2 = {
|
||||||
|
c: 3,
|
||||||
|
d: 4,
|
||||||
|
e: {f: 6},
|
||||||
|
arr: ['c'],
|
||||||
|
};
|
||||||
|
|
||||||
|
//Merge
|
||||||
|
const merged = mergeData(obj1, obj2);
|
||||||
|
|
||||||
|
//Tests
|
||||||
|
it('should merge the two objects', function() {
|
||||||
|
expect(merged).to.have.property('a');
|
||||||
|
expect(merged).to.have.property('b');
|
||||||
|
expect(merged).to.have.property('c');
|
||||||
|
expect(merged).to.have.property('d');
|
||||||
|
expect(merged).to.have.property('e');
|
||||||
|
expect(merged.e).to.have.property('f');
|
||||||
|
});
|
||||||
|
it('should throw on invalid input', function() {
|
||||||
|
expect(function() {
|
||||||
|
mergeData(null, obj2);
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
mergeData(obj1, 4);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
it('should overwrite arrays', function() {
|
||||||
|
expect(merged).to.have.property('arr');
|
||||||
|
expect(merged.arr).to.be.an.instanceof(Array);
|
||||||
|
expect(merged.arr).to.have.lengthOf(1);
|
||||||
|
expect(merged.arr[0]).to.equal('c');
|
||||||
|
});
|
||||||
|
});
|
6
backend/node_modules/@sendgrid/helpers/helpers/split-name-email.d.ts
generated
vendored
Normal file
6
backend/node_modules/@sendgrid/helpers/helpers/split-name-email.d.ts
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Split name and email address from string
|
||||||
|
*/
|
||||||
|
declare function splitNameEmail(str: string): string[];
|
||||||
|
|
||||||
|
export = splitNameEmail;
|
22
backend/node_modules/@sendgrid/helpers/helpers/split-name-email.js
generated
vendored
Normal file
22
backend/node_modules/@sendgrid/helpers/helpers/split-name-email.js
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split name and email address from string
|
||||||
|
*/
|
||||||
|
module.exports = function splitNameEmail(str) {
|
||||||
|
|
||||||
|
//If no email bracket present, return as is
|
||||||
|
if (str.indexOf('<') === -1) {
|
||||||
|
return ['', str];
|
||||||
|
}
|
||||||
|
|
||||||
|
//Split into name and email
|
||||||
|
let [name, email] = str.split('<');
|
||||||
|
|
||||||
|
//Trim and fix up
|
||||||
|
name = name.trim();
|
||||||
|
email = email.replace('>', '').trim();
|
||||||
|
|
||||||
|
//Return as array
|
||||||
|
return [name, email];
|
||||||
|
};
|
22
backend/node_modules/@sendgrid/helpers/helpers/split-name-email.spec.js
generated
vendored
Normal file
22
backend/node_modules/@sendgrid/helpers/helpers/split-name-email.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const splitNameEmail = require('./split-name-email');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('splitNameEmail', function() {
|
||||||
|
it('should not split strings without < symbol', function() {
|
||||||
|
const [name, email] = splitNameEmail('test@test.com');
|
||||||
|
expect(name).to.equal('');
|
||||||
|
expect(email).to.equal('test@test.com');
|
||||||
|
});
|
||||||
|
it('should split strings with < symbol', function() {
|
||||||
|
const [name, email] = splitNameEmail('Tester <test@test.com>');
|
||||||
|
expect(name).to.equal('Tester');
|
||||||
|
expect(email).to.equal('test@test.com');
|
||||||
|
});
|
||||||
|
});
|
19
backend/node_modules/@sendgrid/helpers/helpers/str-to-camel-case.js
generated
vendored
Normal file
19
backend/node_modules/@sendgrid/helpers/helpers/str-to-camel-case.js
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal conversion helper
|
||||||
|
*/
|
||||||
|
module.exports = function strToCamelCase(str) {
|
||||||
|
if (typeof str !== 'string') {
|
||||||
|
throw new Error('String expected for conversion to snake case');
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
.trim()
|
||||||
|
.replace(/_+|\-+/g, ' ')
|
||||||
|
.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function(match, index) {
|
||||||
|
if (Number(match) === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return (index === 0) ? match.toLowerCase() : match.toUpperCase();
|
||||||
|
});
|
||||||
|
};
|
53
backend/node_modules/@sendgrid/helpers/helpers/str-to-camel-case.spec.js
generated
vendored
Normal file
53
backend/node_modules/@sendgrid/helpers/helpers/str-to-camel-case.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const toCamelCase = require('./str-to-camel-case');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('toCamelCase', function() {
|
||||||
|
it('should camel case an already camel cased string', function() {
|
||||||
|
expect(toCamelCase('camelCase')).to.equal('camelCase');
|
||||||
|
});
|
||||||
|
it('should camel case a snake cased string', function() {
|
||||||
|
expect(toCamelCase('camel_case')).to.equal('camelCase');
|
||||||
|
});
|
||||||
|
it('should camel case a dasherized string', function() {
|
||||||
|
expect(toCamelCase('camel-case')).to.equal('camelCase');
|
||||||
|
});
|
||||||
|
it('should camel case a string with spaces', function() {
|
||||||
|
expect(toCamelCase('camel case')).to.equal('camelCase');
|
||||||
|
});
|
||||||
|
it('should camel case a string with multiple spaces', function() {
|
||||||
|
expect(toCamelCase('camel case')).to.equal('camelCase');
|
||||||
|
expect(toCamelCase('camel ca se')).to.equal('camelCaSe');
|
||||||
|
});
|
||||||
|
it('should camel case a mixed string', function() {
|
||||||
|
expect(toCamelCase('CamelCase With snake_case _and dash-erized -andCamel'))
|
||||||
|
.to.equal('camelCaseWithSnakeCaseAndDashErizedAndCamel');
|
||||||
|
expect(toCamelCase('camel_case With vari-ety andCamel'))
|
||||||
|
.to.equal('camelCaseWithVariEtyAndCamel');
|
||||||
|
});
|
||||||
|
it('should lowercase single letters', function() {
|
||||||
|
expect(toCamelCase('A')).to.equal('a');
|
||||||
|
expect(toCamelCase('F')).to.equal('f');
|
||||||
|
expect(toCamelCase('Z')).to.equal('z');
|
||||||
|
});
|
||||||
|
it('should trim and camel case properly with leading/trailing spaces', function() {
|
||||||
|
expect(toCamelCase(' test_me ')).to.equal('testMe');
|
||||||
|
expect(toCamelCase(' test_me')).to.equal('testMe');
|
||||||
|
expect(toCamelCase('test_me ')).to.equal('testMe');
|
||||||
|
expect(toCamelCase(' test_me ')).to.equal('testMe');
|
||||||
|
});
|
||||||
|
it('should throw an error for non string input', function() {
|
||||||
|
expect(function() {
|
||||||
|
toCamelCase(2);
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
toCamelCase(null);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
14
backend/node_modules/@sendgrid/helpers/helpers/str-to-snake-case.js
generated
vendored
Normal file
14
backend/node_modules/@sendgrid/helpers/helpers/str-to-snake-case.js
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal conversion helper
|
||||||
|
*/
|
||||||
|
module.exports = function strToSnakeCase(str) {
|
||||||
|
if (typeof str !== 'string') {
|
||||||
|
throw new Error('String expected for conversion to snake case');
|
||||||
|
}
|
||||||
|
return str.trim().replace(/(\s*\-*\b\w|[A-Z])/g, function($1) {
|
||||||
|
$1 = $1.trim().toLowerCase().replace('-', '');
|
||||||
|
return ($1[0] === '_' ? '' : '_') + $1;
|
||||||
|
}).slice(1);
|
||||||
|
};
|
56
backend/node_modules/@sendgrid/helpers/helpers/str-to-snake-case.spec.js
generated
vendored
Normal file
56
backend/node_modules/@sendgrid/helpers/helpers/str-to-snake-case.spec.js
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const toSnakeCase = require('./str-to-snake-case');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests
|
||||||
|
*/
|
||||||
|
describe('toSnakeCase', function() {
|
||||||
|
it('should snake case an already snake cased string', function() {
|
||||||
|
expect(toSnakeCase('snake_case')).to.equal('snake_case');
|
||||||
|
});
|
||||||
|
it('should snake case a camel cased string', function() {
|
||||||
|
expect(toSnakeCase('snakeCase')).to.equal('snake_case');
|
||||||
|
expect(toSnakeCase('SnakeCase')).to.equal('snake_case');
|
||||||
|
expect(toSnakeCase('SnAkeCASe')).to.equal('sn_ake_c_a_se');
|
||||||
|
});
|
||||||
|
it('should snake case a dasherized string', function() {
|
||||||
|
expect(toSnakeCase('snake-case')).to.equal('snake_case');
|
||||||
|
expect(toSnakeCase('Snake-Case')).to.equal('snake_case');
|
||||||
|
});
|
||||||
|
it('should snake case a string with spaces', function() {
|
||||||
|
expect(toSnakeCase('Snake Case')).to.equal('snake_case');
|
||||||
|
});
|
||||||
|
it('should snake case a string with multiple spaces', function() {
|
||||||
|
expect(toSnakeCase('Snake Case')).to.equal('snake_case');
|
||||||
|
expect(toSnakeCase('Snake Ca se')).to.equal('snake_ca_se');
|
||||||
|
});
|
||||||
|
it('should snake case a mixed string', function() {
|
||||||
|
expect(toSnakeCase('Snake-Case mixEd Stri_ng te-st'))
|
||||||
|
.to.equal('snake_case_mix_ed_stri_ng_te_st');
|
||||||
|
expect(toSnakeCase('CamelCase With snake_case _and dash-erized -andCamel'))
|
||||||
|
.to.equal('camel_case_with_snake_case_and_dash_erized_and_camel');
|
||||||
|
});
|
||||||
|
it('should lowercase single letters', function() {
|
||||||
|
expect(toSnakeCase('A')).to.equal('a');
|
||||||
|
expect(toSnakeCase('F')).to.equal('f');
|
||||||
|
expect(toSnakeCase('Z')).to.equal('z');
|
||||||
|
});
|
||||||
|
it('should trim and snake case properly with leading/trailing spaces', function() {
|
||||||
|
expect(toSnakeCase(' TestMe ')).to.equal('test_me');
|
||||||
|
expect(toSnakeCase(' TestMe')).to.equal('test_me');
|
||||||
|
expect(toSnakeCase('TestMe ')).to.equal('test_me');
|
||||||
|
expect(toSnakeCase(' TestMe ')).to.equal('test_me');
|
||||||
|
});
|
||||||
|
it('should throw an error for non string input', function() {
|
||||||
|
expect(function() {
|
||||||
|
toSnakeCase(2);
|
||||||
|
}).to.throw(Error);
|
||||||
|
expect(function() {
|
||||||
|
toSnakeCase(null);
|
||||||
|
}).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Convert object keys to camel case
|
||||||
|
*/
|
||||||
|
declare function toCamelCase<T extends {}, S extends {}>(obj: T, ignored?: string[]): S;
|
||||||
|
|
||||||
|
export = toCamelCase;
|
|
@ -0,0 +1,14 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependencies
|
||||||
|
*/
|
||||||
|
const convertKeys = require('./convert-keys');
|
||||||
|
const strToCamelCase = require('./str-to-camel-case');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert object keys to camel case
|
||||||
|
*/
|
||||||
|
module.exports = function toCamelCase(obj, ignored) {
|
||||||
|
return convertKeys(obj, strToCamelCase, ignored);
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Convert object keys to snake case
|
||||||
|
*/
|
||||||
|
declare function toSnakeCase<T extends {}, S extends {}>(obj: T, ignored?: string[]): S;
|
||||||
|
|
||||||
|
export = toSnakeCase;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue