Browse Source

initial commit

lblt 2 weeks ago
commit
f3abf78520
13 changed files with 1047 additions and 0 deletions
  1. 25 0
      .gitignore
  2. 0 0
      README.md
  3. 94 0
      examples/main.go
  4. 49 0
      go.mod
  5. 100 0
      go.sum
  6. 57 0
      pkg/auth/jwt.go
  7. 248 0
      pkg/config/config.go
  8. 30 0
      pkg/database/sqlite.go
  9. 117 0
      pkg/errors/errors.go
  10. 51 0
      pkg/logger/logrus.go
  11. 61 0
      pkg/server/gin.go
  12. 139 0
      pkg/server/middleware.go
  13. 76 0
      pkg/storage/minio.go

+ 25 - 0
.gitignore

@@ -0,0 +1,25 @@
+# If you prefer the allow list template instead of the deny list, see community template:
+# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
+#
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+
+# Go workspace file
+go.work
+go.work.sum
+
+# env file
+.env

+ 0 - 0
README.md


+ 94 - 0
examples/main.go

@@ -0,0 +1,94 @@
+package main
+
+import (
+	"net/http"
+	"time"
+
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/database"
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/errors"
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/logger"
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/server"
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/storage"
+	"github.com/gin-gonic/gin"
+	"github.com/minio/minio-go/v7"
+)
+
+func main() {
+	// Initialize logger
+	log, err := logger.NewLogger(logger.LogConfig{
+		Level:     "info",
+		Formatter: "text",
+	})
+	if err != nil {
+		panic(err)
+	}
+
+	// Initialize SQLite
+	db, err := database.NewSQLiteDB(database.SQLiteConfig{
+		Path: "app.db",
+	})
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer db.Close()
+
+	// Initialize Minio
+	minioClient, err := storage.NewMinioClient(storage.MinioConfig{
+		Endpoint:        "localhost:9000",
+		AccessKeyID:     "minioadmin",
+		SecretAccessKey: "minioadmin",
+		UseSSL:          false,
+		BucketName:      "mybucket",
+	})
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// Initialize Gin server with configuration
+	srv, err := server.NewGinServer(log, server.ServerConfig{
+		AllowedOrigins: []string{"http://localhost:3000"},
+		RequestTimeout: 30 * time.Second,
+	})
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// Add health check endpoint
+	srv.AddHealthCheck()
+
+	// Add file upload endpoint
+	router := srv.Router()
+	router.POST("/upload", func(c *gin.Context) {
+		file, header, err := c.Request.FormFile("file")
+		if err != nil {
+			c.Error(errors.NewStorageError("read_form", minioClient.BucketName(), err))
+			return
+		}
+		defer file.Close()
+
+		// Upload the file to Minio using our helper method
+		_, err = minioClient.PutObject(
+			c.Request.Context(),
+			header.Filename,
+			file,
+			header.Size,
+			minio.PutObjectOptions{
+				ContentType: header.Header.Get("Content-Type"),
+			},
+		)
+		if err != nil {
+			c.Error(errors.NewStorageError("upload", minioClient.BucketName(), err))
+			return
+		}
+
+		c.JSON(http.StatusOK, gin.H{
+			"message": "File uploaded successfully",
+			"file":    header.Filename,
+		})
+	})
+
+	// Start server
+	if err := router.Run(":8080"); err != nil {
+		log.Fatal(err)
+	}
+}

+ 49 - 0
go.mod

