help@rskworld.in +91 93305 39277
RSK World
  • Home
  • Development
    • Web Development
    • Mobile Apps
    • Software
    • Games
    • Project
  • Technologies
    • Data Science
    • AI Development
    • Cloud Development
    • Blockchain
    • Cyber Security
    • Dev Tools
    • Testing Tools
  • About
  • Contact

Theme Settings

Color Scheme
Display Options
Font Size
100%
Back to Project
RSK World
go-rest-api
/
internal
/
handlers
RSK World
go-rest-api
Go REST API - Enterprise-grade REST API with JWT Authentication + PostgreSQL + Redis Caching + Docker + Comprehensive Testing + Educational Design
handlers
  • api_handler.go1.4 KB
  • auth_handler.go4.4 KB
  • auth_handler_test.go4.6 KB
  • category_handler.go5.2 KB
  • product_handler.go8.8 KB
  • upload_handler.go5 KB
upload_handler.goconfig_test.goauth_handler.go
internal/handlers/upload_handler.go
Raw Download
Find: Go to:
/*
* Author: RSK World
* Email: help@rskworld.in / support@rskworld.in
* Website: https://rskworld.in/contact.php
* Year: 2026
*/

package handlers

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/rskworld/go-rest-api/internal/config"
	"github.com/rskworld/go-rest-api/internal/database"
	"github.com/rskworld/go-rest-api/internal/models"
	"github.com/rskworld/go-rest-api/internal/response"
)

type UploadHandler struct {
	Config *config.Config
}

func NewUploadHandler(cfg *config.Config) *UploadHandler {
	return &UploadHandler{Config: cfg}
}

// UploadProductImage godoc
// @Summary Upload product image
// @Description Upload an image for a specific product
// @Tags uploads
// @Accept  multipart/form-data
// @Produce  json
// @Param id path int true "Product ID"
// @Param image formData file true "Product image file"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.Response
// @Failure 404 {object} response.Response
// @Failure 500 {object} response.Response
// @Security BearerAuth
// @Router /products/{id}/upload [post]
func (h *UploadHandler) UploadProductImage(c *gin.Context) {
	productID := c.Param("id")

	// Check if product exists
	var product models.Product
	if result := database.DB.First(&product, productID); result.Error != nil {
		response.NotFound(c, "Product")
		return
	}

	// Get uploaded file
	file, header, err := c.Request.FormFile("image")
	if err != nil {
		response.ValidationError(c, "No image file provided")
		return
	}
	defer file.Close()

	// Validate file size
	if header.Size > h.Config.MaxFileSize {
		response.ValidationError(c, "File size too large. Maximum size is 5MB")
		return
	}

	// Validate file type
	contentType := header.Header.Get("Content-Type")
	isAllowed := false
	for _, allowedType := range h.Config.AllowedTypes {
		if contentType == allowedType {
			isAllowed = true
			break
		}
	}

	if !isAllowed {
		response.ValidationError(c, "File type not allowed. Only JPEG, PNG, GIF, and WebP are supported")
		return
	}

	// Create upload directory if it doesn't exist
	if err := os.MkdirAll(h.Config.UploadPath, 0755); err != nil {
		response.InternalServerError(c, "Failed to create upload directory")
		return
	}

	// Generate unique filename
	ext := filepath.Ext(header.Filename)
	if ext == "" {
		// Try to determine extension from content type
		switch contentType {
		case "image/jpeg":
			ext = ".jpg"
		case "image/png":
			ext = ".png"
		case "image/gif":
			ext = ".gif"
		case "image/webp":
			ext = ".webp"
		default:
			ext = ".jpg"
		}
	}

	filename := fmt.Sprintf("%s_%d_%d%s", uuid.New().String(), product.ID, time.Now().Unix(), ext)
	filepath := filepath.Join(h.Config.UploadPath, filename)

	// Save file
	if err := c.SaveUploadedFile(header, filepath); err != nil {
		response.InternalServerError(c, "Failed to save image file")
		return
	}

	// Update product with image path
	oldImage := product.Image
	product.Image = "/uploads/" + filename

	if err := database.DB.Model(&product).Update("image", product.Image).Error; err != nil {
		// Clean up uploaded file if database update fails
		os.Remove(filepath)
		response.InternalServerError(c, "Failed to update product image")
		return
	}

	// Remove old image file if it exists
	if oldImage != "" {
		oldFilepath := strings.TrimPrefix(oldImage, "/uploads/")
		oldFilepath = filepath.Join(h.Config.UploadPath, oldFilepath)
		os.Remove(oldFilepath)
	}

	response.Success(c, http.StatusOK, "Image uploaded successfully", gin.H{
		"product_id": product.ID,
		"image_url":  product.Image,
	})
}

