Skip to content

Commit 96c07b2

Browse files
committed
refactoring
1 parent b1a86d4 commit 96c07b2

File tree

224 files changed

+10124
-2033
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

224 files changed

+10124
-2033
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ APP_NAME="POSBE"
22
APP_PORT=":8000"
33
APP_DEBUG=true
44
APP_VERSION="0.0.1-dev"
5+
API_LIMITER="500-M"
56

67
POSTGRES_DSN_URL="postgresql://postgres:@127.0.0.1:5432/posbe?sslmode=disable"
78
REDIS_DSN_URL="localhost:6379"

.idea/dataSources.xml

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type Config struct {
2929
AppPort string `mapstructure:"APP_PORT"`
3030
AppDebug bool `mapstructure:"APP_DEBUG"`
3131
AppVersion string `mapstructure:"APP_VERSION"`
32+
APILimiter string `mapstructure:"API_LIMITER"`
3233

3334
PostgresDsnURL string `mapstructure:"POSTGRES_DSN_URL"`
3435
RedisDsnURL string `mapstructure:"REDIS_DSN_URL"`

config/engine.go

Lines changed: 169 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,188 @@
11
package config
22

33
import (
4+
"crypto/rand"
5+
"encoding/base64"
46
"fmt"
57
"log"
6-
"slices"
8+
"net/http"
9+
"os"
10+
"strings"
11+
"sync/atomic"
712
"time"
813

914
sentrygin "github.com/getsentry/sentry-go/gin"
10-
"github.com/gin-contrib/cors"
11-
ginzap "github.com/gin-contrib/zap"
15+
gincors "github.com/gin-contrib/cors"
16+
"github.com/gin-contrib/requestid"
1217
"github.com/gin-gonic/gin"
13-
"github.com/ulule/limiter/v3"
18+
"github.com/rs/zerolog"
19+
global "github.com/rs/zerolog/log"
20+
lm "github.com/ulule/limiter/v3"
1421
lmgin "github.com/ulule/limiter/v3/drivers/middleware/gin"
1522
lsredis "github.com/ulule/limiter/v3/drivers/store/redis"
16-
"go.uber.org/zap"
1723
)
1824

19-
var allowOrigins = []string{
20-
"http://localhost:3000",
21-
"http://localhost:5173",
25+
var (
26+
reqIDPrefix string
27+
reqIDCount uint64
28+
29+
corsMaxAge = 12 * time.Hour
30+
corsAllowedOrigins = []string{
31+
"http://localhost:5173",
32+
"http://localhost:8000",
33+
}
34+
corsAllowedHeaders = []string{
35+
"Content-Type",
36+
"Content-Length",
37+
"Accept-Encoding",
38+
"Authorization",
39+
"Cache-Control",
40+
"Origin",
41+
"Cookie",
42+
"X-Requested-With",
43+
"X-Request-ID",
44+
}
45+
corsAllowedMethods = []string{
46+
http.MethodGet,
47+
http.MethodPost,
48+
http.MethodPut,
49+
http.MethodPatch,
50+
http.MethodDelete,
51+
}
52+
)
53+
54+
func init() {
55+
hostname, err := os.Hostname()
56+
if hostname == "" || err != nil {
57+
hostname = "localhost"
58+
}
59+
var buf [12]byte
60+
var b64 string
61+
for len(b64) < 10 {
62+
if _, err = rand.Read(buf[:]); err != nil {
63+
continue
64+
}
65+
b64 = base64.StdEncoding.EncodeToString(buf[:])
66+
b64 = strings.NewReplacer("+", "", "/", "").Replace(b64)
67+
}
68+
reqIDPrefix = fmt.Sprintf("%s-%s", hostname, b64[0:10])
69+
}
70+
71+
func logger() gin.HandlerFunc {
72+
return func(ctx *gin.Context) {
73+
var zll zerolog.Level
74+
start := time.Now()
75+
path := ctx.Request.URL.Path
76+
raw := ctx.Request.URL.RawQuery
77+
ctx.Next()
78+
if raw != "" {
79+
path = path + "?" + raw
80+
}
81+
statusCode := ctx.Writer.Status()
82+
switch {
83+
case statusCode >= http.StatusInternalServerError:
84+
zll = zerolog.ErrorLevel
85+
case statusCode >= http.StatusBadRequest:
86+
zll = zerolog.WarnLevel
87+
default:
88+
zll = zerolog.InfoLevel
89+
}
90+
mtd := ctx.Request.Method
91+
rid := requestid.Get(ctx)
92+
93+
// print log
94+
go global.Logger.WithLevel(zll).
95+
Str("client_ip", ctx.ClientIP()).
96+
Str("request_id", rid).
97+
Str("protocol", ctx.Request.Proto).
98+
Str("agent", ctx.Request.UserAgent()).
99+
Str("method", mtd).
100+
Int("status_code", statusCode).
101+
Int("body_size", ctx.Writer.Size()).
102+
Str("path", path).
103+
Str("latency", time.Since(start).String()).
104+
Str("error", ctx.Errors.ByType(gin.ErrorTypePrivate).String()).
105+
Msg(fmt.Sprintf("HTTPAccessLog | %7s | %3d | %s | rid: %s, uid: %s ",
106+
mtd, statusCode, path, rid, func() string {
107+
uid, ok := ctx.Get("user_id")
108+
if !ok {
109+
return "-"
110+
}
111+
return fmt.Sprintf("%0d", int(uid.(float64)))
112+
}()),
113+
)
114+
115+
// store log - TODO: impl like fiber hook after endpoint called
116+
go func() {
117+
uid, ok := ctx.Get("user_id")
118+
if !ok {
119+
log.Println("failed to get user_id")
120+
ctx.Next()
121+
return
122+
}
123+
rn, ok := ctx.Get("role_name")
124+
if !ok {
125+
log.Println("failed to get role name")
126+
ctx.Next()
127+
return
128+
}
129+
if _, err := PostgresPool.ExecContext(ctx,
130+
"INSERT INTO activity_logs (user_id, role, description, created_at) values ($1, $2, $3, $4)",
131+
int(uid.(float64)), rn.(string), fmt.Sprintf(
132+
"HTTPAccessLog | %7s | %3d | %s | rid: %s, uid: %d ",
133+
mtd, statusCode, path, rid, int(uid.(float64)),
134+
), time.Now().Unix(),
135+
); err != nil {
136+
log.Println("failed to store activity log", err.Error())
137+
ctx.Next()
138+
return
139+
}
140+
ctx.Next()
141+
}()
142+
}
22143
}
23144

24-
var allowHeaders = []string{
25-
"Content-Type",
26-
"Content-Length",
27-
"Accept-Encoding",
28-
"Authorization",
29-
"Cache-Control",
30-
"Origin",
31-
"Cookie",
145+
func cors() gin.HandlerFunc {
146+
return gincors.New(gincors.Config{
147+
AllowOrigins: corsAllowedOrigins,
148+
AllowMethods: corsAllowedMethods,
149+
AllowHeaders: corsAllowedHeaders,
150+
AllowCredentials: true,
151+
MaxAge: corsMaxAge,
152+
})
153+
}
154+
155+
func limiter(
156+
serverLimiter,
157+
serverName string,
158+
) gin.HandlerFunc {
159+
rate, err := lm.NewRateFromFormatted(serverLimiter)
160+
if err != nil {
161+
log.Fatalf("RATELIMITER_ERROR: %s\n", err.Error())
162+
}
163+
store, err := lsredis.NewStoreWithOptions(RedisPool,
164+
lm.StoreOptions{Prefix: serverName})
165+
if err != nil {
166+
log.Fatalf("RATELIMITER_ERROR: %s\n", err.Error())
167+
}
168+
return lmgin.NewMiddleware(lm.New(store, rate))
32169
}
33170

34171
func ServerEngine() Option {
35172
return func(cfg *Config) {
36173
engineOnce.Do(func() {
37-
log.Printf("Trying to init engine (GIN %s) . . . .",
38-
gin.Version)
39-
// set gin mode
40-
gin.SetMode(func() string {
41-
if cfg.AppDebug {
42-
return gin.DebugMode
43-
}
44-
return gin.ReleaseMode
45-
}())
46-
// set global variables
47-
GinEngine = gin.Default()
48-
// set cors middleware
49-
tw := 12
50-
maxAge := time.Hour * time.Duration(tw)
51-
GinEngine.Use(cors.New(cors.Config{
52-
AllowOrigins: allowOrigins,
53-
AllowMethods: []string{"GET, POST, PATCH, DELETE"},
54-
AllowHeaders: allowHeaders,
55-
ExposeHeaders: []string{"Content-Length"},
56-
AllowCredentials: true,
57-
AllowOriginFunc: func(origin string) bool {
58-
return slices.Contains(allowOrigins, origin)
59-
},
60-
MaxAge: maxAge,
61-
}))
174+
log.Printf("Init router engine [GIN Framework %s]\n", gin.Version)
175+
gin.SetMode(gin.ReleaseMode)
176+
if cfg.AppDebug {
177+
gin.SetMode(gin.DebugMode)
178+
}
179+
// setup basic middleware
180+
engine := gin.New()
181+
engine.ForwardedByClientIP = true
182+
engine.Use(requestid.New(requestid.WithGenerator(func() string {
183+
myID := atomic.AddUint64(&reqIDCount, 1)
184+
return fmt.Sprintf("%s-%06d", reqIDPrefix, myID)
185+
})), logger(), gin.Recovery(), cors())
62186
if !cfg.AppDebug {
63187
// setup sentry middleware
64188
GinEngine.Use(sentrygin.New(sentrygin.Options{Repanic: true}))
@@ -69,28 +193,11 @@ func ServerEngine() Option {
69193
}
70194
ctx.Next()
71195
})
72-
// setup rate limiter
73-
rate, err := limiter.NewRateFromFormatted("100-M")
74-
if err != nil {
75-
log.Fatalf("RATELIMITER_ERROR: %s", err.Error())
76-
}
77-
store, err := lsredis.NewStoreWithOptions(RedisPool,
78-
limiter.StoreOptions{Prefix: fmt.Sprintf(
79-
"%s{%s}", cfg.AppName, cfg.AppVersion)})
80-
if err != nil {
81-
log.Fatalf("RATELIMITER_ERROR: %s\n", err.Error())
82-
}
83-
GinEngine.ForwardedByClientIP = true
84-
GinEngine.Use(lmgin.NewMiddleware(limiter.New(store, rate)))
85-
// setup logger
86-
logger, err := zap.NewProduction()
87-
if err != nil {
88-
log.Fatalf("ZAP_LOGGER_ERROR: %s\n", err.Error())
89-
}
90-
defer func() { _ = logger.Sync() }()
91-
GinEngine.Use(ginzap.Ginzap(logger, time.RFC3339, true))
92-
GinEngine.Use(ginzap.RecoveryWithZap(logger, true))
196+
// setup rate limit middleware
197+
serverInitial := fmt.Sprintf("%s %s", cfg.AppName, cfg.AppVersion)
198+
engine.Use(limiter(cfg.APILimiter, serverInitial))
93199
}
200+
GinEngine = engine
94201
})
95202
}
96203
}

config/postgres.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@ package config
33
import (
44
"database/sql"
55
"log"
6+
"time"
67

78
// postgresql
89
_ "github.com/lib/pq"
910
)
1011

12+
const (
13+
maxOpenConn = 50
14+
maxIdleConn = 10
15+
)
16+
1117
func PostgresConnection() Option {
1218
return func(cfg *Config) {
1319
const dbDriverName = "postgres"
@@ -19,6 +25,9 @@ func PostgresConnection() Option {
1925
log.Fatalf("DATABASE_ERROR: %s\n",
2026
err.Error())
2127
}
28+
conn.SetMaxOpenConns(maxOpenConn)
29+
conn.SetMaxIdleConns(maxIdleConn)
30+
conn.SetConnMaxLifetime(time.Hour)
2231
PostgresPool = conn
2332
if err := PostgresPool.Ping(); err != nil {
2433
log.Fatalf("DATABASE_ERROR: %s\n",

db/migrations/20221128121232_create_table_users.up.sql

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ CREATE TABLE IF NOT EXISTS users (
33
name VARCHAR(255),
44
username VARCHAR(255) NOT NULL UNIQUE,
55
email VARCHAR(255) UNIQUE,
6-
phone BIGINT UNIQUE,
6+
phone VARCHAR(255) UNIQUE,
77
password VARCHAR(255) NOT NULL,
88
created_at BIGINT NOT NULL DEFAULT extract(epoch from now()),
9-
updated_at BIGINT
9+
updated_at BIGINT,
10+
deleted_at BIGINT
1011
);

db/migrations/20221128121244_create_table_roles.up.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ CREATE TABLE IF NOT EXISTS roles (
44
description VARCHAR(255)
55
);
66

7-
ALTER TABLE users ADD COLUMN role_Id BIGINT;
7+
ALTER TABLE users ADD COLUMN role_id BIGINT;
88

9-
ALTER TABLE users ADD CONSTRAINT fk_users_roles FOREIGN KEY (role_Id) REFERENCES roles(id);
9+
ALTER TABLE users ADD CONSTRAINT fk_users_roles FOREIGN KEY (role_id) REFERENCES roles(id);

db/migrations/20221128132111_add_account_data.up.sql

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ VALUES
77
-- password is secret
88
INSERT INTO users(role_id, name, username, email, phone, password)
99
VALUES
10-
(1, 'A. A. Sumitro', 'aasumitro', 'hello@aasumitro.id', 82271115593, '2ad1a22d5b3c9396d16243d2fe7f067976363715e322203a456278bb80b0b4a4.7ab4dcccfcd9d36efc68f1626d2fb80804a6508f9c3a7b44f430ba082b6870d2')
10+
(1, 'Test Admin', 'admin', 'admin@posbe.test', '+6282271112233', '2ad1a22d5b3c9396d16243d2fe7f067976363715e322203a456278bb80b0b4a4.7ab4dcccfcd9d36efc68f1626d2fb80804a6508f9c3a7b44f430ba082b6870d2'),
11+
(2, 'Test Cashier', 'cashier', 'cashier@posbe.test', '+6282272223344', '2ad1a22d5b3c9396d16243d2fe7f067976363715e322203a456278bb80b0b4a4.7ab4dcccfcd9d36efc68f1626d2fb80804a6508f9c3a7b44f430ba082b6870d2'),
12+
(3, 'Test Waiter', 'waiter', 'waiter@posbe.test', '+6282273334455', '2ad1a22d5b3c9396d16243d2fe7f067976363715e322203a456278bb80b0b4a4.7ab4dcccfcd9d36efc68f1626d2fb80804a6508f9c3a7b44f430ba082b6870d2'),
13+
(3, 'ToBe Removed', 'tbr', 'tbr@posbe.test', '+6282273334466', '2ad1a22d5b3c9396d16243d2fe7f067976363715e322203a456278bb80b0b4a4.7ab4dcccfcd9d36efc68f1626d2fb80804a6508f9c3a7b44f430ba082b6870d2');
1114

db/migrations/20221206065324_add_common_catalog_data.up.sql

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ INSERT INTO categories (name)
22
VALUES ('foods'), ('beverages');
33

44
INSERT INTO subcategories (category_id, name)
5-
VALUES (1, 'meat'), (1, 'seafood'), (2, 'coffee'), (2, 'juice');
5+
VALUES (1, 'meat'),
6+
(1, 'seafood'),
7+
(2, 'coffee'),
8+
(2, 'juice');
69

710
INSERT INTO units (magnitude, name, symbol)
811
VALUES ('mass', 'gram', 'g'),

db/migrations/20221207075900_create_table_products.up.sql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ CREATE TABLE IF NOT EXISTS products (
77
gallery TEXT,
88
name VARCHAR(255) NOT NULL,
99
description VARCHAR(255),
10-
price FLOAT NOT NULL,
10+
-- we don't need it (price), let's put this item into variant
11+
-- by default when user create new item we will add new base variant
12+
-- price NUMERIC NOT NULL,
1113
created_at BIGINT NOT NULL DEFAULT extract(epoch from now()),
1214
updated_at BIGINT
1315
);

0 commit comments

Comments
 (0)