EUGENIO SOUZA CARVALHO 3 years ago
parent
commit
5456bcb7c5

+ 0 - 0
.gitignore


+ 101 - 0
Dockerfile

@@ -0,0 +1,101 @@
+FROM golang:1.14.6-alpine3.12 as builder
+LABEL maintainer Eugenio Carvalho <eugeniucarvalho@gmail.com>
+ENV NODE_VERSION 12.13.0
+ENV YARN_VERSION 1.19.1
+ENV GIT_TERMINAL_PROMPT=1
+ENV GONOSUMDB=git.eugeniocarvalho.dev/eugeniucarvalho
+ENV GO111MODULE=on
+
+RUN apk --update add git less openssh
+RUN addgroup -g 1000 node \
+    && adduser -u 1000 -G node -s /bin/sh -D node \
+    && apk add --no-cache \
+        libstdc++ \
+    && apk add --no-cache --virtual .build-deps \
+        curl \
+    && ARCH= && alpineArch="$(apk --print-arch)" \
+      && case "${alpineArch##*-}" in \
+        x86_64) \
+          ARCH='x64' \
+          CHECKSUM="f1c73636c4d345c4aefd65cc959f793d7bb795200d43e19e418d7811670b03dd" \
+          ;; \
+        *) ;; \
+      esac \
+  && if [ -n "${CHECKSUM}" ]; then \
+    set -eu; \
+    curl -fsSLO --compressed "https://unofficial-builds.nodejs.org/download/release/v$NODE_VERSION/node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz"; \
+    echo "$CHECKSUM  node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz" | sha256sum -c - \
+      && tar -xJf "node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \
+      && ln -s /usr/local/bin/node /usr/local/bin/nodejs; \
+  else \
+    echo "Building from source" \
+    # backup build
+    && apk add --no-cache --virtual .build-deps-full \
+        binutils-gold \
+        g++ \
+        gcc \
+        gnupg \
+        libgcc \
+        linux-headers \
+        make \
+        python \
+    # gpg keys listed at https://github.com/nodejs/node#release-keys
+    && for key in \
+      94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \
+      FD3A5288F042B6850C66B31F09FE44734EB7990E \
+      71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \
+      DD8F2338BAE7501E3DD5AC78C273792F7D83545D \
+      C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
+      B9AE9905FFD7803F25714661B63B535A4C206CA9 \
+      77984A986EBC2AA786BC0F66B01FBB92821C587A \
+      8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600 \
+      4ED778F539E3634C779C87C6D7062848A1AB005C \
+      A48C2BEE680E841632CD4E44F07496B3EB3C1762 \
+      B9E2F5981AA6E0CD28160D9FF13993A75599653C \
+    ; do \
+      gpg --batch --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys "$key" || \
+      gpg --batch --keyserver hkp://ipv4.pool.sks-keyservers.net --recv-keys "$key" || \
+      gpg --batch --keyserver hkp://pgp.mit.edu:80 --recv-keys "$key" ; \
+    done \
+    && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION.tar.xz" \
+    && curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
+    && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
+    && grep " node-v$NODE_VERSION.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
+    && tar -xf "node-v$NODE_VERSION.tar.xz" \
+    && cd "node-v$NODE_VERSION" \
+    && ./configure \
+    && make -j$(getconf _NPROCESSORS_ONLN) V= \
+    && make install \
+    && apk del .build-deps-full \
+    && cd .. \
+    && rm -Rf "node-v$NODE_VERSION" \
+    && rm "node-v$NODE_VERSION.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt; \
+  fi \
+  && rm -f "node-v$NODE_VERSION-linux-$ARCH-musl.tar.xz" \
+  && apk del .build-deps \
+  && apk add --no-cache --virtual .build-deps-yarn curl gnupg tar \
+  && for key in \
+    6A010C5166006599AA17F08146C2130DFD2497F5 \
+  ; do \
+    gpg --batch --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys "$key" || \
+    gpg --batch --keyserver hkp://ipv4.pool.sks-keyservers.net --recv-keys "$key" || \
+    gpg --batch --keyserver hkp://pgp.mit.edu:80 --recv-keys "$key" ; \
+  done \
+  && curl -fsSLO --compressed "https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz" \
+  && curl -fsSLO --compressed "https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz.asc" \
+  && gpg --batch --verify yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz \
+  && mkdir -p /opt \
+  && tar -xzf yarn-v$YARN_VERSION.tar.gz -C /opt/ \
+  && ln -s /opt/yarn-v$YARN_VERSION/bin/yarn /usr/local/bin/yarn \
+  && ln -s /opt/yarn-v$YARN_VERSION/bin/yarnpkg /usr/local/bin/yarnpkg \
+  && rm yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz \
+  && apk del .build-deps-yarn \
+  && rm -rf /var/lib/apt/lists/* \
+  && rm /var/cache/apk/*
+
+RUN npm install -g typescript typescript-formatter
+
+COPY ./eon-amd64 /usr/bin/eon
+RUN mkdir /workdir
+VOLUME /workdir
+WORKDIR /workdir

+ 149 - 9
api/debug.go