// DeleteProductImage godoc
// @Summary Delete product image
// @Description Delete the image of a specific product
// @Tags uploads
// @Accept  json
// @Produce  json
// @Param id path int true "Product ID"
// @Success 200 {object} response.Response
// @Failure 404 {object} response.Response
// @Failure 500 {object} response.Response
// @Security BearerAuth
// @Router /products/{id}/image [delete]
func (h *UploadHandler) DeleteProductImage(c *gin.Context) {
	productID := c.Param("id")

	// Check if product exists
	var product models.Product
	if result := database.DB.First(&product, productID); result.Error != nil {
		response.NotFound(c, "Product")
		return
	}

	if product.Image == "" {
		response.NotFound(c, "Product image")
		return
	}

	// Delete image file
	imagePath := strings.TrimPrefix(product.Image, "/uploads/")
	imagePath = filepath.Join(h.Config.UploadPath, imagePath)
	if err := os.Remove(imagePath); err != nil && !os.IsNotExist(err) {
		response.InternalServerError(c, "Failed to delete image file")
		return
	}

	// Update product to remove image reference
	if err := database.DB.Model(&product).Update("image", "").Error; err != nil {
		response.InternalServerError(c, "Failed to update product")
		return
	}

	response.Success(c, http.StatusOK, "Image deleted successfully", nil)
}
187 lines•5 KB
go
internal/config/config_test.go
Raw Download
Find: Go to:
/*
* Author: RSK World
* Email: help@rskworld.in / support@rskworld.in
* Website: https://rskworld.in/contact.php
* Year: 2026
*/

package config

import (
	"os"
	"testing"
)

func TestGetEnv(t *testing.T) {
	key := "TEST_ENV_KEY"
	val := "test_value"
	fallback := "fallback_value"

	// Test fallback
	if res := getEnv(key, fallback); res != fallback {
		t.Errorf("Expected fallback %s, got %s", fallback, res)
	}

	// Test actual environment variable
	os.Setenv(key, val)
	defer os.Unsetenv(key)

	if res := getEnv(key, fallback); res != val {
		t.Errorf("Expected %s, got %s", val, res)
	}
}
33 lines•626 B
go
internal/handlers/auth_handler.go
Raw Download
Find: Go to:
/*
* Author: RSK World
* Email: help@rskworld.in / support@rskworld.in
* Website: https://rskworld.in/contact.php
* Year: 2026
*/

package handlers

import (
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/rskworld/go-rest-api/internal/config"
	"github.com/rskworld/go-rest-api/internal/database"
	"github.com/rskworld/go-rest-api/internal/models"
	"github.com/rskworld/go-rest-api/internal/response"
	"github.com/rskworld/go-rest-api/internal/validation"
	"golang.org/x/crypto/bcrypt"
)

type AuthRequest struct {
	Email    string `json:"email" binding:"required,email" validate:"required,email"`
	Password string `json:"password" binding:"required,min=6" validate:"required,min=6,max=128"`
}

