This commit is contained in:
Kar
2024-01-08 14:00:41 +05:30
commit dbe86acfac
31 changed files with 1793 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
package appcontext
import (
"context"
"github.com/google/uuid"
"strings"
)
type customContextType string
const (
AppCtx customContextType = "appCtx"
)
type AppExtContext struct {
UserEmail string // Email of the user
RequestID string // RequestID - used to track logs across a request-response cycle
Locale string // Locale for language
Application string // application for dynamic application auth
}
type AppContext struct {
context.Context
AppExtContext
}
func GetAppCtx(ctx context.Context) (AppExtContext, bool) {
if ctx == nil {
return AppExtContext{}, false
}
appCtx, exists := ctx.Value(AppCtx).(AppExtContext)
return appCtx, exists
}
func WithAppCtx(ctx context.Context, appCtx AppExtContext) context.Context {
return context.WithValue(ctx, AppCtx, appCtx)
}
func UpgradeCtx(ctx context.Context) AppContext {
var appCtx AppContext
tCtx, _ := GetAppCtx(ctx)
appCtx.Context = ctx
appCtx.AppExtContext = tCtx
return appCtx
}
func NewAppContext() AppContext {
return AppContext{
Context: context.Background(),
AppExtContext: AppExtContext{},
}
}
func CopyAppContext(ctx context.Context) AppContext {
appCtx, _ := GetAppCtx(ctx)
return AppContext{
Context: context.Background(),
AppExtContext: appCtx,
}
}
func New(id ...string) AppContext {
var requestID string
if len(id) > 0 {
requestID = id[0]
}
if len(requestID) == 0 {
requestID = strings.ReplaceAll(uuid.NewString(), "-", "")
}
appCtx := AppExtContext{
RequestID: requestID,
}
ctx := UpgradeCtx(WithAppCtx(context.Background(), appCtx))
return ctx
}

61
core/appserver/brotli.go Normal file
View File

@@ -0,0 +1,61 @@
package appserver
import (
"github.com/andybalholm/brotli"
"net/http"
"sync"
)
type brotliResponseWriter struct {
http.ResponseWriter
w *brotli.Writer
statusCode int
headerWritten bool
}
var (
poolbr = sync.Pool{
New: func() interface{} {
w := brotli.NewWriterLevel(nil, brotli.BestSpeed)
return &brotliResponseWriter{
w: w,
}
},
}
)
func (br *brotliResponseWriter) WriteHeader(statusCode int) {
br.statusCode = statusCode
br.headerWritten = true
if br.statusCode != http.StatusNotModified && br.statusCode != http.StatusNoContent {
br.ResponseWriter.Header().Del("Content-Length")
br.ResponseWriter.Header().Set("Content-Encoding", "br")
}
br.ResponseWriter.WriteHeader(statusCode)
}
func (br *brotliResponseWriter) Write(b []byte) (int, error) {
if _, ok := br.Header()["Content-Type"]; !ok {
// If no content type, apply sniffing algorithm to un-gzipped body.
br.ResponseWriter.Header().Set("Content-Type", http.DetectContentType(b))
}
if !br.headerWritten {
// This is exactly what Go would also do if it hasn't been written yet.
br.WriteHeader(http.StatusOK)
}
return br.w.Write(b)
}
func (br *brotliResponseWriter) Flush() {
if br.w != nil {
br.w.Flush()
}
if fw, ok := br.ResponseWriter.(http.Flusher); ok {
fw.Flush()
}
}

62
core/appserver/gzip.go Normal file
View File

