616 lines
17 KiB
Go
616 lines
17 KiB
Go
package main
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"fmt"
|
||
"log"
|
||
"math/big"
|
||
"net/http"
|
||
"net/mail"
|
||
"os"
|
||
"time"
|
||
|
||
"github.com/labstack/echo/v5"
|
||
"github.com/pocketbase/pocketbase"
|
||
"github.com/pocketbase/pocketbase/apis"
|
||
"github.com/pocketbase/pocketbase/core"
|
||
"github.com/pocketbase/pocketbase/tools/mailer"
|
||
)
|
||
|
||
type EmailConfigurationResponse struct {
|
||
Page int `json:"page"`
|
||
PerPage int `json:"perPage"`
|
||
TotalItems int `json:"totalItems"`
|
||
TotalPages int `json:"totalPages"`
|
||
Items []struct {
|
||
Type string `json:"type"`
|
||
Email string `json:"email"`
|
||
} `json:"items"`
|
||
}
|
||
|
||
var app *pocketbase.PocketBase
|
||
|
||
func main() {
|
||
// app := pocketbase.New()
|
||
app = pocketbase.New()
|
||
|
||
app.OnRecordBeforeUpdateRequest().Add(func(e *core.RecordUpdateEvent) error {
|
||
if e.Collection.Name == "users" {
|
||
// Fetch the original record by ID
|
||
originalRecord, err := app.Dao().FindRecordById("users", e.Record.GetString("id"))
|
||
if err != nil {
|
||
return fmt.Errorf("failed to fetch original record: %v", err)
|
||
}
|
||
|
||
// Get the old status before the update
|
||
oldStatus := originalRecord.GetString("status")
|
||
newStatus := e.Record.GetString("status")
|
||
|
||
// If the status has changed, send an email
|
||
if newStatus != oldStatus {
|
||
userEmail := e.Record.GetString("email")
|
||
userName := e.Record.GetString("username")
|
||
if userEmail != "" {
|
||
|
||
var subject string
|
||
var body string
|
||
|
||
// Define the email content based on status
|
||
if newStatus == "Approved" {
|
||
subject = "Registration Approved – FCSC"
|
||
body = fmt.Sprintf(`
|
||
<p>Dear %s,</p>
|
||
<p>We are pleased to inform you that your registration with FCSC has been approved. You can now log in using the credentials that you had created.</p>
|
||
<p>Please access your account using the following link: <a href="http://your-login-url.com">Login</a>.</p>
|
||
<p>If you have any questions, please feel free to contact us for further clarification.</p>
|
||
`, userName)
|
||
} else if newStatus == "Denied" {
|
||
subject = "Registration Update – FCSC"
|
||
body = fmt.Sprintf(`
|
||
<p>Dear %s,</p>
|
||
<p>Thank you for registering with FCSC. After careful review, we regret to inform you that your registration has not been approved at this time.</p>
|
||
<p>If you have any questions or believe this decision was made in error, please feel free to contact us for further clarification.</p>
|
||
<p>We appreciate your understanding and thank you for your interest.</p>
|
||
<p>Best regards,</p>
|
||
`, userName)
|
||
} else {
|
||
// If the status is neither "Approved" nor "Denied", skip sending an email
|
||
return nil
|
||
}
|
||
|
||
// Create the email message
|
||
message := &mailer.Message{
|
||
From: mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress, // Replace with your sender's email
|
||
},
|
||
To: []mail.Address{
|
||
{
|
||
Name: userName,
|
||
Address: userEmail,
|
||
},
|
||
},
|
||
Subject: subject,
|
||
HTML: body,
|
||
}
|
||
|
||
// Send the email
|
||
err = app.NewMailClient().Send(message)
|
||
if err != nil {
|
||
log.Printf("Failed to send status change email: %v", err)
|
||
return err
|
||
}
|
||
|
||
log.Println("Status change email sent successfully to:", userEmail)
|
||
}
|
||
}
|
||
}
|
||
return nil
|
||
})
|
||
|
||
//Hook to send a verification link to register user
|
||
app.OnRecordAfterCreateRequest().Add(func(e *core.RecordCreateEvent) error {
|
||
if e.Record.Collection().Name == "users" {
|
||
// Retrieve user details
|
||
name := e.Record.GetString("username")
|
||
email := e.Record.GetString("email")
|
||
userID := e.Record.Id // User ID
|
||
|
||
// Email subject
|
||
subject := "Please Verify Your Email Address"
|
||
|
||
// Generate a verification URL (example: using user ID or token)
|
||
verificationURL := fmt.Sprintf("https://pb.venbait.in/verify-email?userId=%s", userID)
|
||
|
||
// Email body
|
||
body := fmt.Sprintf(`
|
||
<html>
|
||
<body>
|
||
<p>Dear %s,</p>
|
||
<p>Thank you for registering. Please click the link below to verify your email address:</p>
|
||
<p>
|
||
<a href="%s" style="padding: 10px 15px; background-color: blue; color: white; text-decoration: none; border-radius: 5px;">Verify Email</a>
|
||
</p>
|
||
<p>If you did not register for this account, please ignore this email.</p>
|
||
</body>
|
||
</html>
|
||
`, name, verificationURL)
|
||
|
||
// Create the email message
|
||
message := &mailer.Message{
|
||
From: mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress,
|
||
},
|
||
To: []mail.Address{
|
||
{
|
||
Name: name,
|
||
Address: email,
|
||
},
|
||
},
|
||
Subject: subject,
|
||
HTML: body,
|
||
}
|
||
|
||
// Send the email
|
||
err := app.NewMailClient().Send(message)
|
||
if err != nil {
|
||
log.Printf("Failed to send verification email to user: %v", err)
|
||
return err
|
||
}
|
||
|
||
log.Println("Verification email sent successfully to the user.")
|
||
}
|
||
return nil
|
||
})
|
||
|
||
// Hook to send an email after a user is created
|
||
app.OnRecordAfterCreateRequest().Add(func(e *core.RecordCreateEvent) error {
|
||
|
||
if e.Record.Collection().Name == "users" {
|
||
// Retrieve user details
|
||
name := e.Record.GetString("username")
|
||
email := e.Record.GetString("email")
|
||
adminName := "Admin" // Replace with the admin's actual name
|
||
|
||
// Email subject
|
||
subject := "New User Registration Pending Review"
|
||
|
||
// Email body with Approve/Reject buttons
|
||
body := fmt.Sprintf(`
|
||
<html>
|
||
<body>
|
||
<p>Dear %s,</p>
|
||
<p>A new user has registered and is awaiting your review.</p>
|
||
<p><strong>Details:</strong></p>
|
||
<p>Name: %s<br>
|
||
Email: %s</p>
|
||
<p>Please review the registration and take appropriate action:</p>
|
||
<p>
|
||
<a href="https://pb.venbait.in/api/custom/approve?userId=%s" style="padding: 10px 15px; background-color: green; color: white; text-decoration: none; border-radius: 5px;">Approve</a>
|
||
<a href="https://pb.venbait.in/api/custom/reject?userId=%s" style="padding: 10px 15px; background-color: red; color: white; text-decoration: none; border-radius: 5px;">Reject</a>
|
||
</p>
|
||
</body>
|
||
</html>
|
||
`, adminName, name, email, e.Record.Id, e.Record.Id)
|
||
|
||
// Define the target type (e.g., "smtp")
|
||
targetType := "admin_receive_mail"
|
||
|
||
// Fetch the email configuration record where type matches targetType
|
||
emailConfig, err := app.Dao().FindFirstRecordByData("email_configuration", "type", targetType)
|
||
if err != nil {
|
||
log.Printf("Failed to fetch email configuration for type '%s': %v", targetType, err)
|
||
return fmt.Errorf("email configuration not found for type '%s'", targetType)
|
||
}
|
||
|
||
// Get the email address
|
||
adminEmail := emailConfig.GetString("email")
|
||
if adminEmail == "" {
|
||
log.Printf("No email address configured for type '%s'", targetType)
|
||
return fmt.Errorf("no email address configured for type '%s'", targetType)
|
||
}
|
||
|
||
// Create the email message
|
||
message := &mailer.Message{
|
||
From: mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress,
|
||
},
|
||
To: []mail.Address{
|
||
{
|
||
Name: adminName,
|
||
Address: adminEmail,
|
||
},
|
||
},
|
||
Subject: subject,
|
||
HTML: body,
|
||
}
|
||
|
||
// Send the email
|
||
err = app.NewMailClient().Send(message)
|
||
if err != nil {
|
||
log.Printf("Failed to send admin notification email: %v", err)
|
||
return err
|
||
}
|
||
|
||
log.Println("Admin notification email sent successfully.")
|
||
}
|
||
return nil
|
||
})
|
||
|
||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||
|
||
e.Router.GET("/api/custom/approve", func(c echo.Context) error {
|
||
userId := c.QueryParam("userId") // Get userId from query parameters
|
||
if userId == "" {
|
||
return c.JSON(400, map[string]interface{}{
|
||
"code": 400,
|
||
"message": "Missing userId query parameter.",
|
||
})
|
||
}
|
||
|
||
// Find the user record in PocketBase by userId
|
||
record, err := app.Dao().FindRecordById("users", userId)
|
||
if err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Error finding user.",
|
||
})
|
||
}
|
||
|
||
// Set the verified status to true
|
||
record.Set("verified", true)
|
||
if err := app.Dao().SaveRecord(record); err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Failed to verify user.",
|
||
})
|
||
}
|
||
|
||
// Prepare the email details
|
||
name := record.GetString("username")
|
||
email := record.GetString("email")
|
||
subject := "Registration Approved – FCSC"
|
||
body := fmt.Sprintf(`
|
||
<html>
|
||
<body>
|
||
<p>Dear %s,</p>
|
||
<p>We are pleased to inform you that your registration with FCSC has been approved. You can now log in using the credentials that you had created.</p>
|
||
<p>Please access your account using the following link: <a href="http://your-login-url.com">Login</a>.</p>
|
||
<p>If you have any questions, please feel free to contact us for further clarification.</p>
|
||
</body>
|
||
</html>
|
||
`, name)
|
||
|
||
// Create the email message
|
||
message := &mailer.Message{
|
||
From: mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress,
|
||
},
|
||
To: []mail.Address{
|
||
{
|
||
Name: name,
|
||
Address: email,
|
||
},
|
||
},
|
||
Subject: subject,
|
||
HTML: body,
|
||
}
|
||
|
||
// Send the approval email
|
||
err = app.NewMailClient().Send(message)
|
||
if err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Failed to send approval email.",
|
||
})
|
||
}
|
||
|
||
// Render a success message as an HTML response
|
||
return c.HTML(http.StatusOK, `
|
||
<html>
|
||
<head>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
background-color: #f0f8ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
margin: 0;
|
||
}
|
||
.message-container {
|
||
text-align: center;
|
||
background-color: #e0f7fa;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
}
|
||
.message-container h1 {
|
||
font-size: 24px;
|
||
color: #00796b;
|
||
margin-bottom: 10px;
|
||
}
|
||
.message-container p {
|
||
font-size: 16px;
|
||
color: #004d40;
|
||
}
|
||
.icon {
|
||
font-size: 50px;
|
||
color: #00796b;
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-container">
|
||
<i>✔</i>
|
||
<h1>User Approved Successfully</h1>
|
||
<p>The user has been approved, and the approval email has been sent.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
})
|
||
|
||
return nil
|
||
})
|
||
|
||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||
|
||
// Approve Endpoint
|
||
e.Router.GET("/api/custom/reject", func(c echo.Context) error {
|
||
userId := c.QueryParam("userId") // Get userId from query parameters
|
||
if userId == "" {
|
||
return c.JSON(400, map[string]interface{}{
|
||
"code": 400,
|
||
"message": "Missing userId query parameter.",
|
||
})
|
||
}
|
||
|
||
// Find the user record in PocketBase by userId
|
||
record, err := app.Dao().FindRecordById("users", userId)
|
||
if err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Error finding user.",
|
||
})
|
||
}
|
||
|
||
// Set the verified status to false
|
||
record.Set("verified", false)
|
||
if err := app.Dao().SaveRecord(record); err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Failed to update user status.",
|
||
})
|
||
}
|
||
|
||
// Prepare the rejection email details
|
||
name := record.GetString("username")
|
||
email := record.GetString("email")
|
||
subject := "Registration Update – FCSC"
|
||
body := fmt.Sprintf(`
|
||
<html>
|
||
<body>
|
||
<p>Dear %s,</p>
|
||
<p>Thank you for registering with FCSC. After careful review, we regret to inform you that your registration has not been approved at this time.</p>
|
||
<p>If you have any questions or believe this decision was made in error, please feel free to contact us for further clarification.</p>
|
||
<p>We appreciate your understanding and thank you for your interest.</p>
|
||
<p>Best regards,</p>
|
||
</body>
|
||
</html>
|
||
`, name)
|
||
|
||
// Create the rejection email message
|
||
message := &mailer.Message{
|
||
From: mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress,
|
||
},
|
||
To: []mail.Address{
|
||
{
|
||
Name: name,
|
||
Address: email,
|
||
},
|
||
},
|
||
Subject: subject,
|
||
HTML: body,
|
||
}
|
||
|
||
// Send the rejection email
|
||
err = app.NewMailClient().Send(message)
|
||
if err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Failed to send rejection email.",
|
||
})
|
||
}
|
||
|
||
// Render a success message as an HTML response
|
||
return c.HTML(http.StatusOK, `
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>User Rejected</title>
|
||
<style>
|
||
body {
|
||
margin: 0;
|
||
padding: 0;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 100vh;
|
||
background-color: #ffe6e6; /* Light red background */
|
||
font-family: Arial, sans-serif;
|
||
}
|
||
.message-box {
|
||
text-align: center;
|
||
color: #d9534f; /* Bootstrap red */
|
||
}
|
||
.message-box i {
|
||
font-size: 48px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.message-box p {
|
||
font-size: 18px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-box">
|
||
<i>❌</i>
|
||
<h1>User Rejected</h1>
|
||
<p>Rejection email sent successfully.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
})
|
||
|
||
return nil
|
||
})
|
||
|
||
// Serve static files from the provided public directory (if exists)
|
||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||
e.Router.GET("/verify-email", verifyEmailHandler)
|
||
e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS("./pb_public"), false))
|
||
return nil
|
||
})
|
||
|
||
app.OnRecordAfterCreateRequest("otp_requests").Add(func(e *core.RecordCreateEvent) error {
|
||
// Ensure the record has an email field
|
||
email, ok := e.Record.Get("email").(string)
|
||
if !ok || email == "" {
|
||
log.Println("Invalid or missing email field.")
|
||
return nil // or handle this case appropriately
|
||
}
|
||
|
||
// Generate the OTP
|
||
otp, err := generateOTP()
|
||
if err != nil {
|
||
log.Printf("Failed to generate OTP: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
// Calculate the expiration time (5 minutes from now)
|
||
expiresAt := time.Now().Add(5 * time.Minute)
|
||
|
||
// Create a mail.Address for the sender
|
||
sender := mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress, // Ensure this is a valid email
|
||
}
|
||
|
||
// Create the email message
|
||
message := &mailer.Message{
|
||
From: sender,
|
||
To: []mail.Address{{Address: email}}, // Wrap the recipient email
|
||
Subject: "YOUR VERIFICATION CODE",
|
||
HTML: fmt.Sprintf(
|
||
`<p>Thanks for verifying your <strong>%s</strong> account!</p>
|
||
<p>Your code is: <strong>%d</strong></p>
|
||
<p>Sincerely,<br>Support team.</p>`,
|
||
email, otp,
|
||
),
|
||
}
|
||
|
||
// Send the email
|
||
if err := app.NewMailClient().Send(message); err != nil {
|
||
log.Printf("Failed to send email: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
// Update the record with the OTP
|
||
e.Record.Set("otp", fmt.Sprintf("%04d", otp))
|
||
e.Record.Set("expires_at", expiresAt) // Set the expiration time
|
||
|
||
if err := app.Dao().SaveRecord(e.Record); err != nil {
|
||
log.Printf("Failed to update record with OTP and expiration time: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
log.Printf("OTP sent to %s expires at and saved successfully.\n", email, expiresAt)
|
||
return nil
|
||
})
|
||
|
||
// Start the PocketBase server
|
||
go func() {
|
||
if err := app.Start(); err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
}()
|
||
|
||
//app.Router.GET("/verify-email", verifyEmailHandler)
|
||
|
||
// Start your custom HTTP server on port 8091
|
||
log.Println("Starting custom HTTP server on :8091")
|
||
if err := http.ListenAndServe(":8091", nil); err != nil {
|
||
log.Fatal("Failed to start server: ", err)
|
||
}
|
||
}
|
||
|
||
// generateOTP generates a random 4-digit OTP
|
||
func generateOTP() (int, error) {
|
||
max := big.NewInt(10000) // 4-digit numbers range: 0000 to 9999
|
||
n, err := rand.Int(rand.Reader, max)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return int(n.Int64()), nil
|
||
}
|
||
|
||
// Email verification handler
|
||
func verifyEmailHandler(c echo.Context) error {
|
||
log.Println("Starting verifyEmailHandler")
|
||
|
||
// Get the userId from the query parameter
|
||
userID := c.QueryParam("userId")
|
||
|
||
if userID == "" {
|
||
return c.JSON(http.StatusBadRequest, map[string]string{
|
||
"message": "User ID not provided",
|
||
})
|
||
}
|
||
|
||
// Check if app is properly initialized
|
||
if app == nil {
|
||
log.Fatal("PocketBase app is not initialized")
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{
|
||
"message": "Internal server error",
|
||
})
|
||
}
|
||
|
||
// Find the user record in PocketBase by userId
|
||
record, err := app.Dao().FindRecordById("users", userID)
|
||
if err != nil {
|
||
log.Printf("Error finding user: %v", err)
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Error finding user.",
|
||
})
|
||
}
|
||
|
||
// Set the verified status to true
|
||
record.Set("user_mail_verify", true)
|
||
log.Printf("Failed to verify user: %v", err)
|
||
if err := app.Dao().SaveRecord(record); err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Failed to verify user.",
|
||
})
|
||
}
|
||
|
||
// Redirect to the login page after successful verification
|
||
log.Println("Email verification successful")
|
||
return c.JSON(http.StatusOK, map[string]string{
|
||
"message": "Email verified successfully!",
|
||
})
|
||
|
||
}
|