type RegisterRequest struct {
	Name     string `json:"name" binding:"required,min=2,max=100" validate:"required,min=2,max=100"`
	Email    string `json:"email" binding:"required,email" validate:"required,email"`
	Password string `json:"password" binding:"required,min=6,max=128" validate:"required,min=6,max=128"`
}

type AuthHandler struct {
	Config *config.Config
}

func NewAuthHandler(cfg *config.Config) *AuthHandler {
	return &AuthHandler{Config: cfg}
}

// Register godoc
// @Summary Register a new user
// @Description Register a new user with name, email and password
// @Tags auth
// @Accept  json
// @Produce  json
// @Param user body handlers.RegisterRequest true "Register user"
// @Success 201 {object} response.Response
// @Failure 400 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /register [post]
func (h *AuthHandler) Register(c *gin.Context) {
	var req RegisterRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.Error(c, http.StatusBadRequest, err.Error())
		return
	}

	// Additional validation using our validation package
	if err := validation.ValidateStruct(req); err != nil {
		response.ValidationError(c, err.Error())
		return
	}

	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
	if err != nil {
		response.InternalServerError(c, "Failed to hash password")
		return
	}

	user := models.User{
		Name:     req.Name,
		Email:    req.Email,
		Password: string(hashedPassword),
		Role:     "user", // Default role
		IsActive: true,   // Default active status
	}

	if result := database.DB.Create(&user); result.Error != nil {
		response.Error(c, http.StatusBadRequest, "Email already exists")
		return
	}

	response.Success(c, http.StatusCreated, "User registered successfully", gin.H{
		"id":    user.ID,
		"name":  user.Name,
		"email": user.Email,
		"role":  user.Role,
	})
}

// Login godoc
// @Summary Login user
// @Description Login user and generate JWT token
// @Tags auth
// @Accept  json
// @Produce  json
// @Param user body handlers.AuthRequest true "Login user"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.Response
// @Failure 401 {object} response.Response
// @Failure 500 {object} response.Response
// @Router /login [post]
func (h *AuthHandler) Login(c *gin.Context) {
	var req AuthRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.Error(c, http.StatusBadRequest, err.Error())
		return
	}

	// Validate input
	if err := validation.ValidateStruct(req); err != nil {
		response.Error(c, http.StatusBadRequest, err.Error())
		return
	}

	var user models.User
	if result := database.DB.Where("email = ?", req.Email).First(&user); result.Error != nil {
		response.Error(c, http.StatusUnauthorized, "Invalid credentials")
		return
	}

	if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
		response.Unauthorized(c, "Invalid credentials")
		return
	}

	// Check if user is active
	if !user.IsActive {
		response.Unauthorized(c, "Account is deactivated")
		return
	}

	// Update last login time
	now := time.Now()
	database.DB.Model(&user).Update("last_login", now)

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"user_id": user.ID,
		"role":    user.Role,
		"exp":     time.Now().Add(time.Hour * 24).Unix(),
	})

	tokenString, err := token.SignedString([]byte(h.Config.JWTSecret))
	if err != nil {
		response.Error(c, http.StatusInternalServerError, "Failed to generate token")
		return
	}

	response.Success(c, http.StatusOK, "Login successful", gin.H{"token": tokenString})
}

155 lines•4.4 KB
go

About RSK World

Founded by Molla Samser, with Designer & Tester Rima Khatun, RSK World is your one-stop destination for free programming resources, source code, and development tools.

Founder: Molla Samser
Designer & Tester: Rima Khatun

Development

  • Game Development
  • Web Development
  • Mobile Development
  • AI Development
  • Development Tools

Legal

  • Terms & Conditions
  • Privacy Policy
  • Disclaimer

Contact Info

Nutanhat, Mongolkote
Purba Burdwan, West Bengal
India, 713147

+91 93305 39277

hello@rskworld.in
support@rskworld.in

© 2026 RSK World. All rights reserved.

Content used for educational purposes only. View Disclaimer