@@ -0,0 +1,62 @@
package appserver
import (
"compress/gzip"
"net/http"
"sync"
)
type gzipResponseWriter struct {
http.ResponseWriter
w *gzip.Writer
statusCode int
headerWritten bool
}
var (
pool = sync.Pool{
New: func() interface{} {
w, _ := gzip.NewWriterLevel(nil, gzip.BestSpeed)
return &gzipResponseWriter{
w: w,
}
},
}
)
func (gzr *gzipResponseWriter) WriteHeader(statusCode int) {
gzr.statusCode = statusCode
gzr.headerWritten = true
if gzr.statusCode != http.StatusNotModified && gzr.statusCode != http.StatusNoContent {
gzr.ResponseWriter.Header().Del("Content-Length")
gzr.ResponseWriter.Header().Set("Content-Encoding", "gzip")
}
gzr.ResponseWriter.WriteHeader(statusCode)
}
func (gzr *gzipResponseWriter) Write(b []byte) (int, error) {
if _, ok := gzr.Header()["Content-Type"]; !ok {
// If no content type, apply sniffing algorithm to un-gzipped body.
gzr.ResponseWriter.Header().Set("Content-Type", http.DetectContentType(b))
}
if !gzr.headerWritten {
// This is exactly what Go would also do if it hasn't been written yet.
gzr.WriteHeader(http.StatusOK)
}
return gzr.w.Write(b)
}
func (gzr *gzipResponseWriter) Flush() {
if gzr.w != nil {
gzr.w.Flush()
}
if fw, ok := gzr.ResponseWriter.(http.Flusher); ok {
fw.Flush()
}
}

View File

@@ -0,0 +1,108 @@
package appserver
import (
"errors"
"github.com/gorilla/context"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/log"
"go.elastic.co/apm/module/apmgorilla"
"net/http"
"net/http/pprof"
)
var (
AppName string
)
func (s *server) Start(ctx appcontext.AppContext) {
allowedOrigins := handlers.AllowedOrigins([]string{"*"}) // Allowing all origin as of now
allowedHeaders := handlers.AllowedHeaders([]string{
"Accept",
"Content-Type",
"contentType",
"Content-Length",
"Accept-Encoding",
"Client-Security-Token",
"X-CSRF-Token",
"X-Auth-Token",
"processData",
"Authorization",
"Access-Control-Request-Headers",
"Access-Control-Request-Method",
"Connection",
"Host",
"Origin",
"User-Agent",
"Referer",
"Cache-Control",
"X-header",
"X-Requested-With",
"timezone",
"locale",
"gzip-compress",
"task",
"access_token",
"application",
})
allowedMethods := handlers.AllowedMethods([]string{
"POST",
"GET",
"DELETE",
"PUT",
"PATCH",
"OPTIONS"})
allowCredential := handlers.AllowCredentials()
serverHandler := handlers.CORS(
allowedHeaders,
allowedMethods,
allowedOrigins,
allowCredential)(
context.ClearHandler(
s.newRouter(s.subRoute),
),
)
log.GenericInfo(ctx, "Starting Server",
log.FieldsMap{
"Port": s.port,
"SubRoute": s.subRoute,
"App": AppName,
})
err := http.ListenAndServe(":"+s.port, serverHandler)
if err != nil {
log.GenericError(ctx, errors.New("failed to start appserver"),
log.FieldsMap{
"Port": s.port,
"SubRoute": s.subRoute,
"App": AppName,
})
return
}
}
// Handles all incoming request who matches registered routes against the request.
func (s *server) newRouter(subRoute string) *mux.Router {
muxRouter := mux.NewRouter().StrictSlash(true)
muxRouter.HandleFunc(subRoute+"/debug/pprof", pprof.Index)
muxRouter.HandleFunc(subRoute+"/debug/pprof/cmdline", pprof.Cmdline)
muxRouter.HandleFunc(subRoute+"/debug/pprof/profile", pprof.Profile)
muxRouter.HandleFunc(subRoute+"/debug/pprof/symbol", pprof.Symbol)
muxRouter.HandleFunc(subRoute+"/debug/pprof/trace", pprof.Trace)
muxRouter.Handle(subRoute+"/debug/pprof/goroutine", pprof.Handler("goroutine"))
muxRouter.Handle(subRoute+"/debug/pprof/heap", pprof.Handler("heap"))
muxRouter.Handle(subRoute+"/debug/pprof/thread/create", pprof.Handler("threadcreate"))
muxRouter.Handle(subRoute+"/debug/pprof/block", pprof.Handler("block"))
muxRouter.Use(SetTraceID, apmgorilla.Middleware())
for _, r := range s.routes {
muxRouter.HandleFunc(subRoute+r.Pattern, r.HandlerFunc).Methods(r.Method)
}
return muxRouter
}

View File