@@ -0,0 +1,49 @@
+module git.linuxforward.com/byom/byom-golang-lib
+
+go 1.22.5
+
+require (
+	github.com/mattn/go-sqlite3 v1.14.24
+	github.com/minio/minio-go/v7 v7.0.86
+)
+
+require (
+	github.com/bytedance/sonic v1.12.8 // indirect
+	github.com/bytedance/sonic/loader v0.2.3 // indirect
+	github.com/cloudwego/base64x v0.1.5 // indirect
+	github.com/cloudwego/iasm v0.2.0 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.8 // indirect
+	github.com/gin-contrib/sse v1.0.0 // indirect
+	github.com/gin-gonic/gin v1.10.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.25.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/leodido/go-urn v1.4.0 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.12 // indirect
+	golang.org/x/arch v0.14.0 // indirect
+	google.golang.org/protobuf v1.36.5 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
+
+require (
+	github.com/dustin/go-humanize v1.0.1 // indirect
+	github.com/go-ini/ini v1.67.0 // indirect
+	github.com/goccy/go-json v0.10.5 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/klauspost/compress v1.17.11 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.9 // indirect
+	github.com/minio/crc64nvme v1.0.0 // indirect
+	github.com/minio/md5-simd v1.1.2 // indirect
+	github.com/rs/xid v1.6.0 // indirect
+	github.com/sirupsen/logrus v1.9.3
+	golang.org/x/crypto v0.33.0 // indirect
+	golang.org/x/net v0.35.0 // indirect
+	golang.org/x/sys v0.30.0 // indirect
+	golang.org/x/text v0.22.0 // indirect
+)

+ 100 - 0
go.sum

@@ -0,0 +1,100 @@
+github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
+github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
+github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
+github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
+github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
+github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
+github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
+github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
+github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
+github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
+github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
+github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
+github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
+github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
+github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
+github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
+github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
+github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
+github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+github.com/minio/crc64nvme v1.0.0 h1:MeLcBkCTD4pAoU7TciAfwsfxgkhM2u5hCe48hSEVFr0=
+github.com/minio/crc64nvme v1.0.0/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
+github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
+github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
+github.com/minio/minio-go/v7 v7.0.86 h1:DcgQ0AUjLJzRH6y/HrxiZ8CXarA70PAIufXHodP4s+k=
+github.com/minio/minio-go/v7 v7.0.86/go.mod h1:VbfO4hYwUu3Of9WqGLBZ8vl3Hxnxo4ngxK4hzQDf4x4=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
+github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
+github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
+github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
+golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
+golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
+golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
+golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
+google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 57 - 0
pkg/auth/jwt.go

@@ -0,0 +1,57 @@
+package jwtutils
+
+import (
+	"time"
+
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/errors"
+	"github.com/golang-jwt/jwt/v5"
+)
+
+type JWTService struct {
+	secretKey []byte
+}
+
+func NewJWTService(secretKey string) *JWTService {
+	return &JWTService{
+		secretKey: []byte(secretKey),
+	}
+}
+
+func (s *JWTService) GenerateToken(userID string, duration time.Duration) (string, error) {
+	claims := jwt.MapClaims{
+		"user_id": userID,
+		"exp":     time.Now().Add(duration).Unix(),
+		"iat":     time.Now().Unix(),
+	}
+
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+	signedToken, err := token.SignedString(s.secretKey)
+	if err != nil {
+		return "", errors.NewAuthError("generate", err)
+	}
+
+	return signedToken, nil
+}
+
+func (s *JWTService) ValidateToken(tokenString string) (jwt.MapClaims, error) {
+	if tokenString == "" {
+		return nil, errors.NewAuthError("validate", errors.ErrInvalidInput)
+	}
+
+	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
+		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+			return nil, errors.NewAuthError("validate", errors.ErrInvalidInput)
+		}
+		return s.secretKey, nil
+	})
+
+	if err != nil {
+		return nil, errors.NewAuthError("parse", err)
+	}
+
+	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
+		return claims, nil
+	}
+
+	return nil, errors.NewAuthError("validate", errors.ErrInvalidInput)
+}

+ 248 - 0
pkg/config/config.go

