pocketbase/main.go
2024-12-06 16:55:45 +05:30

616 lines
17 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>&#x2714;</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!",
})
}