@@ -0,0 +1,195 @@
package appserver
import (
"bytes"
"encoding/json"
"fmt"
"github.com/felixge/httpsnoop"
"github.com/google/uuid"
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/log"
"go.elastic.co/apm"
"go.elastic.co/apm/module/apmhttp"
"io/ioutil"
"net/http"
"runtime/debug"
"strings"
"time"
)
const TraceID = "traceid"
func SetTraceID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var traceID apm.TraceID
if values := r.Header[apmhttp.W3CTraceparentHeader]; len(values) == 1 && values[0] != "" {
if c, err := apmhttp.ParseTraceparentHeader(values[0]); err == nil {
traceID = c.Trace
}
}
if err := traceID.Validate(); err != nil {
uuidId := uuid.New()
var spanID apm.SpanID
var traceOptions apm.TraceOptions
copy(traceID[:], uuidId[:])
copy(spanID[:], traceID[8:])
traceContext := apm.TraceContext{
Trace: traceID,
Span: spanID,
Options: traceOptions.WithRecorded(true),
}
r.Header.Set(apmhttp.W3CTraceparentHeader, apmhttp.FormatTraceparentHeader(traceContext))
}
w.Header().Set(TraceID, traceID.String())
r.Header.Set(requestID, traceID.String())
next.ServeHTTP(w, r)
})
}
func enableCompression(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.Header.Get("Accept-Encoding"), "br") && !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
next(w, r)
return
} else if !strings.Contains(r.Header.Get("Accept-Encoding"), "br") {
gzr := pool.Get().(*gzipResponseWriter)
gzr.statusCode = 0
gzr.headerWritten = false
gzr.ResponseWriter = w
gzr.w.Reset(w)
defer func() {
// gzr.w.Close will write a footer even if no data has been written.
// StatusNotModified and StatusNoContent expect an empty body so don't close it.
if gzr.statusCode != http.StatusNotModified && gzr.statusCode != http.StatusNoContent {
if err := gzr.w.Close(); err != nil {
ctx := appcontext.UpgradeCtx(r.Context())
log.GenericError(ctx, err, nil)
}
}
pool.Put(gzr)
}()
next(gzr, r)
return
}
br := poolbr.Get().(*brotliResponseWriter)
br.statusCode = 0
br.headerWritten = false
br.ResponseWriter = w
br.w.Reset(w)
defer func() {
// brotli.w.Close will write a footer even if no data has been written.x
// StatusNotModified and StatusNoContent expect an empty body so don't close it.
if br.statusCode != http.StatusNotModified && br.statusCode != http.StatusNoContent {
if err := br.w.Close(); err != nil {
ctx := appcontext.UpgradeCtx(r.Context())
log.GenericError(ctx, err, nil)
}
}
poolbr.Put(br)
}()
next(br, r)
}
}
func recovery(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
ctx := appcontext.UpgradeCtx(r.Context())
rec := recover()
if rec != nil {
span, _ := apm.StartSpan(ctx.Context, "recovery", "custom")
defer span.End()
trace := string(debug.Stack())
trace = strings.Replace(trace, "\n", " ", -1)
trace = strings.Replace(trace, "\t", " ", -1)
log.GenericError(ctx, fmt.Errorf("%v", rec),
log.FieldsMap{
"msg": "recovering from panic",
"stackTrace": trace,
})
jsonBody, _ := json.Marshal(map[string]string{
"error": "There was an internal server error",
})
e := apm.DefaultTracer.Recovered(rec)
e.SetSpan(span)
e.Send()
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
w.Write(jsonBody)
}
}()
next.ServeHTTP(w, r)
}
}
func logRequest(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
m := httpsnoop.CaptureMetrics(handler, w, r)
log.HTTPLog(constructHTTPLog(r, m, time.Since(start)))
}
}
func constructHTTPLog(r *http.Request, m httpsnoop.Metrics, duration time.Duration) string {
ctx := r.Context().Value(appcontext.AppCtx)
rawBody, _ := ioutil.ReadAll(r.Body)
if len(rawBody) > 0 {
r.Body = ioutil.NopCloser(bytes.NewBuffer(rawBody))
}
var jsonBody interface{}
// For Testing
json.Unmarshal(rawBody, &jsonBody)
bodyJsonByte, _ := json.Marshal(jsonBody)
if ctx != nil {
tCtx := ctx.(appcontext.AppExtContext)
return fmt.Sprintf("|%s|%s|%s|%s|%s|%d|%d|%s|%s|%s|",
tCtx.UserEmail,
"requestId="+tCtx.RequestID,
r.RemoteAddr,
r.Method,
r.URL,
m.Code,
m.Written,
r.UserAgent(),
duration,
"Body:"+string(bodyJsonByte),
)
}
return fmt.Sprintf("|%s|%s|%s|%d|%d|%s|%s|%s|",
r.RemoteAddr,
r.Method,
r.URL,
m.Code,
m.Written,
r.UserAgent(),
duration,
"Body:"+string(rawBody),
)
}
func createContext(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
header := r.Header
ctx := r.Context()
reqID := header.Get(requestID)
if reqID == "" {
reqID = strings.ReplaceAll(uuid.NewString(), "-", "")
}
email, app := header.Get(userEmail), header.Get(application)
locale := header.Get(locale)
tempCtx := appcontext.AppExtContext{
RequestID: reqID,
UserEmail: email,
Locale: locale,
Application: app,
}
ctx = appcontext.WithAppCtx(ctx, tempCtx)
next.ServeHTTP(w, r.WithContext(ctx))
}
}