@@ -0,0 +1,248 @@
+// pkg/config/config.go
+package config
+
+import (
+	"fmt"
+	"os"
+
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/errors"
+	"github.com/sirupsen/logrus"
+	"gopkg.in/yaml.v3"
+)
+
+type Config struct {
+	App            *App            `yaml:"app"`
+	Server         *Server         `yaml:"server"`
+	Database       *Database       `yaml:"database"`
+	Log            *Log            `yaml:"log"`
+	Storage        *Storage        `yaml:"storage"`
+	CloudComputing *CloudComputing `yaml:"cloud_computing,omitempty"`
+	SocialNetworks *SocialNetworks `yaml:"social_networks,omitempty"`
+}
+
+func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	type plain Config
+	err := unmarshal((*plain)(c))
+	if err != nil {
+		return err
+	}
+	if c.App == nil {
+		return errors.NewConfigError("app", errors.ErrInvalidInput)
+	}
+
+	// Conditional validation based on app name
+	switch c.App.Name {
+	case "design":
+		if c.CloudComputing == nil {
+			return errors.NewConfigError("cloud_computing", errors.ErrInvalidInput)
+		}
+	case "trends":
+		if c.SocialNetworks == nil {
+			return errors.NewConfigError("social_networks", errors.ErrInvalidInput)
+		}
+	}
+	return nil
+}
+
+type App struct {
+	Name        string `yaml:"name"`
+	Environment string `yaml:"environment"`
+}
+
+func (c *App) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	type plain App
+	err := unmarshal((*plain)(c))
+	if err != nil {
+		return err
+	}
+	if c.Name == "" {
+		return errors.NewConfigError("app.name", errors.ErrInvalidInput)
+	}
+	if c.Environment == "" {
+		c.Environment = "development"
+	}
+	// Validate app name
+	validNames := map[string]bool{"core": true, "design": true, "trends": true}
+	if !validNames[c.Name] {
+		return errors.NewConfigError("app.name", errors.ErrInvalidInput)
+	}
+	return nil
+}
+
+type Server struct {
+	Port           int      `yaml:"port"`
+	RequestTimeout int      `yaml:"request_timeout"` // in seconds
+	AllowedOrigins []string `yaml:"allowed_origins"`
+}
+
+func (c *Server) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	type plain Server
+	err := unmarshal((*plain)(c))
+	if err != nil {
+		return err
+	}
+	if c.Port == 0 {
+		c.Port = 8080
+	}
+	if c.RequestTimeout == 0 {
+		c.RequestTimeout = 30
+	}
+	return nil
+}
+
+type Database struct {
+	Path            string   `yaml:"path"`
+	MaxOpenConns    int      `yaml:"max_open_conns"`
+	MaxIdleConns    int      `yaml:"max_idle_conns"`
+	ConnMaxLifetime int      `yaml:"conn_max_lifetime"`
+	Pragmas         []string `yaml:"pragmas"`
+}
+
+func (c *Database) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	type plain Database
+	err := unmarshal((*plain)(c))
+	if err != nil {
+		return err
+	}
+	if c.Path == "" {
+		return errors.NewConfigError("database.path", errors.ErrInvalidInput)
+	}
+	if c.MaxOpenConns == 0 {
+		c.MaxOpenConns = 1 // SQLite recommendation
+	}
+	if c.MaxIdleConns == 0 {
+		c.MaxIdleConns = 1
+	}
+	if c.ConnMaxLifetime == 0 {
+		c.ConnMaxLifetime = 300 // 5 minutes
+	}
+	return nil
+}
+
+type Log struct {
+	Level   string `yaml:"level"`
+	Format  string `yaml:"format"`
+	NoColor bool   `yaml:"no_color"`
+}
+
+func (c *Log) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	type plain Log
+	err := unmarshal((*plain)(c))
+	if err != nil {
+		return err
+	}
+	if c.Level == "" {
+		c.Level = "info"
+	}
+	if c.Format == "" {
+		c.Format = "text"
+	}
+	// Validate log level
+	_, err = logrus.ParseLevel(c.Level)
+	if err != nil {
+		return errors.NewConfigError("log.level", err)
+	}
+	return nil
+}
+
+type Storage struct {
+	Endpoint        string `yaml:"endpoint"`
+	AccessKeyID     string `yaml:"access_key_id"`
+	SecretAccessKey string `yaml:"secret_access_key"`
+	UseSSL          bool   `yaml:"use_ssl"`
+	BucketName      string `yaml:"bucket_name"`
+}
+
+func (c *Storage) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	type plain Storage
+	err := unmarshal((*plain)(c))
+	if err != nil {
+		return err
+	}
+	if c.Endpoint == "" {
+		return errors.NewConfigError("storage.endpoint", errors.ErrInvalidInput)
+	}
+	if c.AccessKeyID == "" {
+		return errors.NewConfigError("storage.access_key_id", errors.ErrInvalidInput)
+	}
+	if c.SecretAccessKey == "" {
+		return errors.NewConfigError("storage.secret_access_key", errors.ErrInvalidInput)
+	}
+	if c.BucketName == "" {
+		return errors.NewConfigError("storage.bucket_name", errors.ErrInvalidInput)
+	}
+	return nil
+}
+
+type CloudComputing struct {
+	Provider    string `yaml:"provider"`
+	Region      string `yaml:"region"`
+	MaxCapacity int    `yaml:"max_capacity"`
+}
+
+func (c *CloudComputing) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	type plain CloudComputing
+	err := unmarshal((*plain)(c))
+	if err != nil {
+		return err
+	}
+	if c.Provider == "" {
+		return errors.NewConfigError("cloud_computing.provider", errors.ErrInvalidInput)
+	}
+	if c.Region == "" {
+		return errors.NewConfigError("cloud_computing.region", errors.ErrInvalidInput)
+	}
+	if c.MaxCapacity <= 0 {
+		return errors.NewConfigError("cloud_computing.max_capacity", errors.ErrInvalidInput)
+	}
+	return nil
+}
+
+type SocialNetworks struct {
+	Networks []SocialNetwork `yaml:"networks"`
+}
+
+type SocialNetwork struct {
+	Name     string `yaml:"name"`
+	APIKey   string `yaml:"api_key"`
+	APIToken string `yaml:"api_token"`
+}
+
+func (c *SocialNetworks) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	type plain SocialNetworks
+	err := unmarshal((*plain)(c))
+	if err != nil {
+		return err
+	}
+	if len(c.Networks) == 0 {
+		return errors.NewConfigError("social_networks", errors.ErrInvalidInput)
+	}
+	for i, network := range c.Networks {
+		section := fmt.Sprintf("social_networks.networks[%d]", i)
+		if network.Name == "" {
+			return errors.NewConfigError(section+".name", errors.ErrInvalidInput)
+		}
+		if network.APIKey == "" {
+			return errors.NewConfigError(section+".api_key", errors.ErrInvalidInput)
+		}
+		if network.APIToken == "" {
+			return errors.NewConfigError(section+".api_token", errors.ErrInvalidInput)
+		}
+	}
+	return nil
+}
+
+func ReadConfig(configPath string) (*Config, error) {
+	data, err := os.ReadFile(configPath)
+	if err != nil {
+		return nil, errors.NewConfigError("read", err)
+	}
+
+	config := &Config{}
+	err = yaml.Unmarshal(data, config)
+	if err != nil {
+		return nil, errors.NewConfigError("parse", err)
+	}
+
+	return config, nil
+}

