feat: Add photo management features, duplicate detection, attempt limits, and admin deletion
CI / skip-ci-check (pull_request) Successful in 1m19s
CI / lint-and-type-check (pull_request) Failing after 1m37s
CI / test (pull_request) Successful in 2m16s
CI / build (pull_request) Failing after 1m46s
CI / secret-scanning (pull_request) Successful in 1m20s
CI / dependency-scan (pull_request) Successful in 1m27s
CI / sast-scan (pull_request) Successful in 2m29s
CI / workflow-summary (pull_request) Successful in 1m18s
CI / skip-ci-check (pull_request) Successful in 1m19s
CI / lint-and-type-check (pull_request) Failing after 1m37s
CI / test (pull_request) Successful in 2m16s
CI / build (pull_request) Failing after 1m46s
CI / secret-scanning (pull_request) Successful in 1m20s
CI / dependency-scan (pull_request) Successful in 1m27s
CI / sast-scan (pull_request) Successful in 2m29s
CI / workflow-summary (pull_request) Successful in 1m18s
- Add duplicate photo detection (file hash and URL checking) - Add max attempts per photo with UI counter - Simplify penalty system (auto-enable when points > 0) - Prevent scores from going below 0 - Add admin photo deletion functionality - Improve navigation with always-visible logout - Prevent users from guessing their own photos
This commit is contained in:
+113
@@ -0,0 +1,113 @@
|
||||
import nodemailer from "nodemailer"
|
||||
|
||||
let transporter: nodemailer.Transporter | null = null
|
||||
|
||||
async function getTransporter() {
|
||||
if (transporter) return transporter
|
||||
|
||||
// In development, use Ethereal or console transport
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
// Try to use Ethereal for testing
|
||||
try {
|
||||
const testAccount = await nodemailer.createTestAccount()
|
||||
transporter = nodemailer.createTransport({
|
||||
host: "smtp.ethereal.email",
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: testAccount.user,
|
||||
pass: testAccount.pass,
|
||||
},
|
||||
})
|
||||
return transporter
|
||||
} catch {
|
||||
// Fallback to console transport
|
||||
transporter = nodemailer.createTransport({
|
||||
streamTransport: true,
|
||||
newline: "unix",
|
||||
buffer: true,
|
||||
})
|
||||
return transporter
|
||||
}
|
||||
}
|
||||
|
||||
// Production: use SMTP
|
||||
transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: parseInt(process.env.SMTP_PORT || "587"),
|
||||
secure: process.env.SMTP_PORT === "465",
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASSWORD,
|
||||
},
|
||||
})
|
||||
|
||||
return transporter
|
||||
}
|
||||
|
||||
export async function sendNewPhotoEmail(
|
||||
recipientEmail: string,
|
||||
recipientName: string,
|
||||
photoId: string,
|
||||
uploaderName: string
|
||||
) {
|
||||
const emailTransporter = await getTransporter()
|
||||
const baseUrl = process.env.NEXTAUTH_URL || "http://localhost:3000"
|
||||
const photoUrl = `${baseUrl}/photos/${photoId}`
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.SMTP_FROM || "MirrorMatch <noreply@mirrormatch.com>",
|
||||
to: recipientEmail,
|
||||
subject: "New Photo Ready to Guess!",
|
||||
html: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</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: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
|
||||
<h1 style="color: white; margin: 0;">MirrorMatch</h1>
|
||||
</div>
|
||||
<div style="background: #f9f9f9; padding: 30px; border-radius: 0 0 10px 10px;">
|
||||
<h2 style="color: #667eea; margin-top: 0;">New Photo Uploaded!</h2>
|
||||
<p>Hi ${recipientName},</p>
|
||||
<p><strong>${uploaderName}</strong> has uploaded a new photo for you to guess!</p>
|
||||
<p style="margin: 30px 0;">
|
||||
<a href="${photoUrl}" style="background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px; display: inline-block;">View Photo & Guess</a>
|
||||
</p>
|
||||
<p style="color: #666; font-size: 14px; margin-top: 30px;">
|
||||
Good luck! 🎯
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
text: `
|
||||
Hi ${recipientName},
|
||||
|
||||
${uploaderName} has uploaded a new photo for you to guess!
|
||||
|
||||
View the photo and submit your guess here: ${photoUrl}
|
||||
|
||||
Good luck!
|
||||
`.trim(),
|
||||
}
|
||||
|
||||
try {
|
||||
const info = await emailTransporter.sendMail(mailOptions)
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("Email sent (dev mode):")
|
||||
console.log("Preview URL:", nodemailer.getTestMessageUrl(info) || "Check console")
|
||||
console.log("To:", recipientEmail)
|
||||
console.log("Subject:", mailOptions.subject)
|
||||
}
|
||||
|
||||
return info
|
||||
} catch (error) {
|
||||
console.error("Error sending email:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user