View File

@@ -0,0 +1,19 @@
package appserver
import "net/http"
func (s *server) AddNoAuthRoutes(methodName string, methodType string, mRoute string, handlerFunc http.HandlerFunc) {
r := route{
Name: methodName,
Method: methodType,
Pattern: mRoute,
HandlerFunc: useMiddleware(handlerFunc, recovery, enableCompression, logRequest, createContext)}
s.routes = append(s.routes, r)
}
func useMiddleware(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
for _, m := range middleware {
h = m(h)
}
return h
}

40
core/appserver/server.go Normal file
View File

@@ -0,0 +1,40 @@
package appserver
import (
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
"net/http"
)
type route struct {
Name string
Method string
Pattern string
Modules []string
ResourcesPermissionMap interface{}
HandlerFunc http.HandlerFunc
}
type server struct {
port string
subRoute string
routes []route
}
const (
requestID = "requestId"
userEmail = "email"
application = "application"
locale = "locale"
)
type AppServer interface {
Start(ctx appcontext.AppContext)
AddNoAuthRoutes(methodName string, methodType string, mRoute string, handlerFunc http.HandlerFunc)
}
func NewAppServer(port, subRoute string) AppServer {
return &server{
port: port,
subRoute: subRoute,
}
}

260
core/log/log.go Normal file
View File