+ 30 - 0
pkg/database/sqlite.go

@@ -0,0 +1,30 @@
+package database
+
+import (
+	"database/sql"
+
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/errors"
+	_ "github.com/mattn/go-sqlite3"
+)
+
+type SQLiteConfig struct {
+	Path string
+}
+
+func NewSQLiteDB(config SQLiteConfig) (*sql.DB, error) {
+	db, err := sql.Open("sqlite3", config.Path)
+	if err != nil {
+		return nil, errors.NewDatabaseError("open", err)
+	}
+
+	// Test connection
+	if err := db.Ping(); err != nil {
+		return nil, errors.NewDatabaseError("ping", err)
+	}
+
+	// Set connection pool settings
+	db.SetMaxOpenConns(1) // SQLite supports only one writer at a time
+	db.SetMaxIdleConns(1)
+
+	return db, nil
+}

+ 117 - 0
pkg/errors/errors.go

@@ -0,0 +1,117 @@
+package errors
+
+import (
+	stderrors "errors"
+	"fmt"
+)
+
+// Is reports whether any error in err's chain matches target.
+var Is = stderrors.Is
+
+// As finds the first error in err's chain that matches target, and if so, sets
+// target to that error value and returns true.
+var As = stderrors.As
+
+// Unwrap returns the result of calling the Unwrap method on err, if err's
+// type contains an Unwrap method returning error.
+// Otherwise, Unwrap returns nil.
+var Unwrap = stderrors.Unwrap
+
+// Standard errors that can be used across packages
+var (
+	ErrNotFound          = stderrors.New("resource not found")
+	ErrInvalidInput      = stderrors.New("invalid input")
+	ErrUnauthorized      = stderrors.New("unauthorized")
+	ErrInternalServer    = stderrors.New("internal server error")
+	ErrDatabaseOperation = stderrors.New("database operation failed")
+)
+
+// DatabaseError represents a database-related error
+type DatabaseError struct {
+	Operation string
+	Err       error
+}
+
+func (e *DatabaseError) Error() string {
+	return fmt.Sprintf("database error during %s: %v", e.Operation, e.Err)
+}
+
+func (e *DatabaseError) Unwrap() error {
+	return e.Err
+}
+
+// NewDatabaseError creates a new DatabaseError
+func NewDatabaseError(operation string, err error) *DatabaseError {
+	return &DatabaseError{
+		Operation: operation,
+		Err:       err,
+	}
+}
+
+// AuthError represents an authentication-related error
+type AuthError struct {
+	Action string
+	Err    error
+}
+
+func (e *AuthError) Error() string {
+	return fmt.Sprintf("authentication error during %s: %v", e.Action, e.Err)
+}
+
+func (e *AuthError) Unwrap() error {
+	return e.Err
+}
+
+// NewAuthError creates a new AuthError
+func NewAuthError(action string, err error) *AuthError {
+	return &AuthError{
+		Action: action,
+		Err:    err,
+	}
+}
+
+// StorageError represents a storage-related error
+type StorageError struct {
+	Operation string
+	Bucket    string
+	Err       error
+}
+
+func (e *StorageError) Error() string {
+	return fmt.Sprintf("storage error during %s on bucket %s: %v", e.Operation, e.Bucket, e.Err)
+}
+
+func (e *StorageError) Unwrap() error {
+	return e.Err
+}
+
+// NewStorageError creates a new StorageError
+func NewStorageError(operation, bucket string, err error) *StorageError {
+	return &StorageError{
+		Operation: operation,
+		Bucket:    bucket,
+		Err:       err,
+	}
+}
+
+// ConfigError represents a configuration-related error
+type ConfigError struct {
+	Section string
+	Err     error
+}
+
+func (e *ConfigError) Error() string {
+	return fmt.Sprintf("configuration error in %s: %v", e.Section, e.Err)
+}
+
+func (e *ConfigError) Unwrap() error {
+	return e.Err
+}
+
+// NewConfigError creates a new ConfigError
+func NewConfigError(section string, err error) *ConfigError {
+	return &ConfigError{
+		Section: section,
+		Err:     err,
+	}
+}

