Architectural Diagram

1. Signup Flow Architecture

How does a new user gets created ?

Client
  |
  | POST /api/v1/signup
  | Body: email, password
  v
Gin Router
  |
  v
UserHandler (HTTP Layer)
  |
  |-- Bind & Validate Request (DTO + custom validator)
  |
  v
UserService (Business Logic)
  |
  |-- Hash Password
  |-- Check if user exists
  |
  v
UserRepository (Data Access)
  |
  |-- INSERT INTO users
  |
  v
PostgreSQL

2. Signin Workflow Diagram (Authentication)

How does a user get authenticated and receive a token ?

Client
  |
  | POST /api/v1/signin
  | Body: email, password
  v
Gin Router
  |
  v
UserHandler
  |
  |-- Bind & Validate Request
  |
  v
UserService
  |
  |-- Fetch user by email
  |-- Compare password hash
  |
  |-- JWTManager.GenerateToken(userID)
  |
  v
JWTManager
  |
  |-- Create Claims
  |-- Sign token
  |
  v
Response
  |
  |-- JWT returned to client

3. Authenticated Request After Signin (Token Usage)

What happens after signin ?

Client
  |
  | Authorization: Bearer <JWT>
  v
RateLimitMiddleware
  |
  v
AuthMiddleware
  |
  |-- Validate JWT
  |-- Extract user_id
  |-- Inject into context
  |
  v
Protected Handler
  |
  |-- contextutil.GetUserID()
  |
  v
Service / Repository

Folder Structure

api-go
	|
	|-------> cmd/server
	|             |------> main.go
	|
	|
	|-------> internal 
	|             |
	|             |------> app
	|             |         |-----> app.go
	|             |    
	|             |------> auth
	|             |         |-----> jwt.go 
	|             |
	|             |
	|							|------> db
	|							|         |-----> postgres.go
	|							|
	|							|------> domain 
	|             |         |-----> errors.go
	|             |         |
	|             |         |-----> user.go
	|             |         |
	|							|         |-----> website.go
	|							|
	|							|------> dto
	|							|					|-----> websites.go
	|             |         |
	|             |         |-----> auth.go      // added
	|             |
	|							|
	|							|------> handlers
	|							|					|-----> websites.go
	|             |         |
	|             |         |-----> users.go     // added
	|             |
	|             |
	|             |------> http
	|             |         |-----> apperror
	|							|         |          |-----------> mapper.go
	|             |         |
	|             |         |-----> context
	|             |         |          |-----------> context.go
	|             |         |
	|             |         |-----> middleware
	|             |         |          |-----------> auth.go
	|             |         |          |
	|             |         |          |-----------> rateLimit.go
	|             |         |     
	|             |         |
	|							|         |-----> response
	|							|                    |-----------> response.go 		
	|             |
	|							|
	|							|------> repository
	|							|         |-----> website_repository_pg.go
	|							|         |-----> website_repository.go
	|             |         |-----> user_repository.go     // added
	|             |         |-----> user_repository_pg.go  // added
	|							|
	|							|
	|							|------> routes
	|							|					|-----> websites.go
	|             |         |
	|             |         |-----> health.go
	|             |         |
	|             |         |-----> router.go
	|             |         |
	|             |         |-----> user.go     // added
	|             |
	|							|
	|							|------> service
	|							|					|-----> websites.go	
	|             |         |
	|             |         |-----> users.go // added
	|             |         
	|             |
	|             |------> validation
	|                       |-----> password.go	
	|
	|------> migrations
	|							|------> 20251226095007_create_websites_table.down.sql		
	|							|------> 20251226095007_create_websites_table.up.sql	
	|
	|------> .env	

Step 1 : Create custom validator

Custom validators live in a validation package and must be registered once during app startup before routes run.

1.1 Creating Custom Password Validator

internal/validation/password.go

package validation

import (
	"unicode"

	"github.com/go-playground/validator/v10"
)

/*
RegisterPasswordValidator func :
  - Receives Gin's validator engine
  - v is the object that stores all validation rules.
  - You call this func once at app startup
*/

func RegisterPasswordValidator(v *validator.Validate) {
	/*
		- v.RegisterValidation("password" : Registers a new rule named password
	*/
	v.RegisterValidation("password", func(fl validator.FieldLevel) bool {
		/*
			- fl : current field being validated
			- Field() : Reflect value of that field
			- .String() :get actual string value
		*/
		password := fl.Field().String()

		if len(password) < 8 {
			return false
		}
		var hasUpper, hasLower, hasSpecial, hasNumber bool
		for _, ch := range password {
			switch {
			case unicode.IsUpper(ch):
				hasUpper = true
			case unicode.IsLower(ch):
				hasLower = true
			case unicode.IsDigit(ch):
				hasNumber = true
			case unicode.IsPunct(ch) || unicode.IsSymbol(ch):
				hasSpecial = true
			}
		}
		return hasUpper && hasLower && hasNumber && hasSpecial
	})
}

1.2 Registering Custom Validator at app startup

internal/app/app.go

package app

import (
	"os"
	"time"

	"github.com/RitikaxG/runState/apps/api-go/internal/auth"
	"github.com/RitikaxG/runState/apps/api-go/internal/db"
	"github.com/RitikaxG/runState/apps/api-go/internal/handlers"
	"github.com/RitikaxG/runState/apps/api-go/internal/http/middleware"
	"github.com/RitikaxG/runState/apps/api-go/internal/repository"
	"github.com/RitikaxG/runState/apps/api-go/internal/routes"
	"github.com/RitikaxG/runState/apps/api-go/internal/service"
	"github.com/RitikaxG/runState/apps/api-go/internal/validation"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
)

func BuildServer() *gin.Engine {
	r := gin.Default()

	dbConn := db.NewPostgres(os.Getenv("DATABASE_URL"))

	// 1. Initialise JWT Manager
	jwtManager := auth.NewJWTManager(
		os.Getenv("JWT_SECRET"),
		24*time.Hour,
	)

	// Start cleanup goroutine once
	middleware.StartRateLimiterCleanup()

	// Apply rate limiting to all requests
	r.Use(middleware.RateLimitMiddleware())

	websiteRepo := repository.NewWebsiteRepository(dbConn)
	websiteService := service.NewWebsiteService(websiteRepo)
	websiteHandler := handlers.NewWebsiteHandler(websiteService)

	routes.RegisterRouter(r, websiteHandler, jwtManager)

	/*
		Custom validators live in a validation package and must be registered once during app startup before routes run.

			It gets Gin’s internal validator engine,
			safely casts it to *validator.Validate, and registers your custom password rule.

			- binding.Validator : Gin's global validator instance
			- binding.Validator.Engine() : returns actual underlying validator, its return type is interface
			- .(*validator.Validate) → type assertion
				 Is this engine actually a *validator.Validate ?
				 Since it returned an interface this type check is imp)
			- ok : true if assertion succeeded otherwise false
	*/
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		validation.RegisterPasswordValidator(v)
	}

	return r
}