first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:35:24 +03:00
commit bbbf76b184
592 changed files with 246870 additions and 0 deletions

12
pkg/utils/colors.go Normal file
View File

@@ -0,0 +1,12 @@
package utils
const (
ColorReset = "\033[0m"
ColorRed = "\033[31m"
ColorGreen = "\033[32m"
ColorYellow = "\033[33m"
ColorBlue = "\033[34m"
ColorPurple = "\033[35m"
ColorCyan = "\033[36m"
ColorWhite = "\033[37m"
)

19
pkg/utils/db_utils.go Normal file
View File

@@ -0,0 +1,19 @@
package utils
import (
"errors"
"strings"
"github.com/jackc/pgx/v5/pgconn"
)
// IsDuplicateKeyError checks if the error is a PostgreSQL duplicate key violation
func IsDuplicateKeyError(err error) bool {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
// 23505 is the PostgreSQL error code for unique_violation
return pgErr.Code == "23505"
}
// Fallback for other drivers or if error wrapping is different
return strings.Contains(err.Error(), "duplicate key value violates unique constraint")
}

112
pkg/utils/email.go Normal file
View File

@@ -0,0 +1,112 @@
package utils
import (
"fmt"
"gauth-central/config"
"net/smtp"
)
func SendVerificationEmail(toEmail, token string) error {
// Get config
host := config.AppConfig.EmailHost
port := config.AppConfig.EmailPort
from := config.AppConfig.EmailFrom
if from == "" {
from = "noreply@gauth.local"
}
// Construct verification link
// Assuming frontend handles verification at /verify-email?token=...
// Or backend endpoint directly: /api/v1/auth/verify-email?token=...
// Let's use APP_URL from config
verifyLink := fmt.Sprintf("%s/v1/auth/verify-email?token=%s", config.AppConfig.AppURL, token)
// Email content
subject := "Subject: Verify your email address\n"
mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
body := fmt.Sprintf(`
<html>
<body>
<h2>Welcome to GAuth-Central!</h2>
<p>Please click the link below to verify your email address:</p>
<p><a href="%s">Verify Email</a></p>
<p>Or copy and paste this link: %s</p>
</body>
</html>
`, verifyLink, verifyLink)
msg := []byte(subject + mime + body)
// Address
addr := fmt.Sprintf("%s:%s", host, port)
// Auth (if needed)
var auth smtp.Auth
if config.AppConfig.EmailHostUser != "" && config.AppConfig.EmailHostPassword != "" {
auth = smtp.PlainAuth("", config.AppConfig.EmailHostUser, config.AppConfig.EmailHostPassword, host)
}
// Send email
if err := smtp.SendMail(addr, auth, from, []string{toEmail}, msg); err != nil {
return err
}
return nil
}
func SendContactEmail(name, email, subject, message, ip string) error {
// Get config
host := config.AppConfig.EmailHost
port := config.AppConfig.EmailPort
from := config.AppConfig.EmailFrom
if from == "" {
from = "noreply@gauth.local"
}
// Typically, contact form emails are sent TO the admin, not the user who filled the form.
// However, the original code seemed to imply sending it somewhere.
// Let's assume we send it to a configured admin email or the same 'from' address for now.
// Or maybe we send a confirmation to the user?
// The original python code `send_contact_email` likely sent it to the site admins.
// Let's send it to the configured "EmailFrom" address (acting as admin) for this example.
toEmail := config.AppConfig.EmailFrom
if toEmail == "" {
toEmail = "admin@gauth.local"
}
// Email content
emailSubject := fmt.Sprintf("Subject: New Contact Message: %s\n", subject)
mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
body := fmt.Sprintf(`
<html>
<body>
<h2>New Contact Message</h2>
<p><strong>Name:</strong> %s</p>
<p><strong>Email:</strong> %s</p>
<p><strong>IP:</strong> %s</p>
<p><strong>Subject:</strong> %s</p>
<hr>
<p><strong>Message:</strong></p>
<p>%s</p>
</body>
</html>
`, name, email, ip, subject, message)
msg := []byte(emailSubject + mime + body)
// Address
addr := fmt.Sprintf("%s:%s", host, port)
// Auth (if needed)
var auth smtp.Auth
if config.AppConfig.EmailHostUser != "" && config.AppConfig.EmailHostPassword != "" {
auth = smtp.PlainAuth("", config.AppConfig.EmailHostUser, config.AppConfig.EmailHostPassword, host)
}
// Send email
if err := smtp.SendMail(addr, auth, from, []string{toEmail}, msg); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,137 @@
package utils
import (
"fmt"
"image"
"image/jpeg"
"image/png"
"mime/multipart"
"os"
"path/filepath"
"strings"
"time"
"gauth-central/config"
"github.com/chai2010/webp"
"github.com/disintegration/imaging"
)
type ImageOptions struct {
Width int
Height int
Quality float32 // 1-100 (WebP uses float32)
Format string // "webp", "jpg", "png"
Mode string // "cover", "contain", "resize"
}
// SaveOptimizedImage processes and saves an image
// Returns the relative path to the saved file (e.g., "/uploads/avatars/filename.webp")
func SaveOptimizedImage(fileHeader *multipart.FileHeader, uploadDir string, userID string, opts *ImageOptions) (string, error) {
// If opts is nil, use defaults from config
if opts == nil {
opts = &ImageOptions{
Width: config.AppConfig.AvatarWidth,
Height: config.AppConfig.AvatarHeight,
Quality: float32(config.AppConfig.AvatarQuality),
Format: config.AppConfig.AvatarFormat,
Mode: config.AppConfig.AvatarMode,
}
}
// Open the file
srcFile, err := fileHeader.Open()
if err != nil {
return "", fmt.Errorf("failed to open file: %v", err)
}
defer srcFile.Close()
// Decode image
img, _, err := image.Decode(srcFile)
if err != nil {
return "", fmt.Errorf("failed to decode image: %v", err)
}
// Resize logic
if opts.Width > 0 || opts.Height > 0 {
switch strings.ToLower(opts.Mode) {
case "cover":
// Fill requires both dimensions to be effective for cropping.
// If one is missing, we fall back to Resize which preserves aspect ratio.
if opts.Width > 0 && opts.Height > 0 {
img = imaging.Fill(img, opts.Width, opts.Height, imaging.Center, imaging.Lanczos)
} else {
img = imaging.Resize(img, opts.Width, opts.Height, imaging.Lanczos)
}
case "contain":
// Fit fits the image within the box, preserving aspect ratio.
// If one dimension is 0, Fit might behave like Resize(w, h) if implementation allows,
// but imaging.Fit usually expects a box.
// If one is 0, we assume the user wants to limit the other dimension.
if opts.Width > 0 && opts.Height > 0 {
img = imaging.Fit(img, opts.Width, opts.Height, imaging.Lanczos)
} else {
img = imaging.Resize(img, opts.Width, opts.Height, imaging.Lanczos)
}
default:
// "resize" or empty: Resize preserves aspect ratio if one arg is 0.
// If both are provided, it stretches unless we use Fill/Fit.
// imaging.Resize stretches if both are non-zero.
img = imaging.Resize(img, opts.Width, opts.Height, imaging.Lanczos)
}
}
// Determine output format and filename
ext := "." + strings.ToLower(opts.Format)
if opts.Format == "" {
ext = ".webp" // Default to WebP
opts.Format = "webp"
}
// Generate filename
filename := fmt.Sprintf("%s_%d%s", userID, time.Now().UnixNano(), ext)
// Ensure directory exists
if err := os.MkdirAll(uploadDir, 0755); err != nil {
return "", fmt.Errorf("failed to create directory: %v", err)
}
fullPath := filepath.Join(uploadDir, filename)
outFile, err := os.Create(fullPath)
if err != nil {
return "", fmt.Errorf("failed to create output file: %v", err)
}
defer outFile.Close()
// Encode
switch strings.ToLower(opts.Format) {
case "jpg", "jpeg":
quality := int(opts.Quality)
if quality < 1 || quality > 100 {
quality = 90
}
err = jpeg.Encode(outFile, img, &jpeg.Options{Quality: quality})
case "png":
err = png.Encode(outFile, img) // PNG is lossless
case "webp", "":
err = webp.Encode(outFile, img, &webp.Options{Lossless: false, Quality: opts.Quality})
default:
// Fallback to WebP
err = webp.Encode(outFile, img, &webp.Options{Lossless: false, Quality: opts.Quality})
}
if err != nil {
return "", fmt.Errorf("failed to encode image: %v", err)
}
// Return relative path
relPath := filepath.Join(uploadDir, filename)
if strings.HasPrefix(relPath, ".") {
relPath = relPath[1:]
}
if !strings.HasPrefix(relPath, "/") {
relPath = "/" + relPath
}
return relPath, nil
}

15
pkg/utils/password.go Normal file
View File

@@ -0,0 +1,15 @@
package utils
import (
"golang.org/x/crypto/bcrypt"
)
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}

15
pkg/utils/token.go Normal file
View File

@@ -0,0 +1,15 @@
package utils
import (
"crypto/rand"
"encoding/hex"
)
// GenerateSecureToken returns a cryptographically random hex string (e.g. for email verification).
func GenerateSecureToken(byteLength int) (string, error) {
b := make([]byte, byteLength)
if _, err := rand.Read(b); err != nil {
return "", err
}
return hex.EncodeToString(b), nil
}