+ 51 - 0
pkg/logger/logrus.go

@@ -0,0 +1,51 @@
+package logger
+
+import (
+	"os"
+
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/errors"
+	"github.com/sirupsen/logrus"
+)
+
+type LogConfig struct {
+	Level     string
+	Formatter string // "json" or "text"
+}
+
+func NewLogger(config LogConfig) (*logrus.Logger, error) {
+	logger := logrus.New()
+
+	// Set output
+	logger.SetOutput(os.Stdout)
+
+	// Validate and set formatter
+	switch config.Formatter {
+	case "json":
+		logger.SetFormatter(&logrus.JSONFormatter{})
+	case "text":
+		logger.SetFormatter(&logrus.TextFormatter{
+			FullTimestamp: true,
+		})
+	case "":
+		// Default to text formatter
+		logger.SetFormatter(&logrus.TextFormatter{
+			FullTimestamp: true,
+		})
+	default:
+		return nil, errors.NewConfigError("logger.formatter", errors.ErrInvalidInput)
+	}
+
+	// Validate and set level
+	if config.Level == "" {
+		// Default to info level
+		logger.SetLevel(logrus.InfoLevel)
+	} else {
+		level, err := logrus.ParseLevel(config.Level)
+		if err != nil {
+			return nil, errors.NewConfigError("logger.level", err)
+		}
+		logger.SetLevel(level)
+	}
+
+	return logger, nil
+}