@@ -0,0 +1,260 @@
package log
import (
"encoding/json"
"fmt"
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
"io"
"log"
"os"
"strings"
)
const (
//INFO level 1
INFO = iota
//HTTP level 2
HTTP
//ERROR level 3
ERROR
//TRACE level 4
TRACE
//WARNING level 5
WARNING
)
var (
setLevel = WARNING
trace *log.Logger
info *log.Logger
warning *log.Logger
httplog *log.Logger
errorlog *log.Logger
)
const (
clusterType = "CLUSTER_TYPE"
clusterTypeLocal = "local"
clusterTypeDev = "dev1"
)
// FieldsMap map of key value pair to log
type FieldsMap map[string]interface{}
func init() {
logInit(os.Stdout,
os.Stdout,
os.Stdout,
os.Stdout,
os.Stderr)
}
func logInit(traceHandle, infoHandle, warningHandle, httpHandle, errorHandle io.Writer) {
flagWithClusterType := log.LUTC | log.LstdFlags | log.Lshortfile
flagWithoutClusterType := log.LUTC | log.LstdFlags
var flag int
if os.Getenv(clusterType) == clusterTypeLocal || os.Getenv(clusterType) == clusterTypeDev {
flag = flagWithClusterType
} else {
flag = flagWithoutClusterType
}
trace = log.New(traceHandle, "TRACE|", flag)
info = log.New(infoHandle, "INFO|", flag)
warning = log.New(warningHandle, "WARNING|", flag)
httplog = log.New(httpHandle, "HTTP|", flag)
errorlog = log.New(errorHandle, "ERROR|", flagWithClusterType)
}
func doLog(cLog *log.Logger, level, callDepth int, v ...interface{}) {
if level <= setLevel {
if level == ERROR {
cLog.SetOutput(os.Stderr)
cLog.SetFlags(log.Llongfile)
}
cLog.Output(callDepth, fmt.Sprintln(v...))
}
}
func generatePrefix(ctx appcontext.AppContext) string {
// TODO: Add departmentName once that is implemented fully
return strings.Join([]string{ctx.UserEmail}, ":")
}
func generateTrackingIDs(ctx appcontext.AppContext) (retString string) {
requestID := ctx.RequestID
if requestID != "" {
retString = "requestId=" + requestID
}
return
}
// Trace system gives facility to helps you isolate your system problems by monitoring selected events Ex. entry and exit
func traceLog(v ...interface{}) {
doLog(trace, TRACE, 4, v...)
}
// Info dedicated for logging valuable information
func infoLog(v ...interface{}) {
doLog(info, INFO, 4, v...)
}
// Warning for critical error
func warningLog(v ...interface{}) {
doLog(warning, WARNING, 4, v...)
}
// Error logging error
func errorLog(v ...interface{}) {
doLog(errorlog, ERROR, 4, v...)
}
func fatalLog(v ...interface{}) {
doLog(errorlog, ERROR, 4, v...)
os.Exit(1)
}
func HTTPLog(logMessage string) {
doLog(httplog, HTTP, 6, logMessage)
}
func GenericTrace(ctx appcontext.AppContext, traceMessage string, data ...FieldsMap) {
var fields FieldsMap
if len(data) > 0 {
fields = data[0]
}
if os.Getenv("SERVICE_TRACE") == "true" {
prefix := generatePrefix(ctx)
trackingIDs := generateTrackingIDs(ctx)
msg := fmt.Sprintf("|%s|%s|",
prefix,
trackingIDs)
if fields != nil && len(fields) > 0 {
fieldsBytes, _ := json.Marshal(fields)
fieldsString := string(fieldsBytes)
traceLog(msg, traceMessage, "|", fieldsString)
} else {
traceLog(msg, traceMessage)
}
}
}
func GenericInfo(ctx appcontext.AppContext, infoMessage string, data ...FieldsMap) {
var fields FieldsMap
if len(data) > 0 {
fields = data[0]
}
prefix := generatePrefix(ctx)
trackingIDs := generateTrackingIDs(ctx)
fieldsBytes, _ := json.Marshal(fields)
fieldsString := string(fieldsBytes)
msg := fmt.Sprintf("|%s|%s|",
prefix,
trackingIDs)
if fields != nil && len(fields) > 0 {
infoLog(msg, infoMessage, "|", fieldsString)
} else {
infoLog(msg, infoMessage)
}
}
func GenericWarning(ctx appcontext.AppContext, warnMessage string, data ...FieldsMap) {
var fields FieldsMap
if len(data) > 0 {
fields = data[0]
}
if os.Getenv("SERVICE_WARN") == "true" {
prefix := generatePrefix(ctx)
trackingIDs := generateTrackingIDs(ctx)
msg := fmt.Sprintf("|%s|%s|",
prefix,
trackingIDs)
if fields != nil && len(fields) > 0 {
fieldsBytes, _ := json.Marshal(fields)
fieldsString := string(fieldsBytes)
warningLog(msg, warnMessage, "|", fieldsString)
} else {
warningLog(msg, warnMessage)
}
}
}
func GenericError(ctx appcontext.AppContext, e error, data ...FieldsMap) {
var fields FieldsMap
if len(data) > 0 {
fields = data[0]
}
prefix := generatePrefix(ctx)
trackingIDs := generateTrackingIDs(ctx)
msg := ""
if e != nil {
msg = fmt.Sprintf("|%s|%s|%s", prefix, trackingIDs, e.Error())
} else {
msg = fmt.Sprintf("|%s|%s", prefix, trackingIDs)
}
if fields != nil && len(fields) > 0 {
fieldsBytes, _ := json.Marshal(fields)
fieldsString := string(fieldsBytes)
errorLog(msg, "|", fieldsString)
} else {
errorLog(msg)
}
}
func CriticalError(ctx appcontext.AppContext, e error, data ...FieldsMap) {
var fields FieldsMap
if len(data) > 0 {
fields = data[0]
}
prefix := generatePrefix(ctx)
trackingIDs := generateTrackingIDs(ctx)
msg := ""
if e != nil {
msg = fmt.Sprintf("|%s|%s|%s", prefix, trackingIDs, e.Error())
} else {
msg = fmt.Sprintf("|%s|%s", prefix, trackingIDs)
}
if fields != nil && len(fields) > 0 {
fieldsBytes, _ := json.Marshal(fields)
fieldsString := string(fieldsBytes)
errorLog(msg, "|", fieldsString)
} else {
errorLog(msg)
}
}
func FatalLog(ctx appcontext.AppContext, e error, data ...FieldsMap) {
var fields FieldsMap
if len(data) > 0 {
fields = data[0]
}
prefix := generatePrefix(ctx)
trackingIDs := generateTrackingIDs(ctx)
msg := ""
if e != nil {
msg = fmt.Sprintf("|%s|%s|%s", prefix, trackingIDs, e.Error())
} else {
msg = fmt.Sprintf("|%s|%s", prefix, trackingIDs)
}
if fields != nil && len(fields) > 0 {
fieldsBytes, _ := json.Marshal(fields)
fieldsString := string(fieldsBytes)
fatalLog(msg, "|", fieldsString)
} else {
fatalLog(msg)
}
}

