Skip to content

Commit

Permalink
refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
aasumitro committed Oct 6, 2024
1 parent b1a86d4 commit 96c07b2
Show file tree
Hide file tree
Showing 224 changed files with 10,124 additions and 2,033 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ APP_NAME="POSBE"
APP_PORT=":8000"
APP_DEBUG=true
APP_VERSION="0.0.1-dev"
API_LIMITER="500-M"

POSTGRES_DSN_URL="postgresql://postgres:@127.0.0.1:5432/posbe?sslmode=disable"
REDIS_DSN_URL="localhost:6379"
Expand Down
29 changes: 29 additions & 0 deletions .idea/dataSources.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Config struct {
AppPort string `mapstructure:"APP_PORT"`
AppDebug bool `mapstructure:"APP_DEBUG"`
AppVersion string `mapstructure:"APP_VERSION"`
APILimiter string `mapstructure:"API_LIMITER"`

PostgresDsnURL string `mapstructure:"POSTGRES_DSN_URL"`
RedisDsnURL string `mapstructure:"REDIS_DSN_URL"`
Expand Down
231 changes: 169 additions & 62 deletions config/engine.go
Original file line number Diff line number Diff line change
@@ -1,64 +1,188 @@
package config

import (
"crypto/rand"
"encoding/base64"
"fmt"
"log"
"slices"
"net/http"
"os"
"strings"
"sync/atomic"
"time"

sentrygin "github.com/getsentry/sentry-go/gin"
"github.com/gin-contrib/cors"
ginzap "github.com/gin-contrib/zap"
gincors "github.com/gin-contrib/cors"
"github.com/gin-contrib/requestid"
"github.com/gin-gonic/gin"
"github.com/ulule/limiter/v3"
"github.com/rs/zerolog"
global "github.com/rs/zerolog/log"
lm "github.com/ulule/limiter/v3"
lmgin "github.com/ulule/limiter/v3/drivers/middleware/gin"
lsredis "github.com/ulule/limiter/v3/drivers/store/redis"
"go.uber.org/zap"
)

var allowOrigins = []string{
"http://localhost:3000",
"http://localhost:5173",
var (
reqIDPrefix string
reqIDCount uint64

corsMaxAge = 12 * time.Hour
corsAllowedOrigins = []string{
"http://localhost:5173",
"http://localhost:8000",
}
corsAllowedHeaders = []string{
"Content-Type",
"Content-Length",
"Accept-Encoding",
"Authorization",
"Cache-Control",
"Origin",
"Cookie",
"X-Requested-With",
"X-Request-ID",
}
corsAllowedMethods = []string{
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
}
)

func init() {
hostname, err := os.Hostname()
if hostname == "" || err != nil {
hostname = "localhost"
}
var buf [12]byte
var b64 string
for len(b64) < 10 {
if _, err = rand.Read(buf[:]); err != nil {
continue
}
b64 = base64.StdEncoding.EncodeToString(buf[:])
b64 = strings.NewReplacer("+", "", "/", "").Replace(b64)
}
reqIDPrefix = fmt.Sprintf("%s-%s", hostname, b64[0:10])
}

func logger() gin.HandlerFunc {
return func(ctx *gin.Context) {
var zll zerolog.Level
start := time.Now()
path := ctx.Request.URL.Path
raw := ctx.Request.URL.RawQuery
ctx.Next()
if raw != "" {
path = path + "?" + raw
}
statusCode := ctx.Writer.Status()
switch {
case statusCode >= http.StatusInternalServerError:
zll = zerolog.ErrorLevel
case statusCode >= http.StatusBadRequest:
zll = zerolog.WarnLevel
default:
zll = zerolog.InfoLevel
}
mtd := ctx.Request.Method
rid := requestid.Get(ctx)

// print log
go global.Logger.WithLevel(zll).
Str("client_ip", ctx.ClientIP()).
Str("request_id", rid).
Str("protocol", ctx.Request.Proto).
Str("agent", ctx.Request.UserAgent()).
Str("method", mtd).
Int("status_code", statusCode).
Int("body_size", ctx.Writer.Size()).
Str("path", path).
Str("latency", time.Since(start).String()).
Str("error", ctx.Errors.ByType(gin.ErrorTypePrivate).String()).
Msg(fmt.Sprintf("HTTPAccessLog | %7s | %3d | %s | rid: %s, uid: %s ",
mtd, statusCode, path, rid, func() string {
uid, ok := ctx.Get("user_id")
if !ok {
return "-"
}
return fmt.Sprintf("%0d", int(uid.(float64)))
}()),
)

// store log - TODO: impl like fiber hook after endpoint called
go func() {
uid, ok := ctx.Get("user_id")
if !ok {
log.Println("failed to get user_id")
ctx.Next()
return
}
rn, ok := ctx.Get("role_name")
if !ok {
log.Println("failed to get role name")
ctx.Next()
return
}
if _, err := PostgresPool.ExecContext(ctx,
"INSERT INTO activity_logs (user_id, role, description, created_at) values ($1, $2, $3, $4)",
int(uid.(float64)), rn.(string), fmt.Sprintf(
"HTTPAccessLog | %7s | %3d | %s | rid: %s, uid: %d ",
mtd, statusCode, path, rid, int(uid.(float64)),
), time.Now().Unix(),
); err != nil {
log.Println("failed to store activity log", err.Error())
ctx.Next()
return
}
ctx.Next()
}()
}
}

var allowHeaders = []string{
"Content-Type",
"Content-Length",
"Accept-Encoding",
"Authorization",
"Cache-Control",
"Origin",
"Cookie",
func cors() gin.HandlerFunc {
return gincors.New(gincors.Config{
AllowOrigins: corsAllowedOrigins,
AllowMethods: corsAllowedMethods,
AllowHeaders: corsAllowedHeaders,
AllowCredentials: true,
MaxAge: corsMaxAge,
})
}

func limiter(
serverLimiter,
serverName string,
) gin.HandlerFunc {
rate, err := lm.NewRateFromFormatted(serverLimiter)
if err != nil {
log.Fatalf("RATELIMITER_ERROR: %s\n", err.Error())
}
store, err := lsredis.NewStoreWithOptions(RedisPool,
lm.StoreOptions{Prefix: serverName})
if err != nil {
log.Fatalf("RATELIMITER_ERROR: %s\n", err.Error())
}
return lmgin.NewMiddleware(lm.New(store, rate))
}

func ServerEngine() Option {
return func(cfg *Config) {
engineOnce.Do(func() {
log.Printf("Trying to init engine (GIN %s) . . . .",
gin.Version)
// set gin mode
gin.SetMode(func() string {
if cfg.AppDebug {
return gin.DebugMode
}
return gin.ReleaseMode
}())
// set global variables
GinEngine = gin.Default()
// set cors middleware
tw := 12
maxAge := time.Hour * time.Duration(tw)
GinEngine.Use(cors.New(cors.Config{
AllowOrigins: allowOrigins,
AllowMethods: []string{"GET, POST, PATCH, DELETE"},
AllowHeaders: allowHeaders,
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool {
return slices.Contains(allowOrigins, origin)
},
MaxAge: maxAge,
}))
log.Printf("Init router engine [GIN Framework %s]\n", gin.Version)
gin.SetMode(gin.ReleaseMode)
if cfg.AppDebug {
gin.SetMode(gin.DebugMode)
}
// setup basic middleware
engine := gin.New()
engine.ForwardedByClientIP = true
engine.Use(requestid.New(requestid.WithGenerator(func() string {
myID := atomic.AddUint64(&reqIDCount, 1)
return fmt.Sprintf("%s-%06d", reqIDPrefix, myID)
})), logger(), gin.Recovery(), cors())
if !cfg.AppDebug {
// setup sentry middleware
GinEngine.Use(sentrygin.New(sentrygin.Options{Repanic: true}))
Expand All @@ -69,28 +193,11 @@ func ServerEngine() Option {
}
ctx.Next()
})
// setup rate limiter
rate, err := limiter.NewRateFromFormatted("100-M")
if err != nil {
log.Fatalf("RATELIMITER_ERROR: %s", err.Error())
}
store, err := lsredis.NewStoreWithOptions(RedisPool,
limiter.StoreOptions{Prefix: fmt.Sprintf(
"%s{%s}", cfg.AppName, cfg.AppVersion)})
if err != nil {
log.Fatalf("RATELIMITER_ERROR: %s\n", err.Error())
}
GinEngine.ForwardedByClientIP = true
GinEngine.Use(lmgin.NewMiddleware(limiter.New(store, rate)))
// setup logger
logger, err := zap.NewProduction()
if err != nil {
log.Fatalf("ZAP_LOGGER_ERROR: %s\n", err.Error())
}
defer func() { _ = logger.Sync() }()
GinEngine.Use(ginzap.Ginzap(logger, time.RFC3339, true))
GinEngine.Use(ginzap.RecoveryWithZap(logger, true))
// setup rate limit middleware
serverInitial := fmt.Sprintf("%s %s", cfg.AppName, cfg.AppVersion)
engine.Use(limiter(cfg.APILimiter, serverInitial))
}
GinEngine = engine
})
}
}
9 changes: 9 additions & 0 deletions config/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ package config
import (
"database/sql"
"log"
"time"

// postgresql
_ "github.com/lib/pq"
)

const (
maxOpenConn = 50
maxIdleConn = 10
)

func PostgresConnection() Option {
return func(cfg *Config) {
const dbDriverName = "postgres"
Expand All @@ -19,6 +25,9 @@ func PostgresConnection() Option {
log.Fatalf("DATABASE_ERROR: %s\n",
err.Error())
}
conn.SetMaxOpenConns(maxOpenConn)
conn.SetMaxIdleConns(maxIdleConn)
conn.SetConnMaxLifetime(time.Hour)
PostgresPool = conn
if err := PostgresPool.Ping(); err != nil {
log.Fatalf("DATABASE_ERROR: %s\n",
Expand Down
5 changes: 3 additions & 2 deletions db/migrations/20221128121232_create_table_users.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ CREATE TABLE IF NOT EXISTS users (
name VARCHAR(255),
username VARCHAR(255) NOT NULL UNIQUE,
email VARCHAR(255) UNIQUE,
phone BIGINT UNIQUE,
phone VARCHAR(255) UNIQUE,
password VARCHAR(255) NOT NULL,
created_at BIGINT NOT NULL DEFAULT extract(epoch from now()),
updated_at BIGINT
updated_at BIGINT,
deleted_at BIGINT
);
4 changes: 2 additions & 2 deletions db/migrations/20221128121244_create_table_roles.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ CREATE TABLE IF NOT EXISTS roles (
description VARCHAR(255)
);

ALTER TABLE users ADD COLUMN role_Id BIGINT;
ALTER TABLE users ADD COLUMN role_id BIGINT;

ALTER TABLE users ADD CONSTRAINT fk_users_roles FOREIGN KEY (role_Id) REFERENCES roles(id);
ALTER TABLE users ADD CONSTRAINT fk_users_roles FOREIGN KEY (role_id) REFERENCES roles(id);
5 changes: 4 additions & 1 deletion db/migrations/20221128132111_add_account_data.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ VALUES
-- password is secret
INSERT INTO users(role_id, name, username, email, phone, password)
VALUES
(1, 'A. A. Sumitro', 'aasumitro', 'hello@aasumitro.id', 82271115593, '2ad1a22d5b3c9396d16243d2fe7f067976363715e322203a456278bb80b0b4a4.7ab4dcccfcd9d36efc68f1626d2fb80804a6508f9c3a7b44f430ba082b6870d2')
(1, 'Test Admin', 'admin', 'admin@posbe.test', '+6282271112233', '2ad1a22d5b3c9396d16243d2fe7f067976363715e322203a456278bb80b0b4a4.7ab4dcccfcd9d36efc68f1626d2fb80804a6508f9c3a7b44f430ba082b6870d2'),
(2, 'Test Cashier', 'cashier', 'cashier@posbe.test', '+6282272223344', '2ad1a22d5b3c9396d16243d2fe7f067976363715e322203a456278bb80b0b4a4.7ab4dcccfcd9d36efc68f1626d2fb80804a6508f9c3a7b44f430ba082b6870d2'),
(3, 'Test Waiter', 'waiter', 'waiter@posbe.test', '+6282273334455', '2ad1a22d5b3c9396d16243d2fe7f067976363715e322203a456278bb80b0b4a4.7ab4dcccfcd9d36efc68f1626d2fb80804a6508f9c3a7b44f430ba082b6870d2'),
(3, 'ToBe Removed', 'tbr', 'tbr@posbe.test', '+6282273334466', '2ad1a22d5b3c9396d16243d2fe7f067976363715e322203a456278bb80b0b4a4.7ab4dcccfcd9d36efc68f1626d2fb80804a6508f9c3a7b44f430ba082b6870d2');

5 changes: 4 additions & 1 deletion db/migrations/20221206065324_add_common_catalog_data.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ INSERT INTO categories (name)
VALUES ('foods'), ('beverages');

INSERT INTO subcategories (category_id, name)
VALUES (1, 'meat'), (1, 'seafood'), (2, 'coffee'), (2, 'juice');
VALUES (1, 'meat'),
(1, 'seafood'),
(2, 'coffee'),
(2, 'juice');

INSERT INTO units (magnitude, name, symbol)
VALUES ('mass', 'gram', 'g'),
Expand Down
4 changes: 3 additions & 1 deletion db/migrations/20221207075900_create_table_products.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ CREATE TABLE IF NOT EXISTS products (
gallery TEXT,
name VARCHAR(255) NOT NULL,
description VARCHAR(255),
price FLOAT NOT NULL,
-- we don't need it (price), let's put this item into variant
-- by default when user create new item we will add new base variant
-- price NUMERIC NOT NULL,
created_at BIGINT NOT NULL DEFAULT extract(epoch from now()),
updated_at BIGINT
);
Expand Down
Loading

0 comments on commit 96c07b2

Please sign in to comment.