+ 61 - 0
pkg/server/gin.go

@@ -0,0 +1,61 @@
+package server
+
+import (
+	"time"
+
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/errors"
+	"github.com/gin-gonic/gin"
+	"github.com/sirupsen/logrus"
+)
+
+type ServerConfig struct {
+	AllowedOrigins []string
+	RequestTimeout time.Duration
+}
+
+type Server struct {
+	router *gin.Engine
+	logger *logrus.Logger
+	config ServerConfig
+}
+
+func NewGinServer(logger *logrus.Logger, config ServerConfig) (*Server, error) {
+	if logger == nil {
+		return nil, errors.NewConfigError("server", errors.ErrInvalidInput)
+	}
+
+	// Set Gin mode to release
+	gin.SetMode(gin.ReleaseMode)
+
+	// Initialize router
+	router := gin.New()
+
+	server := &Server{
+		router: router,
+		logger: logger,
+		config: config,
+	}
+
+	// Add middleware in specific order
+	router.Use(server.recoveryMiddleware())
+	router.Use(server.requestIDMiddleware())
+	router.Use(server.loggerMiddleware())
+	router.Use(server.corsMiddleware())
+	router.Use(server.errorHandlerMiddleware())
+
+	return server, nil
+}
+
+func (s *Server) Router() *gin.Engine {
+	return s.router
+}
+
+// AddHealthCheck adds a basic health check endpoint
+func (s *Server) AddHealthCheck() {
+	s.router.GET("/health", func(c *gin.Context) {
+		c.JSON(200, gin.H{
+			"status": "ok",
+			"time":   time.Now().UTC(),
+		})
+	})
+}

+ 139 - 0
pkg/server/middleware.go

@@ -0,0 +1,139 @@
+package server
+
+import (
+	"net/http"
+	"runtime/debug"
+	"time"
+
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/errors"
+	"github.com/gin-gonic/gin"
+	"github.com/google/uuid"
+)
+
+func (s *Server) corsMiddleware() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		origin := c.Request.Header.Get("Origin")
+		if len(s.config.AllowedOrigins) == 0 {
+			s.logger.Warn("no allowed origins configured")
+		}
+
+		allowed := false
+		for _, allowedOrigin := range s.config.AllowedOrigins {
+			if origin == allowedOrigin {
+				c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
+				allowed = true
+				break
+			}
+		}
+
+		if !allowed && origin != "" {
+			s.logger.WithField("origin", origin).Warn("blocked request from unauthorized origin")
+		}
+
+		c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
+		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Request-ID")
+		c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
+
+		if c.Request.Method == "OPTIONS" {
+			c.AbortWithStatus(http.StatusNoContent)
+			return
+		}
+
+		c.Next()
+	}
+}
+
+func (s *Server) requestIDMiddleware() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		requestID := c.GetHeader("X-Request-ID")
+		if requestID == "" {
+			requestID = uuid.New().String()
+		}
+
+		c.Set("RequestID", requestID)
+		c.Header("X-Request-ID", requestID)
+
+		c.Next()
+	}
+}
+
+func (s *Server) loggerMiddleware() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		start := time.Now()
+		path := c.Request.URL.Path
+		query := c.Request.URL.RawQuery
+
+		c.Next()
+
+		s.logger.WithFields(map[string]interface{}{
+			"status_code": c.Writer.Status(),
+			"method":      c.Request.Method,
+			"path":        path,
+			"query":       query,
+			"ip":          c.ClientIP(),
+			"latency":     time.Since(start),
+			"request_id":  c.GetString("RequestID"),
+			"user_agent":  c.Request.UserAgent(),
+		}).Info("request completed")
+	}
+}
+
+func (s *Server) recoveryMiddleware() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		defer func() {
+			if err := recover(); err != nil {
+				requestID := c.GetString("RequestID")
+				s.logger.WithFields(map[string]interface{}{
+					"error":      err,
+					"request_id": requestID,
+					"stack":      string(debug.Stack()),
+				}).Error("panic recovered")
+
+				c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
+					"error":      errors.ErrInternalServer.Error(),
+					"request_id": requestID,
+				})
+			}
+		}()
+		c.Next()
+	}
+}
+
+func (s *Server) errorHandlerMiddleware() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		c.Next()
+
+		if len(c.Errors) > 0 {
+			err := c.Errors.Last().Err
+			requestID := c.GetString("RequestID")
+
+			// Log the error with appropriate level and details
+			s.logger.WithFields(map[string]interface{}{
+				"error":      err.Error(),
+				"request_id": requestID,
+			}).Error("request error")
+
+			// Determine status code based on error type
+			var statusCode int
+			var response gin.H
+
+			switch {
+			case errors.Is(err, errors.ErrNotFound):
+				statusCode = http.StatusNotFound
+			case errors.Is(err, errors.ErrUnauthorized):
+				statusCode = http.StatusUnauthorized
+			case errors.Is(err, errors.ErrInvalidInput):
+				statusCode = http.StatusBadRequest
+			default:
+				statusCode = http.StatusInternalServerError
+			}
+
+			response = gin.H{
+				"error":      err.Error(),
+				"request_id": requestID,
+			}
+
+			c.JSON(statusCode, response)
+		}
+	}
+}

