2024-11-18 15:23:13 +00:00
|
|
|
// Copyright 2024 Patial Tech. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2024-11-10 09:22:33 +00:00
|
|
|
package db
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
|
|
|
"database/sql/driver"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"contrib.go.opencensus.io/integrations/ocsql"
|
|
|
|
"entgo.io/ent/dialect"
|
|
|
|
entsql "entgo.io/ent/dialect/sql"
|
|
|
|
pgx "github.com/jackc/pgx/v5/stdlib"
|
|
|
|
"gitserver.in/patialtech/rano/config"
|
|
|
|
"gitserver.in/patialtech/rano/db/ent"
|
2024-11-15 16:12:15 +00:00
|
|
|
"gitserver.in/patialtech/rano/util/logger"
|
2024-11-10 09:22:33 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type connector struct {
|
|
|
|
dsn string
|
|
|
|
}
|
|
|
|
|
|
|
|
// New *sql.DB instance
|
|
|
|
func New() *sql.DB {
|
|
|
|
databaseUrl := config.Read().DbURL
|
|
|
|
db := sql.OpenDB(connector{dsn: databaseUrl})
|
|
|
|
db.SetMaxOpenConns(50)
|
2024-11-17 16:58:29 +00:00
|
|
|
db.SetMaxIdleConns(5)
|
|
|
|
db.SetConnMaxLifetime(0) // Maximum amount of time a connection can be reused (0 means no limit)
|
2024-11-10 09:22:33 +00:00
|
|
|
return db
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c connector) Connect(context.Context) (driver.Conn, error) {
|
|
|
|
return c.Driver().Open(c.dsn)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (connector) Driver() driver.Driver {
|
|
|
|
return ocsql.Wrap(
|
|
|
|
pgx.GetDefaultDriver(),
|
|
|
|
ocsql.WithAllTraceOptions(),
|
|
|
|
ocsql.WithRowsClose(false),
|
|
|
|
ocsql.WithRowsNext(false),
|
|
|
|
ocsql.WithDisableErrSkip(true),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Client for pgx
|
|
|
|
//
|
|
|
|
// https://entgo.io/docs/sql-integration
|
|
|
|
func Client() *ent.Client {
|
|
|
|
// Create an ent.Driver from `db`.
|
|
|
|
drv := entsql.OpenDB(dialect.Postgres, New())
|
|
|
|
cl := ent.NewClient(ent.Driver(drv))
|
|
|
|
cl.Use(AuditHook)
|
|
|
|
return cl
|
|
|
|
}
|
|
|
|
|
2024-11-18 15:23:13 +00:00
|
|
|
type entity interface {
|
|
|
|
GetID() (int64, error)
|
|
|
|
}
|
|
|
|
|
2024-11-10 09:22:33 +00:00
|
|
|
// A AuditHook is an example for audit-log hook.
|
|
|
|
func AuditHook(next ent.Mutator) ent.Mutator {
|
2024-11-15 16:12:15 +00:00
|
|
|
return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (v ent.Value, err error) {
|
2024-11-10 09:22:33 +00:00
|
|
|
start := time.Now()
|
|
|
|
defer func() {
|
2024-11-18 15:23:13 +00:00
|
|
|
saveAudit(ctx, m.Type(), m.Op(), v, err, time.Since(start))
|
2024-11-10 09:22:33 +00:00
|
|
|
}()
|
2024-11-15 16:12:15 +00:00
|
|
|
|
|
|
|
v, err = next.Mutate(ctx, m)
|
|
|
|
return
|
2024-11-10 09:22:33 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-11-18 15:23:13 +00:00
|
|
|
func saveAudit(ctx context.Context, t string, op ent.Op, v ent.Value, err error, d time.Duration) {
|
|
|
|
if t == "Audit" {
|
|
|
|
// skip Audit table operations
|
|
|
|
return
|
|
|
|
}
|
2024-11-10 09:22:33 +00:00
|
|
|
|
2024-11-18 15:23:13 +00:00
|
|
|
var entOp string
|
|
|
|
switch {
|
|
|
|
case op.Is(ent.OpCreate):
|
|
|
|
entOp = "Create"
|
|
|
|
case op.Is(ent.OpDelete):
|
|
|
|
entOp = "Delete"
|
|
|
|
case op.Is(ent.OpDeleteOne):
|
|
|
|
entOp = "DeleteOne"
|
|
|
|
case op.Is(ent.OpUpdate):
|
|
|
|
entOp = "Update"
|
|
|
|
case op.Is(ent.OpUpdateOne):
|
|
|
|
entOp = "UpdateOne"
|
|
|
|
}
|
2024-11-10 09:22:33 +00:00
|
|
|
|
2024-11-18 15:23:13 +00:00
|
|
|
big.
|
|
|
|
reqID := ctx.Value("RequestID")
|
|
|
|
ip := ctx.Value("RequestIP")
|
|
|
|
ua := ctx.Value("RequestUA")
|
2024-11-10 09:22:33 +00:00
|
|
|
|
2024-11-18 15:23:13 +00:00
|
|
|
if en, ok := v.(entity); ok {
|
|
|
|
id, _ := en.GetID()
|
|
|
|
logger.Info("%s %s %s-%s(%d) ip=%s ua=%s t=%v error=%e", reqID, status, op.String(), t, id, ip, ua, d, err)
|
|
|
|
} else {
|
|
|
|
logger.Info("%s %s %s-%s ip=%s ua=%s t=%v error=%e", reqID, status, op.String(), t, ip, ua, d, err)
|
|
|
|
}
|
2024-11-10 09:22:33 +00:00
|
|
|
}
|