@@ -2,6 +2,7 @@ package api
 
 import (
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"os"
 	"time"
@@ -26,6 +27,12 @@ type DebugEvent struct {
 	Data    interface{} `json:"data"`
 }
 
+type Request struct {
+	ID      string `json:"id"`
+	Status  string `json:"status"`
+	Created int64  `json:"created"`
+}
+
 type DebugTaks struct {
 	ID           string        `json:"id"`
 	Status       string        `json:"status"`
@@ -33,7 +40,7 @@ type DebugTaks struct {
 	Stages       []*DebugStage `json:"stages"`
 	CurrentStage *DebugStage   `json:"-"`
 	Debug        *Debugger     `json:"-"`
-	Request      *http.Request `json:"request"`
+	Request      interface{}   `json:"request"`
 }
 
 func NewDebugTaks() *DebugTaks {
@@ -69,19 +76,30 @@ func (debug *DebugTaks) Event(eventType, eventId string) *DebugEvent {
 
 func (task *DebugTaks) Finalize() {
 	var (
-		debug   = task.Debug
-		out     []byte
-		err     error
-		channel = debug.Hub.GetChannel(debug.ChannelID)
+		debug = task.Debug
+		out   []byte
+		err   error
 	)
 
+	for _, stage := range task.Stages {
+		for _, event := range stage.Events {
+			if event.Error != nil  {
+				event.Status = "error"
+				stage.Status = "error"
+				task.Status = "error"
+				goto end
+			}
+		}
+	}
+	// task.Event()
+	end:
 	if out, err = json.Marshal(task); err != nil {
-		channel.Emit(&sse.Event{
+		debug.Emit(&sse.Event{
 			Kind:    "debugger.error",
 			Payload: err.Error(),
 		})
 	} else {
-		channel.Emit(&sse.Event{
+		debug.Emit(&sse.Event{
 			Kind:    "request",
 			Payload: string(out),
 		})
@@ -96,8 +114,13 @@ type Debugger struct {
 
 func (debug *Debugger) Handler() func(context.Context) {
 	return func(ctx context.Context) {
+		var err error
 		task := debug.CreateTask()
-		task.Request = ctx.Request()
+
+		if task.Request, err = parseRequest(ctx.Request()); err != nil {
+			fmt.Println("Request debug error", err.Error())
+		}
+
 		ctx.Values().Set("#debug", task)
 		ctx.Next()
 	}
@@ -110,18 +133,38 @@ func (debug *Debugger) CreateTask() *DebugTaks {
 		Created: time.Now().Unix(),
 		Debug:   debug,
 	}
-	debug.Tasks = append(debug.Tasks, task)
+	debug.Tasks = append([]*DebugTaks{task}, debug.Tasks...)
 	return task
 }
 
+func (debug *Debugger) Emit(events ...*sse.Event) {
+	channel := debug.Hub.GetChannel(debug.ChannelID)
+	for _, event := range events {
+		channel.Emit(event)
+	}
+}
+
 func (debug *Debugger) EventStream() func(context.Context) (interface{}, *errs.Error) {
 	return func(ctx context.Context) (resp interface{}, err *errs.Error) {
+
+		go func() {
+			if len(debug.Tasks) > 0 {
+				time.Sleep(time.Second)
+				fmt.Println("initialize debug with ", len(debug.Tasks))
+				for _, task := range debug.Tasks[:10] {
+					task.Finalize()
+				}
+				debug.Tasks = debug.Tasks[:10]
+			}
+		}()
+
 		if err = debug.Hub.UpgradeConnection(
 			ctx,
 			debug.ChannelID,
 		); err != nil {
 			return
 		}
+
 		resp = true
 		return
 	}
@@ -138,3 +181,100 @@ func NewDebug() *Debugger {
 		}),
 	}
 }
+func parseRequest(req *http.Request) (result map[string]interface{}, err error) {
+	save := req.Body
+	defer func() {
+		req.Body = save
+	}()
+
+	result = map[string]interface{}{}
+	// if !body || req.Body == nil {
+	// 	req.Body = nil
+	// } else {
+	// 	save, req.Body, err = drainBody(req.Body)
+	// 	if err != nil {
+	// 		return nil, err
+	// 	}
+	// }
+	var (
+		reqURI    string
+		reqMethod string
+	)
+
+	if reqURI = req.RequestURI; reqURI == "" {
+		reqURI = req.URL.RequestURI()
+	}
+
+	if reqMethod = req.Method; req.Method == "" {
+		reqMethod = "GET"
+	}
+
+	result["URI"] = reqURI
+	result["URL"] = map[string]interface{}{
+		"Scheme":      req.URL.Scheme,
+		"Host":        req.URL.Hostname(),
+		"IsAbs":       req.URL.IsAbs(),
+		"EscapedPath": req.URL.EscapedPath(),
+		"Port":        req.URL.Port(),
+		"Uri":         req.URL.String(),
+		"query":       req.URL.Query(),
+	}
+	result["Method"] = reqMethod
+	result["ProtoMajor"] = req.ProtoMajor
+	result["ProtoMinor"] = req.ProtoMinor
+	result["Proto"] = req.Proto
+	result["RemoteAddr"] = req.RemoteAddr
+	result["Header"] = req.Header
+	result["Host"] = req.Host
+	result["Close"] = req.Close
+	result["ContentLength"] = req.ContentLength
+
+	// for name, value := range req.Header {
+	// 	fmt.Printf("%v: %v\n", name, value)
+	// }
+
+	// fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
+	// reqURI, req.ProtoMajor, req.ProtoMinor)
+
+	// absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://")
+	// if !absRequestURI {
+	// 	host := req.Host
+	// 	if host == "" && req.URL != nil {
+	// 		host = req.URL.Host
+	// 	}
+	// 	if host != "" {
+	// 		fmt.Fprintf(&b, "Host: %s\r\n", host)
+	// 	}
+	// }
+
+	// chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
+	// if len(req.TransferEncoding) > 0 {
+	// 	fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
+	// }
+	// if req.Close {
+	// 	fmt.Fprintf(&b, "Connection: close\r\n")
+	// }
+
+	// err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
+	// if err != nil {
+	// 	return nil, err
+	// }
+	// io.WriteString(&b, "\r\n")
+	// if req.Body != nil {
+	// 	var dest io.Writer = &b
+	// 	if chunked {
+	// 		dest = NewChunkedWriter(dest)
+	// 	}
+	// 	_, err = io.Copy(dest, req.Body)
+	// 	if chunked {
+	// 		dest.(io.Closer).Close()
+	// 		io.WriteString(&b, "\r\n")
+	// 	}
+	// }
+	// req.Body = save
+	// if err != nil {
+	// 	return nil, err
+	// }
+	// return b.Bytes(), nil
+	return
+}

+ 3 - 1
api/errs/errs.go

@@ -3,6 +3,8 @@ package errs
 import (
 	"fmt"
 	"runtime"
+
+	"github.com/davecgh/go-spew/spew"
 )
 
 type ErroNil *Error
@@ -397,7 +399,7 @@ func Trace() (stack *ErrorStack) {
 }
 
 func FromError(source error) (err *Error) {
-
+	spew.Dump(source)
 	for _, fn := range errorMapFunction {
 
 		if err = fn(source); err != nil {

+ 379 - 208
api/mongo.go

@@ -4,27 +4,29 @@ import (
 	"bytes"
 	"context"
 	"encoding/base64"
+	"encoding/json"
 	"fmt" // "html/template"
 	"reflect"
 	"regexp"
 	"strconv"
 	"strings"
+	"sync"
 	"text/template"
 	"time"
 
 	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
 	"github.com/davecgh/go-spew/spew"
+	"github.com/jeremywohl/flatten"
 	"github.com/kataras/iris"
 	iriscontext "github.com/kataras/iris/v12/context"
 	"go.mongodb.org/mongo-driver/bson"
 	"go.mongodb.org/mongo-driver/bson/primitive"
 	"go.mongodb.org/mongo-driver/mongo"
 	"go.mongodb.org/mongo-driver/mongo/options"
-	"go.mongodb.org/mongo-driver/mongo/readconcern"
-	"go.mongodb.org/mongo-driver/mongo/readpref"
-	"go.mongodb.org/mongo-driver/mongo/writeconcern"
 )
 
+type ParseQueryHandler = func(ctx iriscontext.Context) (err *errs.Error)
+
 const (
 	DefaultAddrConnect = "mongodb://localhost:27017"
 
@@ -34,8 +36,10 @@ const (
 )
 
 var (
-	QueriesMap       = map[string]*template.Template{}
-	EMPTYQUERY_REGEX = regexp.MustCompile(`{\s*}`)
+	QueriesMap         = map[string]*template.Template{}
+	ParseQueryHandlers = map[string]ParseQueryHandler{}
+	EMPTYQUERY_REGEX   = regexp.MustCompile(`{\s*}`)
+	NextPageTokenMap   = map[string]string{}
 )
 
 func GetSessionContext(ctx iriscontext.Context) mongo.SessionContext {
@@ -54,7 +58,8 @@ func init() {
 		switch {
 		case strings.Contains(message, "E11000"):
 			err = errs.AlreadyExists().Details(&errs.Detail{
-				Message: "DUPLICATED_ITEM",
+				Reason:  "DUPLICATED_ITEM",
+				Message: message,
 			})
 		case strings.Contains(message, "cannot decode"):
 			err = errs.DataCaps().Details(&errs.Detail{
@@ -63,7 +68,8 @@ func init() {
 
 		case strings.Contains(message, "no documents in result"):
 			errs.NotFound().Details(&errs.Detail{
-				Message: "NOT_FOUND",
+				Reason:  "NOT_FOUND",
+				Message: message,
 			})
 		}
 		return
@@ -80,6 +86,9 @@ type Mongo struct {
 	// Password string `json:"password"`
 	// DataBase string `json:"database"`
 	// Addrs    string `json:"addrs"`
+	FindManyNextPaginationMux sync.Mutex
+	// NextPageTokenMap          map[string]string
+	Uniq       int64
 	subject    *BehaviorSubjectStruct
 	client     *mongo.Client
 	Credential *options.Credential
@@ -104,11 +113,13 @@ type Filter struct {
 	Sort    *bson.M
 	Context iriscontext.Context
 	// Sort       []string
+	NextPageToken       string
 	Collection          string
 	QueryType           string
 	DB                  string
 	Format              string
 	Insertion           int
+	ResultSizeEstimate  int64
 	CheckQuery          bool
 	IgnoreNoEntityFound bool
 	MaxResults          int
@@ -118,21 +129,19 @@ type Filter struct {
 	// Aggregate  interface{}
 	Pipeline primitive.A
 	Options  interface{}
+	// PatchOptions = patch.Patch()
 }
 
 func (f *Filter) Aggregate(ctx context.Context, collection *mongo.Collection, opts *options.AggregateOptions) (cursor *mongo.Cursor, err *errs.Error) {
 	var errl error
 
 	if opts == nil {
-		opts = options.Aggregate().SetBatchSize(int32(f.MaxResults))
+		opts = options.Aggregate().SetBatchSize(int32(f.MaxResults)).SetCollation(&options.Collation{
+			Strength: 1,
+			Locale:   "en",
+		})
 	}
 
-	// cmd := command.Aggregate{
-	// 	NS:       ns,
-	// 	Pipeline: pipeline,
-	// 	ReadPref: rp,
-	// }
-
 	if cursor, errl = collection.Aggregate(ctx, f.Pipeline, opts); errl != nil {
 		err = errs.FromError(errl)
 	}
@@ -163,6 +172,11 @@ func (t *Filter) Check() (err *errs.Error) {
 	return
 }
 
+func (mongo *Mongo) uniqID() int64 {
+	mongo.Uniq++
+	return mongo.Uniq
+}
+
 func (t *Mongo) Ready() *BehaviorSubjectStruct {
 
 	if t.subject == nil {
@@ -177,6 +191,9 @@ func (t *Mongo) Init() (err error) {
 		spew.Dump(err)
 	}()
 
+	t.FindManyNextPaginationMux = sync.Mutex{}
+	NextPageTokenMap = map[string]string{}
+
 	fmt.Printf("Connection string '%s'", t.Config)
 	clientOpts := options.Client().ApplyURI(t.Config)
 
@@ -253,14 +270,10 @@ func (t *Mongo) InsertOne(f *Filter) (res *mongo.InsertOneResult, err *errs.Erro
 		})
 	}()
 	f.Insertion = InsertOne
-
 	t.exec(f, func(ctx context.Context, collection *mongo.Collection) {
 		var lerr error
-
 		if res, lerr = collection.InsertOne(ctx, f.Entity); lerr != nil {
-
 			err = errs.FromError(lerr)
-
 		}
 	}, func(e *errs.Error) { err = e })
 
@@ -307,6 +320,9 @@ func (t *Mongo) RemoveOne(f *Filter) (res *mongo.DeleteResult, err *errs.Error)
 	t.exec(f, func(ctx context.Context, collection *mongo.Collection) {
 		var lerr error
 
+		fmt.Println("remove.one")
+		spew.Dump(f.Query)
+
 		if res, lerr = collection.DeleteOne(ctx, f.Query); lerr != nil {
 			err = errs.FromError(lerr)
 
@@ -314,7 +330,6 @@ func (t *Mongo) RemoveOne(f *Filter) (res *mongo.DeleteResult, err *errs.Error)
 			err = errs.NotFound().Details(&errs.Detail{
 				Message: "No entity has been deleted",
 			})
-
 		}
 
 	}, func(e *errs.Error) { err = e })
@@ -324,6 +339,7 @@ func (t *Mongo) RemoveOne(f *Filter) (res *mongo.DeleteResult, err *errs.Error)
 //Remove os elementos da colecao, selecionados pela query
 func (t *Mongo) RemoveMany(f *Filter) (res *mongo.DeleteResult, err *errs.Error) {
 	var lerr error
+	var deleteOptions *options.DeleteOptions
 
 	defer func() {
 		createDebugEvent(f, "models.remove.many", func(event *DebugEvent) {
@@ -332,17 +348,25 @@ func (t *Mongo) RemoveMany(f *Filter) (res *mongo.DeleteResult, err *errs.Error)
 	}()
 
 	f.CheckQuery = true
+	if (f.Options != nil ) {
+		deleteOptions = f.Options.(*options.DeleteOptions)
+	}
 
 	t.exec(f, func(ctx context.Context, collection *mongo.Collection) {
 
-		if res, lerr = collection.DeleteMany(ctx, f.Entities); lerr != nil {
+		if res, lerr = collection.DeleteMany(
+			ctx,
+			f.Query,
+			deleteOptions,
+		); lerr != nil {
 			err = errs.FromError(lerr)
 
-		} else if res.DeletedCount == 0 {
-			err = errs.NotFound().Details(&errs.Detail{
-				Message: "No entity has been deleted",
-			})
-		}
+		} 
+		// else if res.DeletedCount == 0 {
+		// 	err = errs.NotFound().Details(&errs.Detail{
+		// 		Message: "No entity has been deleted",
+		// 	})
+		// }
 
 	}, func(e *errs.Error) { err = e })
 
@@ -417,6 +441,14 @@ func RegisterQuery(id, query string) {
 	QueriesMap[id] = template.Must(template.New(id).Parse(query))
 }
 
+func BeforeParseQueryHandler(id string, fn ParseQueryHandler) {
+	ParseQueryHandlers[id] = fn
+}
+
+func getQueryHandler(id string) ParseQueryHandler {
+	return ParseQueryHandlers[id]
+}
+
 func ParseQuery(ctx iriscontext.Context, filter *Filter, basequery string, data map[string]interface{}) (err *errs.Error) {
 	var (
 		found             bool
@@ -425,15 +457,13 @@ func ParseQuery(ctx iriscontext.Context, filter *Filter, basequery string, data
 		baseQueryTemplate *template.Template
 		// baseQeuryDocument  *bson.M
 		// errbq              *errs.Error
+		nextPageToken      string
 		queryDecodedString []byte
+		values             = ctx.Values()
 		userQueryString    = Q(ctx, "q", "e30=")                                  // default base64 encoded from "{}"
 		includeInTrash, _  = strconv.ParseBool(Q(ctx, "includeInTrash", "false")) // default base64 encoded from "{}"
 	)
 
-	// defer func() {
-	// 	spew.Dump(filter.Query)
-	// }()
-
 	// Faz o parse da userQueryString enviada pelo usuario que deve ser um base64
 	if queryDecodedString, templateErr = base64.StdEncoding.DecodeString(userQueryString); templateErr != nil {
 		err = errs.InvalidArgument().Details(&errs.Detail{
@@ -450,14 +480,25 @@ func ParseQuery(ctx iriscontext.Context, filter *Filter, basequery string, data
 	}
 
 	// transclude the query to base query
-	data["_transclude_"] = string(queryDecodedString)
+	fmt.Println("##########################[", filter.NextPageToken, "]")
+	// spew.Dump(t.NextPageTokenMap)
+	if nextPageToken, found = NextPageTokenMap[filter.NextPageToken]; !found {
+		nextPageToken = "{}"
+	}
+
+	data["_transclude_nextPageToken"] = nextPageToken
+	data["_transclude_true"] = string(queryDecodedString)
 	data["_deleted_"] = includeInTrash
 
-	fmt.Println(
-		basequery,
-		string(queryDecodedString),
-		data,
-	)
+	values.Set("eon.query.data", data)
+	values.Set("eon.query.id", basequery)
+	values.Set("eon.query.options", filter)
+
+	if handler := getQueryHandler(basequery); handler != nil {
+		if err = handler(ctx); err != nil {
+			return
+		}
+	}
 
 	if templateErr = baseQueryTemplate.Execute(&buf, data); templateErr != nil {
 		err = errs.InvalidArgument().Details(&errs.Detail{
@@ -479,97 +520,141 @@ func ParseQuery(ctx iriscontext.Context, filter *Filter, basequery string, data
 	return
 }
 
-func (t *Mongo) PatchOne(f *Filter) (res *mongo.UpdateResult, err *errs.Error) {
+func (this *Mongo) PatchOne(actionOptions *Filter) (res *mongo.UpdateResult, err *errs.Error) {
+	uniq := this.uniqID()
+	fmt.Println("mongo.patch.many", uniq)
 	defer func() {
-		createDebugEvent(f, "models.patch.one", func(event *DebugEvent) {
+		createDebugEvent(actionOptions, "models.patch.many", func(event *DebugEvent) {
 			event.Error = err
 		})
+		fmt.Println("mongo.patch.many.end", uniq)
 	}()
-	f.Insertion = Patch
-	f.CheckQuery = true
+	actionOptions.Insertion = Patch
+	actionOptions.CheckQuery = true
 
-	// out, _ := json.Marshal()
-	// fmt.Println(string(out))
-	// spew.Dump(f.Patchs)
-
-	t.exec(f, func(ctx context.Context, collection *mongo.Collection) {
+	this.exec(actionOptions, func(ctx context.Context, collection *mongo.Collection) {
 
 		var (
 			lerr error
 			// client *mongo.Client
 		)
 
-		// if client, err = t.GetClient(f.DB); err != nil {
-		// 	return
-		// }
-		// spew.Dump(f.Query)
+		for _, p := range *actionOptions.Patchs {
 
-		// spew.Dump(f.Patchs)
-		// id := primitive.NewObjectID()
+			if actionOptions.Options == nil {
+				actionOptions.Options = options.Update()
+			}
 
-		t.client.UseSessionWithOptions(
-			ctx,
-			options.Session().SetDefaultReadPreference(readpref.Primary()),
-			func(sctx mongo.SessionContext) error {
-
-				defer func() {
-					if err != nil {
-						// fmt.Println("abort patch ", id.Hex())
-						sctx.AbortTransaction(sctx)
-					} else {
-						// fmt.Println("commit patch ", id.Hex())
-						sctx.CommitTransaction(sctx)
-					}
-					sctx.EndSession(sctx)
-				}()
+			if res, lerr = collection.UpdateOne(ctx, actionOptions.Query, p, actionOptions.Options.(*options.UpdateOptions)); lerr != nil {
+				// ce := lerr.(mongo.CommandError)
+				// fmt.Println("----------------------------",
+				// ce.Message,
+				// ce.Labels,
+				// ce.Code,
+				// ce.Name,
+				// )
+				// f.Context.Application().Logger().Errorf(lerr.Error())
+				err = errs.FromError(lerr)
+				return
+			}
 
-				sctx.StartTransaction(options.Transaction().
-					SetReadConcern(readconcern.Snapshot()).
-					SetWriteConcern(writeconcern.New(writeconcern.WMajority())))
+			if !actionOptions.IgnoreNoEntityFound && (res.ModifiedCount+res.UpsertedCount) == 0 {
 
-				for _, p := range *f.Patchs {
+				err = errs.NotFound().Details(&errs.Detail{
 
-					if f.Options == nil {
-						f.Options = options.Update()
-					}
+					Message: "No entity has been updated",
+				})
+				return
+			}
+		}
 
-					if res, lerr = collection.UpdateOne(ctx, f.Query, p, f.Options.(*options.UpdateOptions)); lerr != nil {
-						err = errs.FromError(lerr)
-						return lerr
-					}
+		// if client, err = t.GetClient(f.DB); err != nil {
+		// 	return
+		// }
+		// spew.Dump(f.Query)
 
-					if !f.IgnoreNoEntityFound && (res.ModifiedCount+res.UpsertedCount) == 0 {
+		// spew.Dump(f.Patchs)
+		// id := primitive.NewObjectID()
 
-						err = errs.NotFound().Details(&errs.Detail{
-							Message: "No entity has been updated",
-						})
-						return fmt.Errorf("No entity has been updated")
-					}
-				}
-				return nil
-			},
-		)
+		// t.client.UseSessionWithOptions(
+		// 	ctx,
+		// 	options.Session().SetDefaultReadPreference(readpref.Primary()),
+		// 	func(sctx mongo.SessionContext) error {
+
+		// 		defer func() {
+		// 			if err != nil {
+		// 				sctx.AbortTransaction(sctx)
+		// 			} else {
+		// 				sctx.CommitTransaction(sctx)
+		// 			}
+		// 			sctx.EndSession(sctx)
+		// 		}()
+
+		// 		sctx.StartTransaction(options.Transaction().
+		// 			SetReadConcern(readconcern.Snapshot()).
+		// 			SetWriteConcern(writeconcern.New(writeconcern.WMajority())))
+
+		// 		for _, p := range *f.Patchs {
+
+		// 			if f.Options == nil {
+		// 				f.Options = options.Update()
+		// 			}
+
+		// 			if res, lerr = collection.UpdateOne(ctx, f.Query, p, f.Options.(*options.UpdateOptions)); lerr != nil {
+		// 				err = errs.FromError(lerr)
+		// 				return lerr
+		// 			}
+
+		// 			if !f.IgnoreNoEntityFound && (res.ModifiedCount+res.UpsertedCount) == 0 {
+
+		// 				err = errs.NotFound().Details(&errs.Detail{
+		// 					Message: "No entity has been updated",
+		// 				})
+		// 				return fmt.Errorf("No entity has been updated")
+		// 			}
+		// 		}
+		// 		return nil
+		// 	},
+		// )
 	}, func(e *errs.Error) { err = e })
 	return
 }
 
-func (t *Mongo) PatchMany(f *Filter) (res *mongo.UpdateResult, err *errs.Error) {
+func (this *Mongo) PatchMany(actionOptions *Filter) (res *mongo.UpdateResult, err *errs.Error) {
+	uniq := this.uniqID()
+	fmt.Println("mongo.patch.many", uniq)
 	defer func() {
-		createDebugEvent(f, "models.patch.many", func(event *DebugEvent) {
+		createDebugEvent(actionOptions, "models.patch.many", func(event *DebugEvent) {
 			event.Error = err
 		})
+		fmt.Println("mongo.patch.many.end", uniq)
 	}()
 
-	f.Insertion = Patch
-	f.CheckQuery = true
+	actionOptions.Insertion = Patch
+	actionOptions.CheckQuery = true
 
-	t.exec(f, func(ctx context.Context, collection *mongo.Collection) {
+	this.exec(actionOptions, func(ctx context.Context, collection *mongo.Collection) {
 
 		var (
 			lerr error
 			// client *mongo.Client
 		)
+		for _, p := range *actionOptions.Patchs {
+
+			if res, lerr = collection.UpdateMany(ctx, actionOptions.Query, p); lerr != nil {
+				err = errs.FromError(lerr)
+				return
+			}
 
+			if !actionOptions.IgnoreNoEntityFound && (res.ModifiedCount+res.UpsertedCount) == 0 {
+				err = errs.NotFound().Details(&errs.Detail{
+					Message: "No entity has been updated",
+				})
+				err.CodeText = "noEntityUpdated"
+				// fmt.Errorf("No entity has been updated")
+				return
+			}
+		}
 		// if client, err = t.GetClient(f.DB); err != nil {
 		// 	return
 		// }
@@ -579,44 +664,44 @@ func (t *Mongo) PatchMany(f *Filter) (res *mongo.UpdateResult, err *errs.Error)
 		// o, _ = json.MarshalIndent(f.Patchs, "", "  ")
 		// fmt.Println("patch", o)
 
-		t.client.UseSessionWithOptions(
-			ctx,
-			options.Session().SetDefaultReadPreference(readpref.Primary()),
-			func(sctx mongo.SessionContext) error {
-
-				defer func() {
-					if err != nil {
-						sctx.AbortTransaction(sctx)
-					} else {
-						sctx.CommitTransaction(sctx)
-					}
-					sctx.EndSession(sctx)
-				}()
-
-				sctx.StartTransaction(
-					options.Transaction().
-						SetReadConcern(readconcern.Snapshot()).
-						SetWriteConcern(writeconcern.New(writeconcern.WMajority())),
-				)
-
-				for _, p := range *f.Patchs {
-
-					if res, lerr = collection.UpdateMany(ctx, f.Query, p); lerr != nil {
-						err = errs.FromError(lerr)
-						return lerr
-					}
-
-					if !f.IgnoreNoEntityFound && (res.ModifiedCount+res.UpsertedCount) == 0 {
-						err = errs.NotFound().Details(&errs.Detail{
-							Message: "No entity has been updated",
-						})
-						err.CodeText = "noEntityUpdated"
-						return fmt.Errorf("No entity has been updated")
-					}
-				}
-				return nil
-			},
-		)
+		// t.client.UseSessionWithOptions(
+		// 	ctx,
+		// 	options.Session().SetDefaultReadPreference(readpref.Primary()),
+		// 	func(sctx mongo.SessionContext) error {
+
+		// 		defer func() {
+		// 			if err != nil {
+		// 				sctx.AbortTransaction(sctx)
+		// 			} else {
+		// 				sctx.CommitTransaction(sctx)
+		// 			}
+		// 			sctx.EndSession(sctx)
+		// 		}()
+
+		// 		sctx.StartTransaction(
+		// 			options.Transaction().
+		// 				SetReadConcern(readconcern.Snapshot()).
+		// 				SetWriteConcern(writeconcern.New(writeconcern.WMajority())),
+		// 		)
+
+		// 		for _, p := range *f.Patchs {
+
+		// 			if res, lerr = collection.UpdateMany(ctx, f.Query, p); lerr != nil {
+		// 				err = errs.FromError(lerr)
+		// 				return lerr
+		// 			}
+
+		// 			if !f.IgnoreNoEntityFound && (res.ModifiedCount+res.UpsertedCount) == 0 {
+		// 				err = errs.NotFound().Details(&errs.Detail{
+		// 					Message: "No entity has been updated",
+		// 				})
+		// 				err.CodeText = "noEntityUpdated"
+		// 				return fmt.Errorf("No entity has been updated")
+		// 			}
+		// 		}
+		// 		return nil
+		// 	},
+		// )
 	}, func(e *errs.Error) { err = e })
 	return
 }
@@ -646,14 +731,17 @@ func (mongo *Mongo) FindOneRx(options *Filter) *ObservableStruct {
 }
 
 func (t *Mongo) FindOne(f *Filter) (res *mongo.SingleResult, err *errs.Error) {
+	uniq := t.uniqID()
+	fmt.Println("mongo.find.one", uniq)
 	defer func() {
 		createDebugEvent(f, "models.find.one", func(event *DebugEvent) {
 			event.Error = err
 		})
+		fmt.Println("mongo.find.one.end", uniq, f.Collection)
+		spew.Dump(f.Query)
 	}()
 
 	f.CheckQuery = (f.QueryType != "aggregate")
-
 	t.exec(f, func(ctx context.Context, collection *mongo.Collection) {
 		var (
 			lerr   error
@@ -697,133 +785,192 @@ func findIds(filter *Filter) (ids []primitive.ObjectID) {
 	return
 }
 
+// if f.QueryType == "aggregate" {
+// cursor, lerr = f.Aggregate(ctx, collection, nil)
+// fmt.Println("-------------------------------------------------------------------------------after aggregate")
+// } else {
+// 	findOptions := options.Find()
+// 	findOptions.SetLimit(int64(f.MaxResults)).SetCollation(&options.Collation{
+// 		Locale:   "en",
+// 		Strength: 1,
+// 	})
+
+// 	// fmt.Printf("sort of query '%s'\n", f.Format)
+// 	// spew.Dump(f.Sort)
+// 	// spew.Dump(f.Fields)
+// 	// spew.Dump(f.Query)
+
+// 	if f.Sort != nil {
+// 		findOptions.SetSort(f.Sort)
+// 	}
+
+// 	if f.Fields != nil {
+// 		findOptions.SetProjection(f.Fields)
+// 	}
+
+// 	cursor, lerr = collection.Find(ctx, f.Query, findOptions)
+// }
+
 func (t *Mongo) FindMany(f *Filter) (cursor *mongo.Cursor, err *errs.Error) {
 
-	// f.CheckQuery = true
+	uniq := t.uniqID()
+	fmt.Println("mongo.find.many", uniq)
 	defer func() {
 		createDebugEvent(f, "models.find.many", func(event *DebugEvent) {
 			event.Error = err
 		})
+		fmt.Println("mongo.find.many.end", uniq, f.Collection)
 	}()
 
+	if f.Entities == nil {
+		err = errs.Internal().Details(&errs.Detail{
+			Message: "Entities can't be nil",
+		})
+		return
+	}
+
+	entitiesValue := reflect.ValueOf(f.Entities)
+	if entitiesValue.Kind() != reflect.Ptr || entitiesValue.Elem().Kind() != reflect.Slice {
+		err = errs.Internal().Details(&errs.Detail{
+			Message: "Entities argument must be a slice address",
+		})
+	}
+
 	t.exec(f, func(ctx context.Context, collection *mongo.Collection) {
 		var (
 			lerr  error
+			err   *errs.Error
 			elemp reflect.Value
-			// ids   = []primitive.ObjectID{}
-			// limit = int64(f.MaxResults)
-			// fields = f.Fields
 		)
 
-		// defer func() {
-		// 	if errd := recover(); errd != nil {
-		// 		// fmt.Println(errd)
-		// 		// spew.Dump(Trace())
-		// 		// fmt.Println(errors.WithStack(errd.(error)) )
-		// 		LogError(0, fmt.Sprintf("FindMany - %v", errd))
-		// 	}
-		// }()
-		// Carrega os ids e verifica o intervalo de paginacao da consulta.
-		// f.Fields = &bson.M{"_id": 1}
-
-		// ids = findIds(f)
-
-		// Atualiza a consulta para o conjunto de ids da paginacao
-		// f.Fields = fields
-		// f.Query = &bson.M{"_id": bson.M{"$in": ids}}
+		if f.QueryType != "aggregate" {
+			f.Pipeline = bson.A{}
 
-		// fmt.Println("Query Type ", f.QueryType)
-
-		if f.QueryType == "aggregate" {
-			cursor, lerr = f.Aggregate(ctx, collection, nil)
-		} else {
-			findOptions := options.Find()
-			findOptions.SetLimit(int64(f.MaxResults))
+			if f.Sort != nil && len(*f.Sort) > 0 {
+				f.Pipeline = append(f.Pipeline, bson.M{"$sort": f.Sort})
+			}
 
-			// fmt.Printf("sort of query '%s'\n", f.Format)
-			// spew.Dump(f.Sort)
-			// spew.Dump(f.Fields)
-			// spew.Dump(f.Query)
+			f.Pipeline = append(f.Pipeline, bson.M{"$match": f.Query})
 
-			if f.Sort != nil {
-				findOptions.SetSort(f.Sort)
+			if f.Fields != nil && len(*f.Fields) > 0 {
+				f.Pipeline = append(f.Pipeline, bson.M{"$project": f.Fields})
 			}
 
-			if f.Fields != nil {
-				findOptions.SetProjection(f.Fields)
+			if f.MaxResults > 0 {
+				f.Pipeline = append(f.Pipeline, bson.M{"$limit": f.MaxResults})
 			}
-
-			cursor, lerr = collection.Find(ctx, f.Query, findOptions)
+			f.QueryType = "aggregate"
 		}
 
-		if lerr == nil {
-			defer cursor.Close(ctx)
-
-			// spew.Dump(cursor)
+		wg := sync.WaitGroup{}
+		wg.Add(2)
 
-			if f.Entities == nil {
-				lerr = fmt.Errorf("Entities can't be nil")
-				goto FindManyError
+		go func() {
+			var countError error
+			defer wg.Done()
+			if f.ResultSizeEstimate, countError = collection.CountDocuments(nil, f.Query); countError != nil {
+				err = errs.FromError(countError)
 			}
+		}()
 
-			entitiesValue := reflect.ValueOf(f.Entities)
-			if entitiesValue.Kind() != reflect.Ptr || entitiesValue.Elem().Kind() != reflect.Slice {
-				lerr = fmt.Errorf("Entities argument must be a slice address")
-				goto FindManyError
+		go func() {
+			defer wg.Done()
+			if cursor, err = f.Aggregate(ctx, collection, nil); err != nil {
+				return
 			}
 
+			defer cursor.Close(ctx)
+
 			slicev := entitiesValue.Elem()
 			slicev = slicev.Slice(0, slicev.Cap())
 			typ := slicev.Type().Elem()
 
 			for cursor.Next(ctx) {
-
 				elemp = reflect.New(typ)
-
-				// fmt.Println("aqui")
 				if lerr = cursor.Decode(elemp.Interface()); lerr != nil {
-
-					// spew.Dump(elemp)
-					goto FindManyError
+					err = errs.FromError(lerr)
+					return
 				}
 				slicev = reflect.Append(slicev, elemp.Elem())
 			}
 
-			// spew.Dump(slicev)
-			entitiesValue.Elem().Set(slicev)
-			return
-		}
+			if elemp.IsValid() {
+				var (
+					data          = map[string]interface{}{}
+					nextPageQuery = map[string]interface{}{}
+					out           []byte
+					variablesJson string
+				)
+				if out, lerr = json.Marshal(elemp.Elem().Interface()); lerr != nil {
+					err = errs.FromError(lerr)
+					return
+				}
+				if variablesJson, lerr = flatten.FlattenString(string(out), ".", flatten.DotStyle); lerr != nil {
+					err = errs.FromError(lerr)
+					return
+				}
+				if lerr = json.Unmarshal([]byte(variablesJson), &data); err != nil {
+					err = errs.FromError(lerr)
+					return
+				}
+				orderOperator := map[int]string{
+					1:  "$gt",
+					-1: "$lt",
+				}
 
-	FindManyError:
-		err = errs.FromError(lerr)
-	}, func(e *errs.Error) { err = e })
-	return
-}
+				if f.Sort == nil {
+					f.Sort = &bson.M{}
+				}
 
-func createDebugEvent(options *Filter, eventType string, fn func(event *DebugEvent)) {
-	// debug := options.Context.Values().Get("#debug")
-	if options.Context != nil {
+				if (*f.Sort)["_id"] == nil {
+					(*f.Sort)["_id"] = 1
+				}
 
-		debug, defined := options.Context.Values().Get("#debug").(*DebugTaks)
-		if defined {
-			event := debug.Event(eventType, "")
-			event.Data = iris.Map{}
-			fn(event)
-		}
-	}
+				for prop, order := range *f.Sort {
+					value := data[fmt.Sprintf(".%s", prop)]
+					if prop == "_id" {
+						value = bson.M{"$oid": value}
+					}
+					nextPageQuery[prop] = bson.M{
+						orderOperator[order.(int)]: value,
+					}
+				}
+
+				if out, lerr = json.Marshal(nextPageQuery); lerr != nil {
+					err = errs.FromError(lerr)
+					return
+				}
+				f.NextPageToken = primitive.NewObjectID().Hex()
+				t.FindManyNextPaginationMux.Lock()
+				NextPageTokenMap[f.NextPageToken] = string(out)
+				t.FindManyNextPaginationMux.Unlock()
+			}
+			entitiesValue.Elem().Set(slicev)
+		}()
+
+		wg.Wait()
+
+		return
+	},
+		func(e *errs.Error) { err = e },
+	)
+	return
 }
 
-func (models *Mongo) Exists(options *Filter) (exists bool, err *errs.Error) {
+func (this *Mongo) Exists(actionOptions *Filter) (exists bool, err *errs.Error) {
+	uniq := this.uniqID()
+	fmt.Println("mongo.exists", uniq)
 	defer func() {
-		createDebugEvent(options, "models.exists", func(event *DebugEvent) {
-			event.Data = iris.Map{"response": exists}
+		createDebugEvent(actionOptions, "models.exists", func(event *DebugEvent) {
 			event.Error = err
 		})
+		fmt.Println("mongo.exists.end", uniq)
 	}()
 
-	options.Fields = &bson.M{"_id": 1}
+	actionOptions.Entity = map[string]interface{}{}
+	actionOptions.Fields = &bson.M{"_id": 1}
 
-	if _, err = models.FindOne(options); err != nil {
+	if _, err = this.FindOne(actionOptions); err != nil {
 
 		return
 	}
@@ -897,12 +1044,36 @@ func (t *Mongo) exec(f *Filter, execAction execfn, errorAction func(*errs.Error)
 		ctx, _ = context.WithTimeout(context.Background(), time.Minute)
 	}
 
+	
+	fmt.Println("###############", t.client == nil)
+
 	collection := t.client.Database(f.DB).Collection(f.Collection)
 
 	execAction(ctx, collection)
 	return
 }
 
+func createDebugEvent(options *Filter, eventType string, fn func(event *DebugEvent)) {
+	// debug := options.Context.Values().Get("#debug")
+	if options.Context != nil {
+		debug, defined := options.Context.Values().Get("#debug").(*DebugTaks)
+		if defined {
+			event := debug.Event(eventType, "")
+			event.Data = iris.Map{
+				"database":   options.DB,
+				"collection": options.Collection,
+				"query":      options.Query,
+				"aggregate":  options.Pipeline,
+				"sort":       options.Sort,
+				"fields":     options.Fields,
+				"maxResults": options.MaxResults,
+				"patchs":     options.Patchs,
+			}
+			fn(event)
+		}
+	}
+}
+
 func DeletedPatch() *bson.A {
 	return &bson.A{
 		bson.M{

+ 3 - 4
api/sse/hub.go

@@ -129,7 +129,6 @@ func (hub *SSEHub) GetChannel(channelID string) *Channel {
 }
 
 func (hub *SSEHub) Dispatch(event *Event, channels ...string) {
-
 	var (
 		exists bool
 		err    error
@@ -147,8 +146,7 @@ func (hub *SSEHub) Dispatch(event *Event, channels ...string) {
 
 		conn.Do("RPUSH", channel, string(eventBytes))
 	}
-	
-	hub.consume <- true
+	go func(){ hub.consume <- true }()
 }
 
 func (hub *SSEHub) Close(channels ...string) {
@@ -275,6 +273,7 @@ reset:
 			}()
 		case <-hub.consume:
 			// ctx.Application().Logger().Warnf("init.notification.loop 5s")
+			
 			if count, redisErr = redis.Int(conn.Do("LLEN", channelId)); count == 0 || redisErr != nil {
 				continue reset
 			}
@@ -284,7 +283,7 @@ reset:
 			for ; count > 0; count-- {
 
 				if payload, redisErr = redis.String(conn.Do("LPOP", channelId)); redisErr != nil {
-					ctx.Application().Logger().Errorf("NotificationError:", redisErr)
+					// ctx.Application().Logger().Errorf("NotificationError:", redisErr)
 				} else {
 					event := &Event{}
 					json.Unmarshal([]byte(payload), event)

+ 62 - 2
api/utils.go

@@ -16,6 +16,7 @@ import (
 	"go.mongodb.org/mongo-driver/bson"
 	"go.mongodb.org/mongo-driver/bson/primitive"
 	"go.mongodb.org/mongo-driver/mongo"
+	"golang.org/x/crypto/bcrypt"
 )
 
 var (
@@ -23,6 +24,8 @@ var (
 
 	Environment = map[string]interface{}{}
 	ToReference = references{}
+	Format      = fmt.Sprintf
+	Dump        = spew.Dump
 )
 
 type references struct {
@@ -46,6 +49,26 @@ func (this *references) Bool(value bool) *bool {
 	return &value
 }
 
+func (this *references) Int(value int) *int {
+	return &value
+}
+
+func (this *references) Int32(value int32) *int32 {
+	return &value
+}
+
+func (this *references) Int64(value int64) *int64 {
+	return &value
+}
+
+func (this *references) Float32(value float32) *float32 {
+	return &value
+}
+
+func (this *references) Float64(value float32) *float32 {
+	return &value
+}
+
 type ModeInterface interface {
 	Mode() string
 }
@@ -59,6 +82,7 @@ type PatchHistoryRegister struct {
 }
 
 type EntityModel struct {
+	// Id        primitive.ObjectID `bson:"_id" json:"-"`
 	Mode               string  `bson:"-" json:"-"`
 	ApplicationVersion *string `bson:"appVersion,omitempty" json:"-"`
 	Deleted            *bool   `bson:"deleted,omitempty" json:"-"`
@@ -77,6 +101,28 @@ type EntityModel struct {
 // 	return model.Rules
 // }
 
+
+func PasswordBcryptHash(password string)(hash string, err *errs.Error) { 
+	var (
+		bytes  []byte
+		errgen error
+	)
+
+	if bytes, errgen = bcrypt.GenerateFromPassword(
+		[]byte(password),
+		14,
+	); errgen != nil {
+		err = errs.Internal().Details(&errs.Detail{
+			Reason:  "passwordHashFailed",
+			Message: "Falha ao processar a senha",
+		})
+		return
+	}
+
+	hash = string(bytes)
+	return
+}
+
 func UserIDString(ctx context.Context) (id string, err *errs.Error) {
 
 	if user, ok := ctx.Values().Get("$user.ref").(map[string]interface{}); ok {
@@ -315,7 +361,9 @@ func finalizeRequest(ctx context.Context, resp interface{}, err *errs.Error) {
 		ctx.StopExecution()
 
 		if debug := ctx.Values().Get("#debug"); debug != nil {
-			debug.(*DebugTaks).Finalize()
+			go func() {
+				debug.(*DebugTaks).Finalize()
+			}()
 		}
 
 		if err != nil {
@@ -334,7 +382,7 @@ func finalizeRequest(ctx context.Context, resp interface{}, err *errs.Error) {
 		if r := recover(); r != nil {
 			fmt.Println("Recovered in f", r)
 		}
-		fmt.Println(string(ctx.Values().Serialize()))
+		// fmt.Println(string(ctx.Values().Serialize()))
 	}()
 
 	if err != nil {
@@ -674,6 +722,18 @@ func log(color string, m string) {
 	fmt.Printf("\x1b[%s;1m%s\x1b[0m\n", color, m)
 }
 
+func ObjectIDFromHex(objectId string) (id primitive.ObjectID, err *errs.Error) {
+	var errd error
+
+	if id, errd = primitive.ObjectIDFromHex(objectId); errd != nil {
+		err = errs.InvalidArgument().Details(&errs.Detail{
+			Message: errd.Error(),
+			Reason:  "InvalidArgument",
+		})
+	}
+	return
+}
+
 // func ParseRequestTest(ctx context.Context) {
 // 	var (
 // 		err    error

+ 3 - 0
build.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o eon-amd64 && \
+docker build -t eon-compile:0.0.1 .

+ 46 - 38
common/build_commands.go

@@ -1,18 +1,16 @@
 package common
 
 import (
-	"encoding/json"
 	"fmt"
 	"os/exec"
 	"regexp"
 	"strings"
-
-	"github.com/jeremywohl/flatten"
 )
 
 type Command struct {
 	Id          string `json:"id"`
 	Cmd         string `json:"cmd"`
+	Description string `json:"description"`
 	IgnoreErros bool   `json:"ignore.errors"`
 }
 
@@ -21,6 +19,8 @@ type BuildSet struct {
 	Cmd     string                `json:"cmd"`
 	Workdir string                `json:"workdir"`
 	Steps   []*Command            `json:"steps"`
+	Before  []*Command            `json:"beforeCompile"`
+	After   []*Command            `json:"afterCompile"`
 	Options *BuildOptions         `json:"options"`
 }
 
@@ -80,43 +80,34 @@ type BuildMode struct {
 }
 
 var (
-	buildSet = &BuildSet{
-		Steps: []*Command{},
-		Modes: map[string]*BuildMode{
-			"default": &BuildMode{},
-		},
-	}
+	expressionRegexp = regexp.MustCompile(`\{([\w\.]+)\}`)
 )
 
 func NewBuildOptions() *BuildOptions {
 	return &BuildOptions{
-		Mode: "default",
+		Mode:               "default",
+		ProjectDotNotation: map[string]interface{}{},
 	}
 }
 
-func RunBuildCommads(project *Project, buildOptions *BuildOptions) (err error) {
+func runBuildCommads(project *Project, client *Client, buildOptions *BuildOptions) (err error) {
 	var (
-		instance           *exec.Cmd
-		variables          = map[string]interface{}{}
-		variablesJson, cmd string
-		step               int
-		out                []byte
-		buildfile          = "build/build"
-		mode               = project.Mode
+		instance         *exec.Cmd
+		translateOptions *BuildSet
+		step             int
+		cmd              string
+		out              []byte
+		// variables        = map[string]interface{}{}
+		buildfile = fmt.Sprintf("build/%s", client.Id)
+		mode      = project.Mode
+		buildSet  = &BuildSet{
+			Steps: []*Command{},
+			Modes: map[string]*BuildMode{
+				"default": {},
+			},
+		}
 	)
 
-	if out, err = json.Marshal(project); err != nil {
-		return
-	}
-
-	if variablesJson, err = flatten.FlattenString(string(out), "project.", flatten.DotStyle); err != nil {
-		return
-	}
-
-	if err = json.Unmarshal([]byte(variablesJson), &variables); err != nil {
-		return
-	}
-
 	if buildOptions == nil {
 		buildOptions = NewBuildOptions()
 	}
@@ -124,33 +115,54 @@ func RunBuildCommads(project *Project, buildOptions *BuildOptions) (err error) {
 	buildOptions.Parse()
 
 	if err = JsonParseMode(buildfile, mode, &buildSet); err != nil {
-		return
+		err = nil
+		fmt.Printf("[warning] running '%s' without build file", client.Id)
+		buildOptions.Mode = "default"
 	}
 
 	buildSet.Options = buildOptions
 
+	if bo, found := project.TranslatorBuildOptionsMap[client.Id]; found {
+		translateOptions, err = bo(project, buildOptions)
+		if translateOptions.Before != nil {
+			buildSet.Steps = append(translateOptions.Before, buildSet.Steps...)
+		}
+		if translateOptions.After != nil {
+			buildSet.Steps = append(buildSet.Steps, translateOptions.After...)
+		}
+	}
+	if buildSet.After != nil {
+		buildSet.Steps = append(buildSet.Steps, buildSet.After...)
+	}
+
 	fmt.Println("Running build commands... on mode ", mode)
+	fmt.Println("RUN translate ", client.Id)
 
 	cmds := buildSet.Commands()
 	total := len(cmds)
 
 	for k, command := range cmds {
 
-		if cmd, err = parseCommand(command, variables); err != nil {
+		if cmd, err = parseCommand(command, buildOptions.ProjectDotNotation); err != nil {
 			return
 		}
 		step = k + 1
 
+		// goterm.MoveCursor(1, 1)
+
 		if buildOptions.IgnoreStep(step) {
-			fmt.Printf("Step (%d / %d) - [ Skiped ] %s\n", step, total, cmd)
+			// goterm.Printf("\tStep (%d / %d) - [ Skiped ] %s\n", step, total, cmd)
+			fmt.Printf("\tStep (%d / %d) - [ Skiped ] %s\n", step, total, cmd)
 			continue
 		}
 
-		fmt.Printf("Step (%d / %d) - %s\n", step, total, cmd)
+		// goterm.Printf("\tStep (%d / %d) - %s\n", step, total, cmd)
+		fmt.Printf("\tStep (%d / %d) - %s\n", step, total, cmd)
 
 		instance = exec.Command("sh", "-c", cmd)
 		instance.Dir = buildSet.Workdir
 
+		// goterm.Flush() // Call it every time at the end of rendering
 		if out, err = instance.CombinedOutput(); err != nil {
 			fmt.Println(string(out))
 			return
@@ -160,10 +172,6 @@ func RunBuildCommads(project *Project, buildOptions *BuildOptions) (err error) {
 	return
 }
 
-var (
-	expressionRegexp = regexp.MustCompile(`\{([\w\.]+)\}`)
-)
-
 func parseCommand(c *Command, variables map[string]interface{}) (string, error) {
 	var (
 		expression, replace, dotPathName string

+ 102 - 52
common/models.go

@@ -12,6 +12,7 @@ import (
 	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api"
 	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
 	ts "git.eugeniocarvalho.dev/eugeniucarvalho/gg/generators/typescript"
+	"github.com/jeremywohl/flatten"
 	"github.com/kataras/iris/v12/context"
 	"go.mongodb.org/mongo-driver/bson/primitive"
 )
@@ -56,6 +57,7 @@ type BuildOptions struct {
 	Mode                   string
 	IgnoreBuildSteps       string
 	IgnoreBuildStepsValues map[int]bool
+	ProjectDotNotation     map[string]interface{}
 }
 
 func (b *BuildOptions) IgnoreStep(step int) bool {
@@ -90,46 +92,49 @@ func ImportMap(base string) string {
 }
 
 type Project struct {
-	OutPath           string                   `json:"outPath"`
-	Package           string                   `json:"package"`
-	Kind              string                   `json:"kind"`
-	Etag              string                   `json:"etag"`
-	Version           string                   `json:"version"`
-	BuildVersion      string                   `json:"buildVersion"`
-	ID                string                   `json:"id"`
-	Name              string                   `json:"name"`
-	DataBaseSufix     string                   `json:"dataBaseSufix"`
-	Mode              string                   `json:"mode"`
-	Revision          string                   `json:"revision"`
-	Title             string                   `json:"title"`
-	Description       string                   `json:"description"`
-	OwnerDomain       string                   `json:"ownerDomain"`
-	OwnerName         string                   `json:"ownerName"`
-	DocumentationLink string                   `json:"documentationLink"`
-	Protocol          string                   `json:"protocol"`
-	BaseURL           string                   `json:"baseUrl"`
-	BasePath          string                   `json:"basePath"`
-	Middlewares       []string                 `json:"middlewares"`
-	ServicePath       string                   `json:"servicePath"`
-	GitRepository     string                   `json:"git.repository"`
-	Environment       Environment              `json:"environment"`
-	Variables         map[string]interface{}   `json:"variables"`
-	Resource          *Resource                `json:"-"`
-	Schemas           []*Entity                `json:"schemas"`
-	SchemasRef        map[string]*Entity       `json:"-"`
-	Resources         []*Resource              `json:"resources"`
-	Auth              Auth                     `json:"auth"`
-	TypeScriptSource  *ts.File                 `json:"-"`
-	Icons             map[string]string        `json:"icons"`
-	ReplaceWhenEmpty  map[string]bool          `json:"ReplaceWhenEmpty"`
-	OmitEmpty         map[string]bool          `json:"omitempty"`
-	Clients           []*Client                `json:"clients,omitempty"`
-	Translators       map[string]TranslationFn `json:"-"`
-	FormatMap         map[string]string        `json:"-"`
-	Queries           *QueryDef                `json:"queries"`
-	ACL               *ACL                     `json:"acl"`
-	Custom            map[string]interface{}   `json:"custom"`
+	OutPath                   string                                                      `json:"outPath"`
+	CurrentDirectory          string                                                      `json:"outPath"`
+	Package                   string                                                      `json:"package"`
+	Kind                      string                                                      `json:"kind"`
+	Etag                      string                                                      `json:"etag"`
+	Version                   string                                                      `json:"version"`
+	BuildVersion              string                                                      `json:"buildVersion"`
+	ID                        string                                                      `json:"id"`
+	Name                      string                                                      `json:"name"`
+	DataBaseSufix             string                                                      `json:"dataBaseSufix"`
+	Mode                      string                                                      `json:"mode"`
+	Revision                  string                                                      `json:"revision"`
+	Title                     string                                                      `json:"title"`
+	Description               string                                                      `json:"description"`
+	OwnerDomain               string                                                      `json:"ownerDomain"`
+	OwnerName                 string                                                      `json:"ownerName"`
+	DocumentationLink         string                                                      `json:"documentationLink"`
+	Protocol                  string                                                      `json:"protocol"`
+	BaseURL                   string                                                      `json:"baseUrl"`
+	BasePath                  string                                                      `json:"basePath"`
+	Middlewares               []string                                                    `json:"middlewares"`
+	ServicePath               string                                                      `json:"servicePath"`
+	GitRepository             string                                                      `json:"git.repository"`
+	Environment               Environment                                                 `json:"environment"`
+	Variables                 map[string]interface{}                                      `json:"variables"`
+	Resource                  *Resource                                                   `json:"-"`
+	Schemas                   []*Entity                                                   `json:"schemas"`
+	SchemasRef                map[string]*Entity                                          `json:"-"`
+	Resources                 []*Resource                                                 `json:"resources"`
+	Auth                      Auth                                                        `json:"auth"`
+	TypeScriptSource          *ts.File                                                    `json:"-"`
+	Icons                     map[string]string                                           `json:"icons"`
+	ReplaceWhenEmpty          map[string]bool                                             `json:"ReplaceWhenEmpty"`
+	OmitEmpty                 map[string]bool                                             `json:"omitempty"`
+	Clients                   []*Client                                                   `json:"clients,omitempty"`
+	Translators               map[string]TranslationFn                                    `json:"-"`
+	FormatMap                 map[string]string                                           `json:"-"`
+	Queries                   *QueryDef                                                   `json:"queries"`
+	ACL                       *ACL                                                        `json:"acl"`
+	Custom                    map[string]interface{}                                      `json:"custom"`
+	TranslatorBuildOptionsMap map[string]func(*Project, *BuildOptions) (*BuildSet, error) `json:"-"`
 }
+
 type ACL struct {
 	Roles       []*Role       `json:"roles"`
 	Permissions []*Permission `json:"permissions"`
@@ -414,10 +419,11 @@ func RequestParams(args string, params map[string]*Parameter) func(ctx context.C
 
 var (
 	convertionTypeFunctions = map[string]func(interface{}) (interface{}, *errs.Error){
-		"ObjectID": stringToObjectId,
-		"bool":     stringToBool,
-		"int":      stringToInt,
-		"number":   stringToFloat,
+		"ObjectID":      stringToObjectId,
+		"array(string)": stringToArrayString,
+		"bool":          stringToBool,
+		"int":           stringToInt,
+		"number":        stringToFloat,
 	}
 
 	validationParamFunctions = map[string]func(*Parameter, interface{}, interface{}) *errs.Error{
@@ -624,6 +630,21 @@ func convertValueByType(typ string, value interface{}) (interface{}, *errs.Error
 	return value, nil
 }
 
+func stringToArrayString(value interface{}) (stringArray interface{}, err *errs.Error) {
+	var (
+		valueString string
+		ok          bool
+	)
+	if valueString, ok = value.(string); !ok {
+		err = errs.InvalidArgument().Details(&errs.Detail{
+			Reason: "StringToArrayString expect a string value",
+		})
+		return
+	}
+	stringArray = strings.Split(valueString, ",")
+	return
+}
+
 // func validateParam(param *Parameter, value interface{}) (interface{}, *errs.Error) {
 // 	var err *errs.Error
 
@@ -683,21 +704,50 @@ type EntityInfo struct {
 
 type TranslationFn func(p *Project) error
 
-func (p *Project) Build(b *BuildOptions) error {
-	var err error
+func (project *Project) Build(options *BuildOptions) (err error) {
+	var (
+		variablesJson string
+		out           []byte
+	)
+
+	options.ProjectDotNotation = map[string]interface{}{}
 
-	for _, c := range p.Clients {
-		if fn, found := p.Translators[c.Id]; found {
-			if err = fn(p); err != nil {
-				fmt.Println("error on ", c.Id)
+	if out, err = json.Marshal(project); err != nil {
+		return
+	}
+
+	if variablesJson, err = flatten.FlattenString(
+		string(out),
+		"project.",
+		flatten.DotStyle,
+	); err != nil {
+		return
+	}
+
+	if err = json.Unmarshal(
+		[]byte(variablesJson),
+		&options.ProjectDotNotation,
+	); err != nil {
+		return
+	}
+
+	for _, client := range project.Clients {
+		if fn, found := project.Translators[client.Id]; found {
+
+			if err = fn(project); err != nil {
 				return err
 			}
+
+			if err = runBuildCommads(project, client, options); err != nil {
+				return
+			}
 		} else {
-			return fmt.Errorf("Middleware '%s' not defined!", c.Id)
+			err = fmt.Errorf("compiler translation model '%s' not defined", client.Id)
+			return
 		}
 	}
-	// fmt.Println("--- RunBuildCommads")
-	return RunBuildCommads(p, b)
+
+	return
 }
 
 func (p *Project) OutDirectory(path string) {

+ 5 - 2
common/utils.go

@@ -97,6 +97,11 @@ func Mkdir(perm os.FileMode, paths ...string) (err error) {
 }
 
 func JsonParseMode(path, mode string, v interface{}) (err error) {
+	defer func() {
+		if rec := recover(); rec != nil {
+			err = fmt.Errorf("%s", rec)
+		}
+	}()
 	if err = ParseJson(path+".json", v); err != nil {
 		return
 	}
@@ -124,8 +129,6 @@ func ParseJson(filename string, v interface{}, ignoreFileExists ...bool) error {
 	return nil
 }
 
-
-
 func CamelToUnder(input string) string {
 	out := camelToUnderRegex.ReplaceAllStringFunc(input, func(m string) string {
 		return "_" + strings.ToLower(m)

BIN
eon-amd64


+ 10 - 2
gen/gen.go

@@ -36,6 +36,10 @@ func CreateProject(mode string) (*Project, error) {
 				"go":       GO.Translate,
 				"angular6": TS.Translate,
 			},
+			TranslatorBuildOptionsMap: map[string]func(*Project, *BuildOptions) (*BuildSet, error){
+				"go":       GO.Build,
+				"angular6": TS.Build,
+			},
 		}
 	)
 
@@ -43,6 +47,8 @@ func CreateProject(mode string) (*Project, error) {
 		return nil, err
 	}
 
+	p.CurrentDirectory = directory
+
 	// Carrega a configuracao default do projeto
 	// root := []string{fmt.Sprintf("%s/project.json", directory)}
 
@@ -323,7 +329,7 @@ func interpolateProject(project *Project) (err error) {
 }
 
 var (
-	expandTranscludeExpression = regexp.MustCompile(`"\$\._transclude_":true`)
+	expandTranscludeExpression = regexp.MustCompile(`"\$\._transclude_":(true|"\w+")`)
 	expandVarExpression        = regexp.MustCompile(`"\$(\.\w+)"`)
 	removeAllSpacesExpression  = regexp.MustCompile(`(\s|\t|\r|\n)+`)
 )
@@ -352,7 +358,9 @@ func loadQueries(project *Project, directory string) (err error) {
 			data = removeAllSpacesExpression.ReplaceAllString(data, "")
 
 			data = expandTranscludeExpression.ReplaceAllStringFunc(data, func(input string) string {
-				return "{._transclude_}"
+				parts := strings.Split(input, ":")
+				selector := strings.Trim(parts[len(parts)-1], `"`)
+				return fmt.Sprintf("{._transclude_%s}", selector)
 			})
 
 			data = expandVarExpression.ReplaceAllStringFunc(data, func(input string) string {

+ 4 - 3
go.mod

@@ -3,10 +3,11 @@ module git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen
 go 1.13
 
 require (
-	git.eugeniocarvalho.dev/eugeniucarvalho/gg v1.0.1
+	git.eugeniocarvalho.dev/eugeniucarvalho/gg v1.0.2
 	git.eugeniocarvalho.dev/eugeniucarvalho/utils v1.0.2
 	github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a // indirect
 	github.com/DataDog/zstd v1.4.4 // indirect
+	github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129
 	github.com/dave/jennifer v1.4.0
 	github.com/davecgh/go-spew v1.1.1
 	github.com/eugeniucarvalho/validator v0.0.0-20190212223505-9efa4c9aff1a
@@ -25,8 +26,8 @@ require (
 	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
 	github.com/xdg/stringprep v1.0.0 // indirect
 	go.mongodb.org/mongo-driver v1.3.2
-	golang.org/x/crypto v0.0.0-20191219195013-becbf705a915 // indirect
-	golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
+	golang.org/x/crypto v0.0.0-20191219195013-becbf705a915
+	golang.org/x/net v0.0.0-20200528225125-3c3fba18258b // indirect
 	golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 	gopkg.in/yaml.v2 v2.2.7 // indirect

+ 10 - 0
go.sum

@@ -2,6 +2,8 @@ git.eugeniocarvalho.dev/eugeniucarvalho/gg v1.0.0 h1:BcjI7XjCPLrcFeGeV3gJzt8Mf98
 git.eugeniocarvalho.dev/eugeniucarvalho/gg v1.0.0/go.mod h1:MsGB35SSYCc7sCff9PcM08TAd9UvFhFhXMvU3STh82k=
 git.eugeniocarvalho.dev/eugeniucarvalho/gg v1.0.1 h1:vMtnzkyW0NdUtL7/M0ihbLkE06zXzwyGKmLNmfxnRNs=
 git.eugeniocarvalho.dev/eugeniucarvalho/gg v1.0.1/go.mod h1:Uw49/w2AVJNPUDR8uiRVb0cyGg5vy1bThuggLwnziFo=
+git.eugeniocarvalho.dev/eugeniucarvalho/gg v1.0.2 h1:oqhoKd/7GD0oim7Oy+08mBWqkiWf2w1kkTjs310+XcU=
+git.eugeniocarvalho.dev/eugeniucarvalho/gg v1.0.2/go.mod h1:Uw49/w2AVJNPUDR8uiRVb0cyGg5vy1bThuggLwnziFo=
 git.eugeniocarvalho.dev/eugeniucarvalho/utils v1.0.2 h1:pi9aCd86+20NXV+QYITfUuEhY9rqv14mWeuBoT9sLq0=
 git.eugeniocarvalho.dev/eugeniucarvalho/utils v1.0.2/go.mod h1:GoSSifraiFtzF/IVzcqFhy5DKR/npEiTNvpKeL1/Jc4=
 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
@@ -21,6 +23,8 @@ github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqR
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible h1:Ppm0npCCsmuR9oQaBtRuZcmILVE74aXE+AmrJj8L2ns=
 github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
+github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129 h1:gfAMKE626QEuKG3si0pdTRcr/YEbBoxY+3GOH3gWvl4=
+github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129/go.mod h1:u9UyCz2eTrSGy6fbupqJ54eY5c4IC8gREQ1053dK12U=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -159,6 +163,7 @@ github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUr
 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/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -183,6 +188,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
@@ -216,6 +222,8 @@ golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8ou
 golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200528225125-3c3fba18258b h1:IYiJPiJfzktmDAO1HQiwjMjwjlYKHAL7KzeD544RJPs=
+golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -231,6 +239,8 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 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=

+ 52 - 0
translate/got/build.go

@@ -0,0 +1,52 @@
+package got
+
+import (
+	"fmt"
+	"os"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+)
+
+var Build = func(project *Project, buildOptions *BuildOptions) (fn *BuildSet, err error) {
+	var (
+		files  []os.FileInfo
+		before = []*Command{
+			{
+				Cmd:         "rm -f go.*",
+				Description: "Remove os arquivos do módulo do GO",
+			},
+			{
+				Cmd:         "go mod init {project.custom.go.package.repository}/build",
+				Description: "Inicializa o módulo do GO",
+			},
+		}
+	)
+
+	path := fmt.Sprintf("%s/include/go", project.CurrentDirectory)
+	if files, err = GetFiles(path); err == nil {
+		for _, file := range files {
+			if file.Name()[0] == '*' {
+				continue
+			}
+			if file.IsDir() {
+				name := file.Name()
+				before = append(before, &Command{
+					Cmd:         fmt.Sprintf("go mod edit -replace {project.custom.go.package.repository}/build/v1/%s=./v1/%s", name, name),
+					Description: "Inicializa o módulo do GO",
+				})
+			}
+		}
+	}
+
+	fn = &BuildSet{
+		Before: before,
+		After: []*Command{
+			{
+				Id:          "build",
+				Cmd:         "GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o server",
+				Description: "Compila a aplicação server da api",
+			},
+		},
+	}
+	return
+}

+ 23 - 8
translate/got/functions.go

@@ -52,11 +52,11 @@ func generateHookCall(project *Project, method *Method, hookId string) {
 
 func parseMethodActions(actions []Action) string {
 	actionsCallStmt := []string{}
-	var actionStmt string
+	var actionStmt, actionId string
 
 	for _, action := range actions {
-
-		actionStmt = fmt.Sprintf("actions.%s", strings.Title(action.ID))
+		actionId = strings.Title(action.ID)
+		actionStmt = fmt.Sprintf("actions.%s", actionId)
 
 		if action.Context != nil {
 			actionStmt = fmt.Sprintf(
@@ -66,15 +66,30 @@ func parseMethodActions(actions []Action) string {
 			)
 		}
 
-		actionsCallStmt = append(actionsCallStmt, actionStmt)
+		actionsCallStmt = append(
+			actionsCallStmt, 
+			fmt.Sprintf(`{"%s", %s}`, actionId,actionStmt),
+		)
 	}
 
-	return strings.Join(actionsCallStmt, ",\n")
+	if len(actionsCallStmt) > 0{
+		return fmt.Sprintf(`[]Action {
+			%s,
+			}`, strings.Join(actionsCallStmt, ",\n"))
+	}
+	return ""
+
 }
 
-func getCustom(options map[string]interface{}, path string) (resp interface{}) {
-	if options != nil {
-		resp = options[path]
+func getCustom(options map[string]interface{}, path string, fallback ...interface{}) (resp interface{}) {
+	found := false
+	if options != nil  {
+		if resp, found = options[path]; found {
+			return
+		}
+	} 
+	for _, value := range fallback {
+		resp = value
 	}
 	return
 }

+ 18 - 7
translate/got/middleware_delete.go

@@ -17,8 +17,12 @@ var (
 func init() {
 
 	deleteStmtsTmpl, deleteStmtsErr = ParseTemplate(`
+		var (
+			values = ctx.Values()
+		)
+		values.Set("hard.deletion", {{.hardDeletion}})
 
-		filter := ctx.Values().Get("$filter").(*api.Filter)
+		filter := values.Get("$filter").(*api.Filter)
 		filter.DB = "{{.dbName}}"
 		filter.Collection = "{{.collectionName}}"
 			
@@ -31,6 +35,7 @@ func init() {
 		}
 		{{end}}
 
+
 		{{if .preconditions}}
 			if _, err = executeAction(
 				ctx,
@@ -39,14 +44,19 @@ func init() {
 				return
 			}
 		{{end}}
-
-		if replacement := ctx.Values().Get("replace.default.patch"); replacement != nil  {
-			filter.Patchs = replacement.(*bson.A)
+		
+		if hardDeletion, _ := values.GetBool("hard.deletion"); hardDeletion {
+			_, err = models.Api.RemoveOne(filter)
 		} else {
-			filter.Patchs = api.DeletedPatch()
+			if replacement := values.Get("replace.default.patch"); replacement != nil  {
+				filter.Patchs = replacement.(*bson.A)
+			} else {
+				filter.Patchs = api.DeletedPatch()
+			}
+			_, err = models.Api.PatchOne(filter)
 		}
-	
-		if _, err = models.Api.PatchOne(filter); err != nil {
+
+		if err != nil {
 			err.Details(&errs.Detail{
 				Dominio:      "",
 				Reason:       "",
@@ -137,6 +147,7 @@ var (
 					"dependenceMethod": dependenceMethod,
 					"beforePersist":    beforePersist,
 					"beforeSend":       beforeSend,
+					"hardDeletion":     getCustom(method.Custom, "go.entiy.hard.deletion", false),
 					"preconditions":    parseMethodActions(method.Preconditions),
 					"beforeResponse":   parseMethodActions(method.BeforeResponse),
 				}

+ 4 - 4
translate/got/middleware_get_list.go

@@ -18,9 +18,9 @@ func init() {
 
 	getListStmtsTmpl, getListStmtsErr = ParseTemplate(`
 	var (
-		ntoken   string
+		// ntoken   string
+		// count    = 0
 		entities = []*models.{{.entity}}{}
-		count    = 0
 		values = ctx.Values()
 	)
 
@@ -61,8 +61,8 @@ func init() {
 
 	resp = &models.{{.responseStruct}}{
 		Itens:              entities,
-		NextPageToken:      ntoken,
-		ResultSizeEstimate: count,
+		NextPageToken:      options.NextPageToken,
+		ResultSizeEstimate: options.ResultSizeEstimate,
 	}
 
 	return`)

+ 11 - 10
translate/got/middleware_patch.go

@@ -46,13 +46,14 @@ func init() {
 		}
 	{{end}}
 
-	filter := values.Get("$filter").(*api.Filter)
+	options := values.Get("$filter").(*api.Filter)
 
-	filter.DB = "{{.dbName}}"
-	filter.Collection = "{{.collectionName}}"
-	filter.Patchs = patchs.Patch()
+	options.DB = "{{.dbName}}"
+	options.Collection = "{{.collectionName}}"
+	options.Patchs, options.Options = patchs.Patch()
+	api.Dump(options.Options)
 
-	if _, err = models.Api.PatchOne(filter); err != nil {
+	if _, err = models.Api.PatchOne(options); err != nil {
 
 		err.Details(&errs.Detail{
 			Dominio:      "",
@@ -64,9 +65,9 @@ func init() {
 		return
 	}
 
-	filter.Entity = &models.{{.entity}}{}
+	options.Entity = &models.{{.entity}}{}
 
-	if _, err = models.Api.FindOne(filter); err != nil {
+	if _, err = models.Api.FindOne(options); err != nil {
 
 		err.Details(&errs.Detail{
 			Dominio:      "",
@@ -78,11 +79,11 @@ func init() {
 		return
 	}
 
-	values.Set("{{.entityAlias}}.after.patch", filter.Entity)
+	values.Set("{{.entityAlias}}.after.patch", options.Entity)
 
 	{{if .hasUpdateRelation}} // Cria uma thread que executa a atualizaca das referências.
 	go func() {
-		UpdateRelation{{.entity}}(filter)
+		UpdateRelation{{.entity}}(options)
 	}()	{{end}}
 
 	{{if .beforeResponse}}
@@ -93,7 +94,7 @@ func init() {
 		return
 	}
 	{{end}}
-	resp = filter.Entity
+	resp = options.Entity
 	return`)
 
 	if patchStmtsErr != nil {

+ 1 - 1
translate/got/middleware_undelete.go

@@ -44,7 +44,7 @@ func init() {
 	options := values.Get("$filter").(*api.Filter)
 	options.DB = "{{.dbName}}"
 	options.Collection = "{{.collectionName}}"
-	options.Patchs = patchs.Patch()
+	options.Patchs, options.Options = patchs.Patch()
 
 	if _, err = models.Api.PatchOne(options); err != nil {
 		return

+ 2 - 2
translate/got/middleware_update_relation.go

@@ -17,6 +17,7 @@ var (
 func init() {
 	updateRelationStmtsTmpl, updateRelationStmtsErr = ParseTemplate(`
 	func UpdateRelation{{.entity}}(options *api.Filter) {
+		time.Sleep(time.Second * 1)
 		var (
 			filter *api.Filter
 			err    *errs.Error
@@ -44,11 +45,10 @@ func init() {
 			}
 			
 			if _, err = models.Api.PatchMany(filter); err != nil {
-				fmt.Println(err.Error())
+				fmt.Println("[WARNING]", err.Error())
 			}
 		}()
 		{{end}}
-
 		wg.Wait()
 	}`)
 

+ 21 - 8
translate/got/resources.go

@@ -57,7 +57,9 @@ func InitFile(file *G.File, resource *Resource, p *Project) {
 	}
 
 	file.Var().Defs(
+		G.Id(fmt.Sprintf("_%s_spew_%d_ =", resource.Entity, now)).Qual("github.com/davecgh/go-spew/spew", "Dump"),
 		G.Id(fmt.Sprintf("_%s_bson_%d_ *", resource.Entity, now)).Qual(BSON, "M"),
+		G.Id(fmt.Sprintf("_%s_time_%d_ *", resource.Entity, now)).Qual("time", "Duration"),
 		G.Id(fmt.Sprintf("_%s_error_%d_ *", resource.Entity, now)).Qual(API_ERROR, "Error"),
 		G.Id(fmt.Sprintf("_%s_sync_%d_ *", resource.Entity, now)).Qual("sync", "WaitGroup"),
 		G.Id(fmt.Sprintf("_%s_primitive_%d_ *", resource.Entity, now)).Qual("go.mongodb.org/mongo-driver/bson/primitive", "ObjectID"),
@@ -284,9 +286,10 @@ func GenIndexApi(p *Project) error {
 	// Inicializa o mapa de filtros da api
 	Index.Id(`
 		import(
-			"reflect"
-			"runtime"
-			"strings"
+			// "reflect"
+			// "runtime"
+			// "strings"
+			"fmt"
 		)
 		var (
 			filtersApiReference = map[string]*`).Qual(CODE_GEN_V2_COMMON, "ApiFilter").Id(`{}
@@ -315,19 +318,29 @@ func GenIndexApi(p *Project) error {
 
 		}
 
-		func executeAction(ctx context.Context, actions ...func(context.Context) (interface{}, *`).Qual(API_ERROR, "Error").Id(`)) (resp interface{},err *errs.Error){
+		type Action struct {
+			Name string
+			Fn func(context.Context) (interface{}, *`).Qual(API_ERROR, "Error").Id(`)
+		}
+
+		func executeAction(ctx context.Context, actions []Action) (resp interface{},err *errs.Error){
 			var (
 				stopPropagation bool
-				parts []string
+				// parts []string
+				event *api.DebugEvent
 			)
 			debug, debugActive := ctx.Values().Get("#debug").(*api.DebugTaks);
 			
 			for _, action := range actions {
-				resp, err = action(ctx)
 				
 				if debugActive {
-					parts = strings.Split(runtime.FuncForPC(reflect.ValueOf(action).Pointer()).Name(), ".")
-					event := debug.Event("execute.action", parts[len(parts)-1])
+					fmt.Println("\texecuteAction -> ", action.Name)
+					event = debug.Event("execute.action", action.Name)
+				}
+
+				resp, err = action.Fn(ctx)
+		
+				if debugActive {
 					event.Data = resp
 					event.Error = err
 				}

+ 110 - 19
translate/got/schemas.go

@@ -23,9 +23,24 @@ var (
 	// float32 float64
 	// complex64 complex128
 
-	primitiveRegex = regexp.MustCompile(`^(bool|string|u?int\d{0,2}|byte|rune|float\d{0,2}|complex\d{2,3})$`)
+	primitiveRegex = regexp.MustCompile(`^(bool|string|u?int\d{0,2}|byte|rune|float\d{0,2}|interface|complex\d{2,3})$`)
 )
 
+func hasReplaceMethod(typ string) bool{ 
+	typ = strings.Replace(typ, "*","", 1)
+	return !primitiveRegex.MatchString(typ)
+}
+
+func replaceAccessType(typ string) (accessType string) {
+	typ = strings.Replace(typ, "*","", 1)
+	if strings.Contains(typ, "map"){
+		accessType = "map"
+	} else {
+		accessType = "id"
+	}
+	return
+}
+
 func GenSchemas(p *Project) error {
 	var (
 		// requiredOnCreate bool
@@ -43,6 +58,7 @@ func GenSchemas(p *Project) error {
 		// values                  G.Dict
 		cproperties         map[string]*G.Statement
 		addUpdate           map[string]string
+		replaceUpdate       map[string]map[string]string
 		removeUpdate        map[string]map[string]string
 		entityName          string
 		entityPatchName     string
@@ -78,7 +94,7 @@ func GenSchemas(p *Project) error {
 			continue
 		}
 
-		inputName = ""
+		inputName = entity.ID
 		entityPatchName = ""
 		hasAddOrRemove = false
 		// requiredOnCreate = false
@@ -125,6 +141,7 @@ func GenSchemas(p *Project) error {
 		referenceProperties = G.Statement{}
 
 		addUpdate = map[string]string{}
+		replaceUpdate = map[string]map[string]string{}
 		removeUpdate = map[string]map[string]string{}
 
 		for _, meta := range entity.Properties {
@@ -137,9 +154,7 @@ func GenSchemas(p *Project) error {
 
 			propName = strings.Title(meta.ID)
 			propertie = G.Id(propName)
-
 			meta.FillTags(p, propName)
-
 			posfix = ""
 			// Registra a relaao entre as entidades
 			if meta.Relation {
@@ -175,6 +190,22 @@ func GenSchemas(p *Project) error {
 
 					patchProperties = append(patchProperties, extend)
 
+					if hasReplaceMethod(typ) {
+						extName = "Replace" + propName
+
+						replaceUpdate[extName] = map[string]string{
+							"name": meta.ID,
+							"type": replaceAccessType(typ),
+						}
+
+						extend = G.Id(extName).Index().Id(ConvType(typ)).Tag(map[string]string{
+							"json": extName + ",omitempty",
+							"bson": extName + ",omitempty",
+						})
+						patchProperties = append(patchProperties, extend)
+					}
+
+
 					extName = "Remove" + propName
 					removeUpdate[extName] = map[string]string{
 						"type":   ConvType(typ),
@@ -401,11 +432,13 @@ func GenSchemas(p *Project) error {
 
 			f.Func().Params(
 				G.Id("t").Op("*").Id(entityPatchName),
-			).Id("Patch").Params().Op("*").Qual(BSON, "A").Block(
-				G.Id("updt").Op(":=").Qual(BSON, "A").Values().Line(),
-				G.Id(`if t.Set != nil {
-					updt = append(updt, bson.M{"$set": t.Set})
-				}`).Line(),
+			).Id("Patch").Params().Params(
+				G.Op("*").Qual(BSON, "A"),
+				G.Op("*").Qual("go.mongodb.org/mongo-driver/mongo/options", "UpdateOptions"),
+			).Block(
+				
+				G.Id("updt").Op(":=").Qual(BSON, "A").Values(),
+				G.Id("patchOptions := ").Qual("go.mongodb.org/mongo-driver/mongo/options","Update").Call(),
 
 				G.Do(func(y *G.Statement) {
 					if !hasAddOrRemove {
@@ -413,11 +446,13 @@ func GenSchemas(p *Project) error {
 					}
 					stmts := G.Statement{
 						G.Id("hasAdd").Op(":=").Lit(0).Line(),
+						G.Id("hasReplace").Op(":=").Lit(0).Line(),
 						G.Id("hasRemove").Op(":=").Lit(0).Line(),
 						G.Id("pull").Op(":=").Qual(BSON, "M").Values().Line(),
 						G.Id("push").Op(":=").Qual(BSON, "M").Values().Line(),
+						G.Id("dotNotation := map[string]interface{}{}").Line(),
+						G.Id("arrayFilters := []interface{}{}").Line(),
 					}
-
 					// add entitys
 					// spew.Dump(addUpdate)
 					for prop, value := range addUpdate {
@@ -428,7 +463,39 @@ func GenSchemas(p *Project) error {
 							}),
 						).Line())
 					}
-					// spew.Dump(removeUpdate)
+					// replace entitys
+					// spew.Dump(addUpdate)
+					for prop, value := range replaceUpdate {
+						prop = fmt.Sprintf("t.%s", prop)
+
+						stmts = append(stmts, G.If(G.Len(G.Id(prop)).Op(">").Lit(0)).Block(
+							G.Id("hasReplace").Op("++"),
+							G.Do(func(replace *G.Statement) {
+								name := value["name"]
+								typi := value["type"]
+								arrayFiltersTemplates := map[string]string{
+									"id": `arrayFilters = append(arrayFilters, bson.M{
+											api.Format("%s._id", key): value.Id,
+										})`,
+									"map": `arrayFilters = append(arrayFilters, bson.M{
+											api.Format("%s._id", key): value["_id"],
+										})`,
+								}
+								arrayFilters := arrayFiltersTemplates[typi]
+								template := fmt.Sprintf(`for idx, value := range %s {
+									key := api.Format("%s%%d", idx)
+									dotNotation[api.Format("%s.$[%%s]", key)] = value
+									%s
+								}`,
+								prop,
+								name,
+								name,
+								arrayFilters,
+								)
+								replace.Add(G.Id(template))
+							}),
+						).Line())
+					}
 					// remove entitys
 					for prop, value := range removeUpdate {
 						stmts = append(stmts, G.If(G.Len(G.Id("t").Dot(prop)).Op(">").Lit(0)).Block(
@@ -447,7 +514,6 @@ func GenSchemas(p *Project) error {
 										}),
 									}
 								}
-
 								hasRemove.Id("pull").Index(G.Lit(value["propId"])).Op("=").Qual(BSON, "M").Values(assign)
 							}),
 						).Line())
@@ -477,18 +543,43 @@ func GenSchemas(p *Project) error {
 
 						// .Index(G.Lit("$push")).Op("=").Id("push"),
 					).Line())
-
+					
 					stmts = append(stmts, G.If(G.Id("hasRemove").Op(">").Lit(0)).Block(
-						// G.Id("updt").Index(G.Lit("$pull")).Op("=").Id("pull"),
-						G.Id("updt").Op("=").Append(
-							G.Id("updt"),
-							G.Qual(BSON, "M").Values(G.Lit("$pull").Id(": pull")),
-						),
+					// G.Id("updt").Index(G.Lit("$pull")).Op("=").Id("pull"),
+					G.Id("updt").Op("=").Append(
+						G.Id("updt"),
+						G.Qual(BSON, "M").Values(G.Lit("$pull").Id(": pull")),
+					),
+					).Line())
+						
+					stmts = append(stmts, G.If(G.Id("hasReplace").Op(">").Lit(0)).Block(
+						G.Id(fmt.Sprintf(`if t.Set == nil {
+								t.Set = &%s{}
+							}
+							t.Set.DotNotation = dotNotation
+							
+							patchOptions.SetArrayFilters(options.ArrayFilters{
+								Filters: arrayFilters,
+							})
+							`, 
+							inputName,
+						)),
 					).Line())
 
 					y.Add(stmts...)
 				}),
-				G.Return(G.Op("&").Id("updt")),
+
+				G.Id(`if t.Set != nil {
+					updt = append(updt, bson.M{"$set": t.Set})
+				}`).Line(),
+
+				// []interface{}{bson.D{
+				// 	{"elem._id", 1},
+				// }}
+				G.Return(
+					G.Op("&").Id("updt"),
+					G.Id("patchOptions"),
+				),
 			)
 
 		}

+ 17 - 12
translate/got/translate.go

@@ -2,6 +2,7 @@ package got
 
 import (
 	"fmt"
+	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -61,6 +62,7 @@ func Translate(p *Project) (err error) {
 func createSymbolicLinks(basedir string) func(string, os.FileInfo, error) error {
 	var (
 		callback func(string, os.FileInfo, error) error
+		input    []byte
 		relative = func(path string) string {
 			return fmt.Sprintf("%s/%s%s", OutputDirectory, "v1", strings.Replace(filepath.Dir(path), basedir, "", 1))
 		}
@@ -84,7 +86,21 @@ func createSymbolicLinks(basedir string) func(string, os.FileInfo, error) error
 
 		} else {
 			// fmt.Println("crinado link symbolico aqui", relative(pathX))
-			os.Symlink(pathX, filepath.Join(relative(pathX), infoX.Name()))
+			// os.Symlink(pathX, filepath.Join(relative(pathX), infoX.Name()))
+
+			if input, err = ioutil.ReadFile(pathX); err != nil {
+				return
+			}
+
+			// fmt.Println("READ FILE", pathX, filepath.Join(relative(pathX), infoX.Name()))
+
+			if err = ioutil.WriteFile(
+				filepath.Join(relative(pathX), infoX.Name()),
+				input,
+				0644,
+			); err != nil {
+				return
+			}
 
 			// fmt.Printf("  dir ------ : 「%v」\n", pathX)
 			// fmt.Printf("  dir: 「%v」\n", strings.Replace(filepath.Dir(pathX), basedir, "", 1))
@@ -111,17 +127,6 @@ func include(p *Project) error {
 	if err = filepath.Walk(basedir, createSymbolicLinks(basedir)); err != nil {
 		return err
 	}
-
-	// args := fmt.Sprintf("ln -st %s/include/go/* %s/%s/", CurrentDirectory, OutputDirectory, p.Package)
-	// args := fmt.Sprintf("ln -st %s/include/go/* %s/%s/", CurrentDirectory, OutputDirectory, p.Package)
-	// command := exec.Command("sh", "-c", args)
-	// var out bytes.Buffer
-	// var out []byte
-	// // command.Stdout = &out
-	// // if err = command.Run(); err != nil {
-	// if out, err = command.Output(); err != nil {
-	// 	fmt.Println("exec command error ", err, args, string(out))
-	// }
 	return err
 }
 

+ 27 - 0
translate/tst/build.go

@@ -0,0 +1,27 @@
+package tst
+
+import (
+	"fmt"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+)
+
+var Build = func(project *Project, buildOptions *BuildOptions) (fn *BuildSet, err error) {
+	var (
+		before = []*Command{}
+	)
+	// path := fmt.Sprintf("%s", ts.OutputDir, project.ID)
+	outputClientDir := fmt.Sprintf("%s/", project.Client("angular6").OutputDir)
+
+	fn = &BuildSet{
+		Before: before,
+		After: []*Command{
+			{
+				Id:          "format_ts",
+				Cmd:         fmt.Sprintf("tsfmt -r %s*.ts", outputClientDir),
+				Description: "Formata todos os arquivos ts",
+			},
+		},
+	}
+	return
+}

+ 118 - 101
translate/tst/resources.go

@@ -40,14 +40,16 @@ func GenResources(p *Project) {
 	// 	),
 	// ).Line()
 	module.Line().Raw(`
-	
-	export interface HttpOptions {
+	export type HttpOptions = {
 		headers?: HttpHeaders;
 		params?: any;
 		withCredentials?: boolean;
 		id?: string;
-	}
-	
+	} & {
+		[prop: string]: any;
+	};
+	  
+
 	export class ServiceStmt {
 		
 		// constructor(protected api: `).Id(ApiClassName(p)).Raw(`) {}
@@ -65,7 +67,7 @@ func GenResources(p *Project) {
 			return u;
 		}
 	
-		_event(url, opt: HttpOptions): Observable<EventSourcePolyfill> {
+		_event<T = any>(url, opt: HttpOptions): Observable<EventSourcePolyfill> {
 			opt = this.api.options(opt);
 			const _url = new URL(this.Url(url, opt)), { params = null } = opt;
 			if (params) {
@@ -77,29 +79,29 @@ func GenResources(p *Project) {
 			))).pipe(take(1));
 		}
 		
-		_get(url, opt: HttpOptions): Observable<ServiceResponse> {
+		_get<T = any>(url, opt: HttpOptions):Observable<T> {
 			opt = this.api.options(opt);
-			return this.api.http.get<ServiceResponse>(this.Url(url, opt), opt).pipe(take(1));
+			return this.api.http.get<any>(this.Url(url, opt), opt).pipe(take(1));
 		}
 	
-		_put(url, entity: any, opt: HttpOptions): Observable<ServiceResponse> {
+		_put<T = any>(url, entity: any, opt: HttpOptions):Observable<T> {
 			opt = this.api.options(opt);
-			return this.api.http.put<ServiceResponse>(this.Url(url, opt), entity, opt).pipe(take(1));
+			return this.api.http.put<any>(this.Url(url, opt), entity, opt).pipe(take(1));
 		}
 		
-		_patch(url, entity: any, opt: HttpOptions): Observable<ServiceResponse> {
+		_patch<T = any>(url, entity: any, opt: HttpOptions):Observable<T> {
 			opt = this.api.options(opt);
-			return this.api.http.patch<ServiceResponse>(this.Url(url, opt), entity, opt).pipe(take(1));
+			return this.api.http.patch<any>(this.Url(url, opt), entity, opt).pipe(take(1));
 		}
 	
-		_post(url, entity: any, opt: HttpOptions): Observable<ServiceResponse> {
+		_post<T = any>(url, entity: any, opt: HttpOptions):Observable<T> {
 			opt = this.api.options(opt);
-			return this.api.http.post<ServiceResponse>(this.Url(url, opt), entity, opt).pipe(take(1));
+			return this.api.http.post<any>(this.Url(url, opt), entity, opt).pipe(take(1));
 		}
 	
-		_delete(url, opt: HttpOptions): Observable<ServiceResponse> {
+		_delete<T = any>(url, opt: HttpOptions) {
 			opt = this.api.options(opt);
-			return this.api.http.delete<ServiceResponse>(this.Url(url, opt), opt).pipe(take(1));
+			return this.api.http.delete<any>(this.Url(url, opt), opt).pipe(take(1));
 		}
 	}`).Line()
 
@@ -180,7 +182,8 @@ func GenResources(p *Project) {
 
 		module.Line().Export().Class().Id(
 			angularResourceName,
-		).Extends().Id(ServiceStmt).Block(
+		// ).Extends().Id(fmt.Sprintf("%s<%s>",ServiceStmt, resource.Entity )).Block(
+		).Extends().Id(fmt.Sprintf("%s",ServiceStmt )).Block(
 			angularServiceStmtMethods...,
 		).Line()
 
@@ -209,11 +212,11 @@ func createAngularService(p *Project, module *TS.File) error {
 
 	// Define a class que representa uma resposta da api
 	// "Class gerencia todos os serviços da api.",
-	module.Export().Interface().Id("ServiceResponse").Block(
+	module.Export().Interface().Id("ServiceResponse<T = any>").Block(
 		TS.Raw("resultSizeEstimate: number;"),
 		TS.Raw("nextPageToken: string;"),
-		TS.Raw("entity?:object;"),
-		TS.Raw("itens?:object[];"),
+		TS.Raw("entity?:T;"),
+		TS.Raw("itens?:T[];"),
 	)
 
 	// Define todos os parametros atribuiveis as opcoes de uma requisicao da api
@@ -271,56 +274,56 @@ func createAngularService(p *Project, module *TS.File) error {
 			// }
 		}
 
-		options(opts?: HttpOptions): HttpOptions {
-			console.log({ ...this.httpOptions.headers });
-			opts = Object.assign(
-			  {
-				headers: new HttpHeaders({
-				  ...this.httpOptions.headers,
-				}),
-				params: {},
-			  },
-			  opts
-			);
+		// options(opts?: HttpOptions): HttpOptions {
+		// 	console.log({ ...this.httpOptions.headers });
+		// 	opts = Object.assign(
+		// 	  {
+		// 		headers: new HttpHeaders({
+		// 		  ...this.httpOptions.headers,
+		// 		}),
+		// 		params: {},
+		// 	  },
+		// 	  opts
+		// 	);
 		
-			// const headers = {
-			//   Authorization: this.Auth.Token(),
-			// };
+		// 	// const headers = {
+		// 	//   Authorization: this.Auth.Token(),
+		// 	// };
 		
-			opts.headers.set("Authorization", this.Auth.Token());
+		// 	opts.headers.set("Authorization", this.Auth.Token());
 		
-			//this.copyHeader(this.httpOptions.headers, headers);
-			//this.copyHeader(opts.headers, headers);
+		// 	//this.copyHeader(this.httpOptions.headers, headers);
+		// 	//this.copyHeader(opts.headers, headers);
 		
-			const params = opts.params;
-			// Converte a query para base64
-			if (params.q) {
-			  params.q = window.btoa(unescape(encodeURIComponent(params.q)));
-			}
+		// 	const params = opts.params;
+		// 	// Converte a query para base64
+		// 	if (params.q) {
+		// 	  params.q = window.btoa(unescape(encodeURIComponent(params.q)));
+		// 	}
 		
-			// delete opts.headers;
-			// delete opts.params;
+		// 	// delete opts.headers;
+		// 	// delete opts.params;
 		
-			// const options = Object.assign(
-			//   {
-			//     headers: new HttpHeaders(headers),
-			//     params: new HttpParams({ fromObject: params }),
-			//   },
-			//   opts
-			// );
+		// 	// const options = Object.assign(
+		// 	//   {
+		// 	//     headers: new HttpHeaders(headers),
+		// 	//     params: new HttpParams({ fromObject: params }),
+		// 	//   },
+		// 	//   opts
+		// 	// );
 		
-			// const options = Object.assign({
-			// 	headers: new HttpHeaders(hopts),
-			// 	params: {}
-			// }, opts);
+		// 	// const options = Object.assign({
+		// 	// 	headers: new HttpHeaders(hopts),
+		// 	// 	params: {}
+		// 	// }, opts);
 		
-			// // Converte a query para base64
-			// if (options.params.q) {
-			// 	options.params.q = window.btoa(unescape(encodeURIComponent(options.params.q)));
-			// }
+		// 	// // Converte a query para base64
+		// 	// if (options.params.q) {
+		// 	// 	options.params.q = window.btoa(unescape(encodeURIComponent(options.params.q)));
+		// 	// }
 		
-			return opts;
-		  }
+		// 	return opts;
+		//   }
 		  
 		options(opts?: HttpOptions): HttpOptions {
 
@@ -489,46 +492,6 @@ func createClientClass(p *Project, file *TS.File) {
 
 }
 
-// func GenAngularMethod(p *Project, r *Resource, method *Method, methods *[]TS.CodeInterface, angularResourceName string) {
-// 	params := []TS.CodeInterface{}
-// 	param := "entity"
-// 	methodId := method.ID
-
-// 	callOptionsParam := ""
-
-// 	switch strings.ToLower(method.HttpMethod) {
-// 	case "post":
-// 		params = append(params, TS.Id(param).Op(":").Id(method.Entity))
-// 	case "filter":
-// 		// methodId = "get"
-// 		param = ""
-// 	case "get":
-// 		param = ""
-// 		// params = append(params, TS.Id(param).Op(":").String())
-// 	case "get_list":
-// 		param = ""
-// 		// params = append(params, TS.Id(param).Op(":").Id(method.Entity).Op("|").String())
-// 	case "patch":
-// 		param = ""
-// 		params = append(params, TS.Id(param).Op(":").Id(method.Entity))
-// 		// params = append(params, TS.Id(param).Op(":").Id(method.Entity).Op("|").String())
-// 	case "put":
-// 		params = append(params, TS.Id(param).Op(":").Id(method.Entity))
-// 		callOptionsParam = "{id: entity.id}"
-// 	case "delete":
-// 		// params = append(params, TS.Id(param).Op(":").Id(method.Entity).Op("|").String())
-// 		params = append(params, TS.Id(param).Op(":").Id(method.Entity))
-// 		callOptionsParam = "{id: entity.id}"
-// 		param = ""
-// 	default:
-// 		panic(fmt.Sprintf("O método http '%s' para o recurso %s não foi definido!", method.HttpMethod, method.ID))
-// 	}
-
-// 	// *methods = append(*methods, TS.Id(method.ID).Params(params...).Op(":").Id("Observable<ServiceResponse>").Block(
-// 	*methods = append(*methods, TS.Id(method.ID).Params(params...).Op(":").Id("Observable<ServiceResponse>").Block(
-// 		TS.Return().This().Dot("options").Call(TS.Raw(callOptionsParam)).Dot(methodId).Call(TS.Id(param)).Endl(),
-// 	).Line())
-// }
 
 func GenAngularMethodStmt(p *Project, r *Resource, method *Method, methods *[]TS.CodeInterface, angularResourceName string) {
 
@@ -611,11 +574,65 @@ func GenAngularMethodStmt(p *Project, r *Resource, method *Method, methods *[]TS
 
 	paramsHttp = append(paramsHttp, TS.Id("opt"))
 
+	response := strings.Replace(method.Response, "*", "",-1)
+	
+	if response == ""  {
+		response = method.Entity
+	}
+
 	// *methods = append(*methods, TS.Id(method.ID).Params(params...).Op(":").Id("Observable<ServiceResponse>").Block(
 	*methods = append(*methods, TS.Id(method.ID).Params(params...).Block(
 		// TS.Raw("let opt = this.api.Options(this.options)").Endl(),
 		// TS.Raw("let url = ").Lit(p.GetUrlFromMethod(method)).Endl(),
 		// TS.Raw("let url = ").Raw(fmt.Sprintf("`${this.api.BASE_URL}%s`", method.Path)).Endl(),
-		TS.Line().Raw(fmt.Sprintf("return this._%s", typ)).Call(paramsHttp...).Endl(),
+		TS.Line().Raw(
+			fmt.Sprintf(
+				"return this._%s<%s>",
+				 typ,
+				response,
+			),
+		).Call(paramsHttp...).Endl(),
+		// TS.Line().Raw(fmt.Sprintf("return this._%s", typ )).Call(paramsHttp...).Endl(),
 	).Line())
 }
+
+// func GenAngularMethod(p *Project, r *Resource, method *Method, methods *[]TS.CodeInterface, angularResourceName string) {
+// 	params := []TS.CodeInterface{}
+// 	param := "entity"
+// 	methodId := method.ID
+
+// 	callOptionsParam := ""
+
+// 	switch strings.ToLower(method.HttpMethod) {
+// 	case "post":
+// 		params = append(params, TS.Id(param).Op(":").Id(method.Entity))
+// 	case "filter":
+// 		// methodId = "get"
+// 		param = ""
+// 	case "get":
+// 		param = ""
+// 		// params = append(params, TS.Id(param).Op(":").String())
+// 	case "get_list":
+// 		param = ""
+// 		// params = append(params, TS.Id(param).Op(":").Id(method.Entity).Op("|").String())
+// 	case "patch":
+// 		param = ""
+// 		params = append(params, TS.Id(param).Op(":").Id(method.Entity))
+// 		// params = append(params, TS.Id(param).Op(":").Id(method.Entity).Op("|").String())
+// 	case "put":
+// 		params = append(params, TS.Id(param).Op(":").Id(method.Entity))
+// 		callOptionsParam = "{id: entity.id}"
+// 	case "delete":
+// 		// params = append(params, TS.Id(param).Op(":").Id(method.Entity).Op("|").String())
+// 		params = append(params, TS.Id(param).Op(":").Id(method.Entity))
+// 		callOptionsParam = "{id: entity.id}"
+// 		param = ""
+// 	default:
+// 		panic(fmt.Sprintf("O método http '%s' para o recurso %s não foi definido!", method.HttpMethod, method.ID))
+// 	}
+
+// 	// *methods = append(*methods, TS.Id(method.ID).Params(params...).Op(":").Id("Observable<ServiceResponse>").Block(
+// 	*methods = append(*methods, TS.Id(method.ID).Params(params...).Op(":").Id("Observable<ServiceResponse>").Block(
+// 		TS.Return().This().Dot("options").Call(TS.Raw(callOptionsParam)).Dot(methodId).Call(TS.Id(param)).Endl(),
+// 	).Line())
+// }

+ 29 - 19
translate/tst/schemas.go

@@ -27,10 +27,6 @@ func GenSchemas(p *Project) error {
 
 		entityInfo = p.ResponseEntity(entity.ID)
 
-		if entityInfo.IsGeneric {
-			continue
-		}
-
 		for _, meta := range entity.Properties {
 
 			propName = strings.Title(meta.ID)
@@ -101,7 +97,9 @@ func GenSchemas(p *Project) error {
 			attribs...,
 		).Line()
 
-		createPathClass(entity, arrayProperties)
+		if !entityInfo.IsGeneric {
+			createPathClass(entity, arrayProperties)
+		}
 
 	} // Fim do for de schema
 	return nil
@@ -164,7 +162,7 @@ func createSetInitializeMethod(g *TS.Group, entity *Entity, arrayProperties []*P
 
 			for _, meta := range arrayProperties {
 				// block.Add(TS.Raw(fmt.Sprintf("if ($instance.%s) { this._set(this.%s, $instance.%s, '',-1);}", meta.ID, meta.ID, meta.ID)).Line())
-				block.Add(TS.Raw(fmt.Sprintf("if (%s.length) { this._set(this.%s, %s, '',-1);}", meta.ID, meta.ID, meta.ID)).Line())
+				block.Add(TS.Raw(fmt.Sprintf("if (%s && %s.length) { this._set(this.%s, %s, '',-1);}", meta.ID, meta.ID, meta.ID, meta.ID)).Line())
 			}
 
 		}),
@@ -184,7 +182,9 @@ func createPatchMethod(g *TS.Group, entity *Entity, arrayProperties []*Propertie
 					Line().
 					Raw(fmt.Sprintf("Add%s : [],", strings.Title(meta.ID))).
 					Line().
-					Raw(fmt.Sprintf("Remove%s : [],", strings.Title(meta.ID))),
+					Raw(fmt.Sprintf("Remove%s : [],", strings.Title(meta.ID))).
+					Line().
+					Raw(fmt.Sprintf("Replace%s : [],", strings.Title(meta.ID))),
 				)
 			}
 
@@ -217,6 +217,12 @@ func createAddAndRemoveMethods(g *TS.Group, entity *Entity, arrayProperties []*P
 			// Params(TS.Id("itens"), TS.Id("timeout")).
 			Block(
 				TS.Raw(fmt.Sprintf("this._set(this.%s, itens, 'remove', timeout);return this;", meta.ID)),
+			).
+			Line().
+			Id(fmt.Sprintf("Replace%s", strings.Title(meta.ID))).
+			Params(TS.Id("itens"), TS.Id(fmt.Sprintf("timeout = %d", timeout))).
+			Block(
+				TS.Raw(fmt.Sprintf("this._set(this.%s, itens, 'replace', timeout);return this;", meta.ID)),
 			),
 		)
 	}
@@ -245,16 +251,13 @@ func appendBasePatchClass() {
 			this.$subscription = setTimeout(() => this.change$.emit(patch), timeout);
 		}
 
-		protected __set(list, add, rem) {
+		protected __set(list, add, rem, replace) {
 			list.forEach(element => {
-				const item = element.item;
+				const {item} = element;
 				switch (element.action) {
-					case 'add':
-					add.push(item);
-					break;
-					case 'remove':
-					if (element.type !== 'new') { rem.push(item._id || item); } 
-					break;
+					case 'add': add.push(item);	break;
+					case 'replace': replace.push(item);	break;
+					case 'remove': if (element.type !== 'new') { rem.push(item._id || item); }
 				}
 			});
 		}
@@ -279,10 +282,17 @@ func appendBasePatchClass() {
 					, el = _map.get(id);
 		
 				switch (action) {
+					case 'replace': 
+					_map.set(id, {
+						item,
+						action: 'replace',
+						type: 'new',
+					});
+					break;
 					case 'add':
 					if (!has) {
 						_map.set(id, {
-						item: item,
+						item,
 						action: 'add',
 						type: 'new',
 						});
@@ -323,15 +333,15 @@ func appendBasePatchClass() {
 				if (prop.startsWith('$')) {
 					return;
 				}
-				const adds = [], remove = [];
+				const adds = [],replace = [],remove = [];
 				const captalized = prop[0].toUpperCase() + prop.slice(1);
 				
 				if ($this[prop].size) {
 
-					this.__set($this[prop], adds, remove);
+					this.__set($this[prop], adds, remove, replace);
 					
 					if (adds.length){ patchs[`).Raw("`Add${captalized}`").Raw(`] = adds; }
-					
+					if (replace.length){ patchs[`).Raw("`Replace${captalized}`").Raw(`] = replace; }
 					if (remove.length){ patchs[`).Raw("`Remove${captalized}`").Raw(`] = remove; }
 				}
 			});

+ 2 - 3
translate/tst/translate.go

@@ -16,10 +16,9 @@ func Translate(project *Project) (err error) {
 	ts := project.Client("angular6")
 
 	path := fmt.Sprintf("%s/%s.module.ts", ts.OutputDir, project.ID)
-
+	// Write(fmt.Sprintf("%s/%s/params.go", p.OutPath, p.Package), file)
 	module = TS.NewFile(path)
 
-	// fmt.Printf("Store angular module '%s'\n", path)
 	// Adiciona os importes necessarios
 	imports(project, module)
 
@@ -96,4 +95,4 @@ func getCustom(options map[string]interface{}, path string) (resp interface{}) {
 		resp = options[path]
 	}
 	return
-}
+}