archetecture security fix
This commit is contained in:
+68
@@ -0,0 +1,68 @@
|
||||
export type Lang = 'en' | 'fr' | 'ar';
|
||||
export declare function formatDate(date: Date, lang: Lang, opts?: Intl.DateTimeFormatOptions): string;
|
||||
export declare const signupEmail: {
|
||||
subject: (lang: Lang) => string;
|
||||
text: (opts: {
|
||||
firstName: string;
|
||||
companyName: string;
|
||||
plan: string;
|
||||
billingPeriod: string;
|
||||
currency: string;
|
||||
paymentProvider: string;
|
||||
trialEnd: Date;
|
||||
}, lang: Lang) => string;
|
||||
};
|
||||
export declare const resetPasswordEmail: {
|
||||
subject: (lang: Lang) => string;
|
||||
html: (resetUrl: string, firstName: string, expireMinutes: number, lang: Lang) => string;
|
||||
text: (resetUrl: string, firstName: string, expireMinutes: number, lang: Lang) => string;
|
||||
};
|
||||
export declare const marketplaceReservationEmail: {
|
||||
subject: (vehicleName: string, lang: Lang) => string;
|
||||
html: (opts: {
|
||||
firstName: string;
|
||||
vehicleYear: number;
|
||||
vehicleMake: string;
|
||||
vehicleModel: string;
|
||||
companyName: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
totalDays: number;
|
||||
rateDisplay: string;
|
||||
totalDisplay: string;
|
||||
email: string;
|
||||
phone?: string;
|
||||
}, lang: Lang) => string;
|
||||
text: (opts: {
|
||||
firstName: string;
|
||||
vehicleYear: number;
|
||||
vehicleMake: string;
|
||||
vehicleModel: string;
|
||||
companyName: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
totalDays: number;
|
||||
rateDisplay: string;
|
||||
totalDisplay: string;
|
||||
}, lang: Lang) => string;
|
||||
};
|
||||
export declare const bookingConfirmedNotif: {
|
||||
title: (lang: Lang) => string;
|
||||
body: (lang: Lang) => string;
|
||||
};
|
||||
export declare const reviewRequestEmail: {
|
||||
subject: (vehicleLabel: string, lang: Lang) => string;
|
||||
html: (opts: {
|
||||
firstName: string;
|
||||
companyName: string;
|
||||
vehicleLabel: string;
|
||||
reviewUrl: string;
|
||||
}, lang: Lang) => string;
|
||||
text: (opts: {
|
||||
firstName: string;
|
||||
companyName: string;
|
||||
vehicleLabel: string;
|
||||
reviewUrl: string;
|
||||
}, lang: Lang) => string;
|
||||
};
|
||||
//# sourceMappingURL=emailTranslations.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"emailTranslations.d.ts","sourceRoot":"","sources":["../../src/lib/emailTranslations.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAQrC,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,qBAAqB,GAAG,MAAM,CAE5F;AAID,eAAO,MAAM,WAAW;oBACN,IAAI;iBAMP;QACX,SAAS,EAAE,MAAM,CAAA;QACjB,WAAW,EAAE,MAAM,CAAA;QACnB,IAAI,EAAE,MAAM,CAAA;QACZ,aAAa,EAAE,MAAM,CAAA;QACrB,QAAQ,EAAE,MAAM,CAAA;QAChB,eAAe,EAAE,MAAM,CAAA;QACvB,QAAQ,EAAE,IAAI,CAAA;KACf,QAAQ,IAAI,KAAG,MAAM;CA4CvB,CAAA;AAID,eAAO,MAAM,kBAAkB;oBACb,IAAI;qBAMH,MAAM,aAAa,MAAM,iBAAiB,MAAM,QAAQ,IAAI,KAAG,MAAM;qBA4BrE,MAAM,aAAa,MAAM,iBAAiB,MAAM,QAAQ,IAAI,KAAG,MAAM;CAKvF,CAAA;AAID,eAAO,MAAM,2BAA2B;2BACf,MAAM,QAAQ,IAAI;iBAM5B;QACX,SAAS,EAAE,MAAM,CAAA;QACjB,WAAW,EAAE,MAAM,CAAA;QACnB,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,WAAW,EAAE,MAAM,CAAA;QACnB,SAAS,EAAE,IAAI,CAAA;QACf,OAAO,EAAE,IAAI,CAAA;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,KAAK,EAAE,MAAM,CAAA;QACb,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,QAAQ,IAAI,KAAG,MAAM;iBA8CT;QACX,SAAS,EAAE,MAAM,CAAA;QACjB,WAAW,EAAE,MAAM,CAAA;QACnB,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,WAAW,EAAE,MAAM,CAAA;QACnB,SAAS,EAAE,IAAI,CAAA;QACf,OAAO,EAAE,IAAI,CAAA;QACb,SAAS,EAAE,MAAM,CAAA;QACjB,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;KACrB,QAAQ,IAAI,KAAG,MAAM;CASvB,CAAA;AAID,eAAO,MAAM,qBAAqB;kBAClB,IAAI;iBAKL,IAAI;CAKlB,CAAA;AAID,eAAO,MAAM,kBAAkB;4BACL,MAAM,QAAQ,IAAI;iBAM7B;QACX,SAAS,EAAE,MAAM,CAAA;QACjB,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,SAAS,EAAE,MAAM,CAAA;KAClB,QAAQ,IAAI,KAAG,MAAM;iBAyCT;QACX,SAAS,EAAE,MAAM,CAAA;QACjB,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,SAAS,EAAE,MAAM,CAAA;KAClB,QAAQ,IAAI,KAAG,MAAM;CAKvB,CAAA"}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.reviewRequestEmail = exports.bookingConfirmedNotif = exports.marketplaceReservationEmail = exports.resetPasswordEmail = exports.signupEmail = void 0;
|
||||
exports.formatDate = formatDate;
|
||||
function t(map, lang) {
|
||||
return map[lang] ?? map['fr'];
|
||||
}
|
||||
const dateLocale = { en: 'en-GB', fr: 'fr-FR', ar: 'ar-MA' };
|
||||
function formatDate(date, lang, opts) {
|
||||
return date.toLocaleDateString(dateLocale[lang], opts ?? { year: 'numeric', month: 'long', day: 'numeric' });
|
||||
}
|
||||
// ─── Signup confirmation ──────────────────────────────────────────────────────
|
||||
exports.signupEmail = {
|
||||
subject: (lang) => t({
|
||||
en: 'Your workspace is ready — RentalDriveGo',
|
||||
fr: 'Votre espace de travail est prêt — RentalDriveGo',
|
||||
ar: 'مساحة عملك جاهزة — RentalDriveGo',
|
||||
}, lang),
|
||||
text: (opts, lang) => {
|
||||
const trialStr = formatDate(opts.trialEnd, lang);
|
||||
return t({
|
||||
en: [
|
||||
`Hi ${opts.firstName},`,
|
||||
'',
|
||||
`Your RentalDriveGo workspace for ${opts.companyName} has been created successfully.`,
|
||||
`Plan: ${opts.plan} (${opts.billingPeriod.toLowerCase()})`,
|
||||
`Currency: ${opts.currency}`,
|
||||
`Primary payment provider: ${opts.paymentProvider}`,
|
||||
`Free trial ends on ${trialStr}.`,
|
||||
'',
|
||||
'Your workspace is ready. Sign in with the email and password you chose during signup.',
|
||||
'',
|
||||
'RentalDriveGo',
|
||||
].join('\n'),
|
||||
fr: [
|
||||
`Bonjour ${opts.firstName},`,
|
||||
'',
|
||||
`Votre espace de travail RentalDriveGo pour ${opts.companyName} a été créé avec succès.`,
|
||||
`Forfait : ${opts.plan} (${opts.billingPeriod === 'MONTHLY' ? 'mensuel' : 'annuel'})`,
|
||||
`Devise : ${opts.currency}`,
|
||||
`Fournisseur de paiement principal : ${opts.paymentProvider}`,
|
||||
`La période d'essai gratuit se termine le ${trialStr}.`,
|
||||
'',
|
||||
"Votre espace de travail est prêt. Connectez-vous avec l'e-mail et le mot de passe choisis lors de l'inscription.",
|
||||
'',
|
||||
'RentalDriveGo',
|
||||
].join('\n'),
|
||||
ar: [
|
||||
`مرحباً ${opts.firstName}،`,
|
||||
'',
|
||||
`تم إنشاء مساحة عمل RentalDriveGo الخاصة بـ ${opts.companyName} بنجاح.`,
|
||||
`الخطة: ${opts.plan} (${opts.billingPeriod === 'MONTHLY' ? 'شهري' : 'سنوي'})`,
|
||||
`العملة: ${opts.currency}`,
|
||||
`مزود الدفع الرئيسي: ${opts.paymentProvider}`,
|
||||
`تنتهي الفترة التجريبية المجانية في ${trialStr}.`,
|
||||
'',
|
||||
'مساحة عملك جاهزة. سجّل الدخول باستخدام البريد الإلكتروني وكلمة المرور التي اخترتهما عند التسجيل.',
|
||||
'',
|
||||
'RentalDriveGo',
|
||||
].join('\n'),
|
||||
}, lang);
|
||||
},
|
||||
};
|
||||
// ─── Password reset ───────────────────────────────────────────────────────────
|
||||
exports.resetPasswordEmail = {
|
||||
subject: (lang) => t({
|
||||
en: 'Reset your RentalDriveGo password',
|
||||
fr: 'Réinitialisez votre mot de passe RentalDriveGo',
|
||||
ar: 'إعادة تعيين كلمة مرور RentalDriveGo',
|
||||
}, lang),
|
||||
html: (resetUrl, firstName, expireMinutes, lang) => {
|
||||
const isRtl = lang === 'ar';
|
||||
const dir = isRtl ? 'rtl' : 'ltr';
|
||||
return t({
|
||||
en: `<div dir="ltr" style="font-family:sans-serif;max-width:560px;margin:auto;padding:32px;color:#1c1917">
|
||||
<p>Hi ${firstName},</p>
|
||||
<p>You requested a password reset for your RentalDriveGo workspace account.</p>
|
||||
<p><a href="${resetUrl}" style="background:#1d4ed8;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;display:inline-block;font-weight:600;">Reset your password</a></p>
|
||||
<p>This link expires in ${expireMinutes} minutes. If you did not request this, you can safely ignore this email.</p>
|
||||
<p>RentalDriveGo</p>
|
||||
</div>`,
|
||||
fr: `<div dir="ltr" style="font-family:sans-serif;max-width:560px;margin:auto;padding:32px;color:#1c1917">
|
||||
<p>Bonjour ${firstName},</p>
|
||||
<p>Vous avez demandé la réinitialisation du mot de passe de votre compte RentalDriveGo.</p>
|
||||
<p><a href="${resetUrl}" style="background:#1d4ed8;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;display:inline-block;font-weight:600;">Réinitialiser mon mot de passe</a></p>
|
||||
<p>Ce lien expire dans ${expireMinutes} minutes. Si vous n'avez pas fait cette demande, ignorez simplement cet e-mail.</p>
|
||||
<p>RentalDriveGo</p>
|
||||
</div>`,
|
||||
ar: `<div dir="rtl" style="font-family:sans-serif;max-width:560px;margin:auto;padding:32px;color:#1c1917">
|
||||
<p>مرحباً ${firstName}،</p>
|
||||
<p>طلبت إعادة تعيين كلمة مرور حساب RentalDriveGo الخاص بك.</p>
|
||||
<p><a href="${resetUrl}" style="background:#1d4ed8;color:#fff;padding:12px 24px;border-radius:8px;text-decoration:none;display:inline-block;font-weight:600;">إعادة تعيين كلمة المرور</a></p>
|
||||
<p>تنتهي صلاحية هذا الرابط خلال ${expireMinutes} دقيقة. إذا لم تطلب ذلك، يمكنك تجاهل هذا البريد الإلكتروني بأمان.</p>
|
||||
<p>RentalDriveGo</p>
|
||||
</div>`,
|
||||
}, lang);
|
||||
},
|
||||
text: (resetUrl, firstName, expireMinutes, lang) => t({
|
||||
en: `Hi ${firstName},\n\nReset your password here: ${resetUrl}\n\nThis link expires in ${expireMinutes} minutes.\n\nRentalDriveGo`,
|
||||
fr: `Bonjour ${firstName},\n\nRéinitialisez votre mot de passe ici : ${resetUrl}\n\nCe lien expire dans ${expireMinutes} minutes.\n\nRentalDriveGo`,
|
||||
ar: `مرحباً ${firstName}،\n\nأعد تعيين كلمة مرورك هنا: ${resetUrl}\n\nينتهي هذا الرابط خلال ${expireMinutes} دقيقة.\n\nRentalDriveGo`,
|
||||
}, lang),
|
||||
};
|
||||
// ─── Marketplace reservation request ─────────────────────────────────────────
|
||||
exports.marketplaceReservationEmail = {
|
||||
subject: (vehicleName, lang) => t({
|
||||
en: `Reservation Request Received — ${vehicleName}`,
|
||||
fr: `Demande de réservation reçue — ${vehicleName}`,
|
||||
ar: `تم استلام طلب الحجز — ${vehicleName}`,
|
||||
}, lang),
|
||||
html: (opts, lang) => {
|
||||
const startStr = formatDate(opts.startDate, lang);
|
||||
const endStr = formatDate(opts.endDate, lang);
|
||||
const isRtl = lang === 'ar';
|
||||
const l = t({
|
||||
en: {
|
||||
greeting: `Hi ${opts.firstName},`,
|
||||
intro: 'Your reservation request has been received and is pending company confirmation.',
|
||||
company: 'Company', pickup: 'Pick-up date', returnD: 'Return date',
|
||||
duration: 'Duration', daily: 'Daily rate', total: 'Estimated total', days: 'day(s)',
|
||||
footer: `The company will review your request and get in touch shortly. You may be contacted at ${opts.email}${opts.phone ? ` or ${opts.phone}` : ''}.`,
|
||||
},
|
||||
fr: {
|
||||
greeting: `Bonjour ${opts.firstName},`,
|
||||
intro: 'Votre demande de réservation a été reçue et est en attente de confirmation.',
|
||||
company: 'Société', pickup: 'Date de prise en charge', returnD: 'Date de retour',
|
||||
duration: 'Durée', daily: 'Tarif journalier', total: 'Total estimé', days: 'jour(s)',
|
||||
footer: `La société examinera votre demande et vous contactera prochainement. Vous pouvez être contacté à ${opts.email}${opts.phone ? ` ou ${opts.phone}` : ''}.`,
|
||||
},
|
||||
ar: {
|
||||
greeting: `مرحباً ${opts.firstName}،`,
|
||||
intro: 'تم استلام طلب الحجز وهو في انتظار تأكيد الشركة.',
|
||||
company: 'الشركة', pickup: 'تاريخ الاستلام', returnD: 'تاريخ الإرجاع',
|
||||
duration: 'المدة', daily: 'السعر اليومي', total: 'الإجمالي التقديري', days: 'يوم',
|
||||
footer: `ستراجع الشركة طلبك وستتواصل معك قريباً. يمكن التواصل معك على ${opts.email}${opts.phone ? ` أو ${opts.phone}` : ''}.`,
|
||||
},
|
||||
}, lang);
|
||||
return `
|
||||
<div style="font-family:sans-serif;max-width:560px;margin:auto;padding:32px;color:#1c1917;direction:${isRtl ? 'rtl' : 'ltr'}">
|
||||
<h2 style="margin-bottom:4px">${l.greeting}</h2>
|
||||
<p style="color:#57534e">${l.intro}</p>
|
||||
<hr style="border:none;border-top:1px solid #e7e5e4;margin:24px 0"/>
|
||||
<h3 style="margin-bottom:12px">${opts.vehicleYear} ${opts.vehicleMake} ${opts.vehicleModel}</h3>
|
||||
<p><strong>${l.company}:</strong> ${opts.companyName}</p>
|
||||
<p><strong>${l.pickup}:</strong> ${startStr}</p>
|
||||
<p><strong>${l.returnD}:</strong> ${endStr}</p>
|
||||
<p><strong>${l.duration}:</strong> ${opts.totalDays} ${l.days}</p>
|
||||
<p><strong>${l.daily}:</strong> ${opts.rateDisplay} MAD</p>
|
||||
<p><strong>${l.total}:</strong> ${opts.totalDisplay} MAD</p>
|
||||
<hr style="border:none;border-top:1px solid #e7e5e4;margin:24px 0"/>
|
||||
<p style="color:#57534e;font-size:13px">${l.footer}</p>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
text: (opts, lang) => {
|
||||
const startStr = formatDate(opts.startDate, lang);
|
||||
const endStr = formatDate(opts.endDate, lang);
|
||||
return t({
|
||||
en: `Hi ${opts.firstName},\n\nYour reservation request for the ${opts.vehicleYear} ${opts.vehicleMake} ${opts.vehicleModel} from ${opts.companyName} has been received.\n\nDates: ${startStr} → ${endStr}\nDuration: ${opts.totalDays} day(s)\nDaily rate: ${opts.rateDisplay} MAD\nEstimated total: ${opts.totalDisplay} MAD\n\nThe company will confirm your request shortly.\n\nThank you!`,
|
||||
fr: `Bonjour ${opts.firstName},\n\nVotre demande de réservation pour la ${opts.vehicleYear} ${opts.vehicleMake} ${opts.vehicleModel} auprès de ${opts.companyName} a été reçue.\n\nDates : ${startStr} → ${endStr}\nDurée : ${opts.totalDays} jour(s)\nTarif journalier : ${opts.rateDisplay} MAD\nTotal estimé : ${opts.totalDisplay} MAD\n\nLa société confirmera votre demande prochainement.\n\nMerci !`,
|
||||
ar: `مرحباً ${opts.firstName}،\n\nتم استلام طلب حجزك لسيارة ${opts.vehicleYear} ${opts.vehicleMake} ${opts.vehicleModel} من ${opts.companyName}.\n\nالتواريخ: ${startStr} → ${endStr}\nالمدة: ${opts.totalDays} يوم\nالسعر اليومي: ${opts.rateDisplay} MAD\nالإجمالي التقديري: ${opts.totalDisplay} MAD\n\nستؤكد الشركة طلبك قريباً.\n\nشكراً!`,
|
||||
}, lang);
|
||||
},
|
||||
};
|
||||
// ─── Booking confirmed ────────────────────────────────────────────────────────
|
||||
exports.bookingConfirmedNotif = {
|
||||
title: (lang) => t({
|
||||
en: 'Booking Confirmed',
|
||||
fr: 'Réservation confirmée',
|
||||
ar: 'تم تأكيد الحجز',
|
||||
}, lang),
|
||||
body: (lang) => t({
|
||||
en: 'Your booking has been confirmed.',
|
||||
fr: 'Votre réservation a été confirmée.',
|
||||
ar: 'تم تأكيد حجزك.',
|
||||
}, lang),
|
||||
};
|
||||
// ─── Post-rental review request ───────────────────────────────────────────────
|
||||
exports.reviewRequestEmail = {
|
||||
subject: (vehicleLabel, lang) => t({
|
||||
en: `How was your rental? — ${vehicleLabel}`,
|
||||
fr: `Comment s'est passée votre location ? — ${vehicleLabel}`,
|
||||
ar: `كيف كانت تجربة الإيجار؟ — ${vehicleLabel}`,
|
||||
}, lang),
|
||||
html: (opts, lang) => {
|
||||
const isRtl = lang === 'ar';
|
||||
const l = t({
|
||||
en: {
|
||||
greeting: `Hi ${opts.firstName},`,
|
||||
body1: `Thank you for renting with <strong>${opts.companyName}</strong>. We hope you enjoyed your <strong>${opts.vehicleLabel}</strong>.`,
|
||||
body2: 'Your feedback helps the company improve and helps other customers make informed choices. It only takes 30 seconds.',
|
||||
cta: 'Leave a review',
|
||||
disclaimer: 'This link is unique to your reservation and can only be used once. If you did not rent a vehicle recently, you can ignore this email.',
|
||||
},
|
||||
fr: {
|
||||
greeting: `Bonjour ${opts.firstName},`,
|
||||
body1: `Merci d'avoir loué chez <strong>${opts.companyName}</strong>. Nous espérons que vous avez apprécié votre <strong>${opts.vehicleLabel}</strong>.`,
|
||||
body2: "Votre avis aide la société à s'améliorer et aide les autres clients à faire des choix éclairés. Cela ne prend que 30 secondes.",
|
||||
cta: 'Laisser un avis',
|
||||
disclaimer: "Ce lien est unique à votre réservation et ne peut être utilisé qu'une seule fois. Si vous n'avez pas loué de véhicule récemment, vous pouvez ignorer cet e-mail.",
|
||||
},
|
||||
ar: {
|
||||
greeting: `مرحباً ${opts.firstName}،`,
|
||||
body1: `شكراً لاختيار <strong>${opts.companyName}</strong>. نأمل أنك استمتعت بـ <strong>${opts.vehicleLabel}</strong>.`,
|
||||
body2: 'تساعد ملاحظاتك الشركة على التحسين وتساعد العملاء الآخرين على اتخاذ قرارات مستنيرة. لا يستغرق الأمر سوى 30 ثانية.',
|
||||
cta: 'اترك تقييماً',
|
||||
disclaimer: 'هذا الرابط فريد لحجزك ولا يمكن استخدامه إلا مرة واحدة. إذا لم تستأجر مركبة مؤخراً، يمكنك تجاهل هذا البريد الإلكتروني.',
|
||||
},
|
||||
}, lang);
|
||||
return `
|
||||
<div style="font-family:sans-serif;max-width:560px;margin:auto;padding:32px;color:#1c1917;direction:${isRtl ? 'rtl' : 'ltr'}">
|
||||
<h2 style="margin-bottom:4px">${l.greeting}</h2>
|
||||
<p style="color:#57534e">${l.body1}</p>
|
||||
<p style="color:#57534e">${l.body2}</p>
|
||||
<div style="margin:32px 0;text-align:center">
|
||||
<a href="${opts.reviewUrl}" style="background:#1c1917;color:#ffffff;padding:14px 32px;border-radius:999px;text-decoration:none;font-weight:600;font-size:15px;display:inline-block">
|
||||
${l.cta}
|
||||
</a>
|
||||
</div>
|
||||
<p style="color:#a8a29e;font-size:12px">${l.disclaimer}</p>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
text: (opts, lang) => t({
|
||||
en: `Hi ${opts.firstName},\n\nThank you for renting with ${opts.companyName}. We hope you enjoyed your ${opts.vehicleLabel}.\n\nLeave a review here (one-time link):\n${opts.reviewUrl}\n\nThank you!`,
|
||||
fr: `Bonjour ${opts.firstName},\n\nMerci d'avoir loué chez ${opts.companyName}. Nous espérons que vous avez apprécié votre ${opts.vehicleLabel}.\n\nLaissez un avis ici (lien unique) :\n${opts.reviewUrl}\n\nMerci !`,
|
||||
ar: `مرحباً ${opts.firstName}،\n\nشكراً لاختيار ${opts.companyName}. نأمل أنك استمتعت بـ ${opts.vehicleLabel}.\n\nاترك تقييماً هنا (رابط فريد):\n${opts.reviewUrl}\n\nشكراً!`,
|
||||
}, lang),
|
||||
};
|
||||
//# sourceMappingURL=emailTranslations.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
export declare function isDatabaseUnavailableError(error: unknown): boolean;
|
||||
//# sourceMappingURL=isDatabaseUnavailable.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"isDatabaseUnavailable.d.ts","sourceRoot":"","sources":["../../src/lib/isDatabaseUnavailable.ts"],"names":[],"mappings":"AAAA,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIlE"}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isDatabaseUnavailableError = isDatabaseUnavailableError;
|
||||
function isDatabaseUnavailableError(error) {
|
||||
if (!error || typeof error !== 'object')
|
||||
return false;
|
||||
const candidate = error;
|
||||
return candidate.code === 'P1001' || candidate.message?.includes("Can't reach database server") === true;
|
||||
}
|
||||
//# sourceMappingURL=isDatabaseUnavailable.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"isDatabaseUnavailable.js","sourceRoot":"","sources":["../../src/lib/isDatabaseUnavailable.ts"],"names":[],"mappings":";;AAAA,gEAIC;AAJD,SAAgB,0BAA0B,CAAC,KAAc;IACvD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IACrD,MAAM,SAAS,GAAG,KAA4C,CAAA;IAC9D,OAAO,SAAS,CAAC,IAAI,KAAK,OAAO,IAAI,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,6BAA6B,CAAC,KAAK,IAAI,CAAA;AAC1G,CAAC"}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export declare const prisma: any;
|
||||
//# sourceMappingURL=prisma.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/lib/prisma.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,MAAM,KAIf,CAAA"}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.prisma = void 0;
|
||||
const client_1 = require("@rentaldrivego/database/client");
|
||||
const globalForPrisma = global;
|
||||
exports.prisma = globalForPrisma.prisma ??
|
||||
new client_1.PrismaClient({
|
||||
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||
});
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
globalForPrisma.prisma = exports.prisma;
|
||||
}
|
||||
//# sourceMappingURL=prisma.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"prisma.js","sourceRoot":"","sources":["../../src/lib/prisma.ts"],"names":[],"mappings":";;;AAAA,2DAA6D;AAE7D,MAAM,eAAe,GAAG,MAA6C,CAAA;AAExD,QAAA,MAAM,GACjB,eAAe,CAAC,MAAM;IACtB,IAAI,qBAAY,CAAC;QACf,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;KACrF,CAAC,CAAA;AAEJ,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;IAC1C,eAAe,CAAC,MAAM,GAAG,cAAM,CAAA;AACjC,CAAC"}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import Redis from 'ioredis';
|
||||
export declare const redis: Redis;
|
||||
//# sourceMappingURL=redis.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/lib/redis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,SAAS,CAAA;AAE3B,eAAO,MAAM,KAAK,OAGhB,CAAA"}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.redis = void 0;
|
||||
const ioredis_1 = __importDefault(require("ioredis"));
|
||||
exports.redis = new ioredis_1.default(process.env.REDIS_URL ?? 'redis://localhost:6379', {
|
||||
maxRetriesPerRequest: 3,
|
||||
retryStrategy: (times) => Math.min(times * 50, 2000),
|
||||
});
|
||||
exports.redis.on('error', (err) => {
|
||||
console.error('[Redis] Connection error:', err.message);
|
||||
});
|
||||
//# sourceMappingURL=redis.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/lib/redis.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA2B;AAEd,QAAA,KAAK,GAAG,IAAI,iBAAK,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,wBAAwB,EAAE;IAChF,oBAAoB,EAAE,CAAC;IACvB,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,EAAE,IAAI,CAAC;CACrD,CAAC,CAAA;AAEF,aAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;IACxB,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;AACzD,CAAC,CAAC,CAAA"}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
export type StorageVisibility = 'public' | 'private';
|
||||
export declare function getStorageRoot(): string;
|
||||
export declare function getPublicStorageRoot(): string;
|
||||
export declare function getPrivateStorageRoot(): string;
|
||||
export declare function getLegacyStorageRoots(): string[];
|
||||
export declare function assertStorageConfiguration(): string;
|
||||
export declare function getProtectedCustomerLicenseImageUrl(customerId: string): string;
|
||||
export declare function uploadImage(buffer: Buffer, folder: string, publicId?: string, visibility?: StorageVisibility): Promise<string>;
|
||||
export declare function resolveStoredFilePath(imageUrl: string): string | null;
|
||||
export declare function deleteImage(imageUrl: string): Promise<void>;
|
||||
//# sourceMappingURL=storage.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/lib/storage.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,SAAS,CAAA;AAEpD,wBAAgB,cAAc,IAAI,MAAM,CAIvC;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAOD,wBAAgB,0BAA0B,IAAI,MAAM,CAkBnD;AAuCD,wBAAgB,mCAAmC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,EACjB,UAAU,GAAE,iBAA2C,GACtD,OAAO,CAAC,MAAM,CAAC,CAejB;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAcrE;AAED,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKjE"}
|
||||
Vendored
+131
@@ -0,0 +1,131 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getStorageRoot = getStorageRoot;
|
||||
exports.getPublicStorageRoot = getPublicStorageRoot;
|
||||
exports.getPrivateStorageRoot = getPrivateStorageRoot;
|
||||
exports.getLegacyStorageRoots = getLegacyStorageRoots;
|
||||
exports.assertStorageConfiguration = assertStorageConfiguration;
|
||||
exports.getProtectedCustomerLicenseImageUrl = getProtectedCustomerLicenseImageUrl;
|
||||
exports.uploadImage = uploadImage;
|
||||
exports.resolveStoredFilePath = resolveStoredFilePath;
|
||||
exports.deleteImage = deleteImage;
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const crypto_1 = __importDefault(require("crypto"));
|
||||
const APP_PACKAGE_ROOT = path_1.default.resolve(__dirname, '..', '..', '..');
|
||||
const APP_SOURCE_ROOT = path_1.default.join(APP_PACKAGE_ROOT, 'src');
|
||||
const APP_DIST_ROOT = path_1.default.join(APP_PACKAGE_ROOT, 'dist');
|
||||
const DEFAULT_STORAGE_ROOT = path_1.default.join(APP_PACKAGE_ROOT, 'storage');
|
||||
const LEGACY_STORAGE_ROOTS = [
|
||||
path_1.default.join(APP_SOURCE_ROOT, 'lib', 'storage'),
|
||||
];
|
||||
function getStorageRoot() {
|
||||
return process.env.FILE_STORAGE_ROOT
|
||||
? path_1.default.resolve(process.env.FILE_STORAGE_ROOT)
|
||||
: DEFAULT_STORAGE_ROOT;
|
||||
}
|
||||
function getPublicStorageRoot() {
|
||||
return path_1.default.join(getStorageRoot(), 'public');
|
||||
}
|
||||
function getPrivateStorageRoot() {
|
||||
return path_1.default.join(getStorageRoot(), 'private');
|
||||
}
|
||||
function getLegacyStorageRoots() {
|
||||
return LEGACY_STORAGE_ROOTS;
|
||||
}
|
||||
function isWithinPath(targetPath, parentPath) {
|
||||
const relative = path_1.default.relative(parentPath, targetPath);
|
||||
return relative === '' || (!relative.startsWith('..') && !path_1.default.isAbsolute(relative));
|
||||
}
|
||||
function assertStorageConfiguration() {
|
||||
const storageRoot = getStorageRoot();
|
||||
if (process.env.NODE_ENV === 'production' && !process.env.FILE_STORAGE_ROOT) {
|
||||
throw new Error('FILE_STORAGE_ROOT must be set in production so uploads are stored on the mounted volume.');
|
||||
}
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
const forbiddenRoots = [APP_PACKAGE_ROOT, APP_SOURCE_ROOT, APP_DIST_ROOT];
|
||||
const invalidRoot = forbiddenRoots.find((root) => isWithinPath(storageRoot, root));
|
||||
if (invalidRoot) {
|
||||
throw new Error(`FILE_STORAGE_ROOT must point outside the API app tree in production. Received ${storageRoot}, which is inside ${invalidRoot}.`);
|
||||
}
|
||||
}
|
||||
return storageRoot;
|
||||
}
|
||||
function ensureStorageRoot(visibility) {
|
||||
assertStorageConfiguration();
|
||||
const root = visibility === 'public' ? getPublicStorageRoot() : getPrivateStorageRoot();
|
||||
fs_1.default.mkdirSync(root, { recursive: true });
|
||||
return root;
|
||||
}
|
||||
function inferVisibility(folder) {
|
||||
const normalized = folder.replace(/\\/g, '/');
|
||||
if (/\/customers\//.test(`/${normalized}/`))
|
||||
return 'private';
|
||||
if (/\/licenses?\//.test(`/${normalized}/`))
|
||||
return 'private';
|
||||
if (/\/contracts?\//.test(`/${normalized}/`))
|
||||
return 'private';
|
||||
if (/\/documents?\//.test(`/${normalized}/`))
|
||||
return 'private';
|
||||
if (/\/inspections?\//.test(`/${normalized}/`))
|
||||
return 'private';
|
||||
if (/\/internal\//.test(`/${normalized}/`))
|
||||
return 'private';
|
||||
return 'public';
|
||||
}
|
||||
function getApiBase() {
|
||||
return (process.env.API_URL ?? 'http://localhost:4000').replace(/\/$/, '');
|
||||
}
|
||||
function getDashboardBase() {
|
||||
return (process.env.DASHBOARD_URL ??
|
||||
process.env.NEXT_PUBLIC_DASHBOARD_URL ??
|
||||
'http://localhost:3000/dashboard').replace(/\/$/, '');
|
||||
}
|
||||
function getStorageRelativePath(imageUrl) {
|
||||
const marker = '/storage/';
|
||||
const idx = imageUrl.indexOf(marker);
|
||||
if (idx === -1)
|
||||
return null;
|
||||
return imageUrl.slice(idx + marker.length);
|
||||
}
|
||||
function getProtectedCustomerLicenseImageUrl(customerId) {
|
||||
return `${getDashboardBase()}/api/v1/customers/${customerId}/license-image`;
|
||||
}
|
||||
async function uploadImage(buffer, folder, publicId, visibility = inferVisibility(folder)) {
|
||||
const storageRoot = ensureStorageRoot(visibility);
|
||||
const folderPath = path_1.default.join(storageRoot, folder);
|
||||
if (!isWithinPath(folderPath, storageRoot)) {
|
||||
throw new Error('Upload path escapes storage root');
|
||||
}
|
||||
fs_1.default.mkdirSync(folderPath, { recursive: true });
|
||||
const filename = publicId
|
||||
? `${publicId}.jpg`
|
||||
: `${crypto_1.default.randomBytes(16).toString('hex')}.jpg`;
|
||||
fs_1.default.writeFileSync(path_1.default.join(folderPath, filename), buffer);
|
||||
return `${getApiBase()}/storage/${folder}/${filename}`;
|
||||
}
|
||||
function resolveStoredFilePath(imageUrl) {
|
||||
const relative = getStorageRelativePath(imageUrl);
|
||||
if (!relative)
|
||||
return null;
|
||||
const roots = [getPublicStorageRoot(), getPrivateStorageRoot(), ...getLegacyStorageRoots(), assertStorageConfiguration()];
|
||||
for (const root of roots) {
|
||||
const filePath = path_1.default.join(root, relative);
|
||||
if (!isWithinPath(filePath, root))
|
||||
continue;
|
||||
if (fs_1.default.existsSync(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async function deleteImage(imageUrl) {
|
||||
const filePath = resolveStoredFilePath(imageUrl);
|
||||
if (filePath && fs_1.default.existsSync(filePath)) {
|
||||
fs_1.default.unlinkSync(filePath);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=storage.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/lib/storage.ts"],"names":[],"mappings":"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"}
|
||||
Reference in New Issue
Block a user