init
commit
dbe86acfac
|
@ -0,0 +1,3 @@
|
|||
Gopkg.lock
|
||||
Gopkg.toml
|
||||
vendor
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package mongo
|
||||
|
||||
const (
|
||||
obdDatabase = "global"
|
||||
movieCollection = "movies"
|
||||
)
|
|
@ -0,0 +1,18 @@
|
|||
package mongo
|
||||
|
||||
import (
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/mongomanager"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/db"
|
||||
)
|
||||
|
||||
var _ db.Databases = (*ServicesMongo)(nil)
|
||||
|
||||
type ServicesMongo struct {
|
||||
Db mongomanager.MongoDB
|
||||
}
|
||||
|
||||
func NewMongoService(mongoClient mongomanager.MongoDB) db.Databases {
|
||||
return &ServicesMongo{
|
||||
Db: mongoClient,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package mongo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/log"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/models"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func (s *ServicesMongo) GetMovieDetails(ctx appcontext.AppContext, movieName string) (models.MovieDetails, error) {
|
||||
details := models.MovieDetails{}
|
||||
b := bson.M{"title": bson.M{"$regex": movieName, "$options": "i"}}
|
||||
err := s.Db.ReadOne(ctx, obdDatabase, movieCollection, b, &details)
|
||||
if err != nil {
|
||||
log.GenericError(ctx, errors.New("GetMovieDetails DB error: "+err.Error()), log.FieldsMap{"title": movieName})
|
||||
return models.MovieDetails{}, err
|
||||
}
|
||||
return details, nil
|
||||
}
|
||||
func (s *ServicesMongo) SetMovieDetails(ctx appcontext.AppContext, movieDetails models.MovieDetails) error {
|
||||
_, err := s.Db.CreateOne(ctx, obdDatabase, movieCollection, movieDetails)
|
||||
if err != nil {
|
||||
log.GenericError(ctx, errors.New("SetMovieDetails DB error: "+err.Error()))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/models"
|
||||
)
|
||||
|
||||
type Movies interface {
|
||||
GetMovieDetails(ctx appcontext.AppContext, movieName string) (models.MovieDetails, error)
|
||||
SetMovieDetails(ctx appcontext.AppContext, movieDetails models.MovieDetails) error
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package domain
|
||||
|
||||
type Domain interface {
|
||||
Movies
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/models"
|
||||
)
|
||||
|
||||
type Movies interface {
|
||||
GetMovieDetailsFromApi(ctx appcontext.AppContext, movieName string) (models.MovieDetails, error)
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package standard
|
||||
|
||||
import (
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/models"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/utils"
|
||||
)
|
||||
|
||||
func (s *Standard) GetMovieDetailsFromApi(ctx appcontext.AppContext, movieName string) (models.MovieDetails, error) {
|
||||
|
||||
apiParam := utils.ApiParam{
|
||||
Url: "http://www.omdbapi.com/?apikey=d2bd086",
|
||||
Query: map[string]string{"t": movieName},
|
||||
}
|
||||
movieDetails := models.MovieDetails{}
|
||||
err := apiParam.CallAPI(ctx, &movieDetails)
|
||||
if err != nil {
|
||||
return models.MovieDetails{}, err
|
||||
}
|
||||
return movieDetails, nil
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package standard
|
||||
|
||||
import "gitlab.com/arkadooti.sarkar/go-boilerplate/db"
|
||||
|
||||
type Standard struct {
|
||||
DbMongo db.Databases
|
||||
}
|
||||
|
||||
func NewDomainService(mongo db.Databases) *Standard {
|
||||
return &Standard{
|
||||
DbMongo: mongo,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
module gitlab.com/arkadooti.sarkar/go-boilerplate
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/gorilla/context v1.1.2
|
||||
github.com/gorilla/handlers v1.5.2
|
||||
github.com/gorilla/mux v1.8.1
|
||||
go.elastic.co/apm v1.15.0
|
||||
go.elastic.co/apm/module/apmgorilla v1.15.0
|
||||
go.elastic.co/apm/module/apmhttp v1.15.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
github.com/elastic/go-licenser v0.3.1 // indirect
|
||||
github.com/elastic/go-sysinfo v1.1.1 // indirect
|
||||
github.com/elastic/go-windows v1.0.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/jcchavezs/porto v0.1.0 // indirect
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||
github.com/pkg/errors v0.8.1 // indirect
|
||||
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema v1.2.4 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
go.elastic.co/fastjson v1.1.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.13.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
|
||||
)
|
|
@ -0,0 +1,144 @@
|
|||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/elastic/go-licenser v0.3.1 h1:RmRukU/JUmts+rpexAw0Fvt2ly7VVu6mw8z4HrEzObU=
|
||||
github.com/elastic/go-licenser v0.3.1/go.mod h1:D8eNQk70FOCVBl3smCGQt/lv7meBeQno2eI1S5apiHQ=
|
||||
github.com/elastic/go-sysinfo v1.1.1 h1:ZVlaLDyhVkDfjwPGU55CQRCRolNpc7P0BbyhhQZQmMI=
|
||||
github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0=
|
||||
github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7aSY=
|
||||
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/jcchavezs/porto v0.1.0 h1:Xmxxn25zQMmgE7/yHYmh19KcItG81hIwfbEEFnd6w/Q=
|
||||
github.com/jcchavezs/porto v0.1.0/go.mod h1:fESH0gzDHiutHRdX2hv27ojnOVFco37hg1W6E9EZF4A=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
|
||||
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0 h1:c8R11WC8m7KNMkTv/0+Be8vvwo4I3/Ut9AC2FW8fX3U=
|
||||
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
|
||||
github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.elastic.co/apm v1.15.0 h1:uPk2g/whK7c7XiZyz/YCUnAUBNPiyNeE3ARX3G6Gx7Q=
|
||||
go.elastic.co/apm v1.15.0/go.mod h1:dylGv2HKR0tiCV+wliJz1KHtDyuD8SPe69oV7VyK6WY=
|
||||
go.elastic.co/apm/module/apmgorilla v1.15.0 h1:1yTAksffgaFXYEIwlLRiQnxLfy3p3RtpDw8HDupIJfY=
|
||||
go.elastic.co/apm/module/apmgorilla v1.15.0/go.mod h1:+23mZudYvZ9VgxCQjseLo9EF5gkKEr0KSQBupw+rzP8=
|
||||
go.elastic.co/apm/module/apmhttp v1.15.0 h1:Le/DhI0Cqpr9wG/NIGOkbz7+rOMqJrfE4MRG6q/+leU=
|
||||
go.elastic.co/apm/module/apmhttp v1.15.0/go.mod h1:NruY6Jq8ALLzWUVUQ7t4wIzn+onKoiP5woJJdTV7GMg=
|
||||
go.elastic.co/fastjson v1.1.0 h1:3MrGBWWVIxe/xvsbpghtkFoPciPhOCmjsR/HfwEeQR4=
|
||||
go.elastic.co/fastjson v1.1.0/go.mod h1:boNGISWMjQsUPy/t6yqt2/1Wx4YNPSe+mZjlyw9vKKI=
|
||||
go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
|
||||
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5 h1:MeC2gMlMdkd67dn17MEby3rGXRxZtWeiRXOnISfTQ74=
|
||||
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
|
@ -0,0 +1,31 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/log"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/mongomanager"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/db/mongo"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/domain/standard"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/webService"
|
||||
"os"
|
||||
)
|
||||
|
||||
const servicePort = "4100"
|
||||
const serviceRoute = "/boilerplate"
|
||||
|
||||
func main() {
|
||||
ctx := appcontext.New()
|
||||
|
||||
mongoClient, err := mongomanager.NewMongoClient(os.Getenv("MongoHost"), "myapp")
|
||||
if err != nil {
|
||||
log.GenericError(ctx, err)
|
||||
}
|
||||
|
||||
mongoService := mongo.NewMongoService(mongoClient)
|
||||
|
||||
domain := standard.NewDomainService(mongoService)
|
||||
|
||||
newService := webService.NewWebService(domain, mongoService, serviceRoute, servicePort)
|
||||
|
||||
newService.Run(ctx)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package models
|
||||
|
||||
type Ping struct {
|
||||
Ping string `json:"ping"`
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package models
|
||||
|
||||
type MovieDetails struct {
|
||||
Title string `json:"Title" bson:"title"`
|
||||
Year string `json:"Year" bson:"year"`
|
||||
Rated string `json:"Rated" bson:"rated"`
|
||||
Released string `json:"Released" bson:"released"`
|
||||
Runtime string `json:"Runtime" bson:"runtime"`
|
||||
Genre string `json:"Genre" bson:"genre"`
|
||||
Director string `json:"Director" bson:"director"`
|
||||
Writer string `json:"Writer" bson:"writer"`
|
||||
Actors string `json:"Actors" bson:"actors"`
|
||||
Plot string `json:"Plot" bson:"plot"`
|
||||
Language string `json:"Language" bson:"language"`
|
||||
Country string `json:"Country" bson:"country"`
|
||||
Awards string `json:"Awards" bson:"awards"`
|
||||
Poster string `json:"Poster" bson:"poster"`
|
||||
Ratings []MovieDetailsRating `json:"Ratings" bson:"ratings"`
|
||||
Metascore string `json:"Metascore" bson:"metascore"`
|
||||
ImdbRating string `json:"imdbRating" bson:"imdbRating"`
|
||||
ImdbVotes string `json:"imdbVotes" bson:"imdbVotes"`
|
||||
ImdbID string `json:"imdbID" bson:"imdbID"`
|
||||
Type string `json:"Type" bson:"type"`
|
||||
DVD string `json:"DVD" bson:"DVD"`
|
||||
BoxOffice string `json:"BoxOffice" bson:"boxOffice"`
|
||||
Production string `json:"Production" bson:"production"`
|
||||
Website string `json:"Website" bson:"website"`
|
||||
Response string `json:"Response" bson:"response"`
|
||||
}
|
||||
|
||||
type MovieDetailsRating struct {
|
||||
Source string `json:"Source" bson:"source"`
|
||||
Value string `json:"Value" bson:"value"`
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/log"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
RequestID = "requestId"
|
||||
)
|
||||
|
||||
var Client HTTPClient
|
||||
|
||||
// HTTPClient interface
|
||||
type HTTPClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
Client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
Timeout: time.Duration(35) * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// ApiParam api parameters
|
||||
type ApiParam struct {
|
||||
Url string
|
||||
Type string
|
||||
Query map[string]string
|
||||
Headers map[string]string
|
||||
Body io.Reader
|
||||
}
|
||||
|
||||
type responseMetaData struct { //to-do: it must be renamed to a generic response struct
|
||||
Code int `json:"code,omitempty"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (apiParam *ApiParam) CallAPI(ctx appcontext.AppContext, dst interface{}) error {
|
||||
reqMethod := "GET"
|
||||
if apiParam.Type != "" {
|
||||
reqMethod = apiParam.Type
|
||||
}
|
||||
|
||||
if apiParam.Url == "" {
|
||||
return errors.New("unable to make request, url is missing")
|
||||
}
|
||||
|
||||
u, err := url.Parse(apiParam.Url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := u.Query()
|
||||
for key, val := range apiParam.Query {
|
||||
q.Set(key, fmt.Sprintf("%v", val))
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequest(reqMethod, u.String(), apiParam.Body)
|
||||
if req != nil {
|
||||
_, reqIDFound := apiParam.Headers[RequestID]
|
||||
if !reqIDFound {
|
||||
req.Header.Set(RequestID, ctx.RequestID)
|
||||
}
|
||||
for headerKey, headerVal := range apiParam.Headers {
|
||||
req.Header.Set(headerKey, headerVal)
|
||||
}
|
||||
}
|
||||
resp, err := Client.Do(req)
|
||||
if resp != nil {
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
log.GenericError(ctx, err, nil)
|
||||
}
|
||||
}(resp.Body)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
if dst != nil {
|
||||
err = json.NewDecoder(resp.Body).Decode(dst)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "failed to decode response\",")
|
||||
}
|
||||
}
|
||||
_, err := io.Copy(ioutil.Discard, resp.Body)
|
||||
if err != nil {
|
||||
log.GenericError(ctx, err, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
bodyError := fmt.Errorf("unable to make request, error in statusCode: " + resp.Status)
|
||||
if resp.Body != nil {
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
bodyError = errors.New(string(body))
|
||||
}
|
||||
return bodyError
|
||||
}
|
||||
|
||||
func ReturnResponse(ctx appcontext.AppContext, w http.ResponseWriter, statusCode int, response interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
var buf = new(bytes.Buffer)
|
||||
enc := json.NewEncoder(buf)
|
||||
err := enc.Encode(response)
|
||||
if err != nil {
|
||||
log.GenericError(ctx, err)
|
||||
}
|
||||
_, err = w.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
log.GenericError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
func ReturnOKResponse(ctx appcontext.AppContext, w http.ResponseWriter, response interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
var buf = new(bytes.Buffer)
|
||||
enc := json.NewEncoder(buf)
|
||||
err := enc.Encode(response)
|
||||
if err != nil {
|
||||
log.GenericError(ctx, err)
|
||||
}
|
||||
_, err = w.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
log.GenericError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
func ReturnErrorResponse(ctx appcontext.AppContext, w http.ResponseWriter, responseErrorMessage string, statusCode int, logError error, fields ...log.FieldsMap) {
|
||||
var buf = new(bytes.Buffer)
|
||||
encoder := json.NewEncoder(buf)
|
||||
if logError != nil {
|
||||
if len(fields) > 0 {
|
||||
log.GenericError(ctx, logError, fields[0])
|
||||
} else {
|
||||
log.GenericError(ctx, logError, nil)
|
||||
}
|
||||
|
||||
}
|
||||
err := encoder.Encode(responseMetaData{Message: responseErrorMessage})
|
||||
if err != nil {
|
||||
log.GenericError(ctx, err)
|
||||
}
|
||||
w.WriteHeader(statusCode)
|
||||
_, err = w.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
log.GenericError(ctx, err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package utils
|
|
@ -0,0 +1,46 @@
|
|||
package webService
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/log"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/utils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (s *WebService) MovieDetails(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := appcontext.UpgradeCtx(r.Context())
|
||||
|
||||
movieName := r.URL.Query().Get("name")
|
||||
|
||||
details, err := s.DB.GetMovieDetails(ctx, movieName)
|
||||
if err != nil {
|
||||
log.GenericError(ctx, errors.New("not able to get movies from DB:"+err.Error()))
|
||||
}
|
||||
|
||||
if details.Title != "" {
|
||||
log.GenericInfo(ctx, "Sending response from DB")
|
||||
utils.ReturnOKResponse(ctx, w, details)
|
||||
return
|
||||
}
|
||||
|
||||
details, err = s.Domain.GetMovieDetailsFromApi(ctx, movieName)
|
||||
if err != nil {
|
||||
utils.ReturnErrorResponse(ctx, w, "Invalid request or IMDB error", http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if details.Title == "" {
|
||||
utils.ReturnErrorResponse(ctx, w, "No movie found with the title: "+movieName, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if details.Title != "" {
|
||||
err = s.DB.SetMovieDetails(ctx, details)
|
||||
if err != nil {
|
||||
log.GenericError(ctx, errors.New("not able to insert into DB: "+err.Error()))
|
||||
}
|
||||
}
|
||||
log.GenericInfo(ctx, "Sending response from API endpoint")
|
||||
utils.ReturnOKResponse(ctx, w, details)
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package webService
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (s *WebService) Ping(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = fmt.Fprintf(w, "alive")
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package webService
|
||||
|
||||
import (
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appcontext"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (s *WebService) Run(ctx appcontext.AppContext) {
|
||||
s.registerPublicApis()
|
||||
s.server.Start(ctx)
|
||||
}
|
||||
|
||||
func (s *WebService) registerPublicApis() {
|
||||
s.server.AddNoAuthRoutes("Ping", http.MethodGet, "/public/ping", s.Ping)
|
||||
|
||||
//Send movie name in query (query key: 'name')
|
||||
s.server.AddNoAuthRoutes("Ping", http.MethodGet, "/public/movie-details", s.MovieDetails)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package webService
|
||||
|
||||
import (
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/core/appserver"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/db"
|
||||
"gitlab.com/arkadooti.sarkar/go-boilerplate/domain"
|
||||
)
|
||||
|
||||
type WebService struct {
|
||||
Domain domain.Domain
|
||||
DB db.Databases
|
||||
server appserver.AppServer
|
||||
}
|
||||
|
||||
func NewWebService(domainService domain.Domain, db db.Databases, serviceRoute, servicePort string) *WebService {
|
||||
server := appserver.NewAppServer(servicePort, serviceRoute)
|
||||
return &WebService{
|
||||
Domain: domainService,
|
||||
DB: db,
|
||||
server: server,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue