Tanya b6a9765315
Some checks failed
CI / skip-ci-check (push) Successful in 1m27s
CI / skip-ci-check (pull_request) Successful in 1m27s
CI / lint-and-type-check (pull_request) Has been cancelled
CI / python-lint (pull_request) Has been cancelled
CI / test-backend (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / secret-scanning (pull_request) Has been cancelled
CI / dependency-scan (pull_request) Has been cancelled
CI / sast-scan (pull_request) Has been cancelled
CI / workflow-summary (pull_request) Has been cancelled
CI / lint-and-type-check (push) Successful in 2m4s
CI / python-lint (push) Successful in 1m53s
CI / test-backend (push) Successful in 2m37s
CI / build (push) Failing after 2m13s
CI / secret-scanning (push) Successful in 1m40s
CI / dependency-scan (push) Successful in 1m34s
CI / sast-scan (push) Successful in 2m42s
CI / workflow-summary (push) Successful in 1m26s
chore: Update project configuration and enhance code quality
This commit modifies the `.gitignore` file to exclude Python library directories while ensuring the viewer-frontend's `lib` directory is not ignored. It also updates the `package.json` to activate the virtual environment during backend tests, improving the testing process. Additionally, the CI workflow is enhanced to prevent duplicate runs for branches with open pull requests. Various components in the viewer frontend are updated to ensure consistent naming conventions and improve type safety. These changes contribute to a cleaner codebase and a more efficient development workflow.
2026-01-07 12:29:17 -05:00

197 lines
8.4 KiB
TypeScript

import { Resend } from 'resend';
import crypto from 'crypto';
const resend = new Resend(process.env.RESEND_API_KEY);
export function generateEmailConfirmationToken(): string {
return crypto.randomBytes(32).toString('hex');
}
export async function sendEmailConfirmation(
email: string,
name: string,
token: string
): Promise<void> {
const baseUrl = process.env.NEXTAUTH_URL || process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3001';
const confirmationUrl = `${baseUrl}/api/auth/verify-email?token=${token}`;
try {
await resend.emails.send({
from: process.env.RESEND_FROM_EMAIL || 'onboarding@resend.dev',
to: email,
subject: 'Confirm your email address',
html: `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Confirm your email</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background-color: #f8f9fa; padding: 30px; border-radius: 8px;">
<h1 style="color: #2563eb; margin-top: 0;">Confirm your email address</h1>
<p>Hi ${name},</p>
<p>Thank you for signing up! Please confirm your email address by clicking the button below:</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${confirmationUrl}" style="background-color: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block; font-weight: bold;">Confirm Email Address</a>
</div>
<p>Or copy and paste this link into your browser:</p>
<p style="word-break: break-all; color: #666; font-size: 14px;">${confirmationUrl}</p>
<p style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #e5e7eb; color: #6b7280; font-size: 14px;">
If you didn't create an account, you can safely ignore this email.
</p>
</div>
</body>
</html>
`,
});
} catch (error) {
console.error('Error sending confirmation email:', error);
throw new Error('Failed to send confirmation email');
}
}
export async function sendEmailConfirmationResend(
email: string,
name: string,
token: string
): Promise<void> {
const baseUrl = process.env.NEXTAUTH_URL || process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3001';
const confirmationUrl = `${baseUrl}/api/auth/verify-email?token=${token}`;
try {
await resend.emails.send({
from: process.env.RESEND_FROM_EMAIL || 'onboarding@resend.dev',
to: email,
subject: 'Confirm your email address',
html: `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Confirm your email</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background-color: #f8f9fa; padding: 30px; border-radius: 8px;">
<h1 style="color: #2563eb; margin-top: 0;">Confirm your email address</h1>
<p>Hi ${name},</p>
<p>You requested a new confirmation email. Please confirm your email address by clicking the button below:</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${confirmationUrl}" style="background-color: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block; font-weight: bold;">Confirm Email Address</a>
</div>
<p>Or copy and paste this link into your browser:</p>
<p style="word-break: break-all; color: #666; font-size: 14px;">${confirmationUrl}</p>
<p style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #e5e7eb; color: #6b7280; font-size: 14px;">
This link will expire in 24 hours. If you didn't request this email, you can safely ignore it.
</p>
</div>
</body>
</html>
`,
});
} catch (error) {
console.error('Error sending confirmation email:', error);
throw new Error('Failed to send confirmation email');
}
}
export function generatePasswordResetToken(): string {
return crypto.randomBytes(32).toString('hex');
}
export async function sendPasswordResetEmail(
email: string,
name: string,
token: string
): Promise<void> {
const baseUrl = process.env.NEXTAUTH_URL || process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3001';
const resetUrl = `${baseUrl}/reset-password?token=${token}`;
const fromEmail = process.env.RESEND_FROM_EMAIL || 'onboarding@resend.dev';
const replyTo = process.env.RESEND_REPLY_TO || fromEmail;
// Plain text version for better deliverability
const text = `Hi ${name},
You requested to reset your password. Click the link below to create a new password:
${resetUrl}
This link will expire in 1 hour.
If you didn't request a password reset, you can safely ignore this email.
Best regards,
PunimTag Viewer Team`;
try {
console.log('[EMAIL] Sending password reset email:', {
from: fromEmail,
to: email,
replyTo: replyTo,
});
const result = await resend.emails.send({
from: fromEmail,
to: email,
replyTo: replyTo,
subject: 'Reset your password - PunimTag Viewer',
text: text,
html: `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Reset your password</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #ffffff;">
<div style="background-color: #f8f9fa; padding: 30px; border-radius: 8px; border: 1px solid #e5e7eb;">
<h1 style="color: #2563eb; margin-top: 0; font-size: 24px;">Reset your password</h1>
<p style="font-size: 16px;">Hi ${name},</p>
<p style="font-size: 16px;">You requested to reset your password for your PunimTag Viewer account. Click the button below to create a new password:</p>
<div style="text-align: center; margin: 30px 0;">
<a href="${resetUrl}" style="background-color: #2563eb; color: white; padding: 14px 28px; text-decoration: none; border-radius: 6px; display: inline-block; font-weight: bold; font-size: 16px;">Reset Password</a>
</div>
<p style="font-size: 14px; color: #666;">Or copy and paste this link into your browser:</p>
<p style="word-break: break-all; color: #666; font-size: 14px; background-color: #ffffff; padding: 10px; border-radius: 4px; border: 1px solid #e5e7eb;">${resetUrl}</p>
<p style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #e5e7eb; color: #6b7280; font-size: 14px;">
<strong>Important:</strong> This link will expire in 1 hour for security reasons.
</p>
<p style="color: #6b7280; font-size: 14px;">
If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged.
</p>
<p style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #e5e7eb; color: #9ca3af; font-size: 12px;">
This is an automated message from PunimTag Viewer. Please do not reply to this email.
</p>
</div>
</body>
</html>
`,
});
if (result.error) {
console.error('[EMAIL] Resend API error:', result.error);
throw new Error(`Resend API error: ${result.error.message || 'Unknown error'}`);
}
console.log('[EMAIL] Password reset email sent successfully:', {
emailId: result.data?.id,
to: email,
});
} catch (error: any) {
console.error('[EMAIL] Error sending password reset email:', error);
console.error('[EMAIL] Error details:', {
message: error?.message,
name: error?.name,
response: error?.response,
statusCode: error?.statusCode,
});
throw error;
}
}