+ 76 - 0
pkg/storage/minio.go

@@ -0,0 +1,76 @@
+package storage
+
+import (
+	"context"
+	"io"
+
+	"git.linuxforward.com/byom/byom-golang-lib/pkg/errors"
+	"github.com/minio/minio-go/v7"
+	"github.com/minio/minio-go/v7/pkg/credentials"
+)
+
+type MinioConfig struct {
+	Endpoint        string
+	AccessKeyID     string
+	SecretAccessKey string
+	UseSSL          bool
+	BucketName      string
+}
+
+type MinioClient struct {
+	client     *minio.Client
+	bucketName string
+}
+
+func NewMinioClient(config MinioConfig) (*MinioClient, error) {
+	if config.Endpoint == "" {
+		return nil, errors.NewStorageError("initialize", "", errors.ErrInvalidInput)
+	}
+
+	client, err := minio.New(config.Endpoint, &minio.Options{
+		Creds:  credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""),
+		Secure: config.UseSSL,
+	})
+	if err != nil {
+		return nil, errors.NewStorageError("create_client", "", err)
+	}
+
+	// Ensure bucket exists
+	ctx := context.Background()
+	exists, err := client.BucketExists(ctx, config.BucketName)
+	if err != nil {
+		return nil, errors.NewStorageError("check_bucket", config.BucketName, err)
+	}
+
+	if !exists {
+		err = client.MakeBucket(ctx, config.BucketName, minio.MakeBucketOptions{})
+		if err != nil {
+			return nil, errors.NewStorageError("create_bucket", config.BucketName, err)
+		}
+	}
+
+	return &MinioClient{
+		client:     client,
+		bucketName: config.BucketName,
+	}, nil
+}
+
+// Client returns the underlying Minio client
+func (m *MinioClient) Client() *minio.Client {
+	return m.client
+}
+
+// BucketName returns the name of the bucket
+func (m *MinioClient) BucketName() string {
+	return m.bucketName
+}
+
+// PutObject is a helper method to upload an object to the bucket
+func (m *MinioClient) PutObject(ctx context.Context, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error) {
+	return m.client.PutObject(ctx, m.bucketName, objectName, reader, objectSize, opts)
+}
+
+// GetObject is a helper method to download an object from the bucket
+func (m *MinioClient) GetObject(ctx context.Context, objectName string, opts minio.GetObjectOptions) (*minio.Object, error) {
+	return m.client.GetObject(ctx, m.bucketName, objectName, opts)
+}