api-go
|
|-------> cmd/server
| |------> main.go
|
|
|-------> internal
| |
| |------> app
| | |-----> app.go
| |
| |------> auth
| | |-----> jwt.go
| |
| |
| |------> db
| | |-----> postgres.go
| | |
| | |-----> seed
| | |--------> admin.go // added
| |
| |
| |
| |------> domain
| | |-----> errors.go
| | |
| | |-----> user.go
| | |
| | |-----> website.go
| |
| |------> dto
| | |-----> websites.go
| | |
| | |-----> auth.go
| |
| |
| |------> handlers
| | |-----> websites.go
| | |
| | |-----> users.go
| |
| |
| |------> http
| | |-----> apperror
| | | |-----------> mapper.go
| | |
| | |-----> context
| | | |-----------> context.go
| | |
| | |-----> middleware
| | | |-----------> auth.go
| | | |
| | | |-----------> rateLimit.go
| | | |
| | | |-----------> rbac.go // added
| | |
| | |
| | |
| | |-----> response
| | |-----------> response.go
| |
| |
| |------> repository
| | |-----> website_repository_pg.go
| | |-----> website_repository.go
| | |-----> user_repository.go
| | |-----> user_repository_pg.go
| |
| |
| |------> routes
| | |-----> websites.go
| | |
| | |-----> health.go
| | |
| | |-----> router.go
| | |
| | |-----> user.go
| | |
| | |-----> admin.go // added
| |
| |
| |
| |------> service
| | |-----> websites.go
| | |
| | |-----> users.go
| |
| |
| |------> validation
| |-----> password.go
|
|------> migrations
| |------> 20251226095007_create_websites_table.down.sql
| |------> 20251226095007_create_websites_table.up.sql
|
|------> .env
.env file at theseAPP_ENV = "dev"
ADMIN_EMAIL = "[email protected]"
ADMIN_PASSWORD = "runstate-admin-logging"
SeedAdmin functioninternal/db/seed/admin.go
package seed
import (
"errors"
"os"
"github.com/jmoiron/sqlx"
"golang.org/x/crypto/bcrypt"
)
// Creates a default admin user in the database only if it doesn't already exists.
func SeedAdmin(db *sqlx.DB) error {
adminEmail := os.Getenv("ADMIN_EMAIL")
adminPassword := os.Getenv("ADMIN_PASSWORD")
if adminEmail == "" || adminPassword == "" {
return errors.New("ADMIN_EMAIL or ADMIN_PASSWORD not set")
}
query := `
INSERT INTO users (email, password, role)
VALUES ($1, $2, 'ADMIN')
ON CONFLICT (email) DO NOTHING
`
hashed, err := bcrypt.GenerateFromPassword(
[]byte(adminPassword),
bcrypt.DefaultCost,
)
if err != nil {
return err
}
_, err = db.Exec(
query,
adminEmail,
string(hashed),
)
return err
}
internal/app/app.go
package app
import (
"log"
"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/db/seed"
"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"))
// Add Seeding
if os.Getenv("APP_ENV") == "development" || os.Getenv("APP_ENV") == "local" {
if err := seed.SeedAdmin(dbConn); err != nil {
log.Fatal("failed to seed admin:", err)
}
}
// 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)
userRepo := repository.NewUserRepository(dbConn)
userService := service.NewUserService(userRepo, jwtManager)
userHandler := handlers.NewUserHandler(userService)
routes.RegisterRouter(r, websiteHandler, userHandler, 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
}
.env at cmd/server/main.go // Install godotenv package
go get github.com/joho/godotenv
cmd/server/main.go
package main
import (
"github.com/RitikaxG/runState/apps/api-go/internal/app"
"github.com/joho/godotenv"
)
func main() {
// Load env
_ = godotenv.Load()
server := app.BuildServer()
server.Run(":3001")
}