View File

@@ -0,0 +1,239 @@
package mongomanager
import (
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func (client *mongoService) Disconnect(ctx appcontext.AppContext) error {
return client.DB.Disconnect(ctx)
}
func (client *mongoService) CreateOne(ctx appcontext.AppContext, database, collectionName string, d interface{}) (*mongo.InsertOneResult, error) {
collection := client.DB.Database(database).Collection(collectionName)
insertOneResult, err := collection.InsertOne(ctx.Context, d)
if err != nil {
return nil, err
}
return insertOneResult, nil
}
// CreateMany - inserts many data into mongo database
func (client *mongoService) CreateMany(ctx appcontext.AppContext, database, collectionName string, d []interface{}) (*mongo.InsertManyResult, error) {
collection := client.DB.Database(database).Collection(collectionName)
insertManyRslt, err := collection.InsertMany(ctx.Context, d)
if err != nil {
return nil, err
}
return insertManyRslt, nil
}
// ReadOne - reads single document from mongo database
func (client *mongoService) ReadOne(ctx appcontext.AppContext, database, collectionName string, filter, data interface{}) error {
collection := client.DB.Database(database).Collection(collectionName)
err := collection.FindOne(ctx.Context, filter).Decode(data)
if err != nil {
return err
}
return nil
}
// ReadAll - reads multiple documents from mongo database
func (client *mongoService) ReadAll(ctx appcontext.AppContext, database, collectionName string, filter, data interface{}, opts ...*options.FindOptions) error {
var findOptions *options.FindOptions
if len(opts) > 0 {
findOptions = opts[0]
}
collection := client.DB.Database(database).Collection(collectionName)
cursor, err := collection.Find(ctx.Context, filter, findOptions)
if err != nil {
return err
}
defer cursor.Close(ctx.Context)
err = cursor.All(ctx.Context, data)
if err != nil {
return err
}
return nil
}
// Update - updates data into mongo database
func (client *mongoService) Update(ctx appcontext.AppContext, database, collectionName string, filter, update interface{}, options ...*options.UpdateOptions) (*mongo.UpdateResult, error) {
collection := client.DB.Database(database).Collection(collectionName)
updateResult, err := collection.UpdateOne(ctx.Context, filter, update, options...)
if err != nil {
return nil, err
}
return updateResult, nil
}
// ReplaceOne - replace one document into mongo database
func (client *mongoService) ReplaceOne(ctx appcontext.AppContext, database, collectionName string, filter, replacement interface{}, opts ...*options.ReplaceOptions) (*mongo.UpdateResult, error) {
collection := client.DB.Database(database).Collection(collectionName)
updateResult, err := collection.ReplaceOne(ctx.Context, filter, replacement, opts...)
if err != nil {
return nil, err
}
return updateResult, nil
}
// UpdateAndReturn - updates data into mongo database and returns the updated document
func (client *mongoService) UpdateAndReturn(ctx appcontext.AppContext, database, collectionName string, filter, update, data interface{}) error {
collection := client.DB.Database(database).Collection(collectionName)
after := options.After
opts := options.FindOneAndUpdateOptions{
ReturnDocument: &after,
}
err := collection.FindOneAndUpdate(ctx.Context, filter, update, &opts).Decode(data)
if err != nil {
return err
}
return nil
}
// UpdateAll - updates multiple documents into mongo database
func (client *mongoService) UpdateAll(ctx appcontext.AppContext, database, collectionName string, filter, update interface{}, opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) {
collection := client.DB.Database(database).Collection(collectionName)
updateResult, err := collection.UpdateMany(ctx.Context, filter, update, opts...)
if err != nil {
return nil, err
}
return updateResult, nil
}
func (client *mongoService) Upsert(ctx appcontext.AppContext, database, collectionName string, filter,
update interface{}, opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) {
collection := client.DB.Database(database).Collection(collectionName)
updateOptions := options.Update()
if len(opts) >= 1 {
updateOptions = opts[0]
}
updateOptions.SetUpsert(true)
updateResult, err := collection.UpdateOne(ctx.Context, filter, update, updateOptions)
if err != nil {
return nil, err
}
return updateResult, nil
}
// Delete - removes single doc data from the database
func (client *mongoService) Delete(ctx appcontext.AppContext, database, collectionName string, filter interface{}) (*mongo.DeleteResult, error) {
collection := client.DB.Database(database).Collection(collectionName)
deleteResult, err := collection.DeleteOne(ctx.Context, filter)
if err != nil {
return nil, err
}
return deleteResult, nil
}
// DeleteAll - removes all doc data from the database
func (client *mongoService) DeleteAll(ctx appcontext.AppContext, database, collectionName string, filter interface{}) (*mongo.DeleteResult, error) {
collection := client.DB.Database(database).Collection(collectionName)
deleteResult, err := collection.DeleteMany(ctx.Context, filter)
if err != nil {
return nil, err
}
return deleteResult, nil
}
// CountDocuments returns document count of a collection
func (client *mongoService) CountDocuments(ctx appcontext.AppContext, database, collectionName string, filter interface{}, opts ...*options.CountOptions) (int64, error) {
var countOptions *options.CountOptions
if len(opts) > 0 {
countOptions = opts[0]
}
collection := client.DB.Database(database).Collection(collectionName)
count, err := collection.CountDocuments(ctx.Context, filter, countOptions)
if err != nil {
return 0, err
}
return count, nil
}
// Exist verifies if document is present or not
// if it returns error then there is a connection error else boolean value specifies whether doc is present or not
func (client *mongoService) Exist(ctx appcontext.AppContext, database, collectionName string, filter interface{}) (bool, error) {
var i interface{}
collection := client.DB.Database(database).Collection(collectionName)
err := collection.FindOne(ctx.Context, filter).Decode(&i)
if err != nil {
return false, nil
}
return true, nil
}
// GetDistinct gets the distinct values for the field name provided
func (client *mongoService) GetDistinct(ctx appcontext.AppContext, database, collectionName, fieldName string, filter interface{}) (interface{}, error) {
collection := client.DB.Database(database).Collection(collectionName)
result, err := collection.Distinct(ctx.Context, fieldName, filter, nil)
if err != nil {
return nil, err
}
return result, nil
}
// AggregateAll executes aggregation query on a collection
// query []bson.M, data is a pointer to an array
func (client *mongoService) AggregateAll(ctx appcontext.AppContext, database, collectionName string, query, data interface{}, options ...*options.AggregateOptions) error {
collection := client.DB.Database(database).Collection(collectionName)
cursor, err := collection.Aggregate(ctx.Context, query, options...)
if err != nil {
return err
}
err = cursor.All(ctx.Context, data)
return err
}
// FindOneAndUpdate executes a findAndModify command to update at most one document in the collection and returns the
// document as it appeared before updating.
func (client *mongoService) FindOneAndUpdate(ctx appcontext.AppContext, database, collectionName string, filter, update, data interface{},
opts ...*options.FindOneAndUpdateOptions) error {
after := options.After
option := &options.FindOneAndUpdateOptions{
ReturnDocument: &after,
}
if len(opts) > 0 {
option = opts[0]
}
collection := client.DB.Database(database).Collection(collectionName)
result := collection.FindOneAndUpdate(ctx.Context, filter, update, option)
if result.Err() != nil {
return result.Err()
}
decodeErr := result.Decode(data)
if decodeErr != nil {
return decodeErr
}
return nil
}
func (client *mongoService) BulkWrite(ctx appcontext.AppContext, database, collectionName string, operations []mongo.WriteModel, bulkOption *options.BulkWriteOptions) (*mongo.BulkWriteResult, error) {
var err error
collection := client.DB.Database(database).Collection(collectionName)
result, err := collection.BulkWrite(ctx.Context, operations, bulkOption)
if err != nil {
return nil, err
}
return result, nil
}

View File

@@ -0,0 +1,89 @@
package mongomanager
import (
"context"
"errors"
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/log"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"time"
)
type mongoService struct {
DB *mongo.Client
}
type MongoDB interface {
Disconnect(ctx appcontext.AppContext) error
CreateOne(ctx appcontext.AppContext, database, collectionName string, d interface{}) (*mongo.InsertOneResult, error)
CreateMany(ctx appcontext.AppContext, database, collectionName string, d []interface{}) (*mongo.InsertManyResult, error)
ReadOne(ctx appcontext.AppContext, database, collectionName string, filter, data interface{}) error
ReadAll(ctx appcontext.AppContext, database, collectionName string, filter, data interface{}, opts ...*options.FindOptions) error
Update(ctx appcontext.AppContext, database, collectionName string, filter, update interface{}, options ...*options.UpdateOptions) (*mongo.UpdateResult, error)
ReplaceOne(ctx appcontext.AppContext, database, collectionName string, filter, replacement interface{}, opts ...*options.ReplaceOptions) (*mongo.UpdateResult, error)
Upsert(ctx appcontext.AppContext, database, collectionName string, filter, update interface{}, opts ...*options.UpdateOptions) (*mongo.UpdateResult, error)
UpdateAndReturn(ctx appcontext.AppContext, database, collectionName string, filter, update, data interface{}) error
UpdateAll(ctx appcontext.AppContext, database, collectionName string, filter, update interface{}, opts ...*options.UpdateOptions) (*mongo.UpdateResult, error)
Delete(ctx appcontext.AppContext, database, collectionName string, filter interface{}) (*mongo.DeleteResult, error)
DeleteAll(ctx appcontext.AppContext, database, collectionName string, filter interface{}) (*mongo.DeleteResult, error)
CountDocuments(ctx appcontext.AppContext, database, collectionName string, filter interface{}, opts ...*options.CountOptions) (int64, error)
Exist(ctx appcontext.AppContext, database, collectionName string, filter interface{}) (bool, error)
GetDistinct(ctx appcontext.AppContext, database, collectionName, fieldName string, filter interface{}) (interface{}, error)
AggregateAll(ctx appcontext.AppContext, database, collectionName string, query, data interface{}, options ...*options.AggregateOptions) error
FindOneAndUpdate(ctx appcontext.AppContext, database, collectionName string, filter, update, data interface{}, opts ...*options.FindOneAndUpdateOptions) error
BulkWrite(ctx appcontext.AppContext, database, collectionName string, operations []mongo.WriteModel, bulkOption *options.BulkWriteOptions) (*mongo.BulkWriteResult, error)
}
func NewMongoClient(url, appName string) (MongoDB, error) {
ctx := appcontext.NewAppContext()
client, err := connect(url, appName)
if err != nil {
log.GenericError(ctx, errors.New("can't connect to db: "+err.Error()), nil)
return &mongoService{DB: client}, err
}
return &mongoService{DB: client}, nil
}
func connect(host, appName string) (*mongo.Client, error) {
var client *mongo.Client
servSelecTimeout := time.Duration(15) * time.Second
connTimeout := time.Duration(10) * time.Second
idleTime := time.Duration(2) * time.Minute
socketTimeout := time.Duration(2) * time.Minute
maxPooling := uint64(100)
clientOptions := &options.ClientOptions{
AppName: &appName,
ServerSelectionTimeout: &servSelecTimeout,
ConnectTimeout: &connTimeout,
MaxConnIdleTime: &idleTime,
MaxPoolSize: &maxPooling,
SocketTimeout: &socketTimeout,
}
clientOptions = clientOptions.ApplyURI(host)
err := clientOptions.Validate()
if err != nil {
return client, err
}
client, err = mongo.NewClient(clientOptions)
if err != nil {
return client, err
}
err = client.Connect(context.TODO())
if err != nil {
return client, err
}
err = client.Ping(context.TODO(), nil)
if err != nil {
return client, err
}
return client, nil
}