EUGENIO SOUZA CARVALHO 4 years ago
commit
cb5e355800
56 changed files with 12744 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 551 0
      api/errs/errs.go
  3. 97 0
      api/mail.go
  4. 1499 0
      api/mongo.go
  5. 112 0
      api/mongo.rx.go
  6. 205 0
      api/mongo_test.go
  7. 31 0
      api/observable.behavior.go
  8. 193 0
      api/observable.go
  9. 116 0
      api/observable.operators.go
  10. 49 0
      api/observable_test.go
  11. 280 0
      api/sse/hub.go
  12. 162 0
      api/timeago.go
  13. 853 0
      api/utils.go
  14. 63 0
      authorization/authorization.go
  15. 65 0
      commands/common.go
  16. 36 0
      commands/compile.go
  17. 74 0
      commands/generate.go
  18. 300 0
      commands/init.go
  19. 129 0
      commands/serve.go
  20. 185 0
      common/build_commands.go
  21. 855 0
      common/models.go
  22. 210 0
      common/utils.go
  23. 237 0
      common/websoket.models.go
  24. 65 0
      common/ws.go
  25. 142 0
      flag/flag.go
  26. 385 0
      gen/gen.go
  27. 56 0
      gen/service.go
  28. 38 0
      go.mod
  29. 187 0
      go.sum
  30. 16 0
      main.go
  31. 70 0
      translate/got/constants.go
  32. 5 0
      translate/got/control.go
  33. 80 0
      translate/got/environment.go
  34. 73 0
      translate/got/functions.go
  35. 333 0
      translate/got/middleware_delete.go
  36. 35 0
      translate/got/middleware_filter.go
  37. 112 0
      translate/got/middleware_get_list.go
  38. 90 0
      translate/got/middleware_get_one.go
  39. 37 0
      translate/got/middleware_hook_template.go
  40. 52 0
      translate/got/middleware_implement.go
  41. 117 0
      translate/got/middleware_main.go
  42. 142 0
      translate/got/middleware_patch.go
  43. 225 0
      translate/got/middleware_post.go
  44. 431 0
      translate/got/middleware_put.go
  45. 290 0
      translate/got/middleware_undelete.go
  46. 393 0
      translate/got/middleware_update_relation.go
  47. 42 0
      translate/got/middlewares.go
  48. 38 0
      translate/got/params.go
  49. 656 0
      translate/got/resources.go
  50. 795 0
      translate/got/schemas.go
  51. 139 0
      translate/got/translate.go
  52. 1 0
      translate/translate.go
  53. 226 0
      translate/tst/auth.go
  54. 557 0
      translate/tst/resources.go
  55. 521 0
      translate/tst/schemas.go
  56. 92 0
      translate/tst/translate.go

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+eon

+ 551 - 0
api/errs/errs.go

@@ -0,0 +1,551 @@
+package errs
+
+import (
+	"fmt"
+	"runtime"
+)
+
+type ErroNil *Error
+
+type Map map[string]interface{}
+
+type Error struct {
+	HttpStatus int         `json:"httpStatus"`
+	CodeText   string      `json:"codetext"`
+	Message    string      `json:"message"`
+	Stack      *ErrorStack `json:"stack,omitempty"`
+	Errors     []*Detail   `json:"erros,omitempty"`
+}
+
+type Detail struct {
+	Dominio      string `json:"dominio,omitempty"`
+	Reason       string `json:"reason,omitempty"`
+	Message      string `json:"message,omitempty"`
+	LocationType string `json:"locationType,omitempty"`
+	Location     string `json:"location,omitempty"`
+}
+
+type ErrorMapFunction func(error) (err *Error)
+
+// type ErrorMapFunction func(error) (err error, replaced bool)
+
+var (
+	errorMapFunction = map[string]ErrorMapFunction{}
+)
+
+func RegisterMapErrorFunction(id string, fn ErrorMapFunction) {
+	errorMapFunction[id] = fn
+}
+
+// Adiciona uma descricao ao erro
+// func (e *Erro) Add(desc *Detail) *Erro {
+// 	e.Errors = append(e.Errors, desc)
+// 	return e
+// }
+
+func (e *Error) Details(desc *Detail) *Error {
+	e.Errors = append(e.Errors, desc)
+	return e
+}
+
+// Retorna a ultima descricao adicionada
+func (e *Error) LastDescription() *Detail {
+	size := len(e.Errors)
+	if size > 0 {
+		return e.Errors[size-1]
+	}
+	return nil
+}
+
+// Error retorna a mensagem de erro principal
+
+func (e *Error) Error() string {
+	return e.Message
+}
+
+// The operation was cancelled, typically by the caller.
+//
+// HTTP Mapping: 499 Client Closed Request
+func Cancelled() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 499,
+		CodeText:   "client_closed_request",
+		Message:    `Solicitação cancelada pelo cliente.`,
+	})
+}
+
+// Unknown error.  For example, this error may be returned when
+// a `Status` value received from another address space belongs to
+// an error space that is not known in this address space.  Also
+// errors raised by APIs that do not return enough error information
+// may be converted to this error.
+//
+// HTTP Mapping: 500 Internal Server Error
+func Unknown() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 500,
+		CodeText:   "internal_server_error",
+		Message:    "",
+	})
+}
+
+// The client specified an invalid argument.  Note that this differs
+// from `FAILED_PRECONDITION`.  `INVALID_ARGUMENT` indicates arguments
+// that are problematic regardless of the state of the system
+// (e.g., a malformed file name).
+//
+// HTTP Mapping: 400 Bad Request
+func InvalidArgument() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 400,
+		CodeText:   "bad_request",
+		Message:    "O campo de solicitação xyz é xxx, mas o esperado é [yyy, zzz].",
+	})
+}
+
+// The deadline expired before the operation could complete. For operations
+// that change the state of the system, this error may be returned
+// even if the operation has completed successfully.  For example, a
+// successful response from a server could have been delayed long
+// enough for the deadline to expire.
+//
+// HTTP Mapping: 504 Gateway Timeout
+func DeadlineExceeded() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 504,
+		CodeText:   "gateway_timeout",
+		Message:    "",
+	})
+}
+
+// Some requested entity (e.g., file or directory) was not found.
+//
+// Note to server developers: if a request is denied for an entire class
+// of users, such as gradual feature rollout or undocumented whitelist,
+// `NOT_FOUND` may be used. If a request is denied for some users within
+// a class of users, such as user-based access control, `PERMISSION_DENIED`
+// must be used.
+//
+// HTTP Mapping: 404 Not Found
+func NotFound() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 404,
+		CodeText:   "not_found",
+		Message:    `O recurso "xxx" não foi encontrado.`,
+	})
+}
+
+// The entity that a client attempted to create (e.g., file or directory)
+// already exists.
+//
+// HTTP Mapping: 409 Conflict
+func AlreadyExists() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 409,
+		CodeText:   "conflict",
+		Message:    `O recurso já existe.`,
+	})
+}
+
+// The caller does not have permission to execute the specified
+// operation. `PERMISSION_DENIED` must not be used for rejections
+// caused by exhausting some resource (use `RESOURCE_EXHAUSTED`
+// instead for those errors). `PERMISSION_DENIED` must not be
+// used if the caller can not be identified (use `UNAUTHENTICATED`
+// instead for those errors). This error code does not imply the
+// request is valid or the requested entity exists or satisfies
+// other pre-conditions.
+//
+// HTTP Mapping: 403 Forbidden
+func PermissionDenied() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 403,
+		CodeText:   "forbidden",
+		Message:    `Permissão "xxx" negada no arquivo "yyy".`,
+	})
+}
+
+// The request does not have valid authentication credentials for the
+// operation.
+//
+// HTTP Mapping: 401 Unauthorized
+func Unauthenticated() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 401,
+		CodeText:   "unauthorized",
+		Message:    `Credenciais de autenticação inválidas.`,
+	})
+}
+
+// Some resource has been exhausted, perhaps a per-user quota, or
+// perhaps the entire file system is out of space.
+//
+// HTTP Mapping: 429 Too Many Requests
+func ResourceExhausted() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 429,
+		CodeText:   "too_many_requests",
+		Message:    `Limite de cota "xxx" excedido.`,
+	})
+}
+
+// The operation was rejected because the system is not in a state
+// required for the operation's execution.  For example, the directory
+// to be deleted is non-empty, an rmdir operation is applied to
+// a non-directory, etc.
+//
+// Service implementors can use the following guidelines to decide
+// between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`:
+//  (a) Use `UNAVAILABLE` if the client can retry just the failing call.
+//  (b) Use `ABORTED` if the client should retry at a higher level
+//      (e.g., when a client-specified test-and-set fails, indicating the
+//      client should restart a read-modify-write sequence).
+//  (c) Use `FAILED_PRECONDITION` if the client should not retry until
+//      the system state has been explicitly fixed.  E.g., if an "rmdir"
+//      fails because the directory is non-empty, `FAILED_PRECONDITION`
+//      should be returned since the client should not retry unless
+//      the files are deleted from the directory.
+//
+// HTTP Mapping: 400 Bad Request
+func FailedPrecondition() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 400,
+		CodeText:   "bad_request",
+		Message:    `O recurso xxx é um diretório que não está vazio, portanto, não pode ser excluído.`,
+	})
+}
+
+// The operation was aborted, typically due to a concurrency issue such as
+// a sequencer check failure or transaction abort.
+//
+// See the guidelines above for deciding between `FAILED_PRECONDITION`,
+// `ABORTED`, and `UNAVAILABLE`.
+//
+// HTTP Mapping: 409 Conflict
+func Aborted() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 409,
+		CodeText:   "conflict",
+		Message:    `Não foi possível adquirir o bloqueio no recurso "xxx".`,
+	})
+}
+
+// The operation was attempted past the valid range.  E.g., seeking or
+// reading past end-of-file.
+//
+// Unlike `INVALID_ARGUMENT`, this error indicates a problem that may
+// be fixed if the system state changes. For example, a 32-bit file
+// system will generate `INVALID_ARGUMENT` if asked to read at an
+// offset that is not in the range [0,2^32-1], but it will generate
+// `OUT_OF_RANGE` if asked to read from an offset past the current
+// file size.
+//
+// There is a fair bit of overlap between `FAILED_PRECONDITION` and
+// `OUT_OF_RANGE`.  We recommend using `OUT_OF_RANGE` (the more specific
+// error) when it applies so that callers who are iterating through
+// a space can easily look for an `OUT_OF_RANGE` error to detect when
+// they are done.
+//
+// HTTP Mapping: 400 Bad Request
+func OutOfRange() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 400,
+		CodeText:   "bad_request",
+		Message:    `A "idade" do parâmetro está fora do intervalo [0, 125].`,
+	})
+}
+
+func HTTPVersionNotSupported() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 400,
+		CodeText:   "http_version_not_supported",
+		Message:    `The server doesn’t support the major HTTP version the client used to make the request.`,
+	})
+}
+
+// The operation is not implemented or is not supported/enabled in this
+// service.
+//
+// HTTP Mapping: 501 Not Implemented
+func Unimplemented() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 501,
+		CodeText:   "not_implemented",
+		Message:    `Método "xxx" não implementado.`,
+	})
+}
+
+// Internal errors.  This means that some invariants expected by the
+// underlying system have been broken.  This error code is reserved
+// for serious errors.
+//
+// HTTP Mapping: 500 Internal Server Error
+func Internal() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 500,
+		CodeText:   "internal_server_error",
+		Message:    `Error interno.`,
+	})
+}
+
+// The service is currently unavailable.  This is most likely a
+// transient condition, which can be corrected by retrying with
+// a backoff.
+//
+// See the guidelines above for deciding between `FAILED_PRECONDITION`,
+// `ABORTED`, and `UNAVAILABLE`.
+//
+// HTTP Mapping: 503 Service Unavailable
+func Unavaliable() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 503,
+		CodeText:   "service_unavailable",
+		Message:    "Serviço indisponivel.",
+	})
+}
+
+// Unrecoverable data loss or corruption.
+//
+// HTTP Mapping: 500 Internal Server Error
+func DataCaps() *Error {
+	return errorHandler(&Error{
+		HttpStatus: 500,
+		CodeText:   "internal_server_error",
+		Message:    `Error interno.`,
+	})
+}
+
+func errorHandler(err *Error) *Error {
+	err.Stack = Trace()
+	return err
+}
+
+// }
+
+// func Error(code int, m string) *Error {
+
+// 	LogError(code, m)
+
+// 	e := ErrosList[code]
+// 	if m != "" {
+// 		e.Message = m
+// 	}
+// 	e.Details(&Detail{
+// 		Dominio: "global",
+// 		Message: m,
+// 	})
+
+// 	return &e
+// }
+
+// func FullError(code int, err *Detail) *Error {
+// 	// _, fn, line, _ := runtime.Caller(1
+// 	// LogError(code, fmt.Sprintf("%s:%d - %s", fn, line, err.Message))
+
+// 	e := ErrosList[code]
+// 	e.Message = err.Message
+// 	e.Stack = Trace()
+// 	e.Details(err)
+
+// 	return &e
+// }
+
+type ErrorStack struct {
+	Frames []runtime.Frame
+}
+
+func (stack *ErrorStack) Print() {
+	for _, f := range stack.Frames {
+		fmt.Printf("%s\n\t%s:%d\n", f.Function, f.File, f.Line)
+	}
+}
+
+func Trace() (stack *ErrorStack) {
+	stack = &ErrorStack{}
+	pc := make([]uintptr, 20)
+	n := runtime.Callers(2, pc)
+	if n == 0 {
+		// No pcs available. Stop now.
+		// This can happen if the first argument to runtime.Callers is large.
+		return
+	}
+	pc = pc[:n] // pass only valid pcs to runtime.CallersFrames
+
+	frames := runtime.CallersFrames(pc)
+
+	// Loop to get frames.
+	// A fixed number of pcs can expand to an indefinite number of Frames.
+	for {
+		frame, more := frames.Next()
+		stack.Frames = append(stack.Frames, frame)
+		// To keep this example's output stable
+		// even if there are changes in the testing package,
+		// stop unwinding when we leave package runtime.
+
+		// if strings.Contains(frame.Function, "main.main") {
+		// 	break
+		// }
+		// spew.Dump(frame)
+		if !more {
+			break
+		}
+	}
+	return
+}
+
+func FromError(source error) (err *Error) {
+
+	for _, fn := range errorMapFunction {
+
+		if err = fn(source); err != nil {
+			return
+		}
+	}
+
+	err = Internal().Details(&Detail{
+		Message: source.Error(),
+	})
+	return
+}
+
+// 400	invalidParameter	Indica que um parâmetro de solicitação tem um valor inválido. Os campos locationType e location na resposta de erro fornecem informações sobre qual valor era inválido.	Não tente novamente sem corrigir o problema. Você precisa fornecer um valor válido para o parâmetro especificado na resposta de erro.
+// 400	badRequest	Indica que a consulta era inválida. Por exemplo, o ID pai estava ausente ou a combinação de dimensões ou métricas solicitada não era válida.	Não tente novamente sem corrigir o problema. Você precisa fazer alterações na consulta da API para que ela funcione.
+// 401	invalidCredentials	Indica que o token de autenticação é inválido ou expirou.	Não tente novamente sem corrigir o problema. Você precisa receber um novo token de autenticação.
+// 403	insufficientPermissions	Indica que o usuário não tem permissões suficientes para a entidade especificada na consulta.	Não tente novamente sem corrigir o problema. Você precisa receber permissões suficientes para executar a operação na entidade especificada.
+// 403	dailyLimitExceeded	Indica que o usuário ultrapassou a cota diária [por projeto ou vista da propriedade (perfil)].	Não tente novamente sem corrigir o problema. Você esgotou sua cota diária. Consulte Limites e cotas da API.
+// 403	usageLimits.userRateLimitExceededUnreg	Indica que o aplicativo precisa ser registrado no console de APIs do Google.	Não tente novamente sem corrigir o problema. Você precisa se registrar no console de APIs para receber a cota completa da API.
+// 403	userRateLimitExceeded	Indica que o limite da taxa de usuário foi ultrapassado. O limite de taxa máximo é de 10 qps por endereço IP. O valor padrão definido no console de APIs do Google é 1 qps por endereço IP. Você pode aumentar esse limite no console de APIs do Google para no máximo 10 qps.	Tente novamente usando backoff exponencial. Você precisa diminuir a taxa em que envia as solicitações.
+// 403	rateLimitExceeded	Indica que os limites de taxa global ou geral do projeto foram excedidos.	Tente novamente usando backoff exponencial. Você precisa diminuir a taxa em que envia as solicitações.
+// 403	quotaExceeded	Indica que o limite de 10 solicitações simultâneas por vista da propriedade (perfil) na API de relatórios principais foi atingido.	Tente novamente usando backoff exponencial. Você precisa aguardar até que pelo menos uma solicitação em andamento para essa Vista (perfil) seja concluída.
+
+// ERR_BAD_REQUEST: Erro{
+// 	HttpStatus: 400,
+// 	CodeText:   "bad_request",
+// 	Message:    "",
+// },
+// ERR_INVALID_PARAM: Erro{
+// 	HttpStatus: 400,
+// 	CodeText:   "invalid_param",
+// 	Message:    "",
+// },
+// ERR_INVALID_PARAM_VALUE: Erro{
+// 	HttpStatus: 400,
+// 	CodeText:   "invalid_param_value",
+// 	Message:    "",
+// },
+// ERR_INVALID_CREDENTIAL: Erro{
+// 	HttpStatus: 401,
+// 	CodeText:   "invalid_credential",
+// 	Message:    "Invalid Credentials",
+// },
+// ERR_NOT_FOUND: Erro{
+// 	HttpStatus: 404,
+// 	CodeText:   "not_found",
+// 	Message:    "Recurso não existe",
+// },
+// ERR_PERSIST: Erro{
+// 	HttpStatus: 500,
+// 	CodeText:   "persist_error",
+// 	Message:    "Não foi possivel salvar as alterações",
+// },
+// ERR_CONFLICT: Erro{
+// 	HttpStatus: 409,
+// 	CodeText:   "conflict",
+// 	Message:    "A operação falhou porque o item é referenciado por outras.",
+// },
+// ERR_GENERAL: Erro{
+// 	HttpStatus: 500,
+// 	CodeText:   "general",
+// 	Message:    "Parece que algo deu errado. Nós pedimos desculpas. Você pode voltar para a página principal",
+// },
+// ERR_EXPIRED: Erro{
+// 	HttpStatus: 500,
+// 	CodeText:   "expired",
+// 	Message:    "Parece que o recurso não esta mais disponível",
+// },
+// const (
+// 	ERR_NOT_FOUND int = iota
+// 	ERR_GENERAL
+// 	ERR_PERSIST
+// 	ERR_INVALID_CREDENTIAL
+// 	ERR_BAD_REQUEST
+// 	ERR_INVALID_PARAM
+// 	ERR_INVALID_PARAM_VALUE
+// 	ERR_EXPIRED
+// 	ERR_NOT_MODIFIED
+// 	ERR_TEMPORARY_REDIRECT
+// 	ERR_UNAUTHORIZED
+// 	ERR_PAYMENT_REQUIRED
+// 	ERR_FORBIDDEN
+// 	ERR_METHOD_NOT_ALLOWED
+// 	ERR_CONFLICT
+// 	ERR_TOO_MANY_REQUESTS
+// 	ERR_NOT_IMPLEMENTED
+// 	ERR_SERVICE_UNAVAILABLE
+// )
+
+// Error cria uma instancia de um erro
+// code int assume:
+// ERR_NOT_FOUND          int = 0
+// ERR_GENERAL            int = 1
+// ERR_PERSIST            int = 2
+// ERR_INVALID_CREDENTIAL int = 3
+// ERR_BAD_REQUEST        int = 4
+// ERR_INVALID_PARAM      int = 5
+// ERR_EXPIRED            int = 6
+// func Errorf(code int, m string, args ...interface{}) *Erro {
+// 	LogError(code, m)
+
+// 	m = fmt.Sprintf(m, args...)
+
+// 	e := ErrosList[code]
+// 	if m != "" {
+// 		e.Message = m
+// 	}
+// 	e.Add(&Detail{
+// 		Dominio: "global",
+// 		Message: m,
+// 	})
+
+// 	return &e
+// }
+
+// func Falha(ctx context.Context, code int, m string, dev Map) {
+// 	// fmt.Println("CodeText:[", code, "]", m, dev);
+
+// 	// ctx.StopExecution()
+
+// 	base := ErrosList[code]
+// 	base.Development = Map{}
+
+// 	if m != "" {
+// 		base.Message = m
+// 	}
+
+// 	if dev != nil {
+// 		base.Development = dev
+// 	}
+
+// 	accept := ctx.GetHeader("Accept")
+
+// 	ctx.StatusCode(base.Code)
+
+// 	//spew.Dump(base)
+
+// 	if base.Error == "not_found" {
+// 		url := ctx.Host() + ctx.RequestPath(true)
+// 		base.Development["url"] = fmt.Sprintf("A url '%s' não foi encontrada.", url)
+// 	}
+
+// 	if accept == "application/json" {
+
+// 		ctx.JSON(base)
+
+// 	} else {
+
+// 		ctx.ViewData("", base)
+// 		// ctx.View(ViewScript(ctx, "error.html"))
+// 	}
+
+// 	ctx.StopExecution()
+// }

+ 97 - 0
api/mail.go

@@ -0,0 +1,97 @@
+package api
+
+import (
+	"bytes"
+	"crypto/tls"
+	"errors"
+	"html/template"
+
+	gomail "gopkg.in/gomail.v2"
+)
+
+type Profile struct {
+	Host     string `json:"host" bson:"host"`
+	Usuario  string `json:"usuario" bson:"usuario"`
+	Password string `json:"password" bson:"password"`
+	Nome     string `json:"nome" bson:"nome"`
+	Porta    int    `json:"porta" bson:"porta"`
+}
+
+type Message struct {
+	gomail.Message
+	Template string
+	Context  interface{}
+}
+
+var (
+	Serders = map[string]*Profile{}
+)
+
+func New() *Message {
+	return &Message{Message: *gomail.NewMessage()}
+}
+
+func (t *Profile) GetFullNameAdrress() string {
+	return t.Nome + " <" + t.Usuario + ">"
+}
+
+func RegisterSender(nome string, s *Profile) {
+	Serders[nome] = s
+}
+
+func Sender(nome string) (*Profile, bool) {
+	s, ok := Serders[nome]
+	return s, ok
+}
+
+func Send(sender string, m *Message) error {
+	var (
+		s  *Profile
+		ok bool
+	)
+	if s, ok = Sender(sender); !ok {
+		return errors.New("Perfil '" + sender + "' não exites")
+	}
+
+	dialer := gomail.NewDialer(s.Host, s.Porta, s.Usuario, s.Password)
+	dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
+
+	if err := m.parseTemplate(); err != nil {
+		return err
+	}
+
+	return dialer.DialAndSend(&m.Message)
+}
+
+func (t *Message) Send(sender string) error {
+	return Send(sender, t)
+}
+
+func (t *Message) SetTemplate(filename string, ctx interface{}) {
+	t.Template = filename
+	t.Context = ctx
+}
+
+func (m *Message) parseTemplate() (err error) {
+	var (
+		t   *template.Template
+		buf *bytes.Buffer
+	)
+	if m.Template == "" {
+		return nil
+	}
+
+	if t, err = template.ParseFiles(m.Template); err != nil {
+		return err
+	}
+
+	buf = new(bytes.Buffer)
+
+	if err = t.Execute(buf, m.Context); err != nil {
+		return err
+	}
+
+	m.SetBody("text/html", buf.String())
+
+	return nil
+}

File diff suppressed because it is too large
+ 1499 - 0
api/mongo.go


+ 112 - 0
api/mongo.rx.go

@@ -0,0 +1,112 @@
+package api
+
+import (
+	"fmt" // "html/template"
+	"reflect"
+
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
+	"go.mongodb.org/mongo-driver/mongo"
+	"go.mongodb.org/mongo-driver/mongo/options"
+)
+
+func (mongo *Mongo) InsertOneRx(options *Filter) *ObservableStruct {
+
+	return Observable(func(observer *Subscriber) {
+		if res, err := mongo.InsertOne(options); err != nil {
+			observer.Err(err)
+		} else {
+			observer.Next(res)
+		}
+	})
+
+}
+
+func (_mongo *Mongo) FindManyRx(params *Filter) *ObservableStruct {
+
+	var (
+		ctx, collection = _mongo.GetContext(params)
+		cursor          *mongo.Cursor
+		err             error
+	)
+
+	return Observable(func(observer *Subscriber) {
+
+		switch {
+		case params.Pipeline != nil:
+
+			// opts := options.Aggregate().SetBatchSize(int32(params.MaxResults))
+			opts := options.Aggregate()
+
+			if cursor, err = collection.Aggregate(ctx, params.Pipeline, opts); err != nil {
+				err = errs.FromError(err)
+				return
+			}
+		case params.Query != nil:
+			findOptions := options.Find()
+			findOptions.SetLimit(int64(params.MaxResults))
+
+			if params.Sort != nil {
+				findOptions.SetSort(params.Sort)
+			}
+
+			if params.Fields != nil {
+				findOptions.SetProjection(params.Fields)
+			}
+
+			cursor, err = collection.Find(ctx, params.Query, findOptions)
+
+			fmt.Println("erro no query")
+		default:
+			err = errs.Internal().Details(&errs.Detail{
+				Message: "FindMany requires a Pipeline or Query param.",
+			})
+		}
+
+		if err == nil {
+			observer.Next(cursor)
+		} else {
+			observer.Next(err)
+		}
+
+	}).Pipe(
+		RxMap(func(value interface{}) (interface{}, error) {
+			var (
+				cursor, _ = value.(*mongo.Cursor)
+				elemp     reflect.Value
+				err       error
+			)
+			defer cursor.Close(ctx)
+
+			if params.Entities == nil {
+				return nil, fmt.Errorf("Entities can't be nil")
+			}
+
+			entitiesValue := reflect.ValueOf(params.Entities)
+
+			if entitiesValue.Kind() != reflect.Ptr || entitiesValue.Elem().Kind() != reflect.Slice {
+				return nil, fmt.Errorf("Entities argument must be a slice address")
+			}
+
+			sliceVector := entitiesValue.Elem()
+			sliceVector = sliceVector.Slice(0, sliceVector.Cap())
+			typ := sliceVector.Type().Elem()
+
+			// LogError(0, cursor.Err().Error())
+
+			for cursor.Next(ctx) {
+				fmt.Println("interate")
+				elemp = reflect.New(typ)
+
+				if err = cursor.Decode(elemp.Interface()); err != nil {
+					return nil, err
+				}
+				sliceVector = reflect.Append(sliceVector, elemp.Elem())
+			}
+
+			entitiesValue.Elem().Set(sliceVector)
+
+			return entitiesValue.Elem(), nil
+		}),
+	)
+
+}

+ 205 - 0
api/mongo_test.go

@@ -0,0 +1,205 @@
+package api
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
+	"github.com/davecgh/go-spew/spew"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+var (
+	_mongo *Mongo
+	_run   = map[string]bool{
+		"TestList":       false,
+		"TestOne":        false,
+		"TestPatchOne":   true,
+		"TestInsertOne":  false,
+		"TestInsertMany": false,
+	}
+)
+
+func run(id string, fn func()) {
+	if run, found := _run[id]; found && run {
+		fn()
+	}
+}
+
+func init() {
+
+	_mongo = &Mongo{Config: "mongodb://localhost:27020:codegen"}
+
+	if err := _mongo.Init(); err != nil {
+		panic(err)
+	}
+
+	fmt.Println("\ninit....")
+}
+
+type Method struct {
+	Id        primitive.ObjectID `bson:"_id,omitempty" json:"id"`
+	Technique string             `bson:"technique" json:"technique" valid:"required"` // Nome da técnica.
+	Name      string             `bson:"name" json:"name" valid:"required"`
+}
+
+func (t *Method) Patch() bson.A {
+	return bson.A{
+		bson.M{"$set": t},
+	}
+}
+func TestToken(t *testing.T) {
+	// grant := map[string]interface{}{}
+
+	// _, err := _mongo.FindOne(&Filter{
+	// 	DB:         ACCOUNT_DB,
+	// 	Collection: AccessTokensCollection,
+	// 	Entity:     &grant,
+	// 	Query: &bson.M{
+	// 		"clientId":  clientID,
+	// 		"sessionId": session,
+	// 	},
+	// })
+	// if err != nil {
+	// 	t.Error(err)
+	// }
+	// spew.Dump(grant)
+	// spew.Dump(err)
+}
+
+func TestList(t *testing.T) {
+	run("TestList", func() {
+		var (
+			err      *errs.Error
+			entities = []*Method{}
+		)
+
+		filter := &Filter{
+			DB:         "codegen",
+			Collection: "caracterization_method",
+			Entities:   &entities,
+		}
+
+		if _, err = _mongo.FindMany(filter); err != nil {
+			t.Error(err)
+		}
+
+		spew.Dump(entities)
+	})
+}
+
+func TestOne(t *testing.T) {
+	run("TestOne", func() {
+		var (
+			err       *errs.Error
+			entities  = &Method{}
+			oid, errd = primitive.ObjectIDFromHex("5c0e9050a54f4c5d592c24bb")
+		)
+
+		if errd != nil {
+			t.Error(errd)
+			return
+		}
+
+		filter := &Filter{
+			DB:         "codegen",
+			Collection: "caracterization_method",
+			Entity:     entities,
+			Id:         oid,
+		}
+
+		if _, err = _mongo.FindOne(filter); err != nil {
+			t.Error(err)
+		}
+
+		spew.Dump(entities)
+	})
+
+}
+func TestPatchOne(t *testing.T) {
+	run("TestPatchOne", func() {
+		var (
+			err *errs.Error
+			// now
+			entity = &Method{
+				Name: "xxxla" + time.Now().Format(time.RFC3339),
+			}
+			oid, errd = primitive.ObjectIDFromHex("5c0e9809a54f4c5d592c24db")
+			// oid, errd = primitive.ObjectIDFromHex("5c0e9809a54f7c5d592c24db")
+		)
+
+		if errd != nil {
+			t.Error(errd)
+			return
+		}
+
+		filter := &Filter{
+			DB:         "codegen",
+			Collection: "caracterization_method",
+			Entity:     entity,
+			Id:         oid,
+		}
+
+		if _, err = _mongo.PatchOne(filter); err != nil {
+			t.Error(err)
+		}
+
+		spew.Dump(entity)
+	})
+
+}
+func TestInsertOne(t *testing.T) {
+	run("TestInsertOne", func() {
+		var (
+			err    *errs.Error
+			entity = &Method{
+				Name:      "xxlaONE",
+				Technique: "casaONE",
+			}
+		)
+
+		filter := &Filter{
+			DB:         "codegen",
+			Collection: "caracterization_method",
+			Entity:     entity,
+		}
+
+		if _, err = _mongo.InsertOne(filter); err != nil {
+			t.Error(err)
+		}
+
+		spew.Dump(entity)
+	})
+}
+
+func TestInsertMany(t *testing.T) {
+	run("TestInsertMany", func() {
+		var (
+			err      *errs.Error
+			entities = []*Method{
+				&Method{
+					Name:      "xxla",
+					Technique: "casa",
+				},
+				&Method{
+					Name:      "xxla2",
+					Technique: "casa2",
+				},
+			}
+		)
+
+		filter := &Filter{
+			DB:         "codegen",
+			Collection: "caracterization_method",
+			Entities:   entities,
+		}
+
+		if _, err = _mongo.InsertMany(filter); err != nil {
+			t.Error(err)
+		}
+
+		spew.Dump(entities)
+	})
+}

+ 31 - 0
api/observable.behavior.go

@@ -0,0 +1,31 @@
+package api
+
+type BehaviorSubjectStruct struct {
+	Subscriber
+	initial interface{}
+}
+
+func BehaviorSubject(initial ...interface{}) *BehaviorSubjectStruct {
+	var (
+		value interface{}
+	)
+	if len(initial) > 0 {
+		value = initial[0]
+	}
+
+	return &BehaviorSubjectStruct{
+		initial:    value,
+		Subscriber: *NewSubscriber(),
+	}
+}
+
+func (behavior *BehaviorSubjectStruct) Subscribe(handles ...func(...interface{})) (subscription *Subscription) {
+
+	subscription = behavior.Subscriber.Subscribe(handles...)
+
+	if behavior.initial != nil {
+		subscription.Next(behavior.initial)
+	}
+
+	return
+}

+ 193 - 0
api/observable.go

@@ -0,0 +1,193 @@
+package api
+
+import (
+	"fmt"
+	"sync"
+
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
+)
+
+var (
+	subscriptionControlID = 0
+)
+
+type OperatorFunction = func(source *ObservableStruct) *ObservableStruct
+
+type Subscription struct {
+	ID          int
+	callbacks   map[string]func(...interface{})
+	Unsubscribe func()
+}
+
+func (subscription *Subscription) execute(callbackID string, value interface{}) {
+	// fmt.Println("execute", callbackID)
+	// spew.Dump(value)
+	if callback, found := subscription.callbacks[callbackID]; found {
+		callback(value)
+	}
+}
+
+func (subscription *Subscription) Next(value interface{}) {
+	subscription.execute("next", value)
+}
+
+func (subscription *Subscription) Err(value interface{}) {
+	subscription.execute("error", value)
+}
+
+func (subscription *Subscription) Complete(value interface{}) {
+	subscription.execute("complete", value)
+}
+
+type Subscriber struct {
+	subscriptions map[*Subscription]*Subscription
+	callbacks     map[string]interface{}
+	wg            sync.WaitGroup
+}
+
+func (subscriber *Subscriber) Wait() {
+	subscriber.wg.Wait()
+}
+
+func (subscriber *Subscriber) Add(number int) {
+	subscriber.wg.Add(number)
+}
+
+func (subscriber *Subscriber) Done() {
+	defer func() {
+		recover()
+	}()
+	subscriber.wg.Done()
+}
+
+func (subscriber *Subscriber) Next(value ...interface{}) {
+	var send = First(value...)
+	// spew.Dump("call next", value, send)
+	// fmt.Println("----------------------------------")
+	for _, subscription := range subscriber.subscriptions {
+		subscription.Next(send)
+	}
+}
+
+func (subscriber *Subscriber) Err(value ...interface{}) {
+	var send = First(value...)
+	// spew.Dump("call Err", value, send)
+	// fmt.Println("----------------------------------")
+	for _, subscription := range subscriber.subscriptions {
+		subscription.Err(send)
+	}
+}
+
+func (subscriber *Subscriber) Complete(value ...interface{}) {
+	var send = First(value...)
+	// spew.Dump("call Complete", value, send)
+	// fmt.Println("----------------------------------")
+	for _, subscription := range subscriber.subscriptions {
+		subscription.Complete(send)
+	}
+}
+
+var callbacksOrder = []string{"next", "error", "complete"}
+
+// func (subject *Subscriber) Subscribe(options SubscribeOptions) (subscription *Subscription) {
+func (subject *Subscriber) Subscribe(options ...func(...interface{})) (subscription *Subscription) {
+
+	var (
+		callbackID string
+	)
+
+	subscription = &Subscription{
+		callbacks: map[string]func(...interface{}){},
+		Unsubscribe: func() {
+			fmt.Println("Unsubscribe...")
+			delete(subject.subscriptions, subscription)
+		},
+	}
+
+	for index, callback := range options {
+		callbackID = callbacksOrder[index]
+		subscription.callbacks[callbackID] = func(callbackReference func(...interface{})) func(arg ...interface{}) {
+			return func(arg ...interface{}) {
+				defer subject.Done()
+				// spew.Dump("closure callback ", callbacksOrder[index], arg, callback)
+				if callbackReference != nil {
+					callbackReference(arg...)
+				}
+			}
+		}(callback)
+	}
+
+	// spew.Dump(subscription.callbacks)
+
+	subject.subscriptions[subscription] = subscription
+	return
+}
+
+func NewSubscriber() *Subscriber {
+
+	subscriber := &Subscriber{
+		callbacks:     map[string]interface{}{},
+		subscriptions: map[*Subscription]*Subscription{},
+	}
+
+	return subscriber
+}
+
+type ObservableStruct struct {
+	subscribe func(*Subscriber)
+}
+
+func Observable(subscribe func(observer *Subscriber)) *ObservableStruct {
+
+	return &ObservableStruct{
+		subscribe: subscribe,
+	}
+
+}
+
+func (this *ObservableStruct) Pipe(operators ...OperatorFunction) *ObservableStruct {
+
+	observable := this
+
+	for _, function := range operators {
+		observable = function(observable)
+	}
+
+	return observable
+}
+
+func (observable *ObservableStruct) Subscribe(handles ...func(...interface{})) (subscription *Subscription) {
+
+	subject := NewSubscriber()
+
+	subscription = subject.Subscribe(handles...)
+
+	observable.subscribe(subject)
+
+	return
+}
+
+func rxErrorHandle(observer *Subscriber, err error) {
+
+	if err != nil {
+		observer.Err(err)
+
+	} else if err := recover(); err != nil {
+		observer.Err(err.(error))
+	}
+
+}
+
+func First(values ...interface{}) interface{} {
+	if len(values) > 0 {
+		return values[0]
+	}
+	return nil
+}
+
+func FirstError(values ...interface{}) *errs.Error {
+	if len(values) > 0 {
+		return values[0].(*errs.Error)
+	}
+	return nil
+}

+ 116 - 0
api/observable.operators.go

@@ -0,0 +1,116 @@
+package api
+
+import (
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
+	"github.com/davecgh/go-spew/spew"
+)
+
+// Operators
+
+func Take(total int) OperatorFunction {
+
+	return func(source *ObservableStruct) *ObservableStruct {
+
+		return Observable(func(observer *Subscriber) {
+			var (
+				count        = 0
+				subscription *Subscription
+			)
+
+			subscription = source.Subscribe(
+				func(value ...interface{}) {
+					if count == total {
+						spew.Dump(subscription)
+						subscription.Unsubscribe()
+						return
+					}
+					count++
+					observer.Next(First(value...))
+				},
+				observer.Err,
+				observer.Complete,
+			)
+		})
+	}
+}
+
+func RxMap(operator func(interface{}) (interface{}, error)) OperatorFunction {
+
+	return func(source *ObservableStruct) *ObservableStruct {
+
+		return Observable(func(observer *Subscriber) {
+			source.Subscribe(
+				func(value ...interface{}) {
+					var (
+						resp interface{}
+						err  error
+					)
+
+					defer rxErrorHandle(observer, err)
+
+					if resp, err = operator(First(value...)); err == nil {
+						observer.Next(resp)
+					}
+				},
+				observer.Err,
+				observer.Complete,
+			)
+		})
+	}
+}
+
+func RxSwitchMap(operator func(interface{}) *ObservableStruct) OperatorFunction {
+
+	return func(source *ObservableStruct) *ObservableStruct {
+
+		return Observable(func(observer *Subscriber) {
+
+			source.Subscribe(
+				func(value ...interface{}) {
+					defer rxErrorHandle(observer, nil)
+
+					operator(First(value...)).Subscribe(
+						observer.Next,
+						observer.Err,
+						observer.Complete,
+					)
+				},
+				observer.Err,
+				observer.Complete,
+			)
+
+		})
+
+		// return operator()
+	}
+}
+
+func RxCatchError(operator func(interface{}) *ObservableStruct) OperatorFunction {
+
+	return func(source *ObservableStruct) *ObservableStruct {
+
+		return Observable(func(observer *Subscriber) {
+
+			source.Subscribe(
+				observer.Next,
+				func(erros ...interface{}) {
+					var (
+						ok  bool
+						err interface{}
+					)
+
+					if err, ok = First(erros).(*errs.Error); !ok {
+						err = errs.FromError(err.(error))
+					}
+
+					operator(err).Subscribe(
+						observer.Next,
+						observer.Err,
+						observer.Complete,
+					)
+				},
+				observer.Complete,
+			)
+		})
+	}
+}

+ 49 - 0
api/observable_test.go

@@ -0,0 +1,49 @@
+package api
+
+import (
+	"fmt"
+	"testing"
+	"time"
+)
+
+func TestObeservable(t *testing.T) {
+
+	observable := Observable(func(observer *Subscriber) {
+
+		for i := 0; i < 5; i++ {
+			observer.Add(1)
+			go func(number int) {
+				fmt.Println("execute ", number)
+				observer.Next(number)
+				time.Sleep(200 * time.Millisecond)
+			}(i)
+		}
+
+		observer.Wait()
+	})
+
+	// subscription := observable.
+	observable.
+		Pipe(
+			// Take(2),
+			RxMap(func(value interface{}) (interface{}, error) {
+
+				return value.(int) * 2, nil
+			}),
+		).
+		Subscribe(
+			func(value ...interface{}) {
+				fmt.Println("expeted ", value)
+			},
+			func(err ...interface{}) {
+				fmt.Println("error ", err)
+			},
+			func(...interface{}) {
+				fmt.Println("complete")
+				// subscription.Done()
+			},
+		)
+
+	// subscription.Wait()
+
+}

+ 280 - 0
api/sse/hub.go

@@ -0,0 +1,280 @@
+package sse
+
+import (
+	"encoding/json"
+	"net/http"
+	"time"
+
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
+	"github.com/gomodule/redigo/redis"
+	context "github.com/kataras/iris/v12/context"
+)
+
+type Event struct {
+	Timestamp int64  `json:"timestamp"`
+	Payload   string `json:"payload"`
+	Kind      string `json:"kind"`
+}
+
+type ChannelClient struct {
+	Context context.Context
+	Flusher http.Flusher
+}
+
+// type  *event
+
+type Channel struct {
+	ID      string
+	Clients map[*ChannelClient]bool
+}
+
+func (channel *Channel) RemoveClient(client *ChannelClient) {
+	delete(channel.Clients, client)
+}
+
+func (channel *Channel) AddClient(client *ChannelClient) {
+	channel.Clients[client] = true
+}
+
+func (channel *Channel) Emit(event *Event) {
+	for client := range channel.Clients {
+		if event.Kind != "" {
+			client.Context.Writef("event: %s\n", event.Kind)
+		}
+		client.Context.Writef("data: %s\n\n", event.Payload)
+		client.Flusher.Flush()
+	}
+}
+
+type SSEHub struct {
+	// New client connections
+	newClients chan chan []byte
+
+	// Closed client connections
+	closingClients chan chan []byte
+
+	// Client connections registry
+	Channels map[string]*Channel
+
+	ChannelCollection string
+
+	RedisPool *redis.Pool
+}
+
+type SSEOptions struct {
+	URI               string
+	Password          string
+	ChannelCollection string
+}
+
+func NewSSEHub(options *SSEOptions) *SSEHub {
+
+	return &SSEHub{
+		newClients:        make(chan chan []byte),
+		closingClients:    make(chan chan []byte),
+		Channels:          make(map[string]*Channel),
+		ChannelCollection: options.ChannelCollection,
+		RedisPool: &redis.Pool{
+			// Maximum number of idle connections in the pool.
+			MaxIdle: 80,
+			// max number of connections
+			MaxActive: 12000,
+			// Dial is an application supplied function for creating and
+			// configuring a connection.
+			Dial: func() (redis.Conn, error) {
+				conn, err := redis.Dial("tcp", options.URI)
+				if err != nil {
+					panic(err.Error())
+				}
+
+				if options.Password != "" {
+					if _, err := conn.Do("AUTH", options.Password); err != nil {
+						conn.Close()
+						return nil, err
+					}
+				}
+				return conn, err
+			},
+		},
+	}
+}
+
+func (hub *SSEHub) GetChannel(channelID string) *Channel {
+
+	if _, exist := hub.Channels[channelID]; !exist {
+		channel := &Channel{
+			Clients: make(map[*ChannelClient]bool),
+			ID:      channelID,
+		}
+		hub.Channels[channelID] = channel
+	}
+
+	return hub.Channels[channelID]
+}
+
+func (hub *SSEHub) Dispatch(event *Event, channels ...string) {
+
+	var (
+		exists bool
+		err    error
+		conn   = hub.RedisPool.Get()
+	)
+
+	defer conn.Close()
+
+	for _, channel := range channels {
+
+		if exists, err = redis.Bool(conn.Do("HEXISTS", hub.ChannelCollection, channel)); err != nil || !exists {
+			continue
+		}
+
+		eventBytes, _ := json.Marshal(event)
+
+		conn.Do("RPUSH", channel, string(eventBytes))
+	}
+}
+
+func (hub *SSEHub) UpgradeConnection(ctx context.Context, channelId string) (err *errs.Error) {
+
+	var (
+		conn    = hub.RedisPool.Get()
+		payload string
+		count   int
+		// ok, closed bool
+		ok       bool
+		redisErr error
+		flusher  http.Flusher
+	)
+
+	defer conn.Close()
+
+	if flusher, ok = ctx.ResponseWriter().Flusher(); !ok {
+		err = errs.HTTPVersionNotSupported().Details(&errs.Detail{
+			Dominio:      "",
+			Reason:       "Streaming unsupported",
+			Location:     "hook.beforePersist/caracterizationForm",
+			LocationType: "",
+		})
+		return
+	}
+
+	// Each connection registers its own message channel with the Broker's connections registry.
+	// messageChan := make(chan []byte)
+
+	// Signal the broker that we have a new connection.
+	// hub.newClients <- messageChan
+
+	if _, redisErr = redis.Int(conn.Do("HSET", hub.ChannelCollection, channelId, true)); redisErr != nil {
+		err = errs.Internal().Details(&errs.Detail{
+			Dominio:      "",
+			Reason:       "Fail on register channel",
+			Location:     "hook.beforePersist/caracterizationForm",
+			LocationType: "",
+		})
+		return
+	}
+
+	client := &ChannelClient{
+		Flusher: flusher,
+		Context: ctx,
+	}
+
+	channel := hub.GetChannel(channelId)
+	channel.AddClient(client)
+
+	finalizeMain := make(chan bool)
+	finalizePing := make(chan bool)
+	finalized := false
+
+	// Listen to connection close and when the entire request handler chain exits(this handler here) and un-register messageChan.
+	ctx.OnClose(func() {
+		defer func() {
+			// recover from panic caused by writing to a closed channel
+			recover()
+			return
+		}()
+
+		if finalized {
+			return
+		}
+
+		finalized = true
+		// ctx.Application().Logger().Infof("notification.channel.disconect(%s)", channelId)
+		// Remove this client from the map of connected clients
+		// when this handler exits.
+		redis.Int(conn.Do("HDEL", hub.ChannelCollection, channelId))
+		// closesd = true
+		finalizeMain <- finalized
+		finalizePing <- finalized
+		channel.RemoveClient(client)
+	})
+
+	// Set the headers related to event streaming, you can omit the "application/json" if you send plain text.
+	// If you develop a go client, you must have: "Accept" : "application/json, text/event-stream" header as well.
+	ctx.ContentType("text/event-stream")
+	// ctx.Header("Access-Control-Allow-Origin", "*")
+	ctx.Header("Cache-Control", "no-cache")
+	ctx.Header("Connection", "keep-alive")
+	ctx.ResponseWriter().Header().Set("Access-Control-Allow-Origin", "*")
+
+	flusher.Flush()
+	// ctx.Request().Response.Header.Set("Access-Control-Allow-Origin", "*")
+
+	// Block waiting for messages broadcast on this connection's messageChan.
+	// check notification in redis
+	// go func() {
+
+	// ctx.Application().Logger().Warnf("init.notification.loop %v", closesd)
+	// Essa thread dispara um ping para manter a conexão ativa com o client
+	go func() {
+		ticker := time.NewTicker(3 * time.Second)
+		defer ticker.Stop()
+
+		for {
+			select {
+			case <-ticker.C:
+				// ctx.Application().Logger().Warnf("init.notification.loop 2s")
+				flusher.Flush()
+
+			case <-finalizePing:
+				// ctx.Application().Logger().Warnf("finalize init.notification.loop 2s")
+				close(finalizePing)
+				return
+			}
+		}
+	}()
+
+	ticker5Second := time.NewTicker(5 * time.Second)
+	defer ticker5Second.Stop()
+
+reset:
+	for {
+		select {
+		case <-ticker5Second.C:
+			// ctx.Application().Logger().Warnf("init.notification.loop 5s")
+
+			if count, redisErr = redis.Int(conn.Do("LLEN", channelId)); count == 0 || redisErr != nil {
+				continue reset
+			}
+
+			for ; count > 0; count-- {
+
+				if payload, redisErr = redis.String(conn.Do("LPOP", channelId)); redisErr != nil {
+					// ctx.Application().Logger().Errorf("NotificationError:", redisErr)
+				} else {
+					event := &Event{}
+					json.Unmarshal([]byte(payload), event)
+					channel.Emit(event)
+					break
+				}
+			}
+
+		case <-finalizeMain:
+			// ctx.Application().Logger().Infof("notification.finalize.disconect(%s)", channelId)
+			close(finalizeMain)
+			return
+		}
+
+	}
+	return
+}

+ 162 - 0
api/timeago.go

@@ -0,0 +1,162 @@
+package api
+
+import (
+	"fmt"
+	"math"
+	"strings"
+	"time"
+)
+
+// Internationalization (i18n)
+var Internationalization = map[string]map[string]string{}
+
+func init() {
+	Internationalization["pt-BR"] = map[string]string{
+		"future": "em %s",
+		"past":   "%s atrás",
+		"s":      "alguns segundos",
+		"ss":     "%d segundos",
+		"m":      "um minuto",
+		"mm":     "%d minutos",
+		"h":      "uma hora",
+		"hh":     "%d horas",
+		"d":      "um dia",
+		"dd":     "%d dias",
+		"M":      "um mês",
+		"MM":     "%d meses",
+		"y":      "acerca de um ano",
+		"yy":     "%d anos",
+	}
+}
+
+func repeatCount(test bool) int {
+	if test {
+		return 1
+	} else {
+		return 2
+	}
+}
+
+// FromDuration returns a friendly string representing an approximation of the
+// given duration
+func TimeAgoFromDuration(d time.Duration, locale ...string) string {
+	var (
+		loc                                 = localeparse(locale...)
+		seconds                             = int(math.Floor(d.Seconds() + .50))
+		templates                           = Internationalization[loc]
+		minutes, hours, days, months, years int
+		//
+		format = func(prefix string, value int) string {
+			id := strings.Repeat(prefix, repeatCount(value == 1))
+			return fmt.Sprintf(
+				templates[id],
+				value,
+			)
+		}
+	)
+
+	switch {
+	case seconds < 30:
+		return templates["s"]
+	case seconds < 60:
+		return templates["ss"]
+	}
+
+	if minutes = div(seconds, 60); minutes < 60 {
+		return format("m", minutes)
+	}
+
+	if hours = div(minutes, 60); hours < 24 {
+		return format("h", hours)
+	}
+
+	if days = div(hours, 24); days < 31 {
+		return format("d", days)
+	}
+
+	if months = div(days, 31); months < 12 {
+		return format("M", months)
+	}
+
+	years = div(months, 12)
+
+	return format("y", years)
+}
+
+func localeparse(locale ...string) (loc string) {
+	if len(locale) > 0 {
+		loc = locale[0]
+	} else {
+		loc = "pt-BR"
+	}
+	return
+}
+
+// FromTime returns a friendly string representing the approximate difference
+// from the given time and time.Now()
+func TimeAgoFromTime(t time.Time, locale ...string) string {
+
+	var (
+		loc       = localeparse(locale...)
+		now       = time.Now()
+		d         time.Duration
+		id        string
+		templates = Internationalization[loc]
+	)
+
+	if t.Before(now) {
+		d = now.Sub(t)
+		id = "past"
+	} else {
+		d = t.Sub(now)
+		id = "future"
+	}
+
+	return fmt.Sprintf(templates[id], TimeAgoFromDuration(d, locale...))
+}
+
+func div(numerator int, denominator int) int {
+	rem := numerator % denominator
+	result := numerator / denominator
+
+	if rem >= (denominator / 2) {
+		result++
+	}
+
+	return result
+}
+
+// func round(f float64) int {
+// 	return
+// }
+
+// func pluralize(i int, s string) string {
+// 	var buf bytes.Buffer
+// 	buf.WriteString(fmt.Sprintf("%d %s", i, s))
+// 	if i != 1 {
+// 		buf.WriteString("s")
+// 	}
+// 	return buf.String()
+// }
+
+// type TimeAgo struct {
+// 	Locale    string
+// 	templates map[string]string
+// }
+
+// func NewTimeAgo(locale ...string) *TimeAgo {
+
+// 	return &TimeAgo{
+// 		Locale:    L,
+// 		templates: Internationalization[L],
+// 	}
+// }
+
+// const (
+// 	minute = 1
+// 	hour   = minute * 60
+// 	day    = hour * 24
+// 	month  = day * 30
+// 	year   = day * 365
+// 	// quarter = year / 4
+// )

+ 853 - 0
api/utils.go

@@ -0,0 +1,853 @@
+package api
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/url"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
+	"github.com/davecgh/go-spew/spew"
+	"github.com/eugeniucarvalho/validator"
+	"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"
+)
+
+var (
+	AcceptJson = regexp.MustCompile("^application/json")
+
+	Environment = map[string]interface{}{}
+	ToReference = references{}
+)
+
+type references struct {
+}
+
+func (this *references) True() *bool {
+	value := true
+	return &value
+}
+
+func (this *references) False() *bool {
+	value := false
+	return &value
+}
+
+func (this *references) String(value string) *string {
+	return &value
+}
+
+func (this *references) Bool(value bool) *bool {
+	return &value
+}
+
+type ModeInterface interface {
+	Mode() string
+}
+
+type PatchHistoryRegister struct {
+	Id        primitive.ObjectID `bson:"_id" json:"-"`
+	CreatedAt int64              `bson:"createdAt" json:"-"`
+	CreatedBy string             `bson:"createdBy" json:"-"`
+	Parent    string             `bson:"parent" json:"-"`
+	ApiTags   []string           `bson:"apiTags" json:"-"`
+}
+
+type EntityModel struct {
+	Mode               string  `bson:"-" json:"-"`
+	ApplicationVersion *string `bson:"appVersion,omitempty" json:"-"`
+	Deleted            *bool   `bson:"deleted,omitempty" json:"-"`
+	DeletedIn          *int64  `bson:"deletedIn,omitempty" json:"-"`
+}
+
+// type DotNotation struct {
+// 	Rules map[string]interface{} `bson:",inline" json:"-"`
+// }
+
+// func (model *DotNotation) DotNotation() map[string]interface{} {
+
+// 	if model.Rules == nil {
+// 		model.Rules = map[string]interface{}{}
+// 	}
+// 	return model.Rules
+// }
+
+func UserIDString(ctx context.Context) (id string, err *errs.Error) {
+
+	if user, ok := ctx.Values().Get("$user.ref").(map[string]interface{}); ok {
+		id = user["id"].(primitive.ObjectID).Hex()
+		return
+	}
+	err = errs.Internal().Details(&errs.Detail{
+		Message: "Invalid user instance",
+	})
+	return
+}
+
+func (model *EntityModel) SetMode(mode string) {
+	model.Mode = mode
+	switch mode {
+	case "create":
+		model.ApplicationVersion = &BuildVersion
+		deleted := false
+		model.Deleted = &deleted
+
+	case "update":
+		if model.Deleted == nil {
+			deleted := false
+			model.Deleted = &deleted
+		}
+	case "patch":
+		// if model.Deleted == nil {
+		// 	deleted := false
+		// 	model.Deleted = &deleted
+		// }
+		// case "delete":
+		// case "undelete":
+	}
+
+}
+
+func GetIDString(input interface{}) (string, *errs.Error) {
+
+	if value, converted := input.(string); converted {
+		return value, nil
+	}
+	if value, converted := input.(*primitive.ObjectID); converted {
+		return value.Hex(), nil
+	}
+	if value, converted := input.(primitive.ObjectID); converted {
+		return value.Hex(), nil
+	}
+
+	return "", errs.Internal().Details(&errs.Detail{
+		Reason: fmt.Sprintf("Can't convert ID into string. Invalid type '%T", input),
+	})
+}
+
+func NowUnix() int64 {
+	return time.Now().Unix()
+}
+
+func GetUser(ctx context.Context) interface{} {
+	return ctx.Values().Get("$user.ref")
+}
+
+func (model *EntityModel) SetDeleted(deleted bool) {
+	var deletedIn = int64(0)
+	model.Deleted = &deleted
+	if *model.Deleted {
+		deletedIn = time.Now().Unix()
+	}
+	model.DeletedIn = &deletedIn
+}
+
+type CorsOptions struct {
+	ExposeHeaders []string
+	AllowHeaders  []string
+	AllowMethods  []string
+	AllowOrigin   []string
+}
+
+type GetManyResponse struct {
+	ResultSizeEstimate int         `json:"resultSizeEstimate"` // Estimativa do numero total de itens.
+	NextPageToken      string      `json:"nextPageToken"`      // Referência para a proxima pagina de resultados.
+	Itens              interface{} `json:"itens"`              // Lista contento os elementos da resposta.
+}
+
+var (
+	// ParamsFlag         map[string]*ParamFlag
+	CorsDefaultOptions = CorsOptions{
+		AllowOrigin: []string{"*"},
+		// AllowOrigin:   []string{"http://localhost:4200"},
+		AllowMethods:  []string{"OPTIONS", "GET", "POST", "PUT", "DELETE", "PATCH"},
+		AllowHeaders:  []string{"Authorization", "Content-Type", "Origin", "Host", "x-api-build"},
+		ExposeHeaders: []string{"X-total-count"},
+	}
+
+	BuildVersion = "0"
+	ApiVersion   = "0"
+	// Armazena os mimes dos arquivos consultados
+	// MimeCache    = xmap.NewMS2S()
+
+	replaceEmpty = regexp.MustCompile(`\s+`)
+	// pageTokenRegex = regexp.MustCompile(`(?P<ids>\w+):(?P<idc>\w+):(?P<page>\d{1,6})`)
+	pageTokenRegex = regexp.MustCompile(`(?P<idc>[\w-]+):(?P<count>\d+)`)
+	replaceIndex   = regexp.MustCompile(`\.\d+\.`)
+)
+
+func init() {
+	// fmt.Println("Register validation functions")
+	// err := Validator.RegisterValidation("req", func(f validator.FieldLevel) bool {
+	// 	fmt.Println("Running req validation")
+	// 	spew.Dump(f)
+	// 	return true
+	// })
+
+	validator.RegisterValidator("requiredOnCreate", func(i interface{}, o interface{}, v *validator.ValidatorOption) error {
+
+		if schema, ok := o.(ModeInterface); ok {
+			fmt.Println("requiredOnCreate ->", schema.Mode())
+			switch schema.Mode() {
+			case "create":
+				if i == nil {
+					return fmt.Errorf("")
+				}
+			case "update":
+			}
+		}
+
+		return nil
+	})
+	// Validator.RegisterValidation("req",
+	// 	func(fl validator.FieldLevel) bool {
+	// 		// func(v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
+	// 		// func(v *validator.Validate, param string) bool {
+	// 		// return passwordRegex.MatchString(field.String())
+
+	// 	})
+
+	// if err != nil {
+	// 	panic(err)
+	// }
+}
+func Panic() {
+	if err := recover(); err != nil {
+		LogError(0, err.(error).Error())
+	}
+}
+func Validate(i interface{}) *errs.Error {
+
+	// if err := Validator.Struct(i); err != nil {
+
+	// fmt.Println("VALIDATE", result, err)
+	// spew.Dump(i)
+	if errors, valid := validator.Struct(i); !valid {
+
+		err := errs.InvalidArgument()
+
+		// er := Error(ERR_INVALID_PARAM, "Invalid params")
+
+		for _, e := range errors {
+			err.Details(&errs.Detail{
+				Dominio: "global",
+				Reason:  "invalidParameter",
+				Message: e.Message,
+				// Message: fmt.Sprintf("%+v", e.Message),
+				// LocationType:,
+				// Location     :,
+			})
+		}
+
+		// if _, ok := err.(*validator.InvalidValidationError); ok {
+		// 	fmt.Println("INvalid")
+		// 	er.LastDescription().Message = err.Error()
+
+		// } else {
+
+		// 	for _, err := range err.(validator.ValidationErrors) {
+
+		// 		switch err.Tag() {
+		// 		case "required":
+
+		// 		}
+
+		// 		er.Add(&ErrDescription{
+		// 			Dominio: "global",
+		// 			Reason:  "invalidParameter",
+		// 			Message: fmt.Sprintf("%+v", err),
+		// 			// LocationType:,
+		// 			// Location     :,
+		// 		})
+
+		// 		// fmt.Println("1", err.Namespace())
+		// 		// fmt.Println("2", err.Field())
+		// 		// fmt.Println("3", err.StructNamespace()) // can differ when a custom TagNameFunc is registered or
+		// 		// fmt.Println("4", err.StructField())     // by passing alt name to ReportError like below
+		// 		// fmt.Println("5", err.Tag())
+		// 		// fmt.Println("6", err.ActualTag())
+		// 		// fmt.Println("7", err.Kind())
+		// 		// fmt.Println("8", err.Type())
+		// 		// fmt.Println("9", err.Value())
+		// 		// fmt.Println("10", err.Param())
+		// 		// fmt.Println("-------------")
+		// 	}
+
+		// }
+		return err
+		// from here you can create your own error messages in whatever language you wish
+	}
+	return nil
+}
+
+// ApiResponse é a estrutura padrão de respostas
+// Apenas os campos preenchidos serão retornados
+type ApiResponse struct {
+	Entity             interface{} `json:"entity,omitempty"`
+	List               interface{} `json:"list,omitempty"`
+	NextPageToken      string      `json:"nextPageToken,omitempty"`
+	ResultSizeEstimate int         `json:"resultSizeEstimate,omitempty"`
+}
+
+// func ErroCtxHandler(ctx context.Context, err *errs.Error) {
+
+// 	if accept := ctx.GetHeader("Accept"); AcceptJson.Match([]byte(accept)) {
+// 		ctx.JSON(err)
+// 	} else {
+// 		ctx.ViewData("", err)
+// 	}
+
+// }
+
+func finalizeRequest(ctx context.Context, resp interface{}, err *errs.Error) {
+	var (
+		status = 200
+		accept = ctx.Request().Header.Get("accept")
+		types  = strings.Split(accept, ",")
+	)
+
+	defer func() {
+		ctx.StopExecution()
+		if err != nil {
+
+			ctx.Application().Logger().Error(err.Error())
+			if description := err.LastDescription(); description != nil {
+				err.Stack.Print()
+				// ctx.Application().Logger().Error(fmt.Printf("%s\n%s\n", description.Reason, description.Message))
+			}
+
+			// ctx.Application().Logger().Error()
+			spew.Dump(err)
+		}
+
+		fmt.Println("defer of finalizeRequest")
+		if r := recover(); r != nil {
+			fmt.Println("Recovered in f", r)
+		}
+	}()
+
+	if err != nil {
+		status = err.HttpStatus
+
+		// fmt.Println(status, err.Message, "------------------------------------------\n")
+		// spew.Dump(err)
+		// debbug
+		// err.Stack.Print()
+		resp = err
+		abortTransaction(ctx)
+		// fmt.Println("------------------------------------------\n", status)
+		// spew.Dump(err)
+		// fmt.Println("------------------------------------------\n", status)
+	}
+
+	ctx.Values().Set("res", resp)
+	ctx.Header("x-api-build", BuildVersion)
+	// fmt.Println("error")
+	// spew.Dump(resp)
+	// spew.Dump(types)
+	// spew.Dump(ctx.Request().Header)
+	ctx.StatusCode(status)
+
+	for _, mime := range types {
+		switch mime {
+		case "application/json":
+			ctx.JSON(resp)
+			return
+		}
+	}
+	// default response case
+	ctx.WriteString("")
+}
+
+// Call encapsula e trata os erros para cada requisição.
+func CallAction(id string, fn func(context.Context) (interface{}, *errs.Error)) func(context.Context) {
+	return func(ctx context.Context) {
+		var (
+			err      *errs.Error
+			resp     interface{}
+			finalize = true
+		)
+
+		defer func() {
+			if !ctx.IsStopped() {
+
+				if _err := recover(); _err != nil {
+
+					err = errs.Internal().Details(&errs.Detail{
+						Message:      "",
+						Location:     fmt.Sprintf("call.action.%s", id),
+						LocationType: "application.pipe.resource.stage",
+						Reason:       _err.(error).Error(),
+					})
+				}
+
+				if finalize {
+					finalizeRequest(ctx, resp, err)
+				}
+			}
+		}()
+
+		fmt.Println("apply -> ", id)
+
+		if resp, err = fn(ctx); err != nil {
+			return
+		}
+
+		if !ctx.IsStopped() {
+
+			if resp != nil {
+				err = commitTransaction(ctx)
+			} else {
+				ctx.Next()
+				finalize = false
+			}
+		}
+		return
+	}
+}
+
+func abortTransaction(ctx context.Context) (err *errs.Error) {
+	return transactionHandler(ctx, "abort")
+}
+
+func commitTransaction(ctx context.Context) (err *errs.Error) {
+	return transactionHandler(ctx, "commit")
+}
+
+func transactionHandler(ctx context.Context, action string) (err *errs.Error) {
+	var (
+		localErr  error
+		operation func() error
+	)
+	contextSession := GetSessionContext(ctx)
+
+	if contextSession == nil {
+		return
+	}
+
+	switch action {
+	case "abort":
+		operation = func() error { return contextSession.AbortTransaction(contextSession) }
+	case "commit":
+		operation = func() error { return contextSession.CommitTransaction(contextSession) }
+	}
+	try := 4
+	for {
+		if localErr = operation(); localErr == nil {
+			fmt.Println(action, "executed ")
+			return
+		}
+		if try == 0 {
+			return
+		}
+		try--
+
+		fmt.Println(action, "transaction error loop ")
+		// time.Sleep(4 * time.Second)
+		// retry operation when command contains TransientTransactionError
+		// if cmdErr, ok := localErr.(mongo.CommandError); ok && cmdErr.HasErrorLabel("TransientTransactionError") {
+		if cmdErr, ok := localErr.(mongo.CommandError); ok {
+			fmt.Println(action, cmdErr)
+			if cmdErr.HasErrorLabel("TransientTransactionError") {
+				continue
+			}
+		}
+	}
+
+	if localErr != nil {
+		err = errs.Internal().Details(&errs.Detail{
+			Message: localErr.Error(),
+		})
+	}
+	return
+}
+
+func ReadJson(ctx context.Context, entity interface{}) (err *errs.Error) {
+	if err := ctx.ReadJSON(entity); err != nil {
+		err = errs.DataCaps().Details(&errs.Detail{
+			Message: err.Error(),
+		})
+	}
+	return
+}
+
+func MgoSortBson(fields []string) *bson.M {
+	order := bson.M{}
+	for _, field := range fields {
+		n := 1
+		if field != "" {
+			fmt.Printf("sort '%c'\n", field[0])
+			switch field[0] {
+			case '+':
+				field = field[1:]
+			case '-':
+				n = -1
+				field = field[1:]
+			default:
+				panic(fmt.Sprintf("Invalid sort field %s.", field))
+			}
+		}
+		if field == "" {
+			panic("Sort: empty field name")
+		}
+		field = string(replaceIndex.ReplaceAll([]byte(field), []byte(".")))
+		order[field] = n
+	}
+	return &order
+}
+
+func MgoSort(ctx context.Context, field string) []string {
+	result := []string{}
+	if fields := Q(ctx, field, ""); fields != "" {
+		sort := string(replaceEmpty.ReplaceAll([]byte(fields), []byte("")))
+
+		result = strings.Split(sort, ",")
+	}
+	// return nil
+	return result
+}
+
+func MgoFieldsCtx(ctx context.Context, field string) *bson.M {
+	return MgoFields(Q(ctx, field, ""))
+}
+
+func MgoFields(fields string) (projection *bson.M) {
+
+	// fmt.Printf("MgoFields '%s'\n", fields)
+	if fields != "" {
+		projection = &bson.M{}
+		for _, v := range strings.Split(fields, ",") {
+			(*projection)[v] = 1
+		}
+		// spew.Dump(projection)
+	}
+	return
+}
+
+func MgoQuery(ctx context.Context, field string) (*bson.M, *errs.Error) {
+	return MgoQueryString(ctx, Q(ctx, field, ""))
+}
+
+func MgoQueryString(ctx context.Context, query string) (*bson.M, *errs.Error) {
+	var (
+		selector = make(bson.M)
+		// id       = "_id"
+		err error
+	)
+
+	// Unmarshal json query if any
+	if query != "" {
+
+		if err = bson.UnmarshalExtJSON([]byte(query), true, &selector); err != nil {
+			// return nil, Error(ERR_GENERAL, err.Error())
+			return nil, errs.Internal().Details(&errs.Detail{
+				Message: err.Error(),
+			})
+		}
+
+		if query, err = url.QueryUnescape(query); err != nil {
+			return nil, errs.Internal().Details(&errs.Detail{
+				Message: err.Error(),
+			})
+			// return nil, Error(ERR_GENERAL, err.Error())
+		}
+		if err = json.Unmarshal([]byte(query), &selector); err != nil {
+			// return nil, Error(ERR_GENERAL, err.Error())
+			return nil, errs.Internal().Details(&errs.Detail{
+				Message: err.Error(),
+			})
+		}
+		// if selector, err = mejson.Unmarshal(selector); err != nil {
+		// 	return nil, Error(ERR_GENERAL, err.Error())
+		// }
+	}
+
+	// Transform string HexId to ObjectIdHex
+	// if selid, _ := selector[id].(string); selid != "" {
+	// 	if bson.IsObjectIdHex(selid) {
+	// 		selector[id] = bson.ObjectIdHex(selid)
+	// 	}
+	// 	//  else {
+	// 	// 	selector[id] = selid
+	// 	// }
+	// }
+	return &selector, nil
+}
+
+func DefaultCorsHandler() func(ctx context.Context) {
+	return Cors(CorsDefaultOptions)
+}
+
+func Cors(opt CorsOptions) func(ctx context.Context) {
+	return func(ctx context.Context) {
+		
+		ctx.Header("Access-Control-Allow-Credentials", "true")
+
+		if len(opt.AllowOrigin) > 0 {
+			// ctx.Header("Access-Control-Allow-Origin", strings.Join(opt.AllowOrigin, ","))
+			// ctx.Header("Access-Control-Allow-Origin", "*")
+			// ctx.Header("Origin", "*")
+			ctx.Header("Access-Control-Allow-Origin", ctx.GetHeader("Origin"))
+		}
+		if len(opt.AllowMethods) > 0 {
+			ctx.Header("Access-Control-Allow-Methods", strings.Join(opt.AllowMethods, ","))
+		}
+		if len(opt.AllowHeaders) > 0 {
+			ctx.Header("Access-Control-Allow-Headers", strings.Join(opt.AllowHeaders, ","))
+		}
+		if len(opt.ExposeHeaders) > 0 {
+			ctx.Header("Access-Control-Expose-Headers", strings.Join(opt.ExposeHeaders, ","))
+		}
+		ctx.Next()
+	}
+}
+
+// Retorna um valor de um parametro no path da url.
+func P(ctx context.Context, name string, def string) string {
+	val := ctx.Params().Get(name)
+	if val == "" {
+		val = def
+	}
+	return val
+}
+
+//Retorna um valor de um parametro da query. Ex: ?x=1
+func Q(ctx context.Context, name string, def string) string {
+	val := ctx.URLParam(name)
+	if val == "" {
+		val = def
+	}
+	return val
+}
+
+//Retorna um valor de um parametro da query
+func QInt(ctx context.Context, name string, def int) int {
+	val, e := strconv.Atoi(Q(ctx, name, ""))
+	if e != nil {
+		val = def
+	}
+	return val
+}
+
+// Retorna um valor de um parametro do post.
+func F(ctx context.Context, name string, def interface{}) interface{} {
+	var val interface{}
+	val = ctx.FormValue(name)
+	if val == "" {
+		val = def
+	}
+	return val
+}
+
+func LogError(code int, m string) {
+	log("31", fmt.Sprintf("[ERROR| %d] %s", code, m))
+}
+
+func LogInfo(code int, m string) {
+	log("34", fmt.Sprintf("[INFO| %d] %s", code, m))
+}
+
+func LogWarning(code int, m string) {
+	log("35", fmt.Sprintf("[WARNING| %d] %s", code, m))
+}
+
+func log(color string, m string) {
+	fmt.Printf("\x1b[%s;1m%s\x1b[0m\n", color, m)
+}
+
+// func ParseRequestTest(ctx context.Context) {
+// 	var (
+// 		err    error
+// 		filter = &Filter{
+// 			MaxResults: QInt(ctx, "maxResults", 10),
+// 		}
+// 		oid primitive.ObjectID
+// 	)
+// 	// parse parameter of path
+// 	id := P(ctx, "userId", "")
+
+// 	if oid, err = primitive.ObjectIDFromHex(id); err != nil {
+// 		filter.UserId = oid
+// 	}
+
+// 	id = P(ctx, "id", "")
+// 	if oid, err = primitive.ObjectIDFromHex(id); err != nil {
+// 		filter.Id = oid
+// 	}
+
+// 	// filter.PageToken.Parse(Q(ctx, "nextPageToken", ""))
+
+// 	if filter.Query, err = MgoQuery(ctx, "q"); err != nil {
+// 		goto Error
+// 	}
+
+// 	filter.Format = Q(ctx, "format", "full")
+// 	filter.Sort = MgoSortBson(MgoSort(ctx, "sort"))
+// 	filter.Fields = MgoFieldsCtx(ctx, "fields")
+
+// Error:
+// 	if err != nil {
+// 		ErroCtxHandler(
+// 			ctx,
+// 			// Error(ERR_INVALID_PARAM, err.Error()),
+
+// 			errs.Internal().Details(&errs.Detail{
+// 				Message: err.Error(),
+// 			}),
+// 		)
+// 		return
+// 	}
+
+// 	ctx.Values().Set("$filter", filter)
+
+// 	ctx.Next()
+// }
+
+// func MapError(erro *errs.Error) *errs.Error {
+
+// 	if strings.Contains(erro.Message, "E11000") {
+
+// 		return errs.AlreadyExists().Details(&errs.Detail{
+// 			Message: "DUPLICATED_ITEM",
+// 		})
+
+// 	} else if strings.Contains(erro.Message, "no documents in result") {
+// 		return errs.Internal().Details(&errs.Detail{
+// 			Message: "NOT_FOUND",
+// 		})
+// 	}
+
+// 	return erro
+// }
+
+// type PageToken struct {
+// 	// StartID   string
+// 	// CurrentID string
+// 	Cursor    string
+// 	NewCursor string
+// 	Page      int
+// 	Count     int
+// }
+
+// // Encode cria um novo token formatado
+// func (p *PageToken) Encode() string {
+// 	out := ""
+// 	if p.NewCursor != "" {
+// 		out = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%d", p.NewCursor, p.Count)))
+// 	}
+// 	return out
+// 	// return string([]byte(fmt.Sprintf("%s:%d", p.NewCursor, p.Count)))
+// }
+
+// // HasToken determina se a requisição apresenta um token de paginacao
+// func (p *PageToken) HasToken() bool {
+// 	return p.Cursor != ""
+// }
+
+// // func (p *PageToken) GetBsonID() bson.ObjectId {
+// // 	if !bson.IsObjectIdHex(p.ID) {
+// // 		return nil
+// // 	}
+// // 	return bson.ObjectIdHex(p.ID)
+// // }
+
+// func (p *PageToken) Parse(s string) error {
+// 	var (
+// 		decoded []byte
+// 		err     error
+// 	)
+
+// 	if decoded, err = base64.StdEncoding.DecodeString(s); err != nil {
+// 		return err
+// 	}
+
+// 	match := pageTokenRegex.FindStringSubmatch(string(decoded))
+
+// 	if len(match) != 3 {
+// 		return fmt.Errorf("Invalid Page Token")
+// 	}
+
+// 	p.Cursor = match[1]
+// 	// p.Page, err = strconv.Atoi(match[2])
+// 	p.Count, err = strconv.Atoi(match[2])
+
+// 	return err
+// }
+
+// Layout aplica o path do layout
+
+// func Layout(ctx context.Context) {
+// 	ctx.ViewLayout(ViewScript(ctx, "layout/layout.html"))
+// 	ctx.Next()
+// }
+
+// // ViewScript devolve o path do arquivo de script a ser renderizaco
+// func ViewScript(ctx context.Context, filename string) string {
+// 	var (
+// 		base string
+// 		ok   bool
+// 	)
+// 	domain := strings.Split(ctx.Request().Host, ":")[0]
+
+// 	if base, ok = TemplateDomainMap[domain]; !ok {
+// 		base = "default"
+// 	}
+// 	return base + "/" + filename
+// }
+
+// defer func() {
+// 	var (
+// 		erro *errs.Error
+// 		// ok   bool
+// 		err error
+// 	)
+
+// 	if err = recover(); err != nil {
+
+// 		if erro, ok = err.(*errs.Error); !ok {
+// 			erro = Error(ERR_GENERAL, err.Error())
+// 		}
+
+// 		ErroCtxHandler(ctx, erro)
+// 	}
+// 	// ctx.Header("Accept", "application/json")
+// 	// spew.Dump(err)
+// }()
+
+// func CallAction(f func(context.Context, *ApiResponse) *errs.Error) func(ctx context.Context) {
+// 	return func(ctx context.Context) {
+// 		var (
+// 			err      *errs.Error
+// 			response = &ApiResponse{}
+// 		)
+// 		if err = f(ctx, response); err != nil {
+// 			ErroCtxHandler(ctx, err)
+// 			return
+// 		}
+
+// 		ctx.JSON(response)
+// 	}
+// }
+
+// Verifica se existe alguma pagina para ser carragada.
+
+// func UpdateCursorResponse(models *Mongo, f *Filter, resp interface{}) bool {
+
+// 	count := f.PageToken.Count
+// 	// Se não foi encontrado nenhum registro
+
+// 	if count > 0 {
+
+// 		resp.ResultSizeEstimate = count
+// 		resp.NextPageToken = f.PageToken.Encode()
+
+// 		return true
+// 	}
+// 	return false
+
+// }

+ 63 - 0
authorization/authorization.go

@@ -0,0 +1,63 @@
+package authorization
+
+import (
+	"strings"
+	"time"
+
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
+	context "github.com/kataras/iris/v12/context"
+	"github.com/pascaldekloe/jwt"
+)
+
+var (
+	SECRET                     = []byte{}
+	PREFIX_AUTHORIZATION_TOKEN = "Bearer "
+)
+
+type JwtChk func(ctx context.Context, resourceID string, claims *jwt.Claims) (err *errs.Error)
+
+func Handler(resource string, fn JwtChk) func(ctx context.Context) (resp interface{}, err *errs.Error) {
+
+	return func(ctx context.Context) (resp interface{}, err *errs.Error) {
+		var (
+			claims *jwt.Claims
+		)
+
+		if claims, err = ParseToken(ctx.GetHeader("Authorization")); err != nil {
+			return
+		}
+
+		if err = fn(ctx, resource, claims); err != nil {
+			return
+		}
+
+		ctx.Next()
+		return
+	}
+}
+
+func ParseToken(token string) (claims *jwt.Claims, err *errs.Error) {
+	var (
+		_err error
+	)
+
+	token = strings.Replace(token, PREFIX_AUTHORIZATION_TOKEN, "", -1)
+
+	if claims, _err = jwt.HMACCheck([]byte(token), SECRET); _err != nil {
+		err = errs.Unauthenticated().Details(&errs.Detail{
+			Message:      "Login required",
+			Location:     "Authorization",
+			LocationType: "header",
+			Reason:       "InvalidAuthorizationToken",
+		})
+
+	} else if !claims.Valid(time.Now()) {
+		err = errs.Unauthenticated().Details(&errs.Detail{
+			Message:      "Login required",
+			Location:     "Authorization",
+			LocationType: "header",
+			Reason:       "TokenExpired",
+		})
+	}
+	return
+}

+ 65 - 0
commands/common.go

@@ -0,0 +1,65 @@
+package commands
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/flag"
+)
+
+const (
+	EntitiesPath    = "entities"
+	ResourcesPath   = "resources"
+	EnvironmentPath = "env"
+)
+
+var (
+	upperCaseRegex = regexp.MustCompile(`(\s*([A-Z]))`)
+	Commands       = map[string]func() error{
+		"init":       initialize,
+		"compile":    compile,
+		"serve":      serve,
+		"g.entity":   generateEntity,
+		"g.resource": generateResource,
+	}
+	basePathFormats = map[bool]string{false: "./%s", true: "./%s/%s"}
+)
+
+func ExecuteWithFlags() (err error) {
+
+	if err = flag.Initialize(); err != nil {
+		return
+	}
+
+	command := flag.Command
+
+	if callback, exist := Commands[command]; exist {
+		err = callback()
+		return
+	}
+
+	err = fmt.Errorf("The command '%s' is not valid", command)
+	return
+}
+
+func basePath() string {
+	return basePathWithComplement("")
+}
+
+func basePathWithComplement(complement string) string {
+	return fmt.Sprintf(
+		basePathFormats[complement != ""],
+		project.ID,
+		complement,
+	)
+}
+
+func normalizePath(path string) string {
+	path = upperCaseRegex.ReplaceAllStringFunc(path, func(m string) string {
+		return fmt.Sprintf("-%s", strings.ToLower(strings.Trim(m, " ")))
+	})
+	path = strings.ReplaceAll(path, " ", "-")
+	path = strings.Trim(path, "-")
+	return path
+}

+ 36 - 0
commands/compile.go

@@ -0,0 +1,36 @@
+package commands
+
+import (
+	"fmt"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/gen"
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/flag"
+	"github.com/davecgh/go-spew/spew"
+)
+
+func compile() (err error) {
+	var project *Project
+
+	fmt.Println("COMPILE WITH SETUP...", *flag.Mode, *flag.BuildProfile, *flag.Out)
+	// Cria um novo projeto a partir do diretorio atual
+	if project, err = CreateProject(*flag.Mode); err != nil {
+		panic(err)
+	}
+
+	project.OutDirectory(*flag.Out)
+
+	// Executa a geracao de codigo para o projeto
+	if err = project.Build(&BuildOptions{
+		Mode: *flag.BuildProfile,
+	}); err != nil {
+		spew.Dump(err)
+		panic(err)
+	}
+	// Salva o json contendo a descricao completa do projeto
+
+	if err = project.Save(fmt.Sprintf("%s/project.gen.json", *flag.Out)); err != nil {
+		panic(err)
+	}
+	return
+}

+ 74 - 0
commands/generate.go

@@ -0,0 +1,74 @@
+package commands
+
+import (
+	"fmt"
+	"strings"
+
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/flag"
+)
+
+const (
+// ComponentNameInput = 1
+)
+
+var ()
+
+func generateEntity() (err error) {
+	var meta = flag.GenerateEntityControl
+
+	entity := common.Entity{
+		ID:   strings.ToLower(meta.Name),
+		Type: meta.Type,
+	}
+
+	for _, property := range strings.Split(meta.Properties, ",") {
+		property = strings.Trim(property, " ")
+		property = common.LcFirst(property)
+		if property != "" {
+			entity.Properties = append(entity.Properties, &common.Propertie{
+				ID: property,
+			})
+		}
+	}
+
+	filename := normalizePath(meta.Name)
+
+	if err = common.WriteToJson(
+		basePathWithComplement(fmt.Sprintf("%s/%s.json", EntitiesPath, filename)),
+		entity,
+	); err != nil {
+		return
+	}
+
+	return
+}
+
+func generateResource() (err error) {
+	var meta = flag.GenerateResourceControl
+
+	resource := common.Resource{
+		ID:     strings.ToLower(meta.Name),
+		Entity: meta.Entity,
+	}
+
+	for _, id := range strings.Split(meta.Methods, ",") {
+		id = strings.Trim(id, " ")
+		id = common.LcFirst(id)
+		if id != "" {
+			resource.Methods = append(resource.Methods, &common.Method{
+				ID: id,
+			})
+		}
+	}
+
+	filename := normalizePath(meta.Name)
+
+	if err = common.WriteToJson(
+		basePathWithComplement(fmt.Sprintf("%s/%s.json", ResourcesPath, filename)),
+		resource,
+	); err != nil {
+		return
+	}
+	return
+}

+ 300 - 0
commands/init.go

@@ -0,0 +1,300 @@
+package commands
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"strings"
+
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+)
+
+var (
+	project        = common.NewProject()
+	initHeaderText = `
+This utility will walk you through creating a project.json file.
+It only covers the most common items, and tries to guess sensible defaults.
+	
+Press ^C at any time to quit.
+	`
+	questionFormats    = map[bool]string{true: "%s: ", false: "%s: (%s) "}
+	userInputQuestions = []struct {
+		ask       string
+		inputType string
+		value     interface{}
+		_default  interface{}
+	}{
+		{
+			"project name",
+			"",
+			&project.Name,
+			"",
+		},
+		{
+			"author",
+			"",
+			&project.OwnerName,
+			"",
+		},
+		{
+			"version",
+			"",
+			&project.Version,
+			"0.0.1",
+		},
+		{
+			"description",
+			"",
+			&project.Description,
+			"",
+		},
+		// {
+		// 	"git repository",
+		// 	"",
+		// 	&project.Custom["go.package.repository"],
+		// 	"",
+		// },
+	}
+)
+
+func initialize() (err error) {
+
+	if common.FileExists("project.json") {
+		err = fmt.Errorf("This folder already contains a project.")
+		return
+	}
+
+	if err = readInitialUserInputData(); err != nil {
+		return
+	}
+
+	if err = initializeProjectPropertires(); err != nil {
+		return
+	}
+
+	if err = initializeBaseFilesAndSamples(); err != nil {
+		return
+	}
+	if err = createRootDirectories(); err != nil {
+		return
+	}
+
+	return
+}
+
+func readInitialUserInputData() (err error) {
+	reader := bufio.NewReader(os.Stdin)
+	fmt.Println(initHeaderText)
+	for _, question := range userInputQuestions {
+		args := []interface{}{question.ask}
+		hasDefault := !common.IsEmpty(question._default)
+
+		if hasDefault {
+			args = append(args, question._default)
+		}
+
+		fmt.Printf(
+			questionFormats[len(args) == 1],
+			args...,
+		)
+
+		input, _ := reader.ReadString('\n')
+
+		switch question.inputType {
+		// case "int64":
+		// case "float64":
+		default:
+			reference := question.value.(*string)
+			input = strings.Replace(input, "\n", "", -1)
+			if input == "" && hasDefault {
+				input = question._default.(string)
+			}
+			*reference = input
+		}
+	}
+	return
+}
+
+func initializeProjectPropertires() (err error) {
+	project.ID = strings.ToLower(project.Name)
+	return
+}
+
+func createRootDirectories() (err error) {
+
+	three := []string{
+		"build",
+		EntitiesPath,
+		ResourcesPath,
+		EnvironmentPath,
+		"include/go",
+	}
+
+	for index, path := range three {
+		three[index] = basePathWithComplement(path)
+	}
+
+	err = common.Mkdir(0777, three...)
+
+	return
+}
+
+func initializeBaseFilesAndSamples() (err error) {
+	// Create main project description
+	if err = common.WriteToJson(basePathWithComplement("project.json"), project); err != nil {
+		return
+	}
+
+	if err = createEnvironmentFiles(); err != nil {
+		return
+	}
+
+	if err = createQueriesFile(); err != nil {
+		return
+	}
+
+	if err = createCommonFile(); err != nil {
+		return
+	}
+
+	return
+}
+
+func createEnvironmentFiles() (err error) {
+
+	var (
+		environment = common.Environment{
+			"APP_ADDRS": {
+				Default:     "",
+				Required:    true,
+				Description: "Server address will listen for requests.",
+			},
+		}
+		production = common.Environment{}
+		test       = common.Environment{}
+	)
+
+	// Create main project description
+	if err = common.WriteToJson(basePathWithComplement("env/environment.json"), environment); err != nil {
+		return
+	}
+
+	if err = common.WriteToJson(basePathWithComplement("env/environment.prod.json"), production); err != nil {
+		return
+	}
+
+	if err = common.WriteToJson(basePathWithComplement("env/environment.prod.json"), test); err != nil {
+		return
+	}
+	return
+}
+
+func createCommonFile() (err error) {
+	commonProperties := `
+	{
+		"commonParams": {
+			"userId": {
+				"id": "userId",
+				"type": "string",
+				"description": "Identificação do usuário que esta realizando a consulta.",
+				"default": "me",
+				"required": true,
+				"location": "path"
+			},
+			"resource": {
+				"id": "resource",
+				"type": "string",
+				"description": "Identificação do recurso do conjunto de regras de acesso.",
+				"required": true,
+				"location": "path"
+			},
+			"id": {
+				"id": "id",
+				"type": "string",
+				"description": "Identificação da conta de usuário consultado.",
+				"required": true,
+				"location": "path"
+			},
+			"fields": {
+				"id": "fields",
+				"type": "string",
+				"description": "Identifica um subconjunto de campos do modelo.",
+				"required": false,
+				"location": "query"
+			},
+			"includeInTrash": {
+				"id": "includeInTrash",
+				"type": "bool",
+				"description": "Especifica se a consulta deve levar em consideração os registros deletados.",
+				"required": false,
+				"location": "query",
+				"accept": [
+					"true"
+				]
+			},
+			"format": {
+				"id": "format",
+				"type": "string",
+				"description": "Especifica configurações de campos predefinidas.",
+				"required": false,
+				"location": "query",
+				"accept": [
+					"minimal",
+					"full"
+				]
+			},
+			"maxResults": {
+				"id": "maxResults",
+				"type": "int",
+				"description": "Identifica a quantidade de itens retornados da consulta.",
+				"required": false,
+				"location": "query"
+			},
+			"q": {
+				"id": "q",
+				"type": "string",
+				"description": "Filtra as entidades de acordo com o criterio.",
+				"required": false,
+				"location": "query"
+			},
+			"nextPageToken": {
+				"id": "nextPageToken",
+				"type": "string",
+				"description": "Token para consultar a proxima pagina contendo a lista de resultados.",
+				"required": false,
+				"location": "query"
+			}
+		}
+	}`
+
+	if err = common.FilePutContents(
+		basePathWithComplement("common.json"),
+		commonProperties,
+		0777,
+	); err != nil {
+		return
+	}
+	return
+}
+
+func createQueriesFile() (err error) {
+	template := `
+	{
+		"common": {
+			"@mongo.empty": "{}",
+			"@mongo.undelete": "{\"_id\":{\"$oid\":\"{{.id}}\"}}",
+			"@mongo.listWithACL": "{\"$and\":[{ \"permissions.user._id\": \"{{.user}}\", \"deleted\": {{._deleted_}}},{{._transclude_}}]}",
+			"@mongo.getWithACL": "{\"_id\":{\"$oid\":\"{{.id}}\"},\"permissions.user._id\":\"{{.user}}\",\"deleted\": {{._deleted_}}}",
+			"@mongo.list": "{\"$and\":[{ \"deleted\": {{._deleted_}}},{{._transclude_}}]}",
+			"@mongo.get": "{\"_id\":{\"$oid\":\"{{.id}}\"},\"deleted\": {{._deleted_}}}"
+		},
+		"queries": {}
+	}`
+
+	err = common.FilePutContents(
+		basePathWithComplement("queries.json"),
+		template,
+		0777,
+	)
+	return
+}

+ 129 - 0
commands/serve.go

@@ -0,0 +1,129 @@
+package commands
+
+import (
+	"fmt"
+	"time"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/gen"
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/flag"
+	"github.com/kataras/iris/v12"
+	"github.com/kataras/iris/v12/middleware/logger"
+	"github.com/kataras/iris/v12/middleware/recover"
+	"github.com/fsnotify/fsnotify"
+	"os"
+	"path/filepath"
+	"encoding/json"
+)
+
+var watcher, _ = fsnotify.NewWatcher()
+
+func serve() (err error) {
+	var (
+		project    *Project
+		// done       = make(chan bool)
+	)
+	defer watcher.Close()
+
+	fmt.Println("Serve WITH SETUP...")
+	// Cria um novo projeto a partir do diretorio atual
+	if project, err = CreateProject(*flag.Mode); err != nil {
+		panic(err)
+	}
+
+	// starting at the root of the project, walk each file/directory searching for
+	// directories
+	if err := filepath.Walk("./", watchDir); err != nil {
+		fmt.Println("ERROR", err)
+	}
+
+
+	HubInstance.EventHandlers["initialize"] = func(hub *Hub, event *Event) {
+		payload, err := json.Marshal(project)
+		if err != nil {
+			fmt.Println("error:", err)
+			return
+		}
+		
+		fmt.Println("run .. initialize")
+
+		go func(){
+			hub.Broadcast <- &Event{
+				Kind      : "project:update",
+				Payload   : string(payload),
+				Date      : time.Now(),
+			}
+		}()
+	}
+
+	go func() {
+		count := 10
+		for {
+			select {
+			case event, ok := <-watcher.Events:
+				if !ok {
+					return
+				}
+				// log.Println("event:", event)
+				
+				if event.Op & fsnotify.Write == fsnotify.Write {
+					fmt.Println("modified file:", event.Name)
+					fmt.Println("update...")
+					time.Sleep(time.Second)
+					if project, err = CreateProject(*flag.Mode); err != nil {
+						panic(err)
+					}
+					
+					payload, err := json.Marshal(project)
+					if err != nil {
+						fmt.Println("error:", err)
+						return
+					}
+
+					HubInstance.Broadcast <- &Event{
+						Kind      : "project:update",
+						Payload   : string(payload),
+						Date      : time.Now(),
+					}
+				}
+				
+				if count = count - 1; count == 0 {
+					break
+				}
+			case err := <-watcher.Errors:
+				fmt.Println("ERROR", err)
+			}
+		}
+	}()
+
+
+	project.OutDirectory(*flag.Out)
+
+	app := iris.New()
+	app.Logger().SetLevel("debug")
+	// Optionally, add two built'n handlers
+	// that can recover from any http-relative panics
+	// and log the requests to the terminal.
+	app.Use(recover.New())
+	app.Use(logger.New())
+
+	// Method:   GET
+	// Resource: http://localhost:8080/hello
+	app.Get("/project", func(ctx iris.Context) {
+		ctx.JSON(project)
+	})
+	
+	app.Get("/ws", WsUpgrader)
+
+	app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed))
+	return
+}
+
+func watchDir(path string, fi os.FileInfo, err error) error {
+	
+	if fi.Mode().IsDir() {
+		return watcher.Add(path)
+	}
+
+	return nil
+}

+ 185 - 0
common/build_commands.go

@@ -0,0 +1,185 @@
+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"`
+	IgnoreErros bool   `json:"ignore.errors"`
+}
+
+type BuildSet struct {
+	Modes   map[string]*BuildMode `json:"modes"`
+	Cmd     string                `json:"cmd"`
+	Workdir string                `json:"workdir"`
+	Steps   []*Command            `json:"steps"`
+	Options *BuildOptions         `json:"options"`
+}
+
+func (b *BuildSet) Commands() []*Command {
+	var (
+		mode       *BuildMode
+		ok, ignore bool
+		steps      = []*Command{}
+		modeKey    = b.Options.Mode
+	)
+
+	if modeKey == "" {
+		modeKey = "default"
+	}
+
+	if mode, ok = b.Modes[modeKey]; !ok {
+		panic(fmt.Sprintf("Build mode '%s' not defined", modeKey))
+	}
+
+	if (len(mode.Ignore) == 0) &&
+		len(mode.ExecuteOnly) == 0 {
+		return b.Steps
+	}
+
+	ignore = len(mode.Ignore) > 0
+
+	if ignore && len(mode.ExecuteOnly) > 0 {
+		panic("Ignore and ExecuteOnly are mutually exclusive")
+	}
+
+	ids := map[string]bool{}
+
+	for _, v := range append(mode.Ignore, mode.ExecuteOnly...) {
+		ids[v] = true
+	}
+
+	if ignore {
+		for _, step := range b.Steps {
+			if _, ok := ids[step.Id]; !ok {
+				steps = append(steps, step)
+			}
+		}
+	} else {
+		for _, step := range b.Steps {
+			if _, ok := ids[step.Id]; ok {
+				steps = append(steps, step)
+			}
+		}
+	}
+
+	return steps
+}
+
+type BuildMode struct {
+	Ignore      []string `json:"ignore"`
+	ExecuteOnly []string `json:"executeOnly"`
+}
+
+var (
+	buildSet = &BuildSet{
+		Steps: []*Command{},
+		Modes: map[string]*BuildMode{
+			"default": &BuildMode{},
+		},
+	}
+)
+
+func NewBuildOptions() *BuildOptions {
+	return &BuildOptions{
+		Mode: "default",
+	}
+}
+
+func RunBuildCommads(project *Project, 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
+	)
+
+	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()
+	}
+
+	buildOptions.Parse()
+
+	if err = JsonParseMode(buildfile, mode, &buildSet); err != nil {
+		return
+	}
+
+	buildSet.Options = buildOptions
+
+	fmt.Println("Running build commands... on mode ", mode)
+
+	cmds := buildSet.Commands()
+	total := len(cmds)
+
+	for k, command := range cmds {
+
+		if cmd, err = parseCommand(command, variables); err != nil {
+			return
+		}
+		step = k + 1
+
+		if buildOptions.IgnoreStep(step) {
+			fmt.Printf("Step (%d / %d) - [ Skiped ] %s\n", step, total, cmd)
+			continue
+		}
+
+		fmt.Printf("Step (%d / %d) - %s\n", step, total, cmd)
+
+		instance = exec.Command("sh", "-c", cmd)
+		instance.Dir = buildSet.Workdir
+
+		if out, err = instance.CombinedOutput(); err != nil {
+			fmt.Println(string(out))
+			return
+		}
+	}
+
+	return
+}
+
+var (
+	expressionRegexp = regexp.MustCompile(`\{([\w\.]+)\}`)
+)
+
+func parseCommand(c *Command, variables map[string]interface{}) (string, error) {
+	var (
+		expression, replace, dotPathName string
+		cmd                              = c.Cmd
+		matches                          = expressionRegexp.FindAllStringSubmatch(cmd, -1)
+	)
+
+	for _, match := range matches {
+		dotPathName = match[1]
+		if value, exists := variables[dotPathName]; !exists || value == nil {
+			return "", fmt.Errorf("Value for expression '%s' not defined", dotPathName)
+		}
+		expression = match[0]
+		replace = fmt.Sprintf("%v", variables[dotPathName])
+		cmd = strings.ReplaceAll(cmd, expression, replace)
+	}
+
+	return cmd, nil
+}

+ 855 - 0
common/models.go

@@ -0,0 +1,855 @@
+package common
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"regexp"
+	"strconv"
+	"strings"
+	"text/template"
+
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api"
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
+	ts "git.eugeniocarvalho.dev/eugeniucarvalho/gg/generators/typescript"
+	"github.com/kataras/iris/v12/context"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+const (
+	BSON             = "go.mongodb.org/mongo-driver/bson"
+	BSONX            = "go.mongodb.org/mongo-driver/x/bsonx"
+	MONGO            = "go.mongodb.org/mongo-driver/mongo"
+	BSON_PRIMITIVE   = "go.mongodb.org/mongo-driver/bson/primitive"
+	IRIS_CTX         = "github.com/kataras/iris/v12/context"
+	IRIS             = "github.com/kataras/iris/v12"
+	UPDATE_RELATION  = "UpdateRelation"
+	BASE_HAS_DEPENDE = "HasDep"
+)
+
+var (
+	API_URL                   = "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api"
+	API_ERROR                 = "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
+	CODE_GEN_V2_COMMON        = "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	CODE_GEN_V2_AUTHORIZATION = "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/authorization"
+
+	// Variavel de controle de acesso aos models da API.
+	Models = &api.Mongo{}
+
+	camelToUnderRegex = regexp.MustCompile(`([^[:lower:]])`)
+	//Generic e
+	Generic = regexp.MustCompile("(?P<type>[\\w-_]+)<(?P<dtype>[\\w\\*]+)>")
+	//GenericPart e
+	GenericPart = regexp.MustCompile("<(?P<id>[\\w\\*]+)>")
+	//ImportMap e
+	importMap = map[string]string{
+		"bson":      BSON,
+		"primitive": BSON_PRIMITIVE,
+	}
+
+	SR = SchemasRelations{
+		R: map[string][]*Relation{},
+	}
+)
+
+type BuildOptions struct {
+	Mode                   string
+	IgnoreBuildSteps       string
+	IgnoreBuildStepsValues map[int]bool
+}
+
+func (b *BuildOptions) IgnoreStep(step int) bool {
+	_, ok := b.IgnoreBuildStepsValues[step]
+	return ok
+}
+
+func (b *BuildOptions) Parse() error {
+	var (
+		value int
+		err   error
+	)
+	if b.IgnoreBuildStepsValues == nil {
+		b.IgnoreBuildStepsValues = map[int]bool{}
+	}
+
+	for _, v := range strings.Split(b.IgnoreBuildSteps, ",") {
+		if value, err = strconv.Atoi(v); err != nil {
+			return err
+		}
+		b.IgnoreBuildStepsValues[value] = true
+	}
+
+	return nil
+}
+
+func ImportMap(base string) string {
+	if v, ok := importMap[base]; ok {
+		return v
+	}
+	panic(fmt.Sprintf("Import %s não definido", base))
+}
+
+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"`
+}
+type ACL struct {
+	Roles       []*Role       `json:"roles"`
+	Permissions []*Permission `json:"permissions"`
+}
+type QueryDef struct {
+	Blacklistwords map[string][]string `json:"blacklistwords"`
+	Queries        map[string]string   `json:"queries"`
+	Common         map[string]string   `json:"common"`
+}
+type Role struct {
+	Title       string   `json:"title"`
+	Description string   `json:"description"`
+	ID          string   `json:"id"`
+	AllowRemove bool     `json:"allowRemove,omitempty"`
+	Permissions []string `json:"permissions"`
+}
+type Permission struct {
+	Title       string `json:"title"`
+	Description string `json:"description"`
+	ID          string `json:"id"`
+}
+type Client struct {
+	Id        string `json:"id,omitempty"`
+	OutputDir string `json:"outputDir,omitempty"`
+}
+
+type Auth struct {
+	AuthCookieDomain string `json:"authCookieDomain"`
+	AuthTokenID      string `json:"authTokenId"`
+	Oauth2           Oauth2 `json:"oauth2"`
+}
+
+type Oauth2 struct {
+	URI    string       `json:"uri"`
+	Client Oauth2Client `json:"client"`
+	Scopes []Scope      `json:"scopes"`
+}
+
+type Oauth2Client struct {
+	RedirectURI  string   `json:"redirect_uri"`
+	ClientID     string   `json:"client_id"`
+	ClientSecret string   `json:"client_secret"`
+	Scope        []string `json:"scope"`
+}
+
+type Scope struct {
+	ID           string   `json:"id"`
+	PromptToUser []string `json:"promptToUser"`
+	Description  string   `json:"description"`
+}
+
+type EnvironmentVariable struct {
+	ID          string `json:"id"`
+	CamelID     string `json:"-"`
+	Default     string `json:"default"`
+	Required    bool   `json:"required,omitempty"`
+	Description string `json:"description"`
+}
+
+type Environment map[string]*EnvironmentVariable
+
+type Entity struct {
+	HasMode         bool                   `json:"hasMode"`
+	ID              string                 `json:"id"`
+	Type            string                 `json:"type"`
+	Description     string                 `json:"description"`
+	Collection      string                 `json:"collection"`
+	DB              string                 `json:"db"`
+	Extends         []string               `json:"extends"`
+	Properties      []*Propertie           `json:"properties"`
+	Representations map[string][]string    `json:"representations"`
+	Custom          map[string]interface{} `json:"custom"`
+}
+
+type Resource struct {
+	ID           string                 `json:"id"`
+	Description  string                 `json:"description"`
+	Entity       string                 `json:"entity"`
+	Formats      []*Value               `json:"formats"`
+	Methods      []*Method              `json:"methods"`
+	CommonParams map[string]*Parameter  `json:"commonParams"`
+	Custom       map[string]interface{} `json:"custom"`
+}
+
+type Method struct {
+	ID                  string                 `json:"id"`
+	Entity              string                 `json:"entity"`
+	Type                string                 `json:"type"` // Assume valores {one, list, implement}
+	Path                string                 `json:"path"`
+	Template            string                 `json:"template"`
+	BeforePersistAction bool                   `json:"beforePersistAction"`
+	HttpMethod          string                 `json:"httpMethod"`
+	Description         string                 `json:"description"`
+	Response            string                 `json:"response"`
+	Request             string                 `json:"request"`
+	Scopes              []string               `json:"scopes"`
+	Middlewares         []string               `json:"middlewares"`
+	Postresponse        []string               `json:"postresponse"`
+	ParameterOrder      []string               `json:"parameterOrder"`
+	ParametersString    []string               `json:"parameters"`
+	Resource            *Resource              `json:"-"`
+	Hooks               map[string]bool        `json:"hooks"`
+	Parameters          map[string]*Parameter  `json:"parametersmap"`
+	Preconditions       []Action               `json:"preconditions"`
+	BeforeResponse      []Action               `json:"beforeResponse"`
+	Custom              map[string]interface{} `json:"custom"`
+	// Parameters     map[string]*Parameter `json:"parameters"`
+}
+
+type Action struct {
+	ID      string                 `json:"id"`
+	Context map[string]interface{} `json:"context"`
+}
+
+type Parameter struct {
+	ID          string                 `json:"id"`
+	Type        string                 `json:"type"`
+	Required    bool                   `json:"required"`
+	Description string                 `json:"description"`
+	Default     string                 `json:"default"`
+	Location    string                 `json:"location"`
+	ConvertTo   string                 `json:"convertTo"`
+	Custom      map[string]interface{} `json:"custom"`
+	// Validation  *ValidationRule        `json:"validation"`
+	Validation map[string]interface{} `json:"validation"`
+}
+
+// type ValidationRule struct {
+// 	Accept    []*Value `json:"-"`
+// 	AcceptRef []string `json:"accept"`
+// 	Reject    []*Value `json:"reject"`
+// 	RejectRef []string `json:"-"`
+// 	In        []string `json:"in"`
+// 	Contains  string   `json:"contains"`
+// 	Regex     string   `json:"regex"`
+// 	Min       string   `json:"min"`
+// 	Max       string   `json:"max"`
+// 	Type      string   `json:"type"`
+// }
+
+type Value struct {
+	Id          string `json:"id"`
+	Value       string `json:"value"`
+	Default     bool   `json:"default"`
+	Fields      string `json:"fields"`
+	Description string `json:"description"`
+}
+
+type Propertie struct {
+	ID                string                 `json:"id"`
+	Name              string                 `json:"name"`
+	Type              string                 `json:"type"`
+	Description       string                 `json:"description"`
+	AutogenerateInput string                 `json:"autogenerate"`
+	Autogenerate      map[string]AutoGenDef  `json:"-"`
+	Targets           string                 `json:"targets"`
+	Array             bool                   `json:"array"`
+	Relation          bool                   `json:"relation"`
+	TagVisited        bool                   `json:"-"`
+	Reference         bool                   `json:"reference"`
+	Readonly          bool                   `json:"readonly"`
+	Unique            bool                   `json:"uniq"`
+	Default           interface{}            `json:"default"`
+	Enum              []string               `json:"enum"`
+	Values            []interface{}          `json:"values"`
+	EnumDescriptions  []string               `json:"enumDescriptions"`
+	Tags              map[string]string      `json:"tags"`
+	Filter            []*Filter              `json:"filter"`
+	Custom            map[string]interface{} `json:"custom"`
+}
+
+type AutoGenDef struct {
+	Type string
+	Args []string
+}
+
+type Filter struct {
+	Path              string         `json:"path"`
+	Type              string         `json:"type"`
+	Label             string         `json:"label"`
+	UserEnumAsOptions bool           `json:"userEnumAsOptions"`
+	Multiples         bool           `json:"multiples"`
+	Options           []FilterOption `json:"options,omitempty"`
+}
+
+type FilterOption struct {
+	Value interface{} `json:"value"`
+	Label string      `json:"label"`
+}
+
+type ApiFilter struct {
+	Id     string    `json:"id"`
+	Date   int64     `json:"date"`
+	Fields []*Filter `json:"fields"`
+}
+
+func NewApiFilter(id string) *ApiFilter {
+	return &ApiFilter{
+		Id:     id,
+		Fields: []*Filter{},
+	}
+}
+
+func RequestParams(args string, params map[string]*Parameter) func(ctx context.Context) (resp interface{}, err *errs.Error) {
+	argsList := strings.Split(args, ",")
+
+	return func(ctx context.Context) (resp interface{}, err *errs.Error) {
+		var (
+			values      = ctx.Values()
+			id          string
+			value       interface{}
+			sourceValue interface{}
+			param       *Parameter
+			paramsMap   = map[string]interface{}{}
+		)
+
+		values.Set("$params", paramsMap)
+
+		for _, arg := range argsList {
+			param = params[arg]
+
+			switch param.Location {
+
+			case "query":
+				id = "q." + arg
+				value = api.Q(ctx, arg, param.Default)
+			case "path":
+				id = "p." + arg
+				value = api.P(ctx, arg, param.Default)
+			}
+			sourceValue = value
+
+			if param.Required && (value == "" || value == nil) {
+				invalidArgument := errs.InvalidArgument()
+				invalidArgument.Message = fmt.Sprintf("ParamRequired:'%s'", param.ID)
+				return nil, invalidArgument
+			}
+
+			if param.ConvertTo != "" {
+				if value, err = convertValueByType(param.ConvertTo, value); err != nil {
+					invalidArgument := errs.InvalidArgument()
+					invalidArgument.Message = fmt.Sprintf("TypeConversionError:'%v'. Waiting a %s ", value, param.ConvertTo)
+					return nil, invalidArgument
+				}
+			}
+
+			if param.Validation != nil {
+				for validator, args := range param.Validation {
+					if fn, found := validationParamFunctions[validator]; found {
+						if err = fn(value, args); err != nil {
+							return nil, err
+						}
+					}
+				}
+			}
+
+			values.Set(id, value)
+			paramsMap[fmt.Sprintf("%s_conv", arg)] = value
+			paramsMap[arg] = sourceValue
+		}
+		ctx.Next()
+		return
+	}
+}
+
+var (
+	convertionTypeFunctions = map[string]func(interface{}) (interface{}, *errs.Error){
+		"ObjectID": stringToObjectId,
+		"bool":     stringToBool,
+		"int":      stringToInt,
+		"number":   stringToFloat,
+	}
+
+	validationParamFunctions = map[string]func(interface{}, interface{}) *errs.Error{
+		"min": func(value interface{}, minString interface{}) *errs.Error {
+			min, err := strconv.Atoi(minString.(string))
+			if err != nil || value.(int) < min {
+				invalidArgument := errs.InvalidArgument()
+				invalidArgument.Message = fmt.Sprintf("ValueRestriction: value > %s. Received (%d)", minString, value)
+				return invalidArgument
+			}
+			return nil
+		},
+		"max": func(value interface{}, maxString interface{}) *errs.Error {
+			max, err := strconv.Atoi(maxString.(string))
+			if err != nil || value.(int) > max {
+				invalidArgument := errs.InvalidArgument()
+				invalidArgument.Message = fmt.Sprintf("ValueRestriction: value < %s. Received (%d)", maxString, value)
+				return invalidArgument
+			}
+			return nil
+		},
+		"accept": func(input interface{}, accept interface{}) *errs.Error {
+			var (
+				acceptValues = accept.([]string)
+				value        = input.(string)
+			)
+
+			for _, acceptValue := range acceptValues {
+				if value == acceptValue {
+					return nil
+				}
+			}
+
+			invalidArgument := errs.InvalidArgument()
+			invalidArgument.Message = fmt.Sprintf(
+				"ValueRestriction: '%s' isn't accept. Accept [%s]",
+				value,
+				strings.Join(acceptValues, ","),
+			)
+			return invalidArgument
+		},
+		"reject": func(input interface{}, reject interface{}) *errs.Error {
+			var (
+				rejectValues = reject.([]string)
+				value        = input.(string)
+			)
+
+			for _, rejectValue := range rejectValues {
+				if value == rejectValue {
+					invalidArgument := errs.InvalidArgument()
+					invalidArgument.Message = fmt.Sprintf(
+						"ValueRestriction: '%s' isn't accept. Rejected terms [%s]",
+						value,
+						strings.Join(rejectValues, ","),
+					)
+					return invalidArgument
+				}
+			}
+			return nil
+		},
+		"regex": func(input interface{}, regex interface{}) *errs.Error {
+			var (
+				regexString = regex.(string)
+				value       = input.(string)
+			)
+
+			regexInstance := regexp.MustCompile(regexString)
+
+			if !regexInstance.Match([]byte(value)) {
+
+				invalidArgument := errs.InvalidArgument()
+				invalidArgument.Message = fmt.Sprintf(
+					"ValueRestriction: '%s' isn't accept",
+					value,
+				)
+
+				return invalidArgument
+			}
+
+			return nil
+		},
+	}
+)
+
+func stringToObjectId(value interface{}) (interface{}, *errs.Error) {
+	var (
+		valueString   = value.(string)
+		valueObjectID primitive.ObjectID
+		err           error
+	)
+
+	if valueObjectID, err = primitive.ObjectIDFromHex(valueString); err != nil {
+		invalidArgument := errs.InvalidArgument()
+		invalidArgument.Message = fmt.Sprintf("The value '%s' is'nt a valid ObjectId", valueString)
+		return nil, invalidArgument
+	}
+
+	return valueObjectID, nil
+}
+
+func stringToBool(value interface{}) (interface{}, *errs.Error) {
+
+	var (
+		valueBool bool
+		err       error
+	)
+
+	if valueBool, err = strconv.ParseBool(value.(string)); err != nil {
+		invalidArgument := errs.InvalidArgument()
+		invalidArgument.Message = fmt.Sprintf("The value '%s' is'nt a valid boolean. Accept [true,1,T,false,0,F]", valueBool)
+		return nil, invalidArgument
+	}
+
+	return valueBool, nil
+}
+
+func stringToInt(value interface{}) (interface{}, *errs.Error) {
+
+	var (
+		valueInt int64
+		err      error
+	)
+
+	if valueInt, err = strconv.ParseInt(value.(string), 10, 64); err != nil {
+		invalidArgument := errs.InvalidArgument()
+		invalidArgument.Message = fmt.Sprintf("The value '%s' is'nt a valid int", valueInt)
+		return nil, invalidArgument
+	}
+
+	return valueInt, nil
+}
+
+func stringToFloat(value interface{}) (interface{}, *errs.Error) {
+
+	var (
+		valueFloat float64
+		err        error
+	)
+
+	if valueFloat, err = strconv.ParseFloat(value.(string), 64); err != nil {
+		invalidArgument := errs.InvalidArgument()
+		invalidArgument.Message = fmt.Sprintf("The value '%s' is'nt a valid number", valueFloat)
+		return nil, invalidArgument
+	}
+
+	return valueFloat, nil
+}
+
+func convertValueByType(typ string, value interface{}) (interface{}, *errs.Error) {
+	var err *errs.Error
+	if fn, found := convertionTypeFunctions[typ]; found {
+		if value, err = fn(value); err != nil {
+			return nil, err
+		}
+	}
+	return value, nil
+}
+
+// func validateParam(param *Parameter, value interface{}) (interface{}, *errs.Error) {
+// 	var err *errs.Error
+
+// 	return value, nil
+// }
+
+func (t *Method) Hook(id string) bool {
+	// active := t.Hooks[id]
+	// return active
+	return t.Hooks[id]
+}
+
+func (t *Propertie) ParseAutogenerate() error {
+	if t.AutogenerateInput != "" {
+		parts := strings.Split(t.AutogenerateInput, ":")
+		if len(parts) < 2 {
+			return fmt.Errorf("Invalid autogenerate input  '%s' in attribute '%s'.", t.AutogenerateInput, t.ID)
+		}
+
+		if t.Autogenerate == nil {
+			t.Autogenerate = map[string]AutoGenDef{}
+		}
+
+		args := strings.Split(parts[1], "#")
+
+		for _, k := range strings.Split(parts[0], ",") {
+			t.Autogenerate[k] = AutoGenDef{
+				Type: args[0],
+				Args: args[1:],
+			}
+		}
+	}
+	return nil
+}
+
+type SchemasRelations struct {
+	R map[string][]*Relation
+}
+
+type Relation struct {
+	Source     string
+	Target     string
+	Attr       string
+	Collection string
+	DB         string
+	IsArray    bool
+}
+
+type EntityInfo struct {
+	Name          string
+	Origin        string
+	NewName       string
+	DynamicType   string
+	DynamicTypeId string
+	IsGeneric     bool
+}
+
+type TranslationFn func(p *Project) error
+
+func (p *Project) Build(b *BuildOptions) error {
+	var err error
+
+	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)
+				return err
+			}
+		} else {
+			return fmt.Errorf("Middleware '%s' not defined!", c.Id)
+		}
+	}
+	// fmt.Println("--- RunBuildCommads")
+	return RunBuildCommads(p, b)
+}
+
+func (p *Project) OutDirectory(path string) {
+	p.OutPath = path
+}
+
+func (p *Project) Client(id string) *Client {
+	for _, c := range p.Clients {
+		if c.Id == id {
+			return c
+		}
+	}
+	return nil
+}
+
+func (p *Project) Save(path string) error {
+	data, err := json.MarshalIndent(p, "", "    ")
+	if err == nil {
+		err = FilePutContentsBytes(path, data, 0777)
+	}
+	return err
+}
+
+func (p *Project) GetCollection(entity string) string {
+	for _, e := range p.Schemas {
+		if e.ID == entity {
+			return e.Collection
+		}
+	}
+	return "undefined"
+}
+
+func (p *Project) GetEntityDB(entity string) string {
+	if en, found := p.SchemasRef[entity]; found {
+		return en.DB + p.DataBaseSufix
+	}
+	panic(fmt.Sprintf("DB attribute is empty in entity '%s'", entity))
+}
+
+func (p *Project) EntityDesc(ID string) *Entity {
+	if _, y := p.SchemasRef[ID]; !y {
+		fmt.Println("EntityDesc(ID)", ID)
+		return nil
+	}
+	return p.SchemasRef[ID]
+}
+
+func (m *Method) HasPathParams() bool {
+	return len(m.ParameterOrder) > 0
+}
+
+func (m *Method) HasFormatParam() (bool, *Parameter) {
+
+	for id, param := range m.Parameters {
+		// param = m.Parameters[id]
+		// fmt.Println("param:", param.ID)
+		if id == "format" {
+			return true, param
+		}
+	}
+	return false, nil
+}
+
+func (p *Project) GetUrlFromMethod(method *Method) string {
+	return p.BaseURL + method.Path
+}
+
+func (p *Project) ResponseEntity(property string) *EntityInfo {
+	var (
+		pi = &EntityInfo{
+			Origin: property,
+		}
+	)
+	match := Generic.FindStringSubmatch(property)
+
+	if len(match) == 0 {
+		return pi
+	}
+
+	for i, name := range Generic.SubexpNames() {
+		switch name {
+		case "type":
+			pi.Name = match[i]
+		case "dtype":
+			pi.DynamicType = match[i]
+			pi.IsGeneric = true
+		}
+	}
+
+	if pi.IsGeneric {
+		entity := p.GetSchema(pi.Name)
+
+		match = GenericPart.FindStringSubmatch(entity.ID)
+
+		for i, name := range GenericPart.SubexpNames() {
+			switch name {
+			case "id":
+				pi.DynamicTypeId = match[i]
+			}
+		}
+	}
+	pi.NewName = pi.Name + UpFirst(strings.Replace(pi.DynamicType, "*", "", -1))
+
+	return pi
+}
+
+func (p *Project) GetPath(m *Method) string {
+
+	path := []byte(p.BasePath + m.Path)
+
+	for attr, param := range m.Parameters {
+		path = regexp.MustCompile("{"+attr+"}").ReplaceAll(path, []byte("{"+attr+":"+param.Type+"}"))
+	}
+
+	return string(path)
+}
+func (p *Project) GetSchema(id string) *Entity {
+	id = strings.Replace(id, "*", "", -1)
+	if model, ok := p.SchemasRef[id]; ok {
+		return model
+	}
+	panic(fmt.Sprintf("Entity '%s' not defined!", id))
+}
+
+// Metodos das propriedades
+
+func (p *Propertie) FillTags(project *Project, propName string) {
+	if p.TagVisited {
+		return
+	}
+	if propName == "Id" {
+
+	}
+	if p.Tags != nil {
+		for k, v := range p.Tags {
+
+			if _, found := project.ReplaceWhenEmpty[k]; found && v == "" {
+				p.Tags[k] = LcFirst(p.ID)
+			}
+
+			if _, found := project.OmitEmpty[k]; found {
+				if p.Tags[k] != "-" {
+					p.Tags[k] += ",omitempty"
+				}
+			}
+		}
+	}
+
+	p.TagVisited = true
+}
+
+func (p *Propertie) GetType() string {
+	return strings.Replace(p.Type, "*", "", 1)
+}
+
+// Metodos das informacoes da entidade
+
+func (p *EntityInfo) TranslateType(typ string) string {
+
+	if typ == p.DynamicTypeId {
+		return p.DynamicType
+	}
+	return typ
+}
+
+// Metodos do esquema de relacoes
+
+// Add adiciona uma relação ao esquema
+func (s *SchemasRelations) Has(entity string) bool {
+	// spew.Dump(s)
+	_, found := s.R[entity]
+	return found
+}
+
+// Add adiciona uma relação ao esquema
+func (s *SchemasRelations) Get(entity string) []*Relation {
+	if e, found := s.R[entity]; found {
+		return e
+	}
+	return []*Relation{}
+}
+
+// Add adiciona uma relação ao esquema
+func (s *SchemasRelations) Add(r *Relation) {
+	if _, found := s.R[r.Source]; !found {
+		s.R[r.Source] = []*Relation{}
+	}
+
+	s.R[r.Source] = append(s.R[r.Source], r)
+}
+
+func ParseTemplate(input string, name ...string) (*template.Template, error) {
+	var tmpl, err = template.New(strings.Join(name, "")).Parse(input)
+	return tmpl, err
+}
+
+func TemplateToString(template *template.Template, data interface{}) (string, error) {
+	var result bytes.Buffer
+
+	if err := template.Execute(&result, data); err != nil {
+		return "", err
+	}
+
+	return result.String(), nil
+}
+
+func NewProject() *Project {
+	return &Project{
+		Mode:             "",
+		SchemasRef:       map[string]*Entity{},
+		Icons:            map[string]string{},
+		ReplaceWhenEmpty: map[string]bool{},
+		OmitEmpty:        map[string]bool{},
+		FormatMap:        map[string]string{},
+		Queries:          &QueryDef{},
+		Schemas:          []*Entity{},
+		Resources:        []*Resource{},
+		Translators:      map[string]TranslationFn{},
+	}
+}

+ 210 - 0
common/utils.go

@@ -0,0 +1,210 @@
+package common
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"reflect"
+	"regexp"
+	"strings"
+	"unicode"
+)
+
+var (
+	environment = map[string]string{}
+	wordRegexp  = regexp.MustCompile(`\w+`)
+)
+
+// Setenv - cria ou atualiza uma variavel de ambiente dentro da aplicação
+func Setenv(id, value string) {
+	environment[id] = value
+}
+
+// Getenv - recupera uma variavel de ambiente dentro da aplicação
+func Getenv(id string, fallback ...string) (value string) {
+	var exist bool
+
+	if value, exist = environment[id]; !exist {
+		for _, value = range fallback {
+			break
+		}
+	}
+	return value
+}
+
+func LcFirst(str string) string {
+	for i, v := range str {
+		return string(unicode.ToLower(v)) + str[i+1:]
+	}
+	return ""
+}
+func UpFirst(str string) string {
+	for i, v := range str {
+		return string(unicode.ToUpper(v)) + str[i+1:]
+	}
+	return ""
+}
+func JSONStringfy(v interface{}) (string, error) {
+	o, err := json.Marshal(v)
+	if err != nil {
+		return "", err
+	}
+	return string(o), nil
+}
+
+// FileExists reports whether the named file or directory exists.
+func FileExists(name string) bool {
+	if _, err := os.Stat(name); err != nil {
+		if os.IsNotExist(err) {
+			return false
+		}
+	}
+	return true
+}
+
+func ResolveParams(format string, data map[string]interface{}) string {
+	for k, v := range data {
+		format = strings.Replace(
+			format,
+			fmt.Sprintf("${%s}", k),
+			fmt.Sprintf("%v", v),
+			-1,
+		)
+	}
+	return format
+}
+
+func UnderToCamel(input string) string {
+	input = strings.ToLower(input)
+	input = strings.ReplaceAll(input, "_", " ")
+	input = strings.Title(input)
+	return strings.ReplaceAll(input, " ", "")
+}
+
+func Mkdir(perm os.FileMode, paths ...string) (err error) {
+
+	for _, path := range paths {
+		if _, err := os.Stat(path); os.IsNotExist(err) {
+
+			if err = os.MkdirAll(path, perm); err != nil {
+				return err
+			}
+		}
+	}
+	return
+}
+
+func JsonParseMode(path, mode string, v interface{}) (err error) {
+	if err = ParseJson(path+".json", v); err != nil {
+		return
+	}
+	// fmt.Println(fmt.Sprintf("%s.%s.json", path, mode))
+
+	err = ParseJson(fmt.Sprintf("%s.%s.json", path, mode), v, true)
+	return
+}
+
+func ParseJson(filename string, v interface{}, ignoreFileExists ...bool) error {
+	// fmt.Println("load file ", filename)
+	data, err := FileGetContents(filename)
+
+	if err != nil {
+		if len(ignoreFileExists) > 0 && ignoreFileExists[0] {
+			return nil
+		}
+		return err
+	}
+
+	if err = json.Unmarshal([]byte(data), v); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+
+
+func CamelToUnder(input string) string {
+	out := camelToUnderRegex.ReplaceAllStringFunc(input, func(m string) string {
+		return "_" + strings.ToLower(m)
+	})
+	return strings.TrimLeft(out, "_")
+}
+func FilePutContents(filename string, content string, perm os.FileMode) error {
+	return FilePutContentsBytes(filename, []byte(content), perm)
+}
+
+func FilePutContentsBytes(filename string, content []byte, perm os.FileMode) error {
+
+	var (
+		dir = path.Dir(filename)
+	)
+
+	if _, err := os.Stat(dir); os.IsNotExist(err) {
+
+		if err = os.MkdirAll(dir, perm); err != nil {
+			return err
+		}
+	}
+	return ioutil.WriteFile(filename, []byte(content), 0644)
+}
+
+func RemoveFile(file string) error {
+
+	if err := os.Remove(file); err != nil {
+		return err
+	}
+	return nil
+}
+
+func GetFiles(directory string) ([]os.FileInfo, error) {
+
+	files, err := ioutil.ReadDir(directory)
+	if err != nil {
+		return []os.FileInfo{}, err
+	}
+	return files, nil
+}
+func FileGetContents(filename string) (string, error) {
+	b, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+}
+
+func WriteToJson(path string, object interface{}) (err error) {
+	var data []byte
+
+	if data, err = json.MarshalIndent(object, "", "    "); err == nil {
+		err = FilePutContentsBytes(path, data, 0777)
+	}
+
+	return
+}
+
+func IsEmpty(reference interface{}) bool {
+	return isEmptyReflection(reflect.ValueOf(reference))
+}
+
+func isEmptyReflection(v reflect.Value) bool {
+	switch v.Kind() {
+	case reflect.String, reflect.Array:
+		return v.Len() == 0
+	case reflect.Map, reflect.Slice:
+		return v.Len() == 0 || v.IsNil()
+	// case reflect.Bool:
+	// 	return !v.Bool()
+	// case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+	// 	return v.Int() == 0
+	// case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+	// 	return v.Uint() == 0
+	// case reflect.Float32, reflect.Float64:
+	// 	return v.Float() == 0
+	case reflect.Interface, reflect.Ptr:
+		return v.IsNil()
+	}
+	return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
+}

+ 237 - 0
common/websoket.models.go

@@ -0,0 +1,237 @@
+package common
+
+import (
+	"fmt"
+	"log"
+	"time"
+
+	// api "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api"
+	// "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
+	// "github.com/davecgh/go-spew/spew"
+	"github.com/gorilla/websocket"
+	// bson "go.mongodb.org/mongo-driver/bson"
+	// "go.mongodb.org/mongo-driver/bson/primitive"
+)
+
+var (
+	visits  uint64
+	newline = []byte{'\n'}
+	space   = []byte{' '}
+)
+
+type Event struct {
+	Kind      string      `json:"kind"`
+	ThreadID  string      `json:"thread,omitempty"`
+	SectionID string      `json:"section,omitempty"`
+	UserId    string      `json:"-"`
+	Payload   string      `json:"payload,omitempty"`
+	Entity    interface{} `json:"-"`
+	Date      time.Time   `json:"date"`
+}
+
+const (
+	// Time allowed to write a message to the peer.
+	writeWait = 10 * time.Second
+
+	// Time allowed to read the next pong message from the peer.
+	pongWait = 60 * time.Second
+
+	// Send pings to peer with this period. Must be less than pongWait.
+	pingPeriod = (pongWait * 9) / 10
+
+	// Maximum message size allowed from peer.
+	maxMessageSize = 512
+)
+
+type WsClient struct {
+	Id  string
+	Hub *Hub
+	// The websocket connection.
+	Conn *websocket.Conn
+	// Buffered channel of outbound messages.
+	// Send chan []byte
+	Send            chan *Event
+}
+
+// Hub maintains the set of active Clients and Broadcasts messages to the
+// Clients.
+type Hub struct {
+	// Registered Clients.
+	Clients map[string]*WsClient
+	// Inbound messages from the Clients.
+	Broadcast     chan *Event
+	Inbound       chan *Event
+	EventHandlers map[string]func(*Hub, *Event)
+	// Register requests from the WsClient.
+	Register chan *WsClient
+	// Unregister requests from WsClient.
+	Unregister chan *WsClient
+}
+
+func NewHub() *Hub {
+	return &Hub{
+		Broadcast:  make(chan *Event),
+		Inbound:    make(chan *Event),
+		Register:   make(chan *WsClient),
+		Unregister: make(chan *WsClient),
+		Clients:    make(map[string]*WsClient),
+		EventHandlers: map[string]func(*Hub, *Event){
+		},
+	}
+}
+
+// func (control *ThreadCtrl) RegisterClient(client *WsClient) {
+// 	for _, member := range *(control.Thread.Members) {
+// 		if member.Id == client.Id {
+// 			control.Clients[client.Id] = client
+// 			break
+// 		}
+// 	}
+// }
+
+
+// func tsyncHandler(hub *Hub, event *Event) {
+
+// 	// ctrl, err := hub.GetThread(event.ThreadID)
+
+// 	// if err != nil {
+// 	// 	fmt.Println(err)
+// 	// 	return
+// 	// }
+
+// 	// for _, cli := range ctrl.Clients {
+// 	// 	if cli.Id != event.UserId {
+// 	// 		cli.Send <- event
+// 	// 	}
+// 	// }
+// }
+
+
+func (hub *Hub) Run() {
+	for {
+		select {
+		case client := <-hub.Register:
+
+			// Se o cliente possui uma conexão. Encerra e seta o novo cliente
+			// if c, found := hub.Clients[client.Id]; found {
+			// 	hub.Unregister <- c
+			// }
+			// fmt.Println("register client ", client)
+
+			hub.Clients[client.Id] = client
+
+		case client := <-hub.Unregister:
+			if _, ok := hub.Clients[client.Id]; ok {
+				delete(hub.Clients, client.Id)
+				close(client.Send)
+			}
+
+		case event := <-hub.Inbound:
+			// fmt.Println("runing dispatch ", event)
+			hub.dispatch(event)
+
+		case event := <-hub.Broadcast:
+			// spew.Dump(hub.Clients)
+			for ID, client := range hub.Clients {
+				// fmt.Println("broadcast to client")
+				select {
+				case client.Send <- event:
+				default:
+					delete(hub.Clients, ID)
+					close(client.Send)
+				}
+			}
+		}
+
+	}
+}
+
+func (h *Hub) dispatch(event *Event) {
+	fmt.Println("dispatch", event)
+	if fn, found := h.EventHandlers[event.Kind]; found {
+		fn(h, event)
+	} else {
+		//TODO register erro
+	}
+}
+
+func (c *WsClient) Handle() {
+	go c.WriteHandler()
+	go c.ReadHandler()
+}
+
+func (c *WsClient) ReadHandler() {
+	var (
+		// payload []byte
+		err   error
+		event *Event
+		hub   = c.Hub
+	)
+	defer func() {
+		// fmt.Println("FINALIZANDO ReadHandler()")
+		if err != nil {
+			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
+				log.Printf("error: %v", err)
+			}
+		}
+		hub.Unregister <- c
+		c.Conn.Close()
+	}()
+	// c.Conn.SetReadLimit(maxMessageSize)
+	// c.Conn.SetReadDeadline(time.Now().Add(pongWait))
+	// c.Conn.SetPongHandler(func(string) error { c.Conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
+	for {
+		event = &Event{}
+
+		if err = c.Conn.ReadJSON(event); err != nil {
+			break
+		}
+
+		fmt.Println("read payload")
+		// spew.Dump(event)
+		event.UserId = c.Id
+		hub.Inbound <- event
+	}
+}
+
+func (c *WsClient) WriteHandler() {
+	var (
+		// ticker  = time.NewTicker(pingPeriod)
+		// message []byte
+		err error
+	)
+	defer func() {
+
+		if err != nil {
+			c.Conn.WriteMessage(websocket.CloseMessage, []byte{})
+		}
+
+		// fmt.Println("FINALIZANDO WriteHandler()")
+		// ticker.Stop()
+		c.Conn.Close()
+	}()
+
+	for {
+		select {
+		case event, ok := <-c.Send:
+			// spew.Dump(event)
+			fmt.Println("write message", !ok)
+			// c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
+			if !ok {
+				err = fmt.Errorf("0000000000")
+				return
+			}
+
+			if err = c.Conn.WriteJSON(event); err != nil {
+				return
+			}
+
+			// case <-ticker.C:
+			// 	// c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
+			// 	if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
+			// 		return
+			// 	}
+			// }
+		}
+	}
+}

+ 65 - 0
common/ws.go

@@ -0,0 +1,65 @@
+package common
+
+import (
+	"fmt"
+	"net/http"
+
+	api "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api"
+	// errs "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
+	"github.com/gorilla/websocket"
+	context "github.com/kataras/iris/v12/context"
+)
+
+var (
+	HubInstance = NewHub()
+	upgrader        = websocket.Upgrader{} // use default options
+)
+
+func init() {
+	upgrader.CheckOrigin = func(r *http.Request) bool { return true }
+	//Inicializa a thread do servidor de chat
+	go HubInstance.Run()
+}
+
+// Converte a requisição em um ws.
+/*
+{
+    "id": "ws",
+    "entity": "Thread",
+    "type": "",
+    "path": "/ws",
+    "template": "implement",
+    "beforePersistAction": false,
+    "httpMethod": "GET",
+    "description": "Converte a requisição em um ws.",
+    "response": "Thread",
+    "request": "",
+    "scopes": [],
+    "middlewares": [],
+    "postresponse": null,
+    "parameterOrder": [],
+    "parameters": [],
+    "parametersmap": {}
+}
+*/
+func WsUpgrader(ctx context.Context) {
+	var (
+		con, err = upgrader.Upgrade(ctx.ResponseWriter(), ctx.Request(), nil)
+	)
+	if err != nil {
+		fmt.Println("error:", err)
+		return 
+	}
+
+	client := &WsClient{
+		Id:   api.Q(ctx, "i", ""),
+		Hub:  HubInstance,
+		Conn: con,
+		Send: make(chan *Event, 256),
+	}
+
+	HubInstance.Register <- client
+
+	client.Handle()
+	return 
+}

+ 142 - 0
flag/flag.go

@@ -0,0 +1,142 @@
+package flag
+
+import (
+	"flag"
+	"fmt"
+	"os"
+
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api"
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+)
+
+var (
+	GenerateCommandHandles = map[string]func(*flag.FlagSet) error{
+		"entity":   handleEntityCommand,
+		"resource": handleResourceCommand,
+	}
+	Command                 string
+	Mode, Out, BuildProfile *string
+	String                  = flag.String
+	Bool                    = flag.Bool
+	Parse                   = flag.Parse
+	Arg                     = flag.Arg
+	Args                    = flag.Args
+	NewFlagSet              = flag.NewFlagSet
+	ExitOnError             = flag.ExitOnError
+	PanicOnError            = flag.PanicOnError
+	ContinueOnError         = flag.ContinueOnError
+	GenerateEntityControl   = struct {
+		Name       string
+		Type       string
+		Properties string
+	}{}
+	GenerateResourceControl = struct {
+		Name    string
+		Entity  string
+		Methods string
+	}{}
+	compileFlags, generateFlags *flag.FlagSet
+)
+
+type FlagSet = flag.FlagSet
+
+func Initialize() (err error) {
+
+	defer func() {
+		if err != nil {
+			api.LogInfo(0, "eon [init|compile|serve] ...options")
+			api.LogError(0, err.Error())
+			flag.PrintDefaults()
+		}
+	}()
+
+	Out = flag.String("out", "../build", "Target directory of the compilation.")
+
+	// todo add list of valid commands on erro
+	Command = os.Args[1]
+
+	switch Command {
+	case "g":
+		err = generateCommandParse()
+	case "compile":
+		err = generateCompileParse()
+	case "serve":
+		err = generateServeParse()
+
+	default:
+		fmt.Printf("%q is not valid command.\n", Command)
+		os.Exit(2)
+	}
+
+	return
+}
+
+func generateServeParse() (err error) {
+	compileFlags = flag.NewFlagSet("serve", flag.ExitOnError)
+	Mode = compileFlags.String("mode", "", "Defines the data source used to compile the project.")
+	// Mode = compileFlags.String("mode", "", "Defines the data source used to compile the project.")
+	// BuildProfile = compileFlags.String("build", "", "Profile of build functions.")
+
+	compileFlags.Parse(os.Args[2:])
+
+	if compileFlags.Parsed() {
+		return
+	}
+
+	err = fmt.Errorf("Error....")
+	return
+}
+
+func generateCompileParse() (err error) {
+	compileFlags = flag.NewFlagSet("compile", flag.ExitOnError)
+
+	Mode = compileFlags.String("mode", "", "Defines the data source used to compile the project.")
+	BuildProfile = compileFlags.String("build", "", "Profile of build functions.")
+
+	compileFlags.Parse(os.Args[2:])
+
+	if compileFlags.Parsed() {
+		return
+	}
+
+	err = fmt.Errorf("Error....")
+	return
+}
+
+func generateCommandParse() (err error) {
+	generateFlags = flag.NewFlagSet("g", flag.ExitOnError)
+
+	command := os.Args[2]
+
+	if fn, exist := GenerateCommandHandles[command]; exist {
+
+		if err = fn(generateFlags); err != nil {
+			return
+		}
+
+		if generateFlags.Parsed() {
+			return
+		}
+	}
+
+	err = fmt.Errorf("Error....")
+	return
+}
+
+func handleResourceCommand(generateFlags *flag.FlagSet) (err error) {
+	Command = "g.resource"
+	generateFlags.StringVar(&GenerateResourceControl.Entity, "entity", "", "Entity ")
+	generateFlags.StringVar(&GenerateResourceControl.Methods, "methods", "", "Comma-separated list of resource methods.")
+	GenerateResourceControl.Name = common.LcFirst(os.Args[3])
+	generateFlags.Parse(os.Args[4:])
+	return
+}
+
+func handleEntityCommand(generateFlags *flag.FlagSet) (err error) {
+	Command = "g.entity"
+	generateFlags.StringVar(&GenerateEntityControl.Type, "type", "object", "Type of entity. [object, nested.object, abstract]")
+	generateFlags.StringVar(&GenerateEntityControl.Properties, "properties", "", "Comma-separated list of structure properties.")
+	GenerateEntityControl.Name = common.LcFirst(os.Args[3])
+	generateFlags.Parse(os.Args[4:])
+	return
+}

+ 385 - 0
gen/gen.go

@@ -0,0 +1,385 @@
+package gen
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strconv"
+	"strings"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	GO "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/translate/got"
+	TS "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/translate/tst"
+)
+
+func CreateProject(mode string) (*Project, error) {
+
+	var (
+		err              error
+		path             string
+		id               string
+		files            []os.FileInfo
+		entity           *Entity
+		resource         *Resource
+		directory        string
+		paramAcceptFiled = map[string]bool{}
+		p                = &Project{
+			Mode:             mode,
+			SchemasRef:       map[string]*Entity{},
+			Icons:            map[string]string{},
+			ReplaceWhenEmpty: map[string]bool{},
+			OmitEmpty:        map[string]bool{},
+			FormatMap:        map[string]string{},
+			Queries:          &QueryDef{},
+			Translators: map[string]TranslationFn{
+				"go":       GO.Translate,
+				"angular6": TS.Translate,
+			},
+		}
+	)
+
+	if directory, err = os.Getwd(); err != nil {
+		return nil, err
+	}
+
+	// Carrega a configuracao default do projeto
+	// root := []string{fmt.Sprintf("%s/project.json", directory)}
+
+	// if mode != "" {
+	// 	root = append(root, fmt.Sprintf("%s/project.%s.json", directory, mode))
+	// }
+
+	// for _, file := range root {
+	// 	if file == "" {
+	// 		continue
+	// 	}
+	// 	if err = ParseJson(file, p); err != nil {
+	// 		return nil, err
+	// 	}
+	// }
+
+	if err = JsonParseMode(fmt.Sprintf("%s/project", directory), mode, p); err != nil {
+		return nil, err
+	}
+
+	if err = ParseJson("queries.json", &p.Queries); err != nil {
+		p.Queries.Blacklistwords = map[string][]string{}
+		p.Queries.Queries = map[string]string{}
+	}
+
+	if err = loadQueries(p, directory); err != nil {
+		return nil, err
+	}
+
+	if err = expandQueries(p); err != nil {
+		return nil, err
+	}
+
+	if err = ParseJson("common.json", &p.Resource); err != nil {
+		p.Resource = &Resource{}
+	}
+
+	for id, v := range p.Resource.CommonParams {
+		if v.ID == "" {
+			v.ID = id
+		}
+	}
+
+	if err = JsonParseMode("env/environment", mode, &p.Environment); err != nil {
+		return nil, err
+	}
+
+	for id, variable := range p.Environment {
+		variable.ID = id
+	}
+
+	// Carrega os arquivos de entidades localizados na pasta entities
+	path = fmt.Sprintf("%s/entities", directory)
+	if files, err = GetFiles(path); err == nil {
+		fmt.Println("Read entities...")
+		for _, file := range files {
+			if file.Name()[0] == '*' {
+				continue
+			}
+			if !file.IsDir() {
+				entity = &Entity{}
+				if err = ParseJson(filepath.Join(path, file.Name()), entity); err != nil {
+					return nil, err
+				}
+				p.Schemas = append(p.Schemas, entity)
+			}
+		}
+	}
+
+	// Carrega os arquivos de recursos localizados na pasta resources
+	path = fmt.Sprintf("%s/resources", directory)
+	if files, err = GetFiles(path); err == nil {
+		fmt.Println("Read resources...")
+		for _, file := range files {
+			if file.Name()[0] == '*' {
+				continue
+			}
+
+			resource = &Resource{}
+			if err = ParseJson(filepath.Join(path, file.Name()), resource); err != nil {
+				return nil, err
+			}
+			if file.Name() == "global.json" {
+				p.Resource = resource
+
+			} else {
+				p.Resources = append(p.Resources, resource)
+			}
+		}
+	}
+	// Inicializa variaveis do projeto
+	if p.OmitEmpty == nil {
+		p.OmitEmpty = map[string]bool{}
+	}
+
+	p.ReplaceWhenEmpty = map[string]bool{
+		"json": true,
+		"bson": true,
+	}
+
+	// p.OmitEmpty["json"] = true
+	// p.OmitEmpty["bson"] = true
+
+	// Atualiza o mapeamento de entidades e resources
+	p.SchemasRef = map[string]*Entity{}
+
+	for _, s := range p.Schemas {
+		id = GenericPart.ReplaceAllString(s.ID, "")
+		for _, p := range s.Properties {
+			if err = p.ParseAutogenerate(); err != nil {
+				return nil, err
+			}
+		}
+		p.SchemasRef[id] = s
+		p.SchemasRef[id+"Reference"] = SchemaReference(s)
+		p.SchemasRef[id+"Input"] = SchemaInput(s)
+	}
+
+	for _, r := range p.Resources {
+
+		format := map[string]*Value{}
+
+		for _, f := range r.Formats {
+			switch f.Fields {
+			case "@all":
+				f.Fields = ""
+			case "@reference":
+				if ref, found := p.SchemasRef[r.Entity+"Reference"]; found {
+					fields := []string{}
+
+					for _, propertie := range ref.Properties {
+						fields = append(fields, propertie.ID)
+					}
+
+					f.Fields = strings.Join(fields, ",")
+				}
+			}
+
+			format[f.Value] = f
+		}
+
+		for _, m := range r.Methods {
+			m.Resource = r
+
+			m.Entity = r.Entity
+
+			m.Parameters = map[string]*Parameter{}
+
+			if p.Resource.CommonParams == nil {
+				continue
+			}
+
+			for _, p1 := range m.ParametersString {
+
+				if p2, ok := p.Resource.CommonParams[p1]; ok {
+					m.Parameters[p1] = p2
+				} else {
+					panic(fmt.Errorf("Param '%s' not defined!", p1))
+				}
+			}
+
+			for _, param := range m.Parameters {
+				if _, ok := paramAcceptFiled[param.ID]; ok {
+					continue
+				}
+				paramAcceptFiled[param.ID] = true
+
+				if param.ConvertTo == "" {
+					param.ConvertTo = param.Type
+				}
+
+				// if param.Validation != nil {
+				// 	if param.Validation.AcceptRef != nil {
+				// 		for _, accept := range param.Validation.AcceptRef {
+				// 			param.Validation.Accept = append(param.Validation.Accept, format[accept])
+				// 		}
+				// 	}
+				// 	if param.Validation.RejectRef != nil {
+				// 		for _, reject := range param.Validation.RejectRef {
+				// 			param.Validation.Reject = append(param.Validation.Reject, format[reject])
+				// 		}
+				// 	}
+				// }
+			}
+		}
+	}
+
+	if err = interpolateProject(p); err != nil {
+		return nil, err
+	}
+
+	// o, _ := json.Marshal(p)
+
+	readBuildVersion(p, directory)
+
+	return p, nil
+}
+
+func readBuildVersion(project *Project, directory string) {
+	var (
+		filename = fmt.Sprintf("%s/buildversion", directory)
+		build    = ""
+		buildInt = 0
+		err      error
+	)
+	// defer func() {
+	// 	FilePutContents(filename, strconv.Itoa(buildInt), 0777)
+	// }()
+
+	if build, err = FileGetContents(filename); err != nil {
+		build = "0"
+	}
+
+	buildInt, _ = strconv.Atoi(build)
+
+	buildInt++
+
+	project.BuildVersion = fmt.Sprintf("%s.%d", project.Version, buildInt)
+}
+
+func SchemaReference(s *Entity) *Entity {
+	x := *s
+	x.Properties = []*Propertie{}
+	for _, p := range s.Properties {
+
+		if p.Reference {
+			x.Properties = append(x.Properties, p)
+		}
+	}
+	return &x
+}
+
+func SchemaInput(s *Entity) *Entity {
+	x := *s
+	x.Properties = []*Propertie{}
+	for _, p := range s.Properties {
+		if !p.Readonly {
+			x.Properties = append(x.Properties, p)
+		}
+	}
+	return &x
+}
+
+func interpolateProject(project *Project) (err error) {
+	data := map[string]interface{}{
+		"baseUrl": project.BaseURL,
+	}
+
+	for _, resource := range project.Resources {
+		data["entity"] = resource.Entity
+		for _, method := range resource.Methods {
+
+			method.Request = ResolveParams(method.Request, data)
+
+			method.Response = ResolveParams(method.Response, data)
+
+			// replace baseurl in scopes
+			scopes := []string{}
+			for _, scope := range method.Scopes {
+				scopes = append(
+					scopes,
+					ResolveParams(scope, data),
+				)
+			}
+			method.Scopes = scopes
+		}
+	}
+
+	// enviroment variables
+
+	//
+
+	for _, variable := range project.Environment {
+		variable.Default = ResolveParams(variable.Default, data)
+	}
+
+	return
+}
+
+var (
+	expandTranscludeExpression = regexp.MustCompile(`"\$\._transclude_":true`)
+	expandVarExpression        = regexp.MustCompile(`"\$(\.\w+)"`)
+	removeAllSpacesExpression  = regexp.MustCompile(`(\s|\t|\r|\n)+`)
+)
+
+func loadQueries(project *Project, directory string) (err error) {
+	var (
+		files    []os.FileInfo
+		data     string
+		queryKey string
+	)
+	path := fmt.Sprintf("%s/queries", directory)
+
+	if files, err = GetFiles(path); err == nil {
+		fmt.Println("Read queries...")
+		for _, file := range files {
+			if file.Name()[0] == '*' {
+				continue
+			}
+			// resource = &Resource{}
+			if data, err = FileGetContents(filepath.Join(path, file.Name())); err != nil {
+				return
+			}
+
+			queryKey = strings.ReplaceAll(strings.ReplaceAll(file.Name(), "_", ":"), ".json", "")
+
+			data = removeAllSpacesExpression.ReplaceAllString(data, "")
+
+			data = expandTranscludeExpression.ReplaceAllStringFunc(data, func(input string) string {
+				return "{._transclude_}"
+			})
+
+			data = expandVarExpression.ReplaceAllStringFunc(data, func(input string) string {
+				input = strings.TrimLeft(strings.TrimRight(input, `"`), `"$`)
+				return fmt.Sprintf("{{%s}}", input)
+			})
+
+			project.Queries.Queries[queryKey] = data
+		}
+	}
+	return
+}
+func expandQueries(project *Project) (err error) {
+	var (
+		newquery string
+		found    bool
+	)
+
+	for key, query := range project.Queries.Queries {
+		if len(query) > 0 && query[0] == '$' {
+
+			if newquery, found = project.Queries.Queries[query[1:]]; !found {
+				panic(fmt.Errorf("Query `%s` not defined", query[1:]))
+			}
+
+			project.Queries.Queries[key] = newquery
+		}
+	}
+	return
+}

+ 56 - 0
gen/service.go

@@ -0,0 +1,56 @@
+package gen
+
+import (
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+)
+
+var ServiceTemplate = `
+[Unit]
+Description=Gojus
+Documentation=https://gojus.com.br
+After=network-online.target
+Wants=network-online.target systemd-networkd-wait-online.service
+[Service]
+#Restart=on-failure
+#StartLimitInterval=86400
+#StartLimitBurst=5
+; User and group the process will run as.
+#User=www-data
+#Group=www-data
+User=root
+Group=root
+; Letsencrypt-issued certificates will be written to this directory.
+;Environment="GOOGLE_APPLICATION_CREDENTIALS=/apps/sys/gojus/ky.json" GOOGLE_CLOUD_PROJECT=173520589311
+; Always set "-root" to something safe in case it gets forgotten in the Caddyfile.
+ExecStart=/apps/sys/gojus/main -config=/apps/sys/gojus/config.production.json
+ExecReload=/bin/kill -USR1 $MAINPID
+#RootDirectory=/apps/sys/gojus
+WorkingDirectory=/apps/sys/gojus
+; Limit the number of file descriptors; see "man systemd.exec" for more limit settings.
+LimitNOFILE=1048576
+; Unmodified caddy is not expected to use more than that.
+LimitNPROC=64
+; Use private /tmp and /var/tmp, which are discarded after caddy stops.
+PrivateTmp=true
+; Use a minimal /dev
+PrivateDevices=true
+; Hide /home, /root, and /run/user. Nobody will steal your SSH-keys.
+ProtectHome=true
+; Make /usr, /boot, /etc and possibly some more folders read-only.
+ProtectSystem=full
+; … except /etc/ssl/caddy, because we want Letsencrypt-certificates there.
+;   This merely retains r/w access rights, it does not add any new. Must still be writable on the host!
+ReadWriteDirectories=/apps/sys/gojus
+; The following additional security directives only work with systemd v229 or later.
+; They further retrict privileges that can be gained by caddy. Uncomment if you like.
+; Note that you may have to add capabilities required by any plugins in use.
+;CapabilityBoundingSet=CAP_NET_BIND_SERVICE
+AmbientCapabilities=CAP_NET_BIND_SERVICE
+;NoNewPrivileges=true
+[Install]
+WantedBy=multi-user.target
+`
+
+func CreateServiceFile(project *Project) {
+
+}

+ 38 - 0
go.mod

@@ -0,0 +1,38 @@
+module git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen
+
+go 1.13
+
+require (
+	git.eugeniocarvalho.dev/eugeniucarvalho/gg v1.0.1
+	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/dave/jennifer v1.4.0
+	github.com/davecgh/go-spew v1.1.1
+	github.com/eugeniucarvalho/validator v0.0.0-20190212223505-9efa4c9aff1a
+	github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4 // indirect
+	github.com/fsnotify/fsnotify v1.4.7
+	github.com/go-stack/stack v1.8.0 // indirect
+	github.com/golang/snappy v0.0.1 // indirect
+	github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38
+	github.com/gorilla/schema v1.1.0 // indirect
+	github.com/gorilla/websocket v1.4.1
+	github.com/iris-contrib/formBinder v5.0.0+incompatible // indirect
+	github.com/jeremywohl/flatten v1.0.1
+	github.com/json-iterator/go v1.1.9 // indirect
+	github.com/kataras/golog v0.0.10
+	github.com/kataras/iris v11.1.1+incompatible
+	github.com/kataras/iris/v12 v12.1.3
+	github.com/klauspost/compress v1.9.4 // indirect
+	github.com/pascaldekloe/jwt v1.7.0
+	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
+	github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect
+	github.com/xdg/stringprep v1.0.0 // indirect
+	go.mongodb.org/mongo-driver v1.2.0
+	golang.org/x/crypto v0.0.0-20191219195013-becbf705a915 // indirect
+	golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
+	golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
+	golang.org/x/text v0.3.2 // indirect
+	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
+	gopkg.in/yaml.v2 v2.2.7 // indirect
+)

+ 187 - 0
go.sum

@@ -0,0 +1,187 @@
+git.eugeniocarvalho.dev/eugeniucarvalho/gg v1.0.0 h1:BcjI7XjCPLrcFeGeV3gJzt8Mf981eAG7Jf2X+fLGL0Q=
+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/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=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a h1:3SgJcK9l5uPdBC/X17wanyJAMxM33+4ZhEIV96MIH8U=
+github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
+github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible h1:rZgFj+Gtf3NMi/U5FvCvhzaxzW/TaPYgUYx3bAPz9DE=
+github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
+github.com/DataDog/zstd v1.4.4 h1:+IawcoXhCBylN7ccwdwf8LOH2jKq7NavGpEPanrlTzE=
+github.com/DataDog/zstd v1.4.4/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
+github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
+github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7 h1:mreN1m/5VJ/Zc3b4pzj9qU6D9SRQ6Vm+3KfI328t3S8=
+github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
+github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398 h1:WDC6ySpJzbxGWFh4aMxFFC28wwGp5pEuoTtvA4q/qQ4=
+github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
+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/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=
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/dave/jennifer v1.4.0 h1:tNJFJmLDVTLu+v05mVZ88RINa3vQqnyyWkTKWYz0CwE=
+github.com/dave/jennifer v1.4.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
+github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
+github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
+github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
+github.com/eugeniucarvalho/validator v0.0.0-20190212223505-9efa4c9aff1a h1:0veGAFpfIi8XdZMtm9HaF+3gzbAtSpk4XefPG4mo4Mw=
+github.com/eugeniucarvalho/validator v0.0.0-20190212223505-9efa4c9aff1a/go.mod h1:f1KohTB5V8xVwfJqKKKu4pnizhbsZFAQ1gm8CE19Nj8=
+github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
+github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
+github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4 h1:GY1+t5Dr9OKADM64SYnQjw/w99HMYvQ0A8/JoUkxVmc=
+github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
+github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
+github.com/go-fsnotify/fsnotify v0.0.0-20180321022601-755488143dae h1:PeVNzgTRtWGm6fVic5i21t+n5ptPGCZuMcSPVMyTWjs=
+github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
+github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38 h1:y0Wmhvml7cGnzPa9nocn/fMraMH/lMDdeG+rkx4VgYY=
+github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
+github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
+github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/iris-contrib/blackfriday v2.0.0+incompatible h1:o5sHQHHm0ToHUlAJSTjW9UWicjJSDDauOOQ2AHuIVp4=
+github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
+github.com/iris-contrib/formBinder v5.0.0+incompatible h1:jL+H+cCSEV8yzLwVbBI+tLRN/PpVatZtUZGK9ldi3bU=
+github.com/iris-contrib/formBinder v5.0.0+incompatible/go.mod h1:i8kTYUOEstd/S8TG0ChTXQdf4ermA/e8vJX0+QruD9w=
+github.com/iris-contrib/go.uuid v2.0.0+incompatible h1:XZubAYg61/JwnJNbZilGjf3b3pB80+OQg2qf6c8BfWE=
+github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
+github.com/iris-contrib/pongo2 v0.0.1 h1:zGP7pW51oi5eQZMIlGA3I+FHY9/HOQWDB+572yin0to=
+github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
+github.com/iris-contrib/schema v0.0.1 h1:10g/WnoRR+U+XXHWKBHeNy/+tZmM2kcAVGLOsz+yaDA=
+github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
+github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs=
+github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 h1:rhqTjzJlm7EbkELJDKMTU7udov+Se0xZkWmugr6zGok=
+github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
+github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
+github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
+github.com/kataras/golog v0.0.10 h1:vRDRUmwacco/pmBAm8geLn8rHEdc+9Z4NAr5Sh7TG/4=
+github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
+github.com/kataras/iris v11.1.1+incompatible h1:c2iRKvKLpTYMXKdVB8YP/+A67NtZFt9kFFy+ZwBhWD0=
+github.com/kataras/iris v11.1.1+incompatible/go.mod h1:ki9XPua5SyAJbIxDdsssxevgGrbpBmmvoQmo/A0IodY=
+github.com/kataras/iris/v12 v12.1.3 h1:Xa0w04EmivKIsfTU0sYGA38WHO0NVZhkREJvwfHC+Ss=
+github.com/kataras/iris/v12 v12.1.3/go.mod h1:x8XU9FcsqdBvFyDA9brryw7Px8UZ8lquXwVaYKRzBBc=
+github.com/kataras/neffos v0.0.12/go.mod h1:V8lRtjUHJWfwwoJHI0Pv/AYnJa3gvr6TPBTQfGvGEaE=
+github.com/kataras/pio v0.0.2 h1:6NAi+uPJ/Zuid6mrAKlgpbI11/zK/lV4B2rxWaJN98Y=
+github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
+github.com/kataras/sitemap v0.0.5 h1:4HCONX5RLgVy6G4RkYOV3vKNcma9p236LdGOipJsaFE=
+github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
+github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.9.4 h1:xhvAeUPQ2drNUhKtrGdTGNvV9nNafHMUkRyLkzxJoB4=
+github.com/klauspost/compress v1.9.4/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
+github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
+github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
+github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
+github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
+github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/pascaldekloe/jwt v1.7.0 h1:0vNebf7Whqyv8yrly+BSm4B4Y0aAprLTACaX5eLBng8=
+github.com/pascaldekloe/jwt v1.7.0/go.mod h1:TKhllgThT7TOP5rGr2zMLKEDZRAgJfBbtKyVeRsNB9A=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk=
+github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
+github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/stretchr/objx v0.1.0/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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
+github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
+github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
+github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.mongodb.org/mongo-driver v1.2.0 h1:6fhXjXSzzXRQdqtFKOI1CDw6Gw5x6VflovRpfbrlVi0=
+go.mongodb.org/mongo-driver v1.2.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20191219195013-becbf705a915 h1:aJ0ex187qoXrJHPo8ZasVTASQB7llQP6YeNzgDALPRk=
+golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
+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/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
+golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/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=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
+gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
+gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

+ 16 - 0
main.go

@@ -0,0 +1,16 @@
+package main
+
+import (
+	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/commands"
+)
+
+func main() {
+	var (
+		err error
+	)
+
+	if err = commands.ExecuteWithFlags(); err != nil {
+		panic(err)
+	}
+
+}

+ 70 - 0
translate/got/constants.go

@@ -0,0 +1,70 @@
+package got
+
+import (
+	"fmt"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+)
+
+func CreateVariables(p *Project) error {
+
+	var (
+		varList            = G.Statement{}
+		constList          = G.Statement{}
+		filterFunctionList = G.Statement{}
+		dbid, collectionid string
+	)
+
+	// file := G.NewFile(p.Package)
+	file := G.NewFile("models")
+
+	for _, entity := range p.Schemas {
+		if entity.Type == "object" {
+
+			dbid = fmt.Sprintf("%sDataBase", entity.ID)
+			collectionid = fmt.Sprintf("%sCollection", entity.ID)
+
+			constList = append(constList, G.Id(dbid).Op("=").Lit(entity.DB))
+			constList = append(constList, G.Id(collectionid).Op("=").Lit(entity.Collection))
+
+			filterFunctionList = append(filterFunctionList, G.Id(`
+			func `).Id(
+				fmt.Sprintf("Filter%sOptions", entity.ID),
+			).Params(
+				G.Id("ctx").Qual(IRIS_CTX, "Context"),
+			).Id(` *`).Qual(API_URL, "Filter").Id(`{
+				var session `).Qual(MONGO, "SessionContext").Id(`
+				
+				if ctx != nil {
+					session = api.GetSessionContext(ctx)
+				}
+
+				return &api.Filter{
+					DB:`).Id(dbid).Id(`,
+					Collection:`).Id(collectionid).Id(`,
+					SessionContext: session,
+				}
+			} 
+			`).Line())
+
+		}
+	}
+
+	file.Const().Defs(constList...).Line()
+
+	for id, v1 := range p.Variables {
+		varList = append(varList, G.Id(id).Op("=").Lit(v1))
+	}
+
+	file.Var().Defs(varList...).Line()
+
+	file.Add(filterFunctionList...)
+
+	return Write(fmt.Sprintf("%s/%s/models/variables_gen.go", p.OutPath, p.Package), file)
+}
+
+// ctx, _ = `).Qual("context", "WithTimeout").Call(
+// 	G.Qual("context", "Background()"),
+// 	G.Lit(30).Op("*").Qual("time", "Second"),
+// ).Id(`

+ 5 - 0
translate/got/control.go

@@ -0,0 +1,5 @@
+package got
+
+var (
+	relationFunctionDefined = map[string]bool{}
+)

+ 80 - 0
translate/got/environment.go

@@ -0,0 +1,80 @@
+package got
+
+import (
+	"fmt"
+	"text/template"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+)
+
+var (
+	environmentStmtsTmpl *template.Template
+	environmentStmtsErr  error
+)
+
+func init() {
+
+	environmentStmtsTmpl, environmentStmtsErr = ParseTemplate(`
+
+	import (
+		"fmt"
+		common "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+		"os"
+	)
+	
+	const (
+		{{range .variables}} Env{{.CamelID}} = "{{.ID}}"
+		{{end}}
+	)
+
+	func InitEnvironment() error {
+		var (
+			value     string
+			variables = []common.EnvironmentVariable{
+				{{range .variables}}{
+					ID:          Env{{.CamelID}},
+					Description: "{{.Description}}",
+					Required:    {{.Required}},
+					Default:     "{{.Default}}",
+				},{{end}}
+			}
+		)
+	
+		for _, variable := range variables {
+			if value = os.Getenv(variable.ID); value == "" {
+				if variable.Default == "" && variable.Required {
+					panic(fmt.Errorf("Environment Variable '%s' not defined! %s", variable.ID, variable.Description))
+				}
+				value = variable.Default
+			}
+			common.Setenv(variable.ID, value)
+		}
+		fmt.Println("Environment...")
+		for _, pair := range os.Environ() {
+			fmt.Printf("\t├ %s\n",pair)
+		}
+		return nil
+	}`)
+}
+
+func CreateEnvironment(p *Project) error {
+
+	for id, environment := range p.Environment {
+		environment.CamelID = UnderToCamel(id)
+	}
+
+	var (
+		context = map[string]interface{}{
+			"variables": p.Environment,
+		}
+	)
+
+	out, _ := TemplateToString(environmentStmtsTmpl, context)
+
+	file := G.NewFile(p.Package)
+
+	file.Id(out)
+
+	return Write(fmt.Sprintf("%s/%s/environment.go", p.OutPath, p.Package), file)
+}

+ 73 - 0
translate/got/functions.go

@@ -0,0 +1,73 @@
+package got
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+	"github.com/davecgh/go-spew/spew"
+)
+
+func generateHookCall(project *Project, method *Method, hookId string) {
+	if !method.Hook(hookId) {
+		return
+	}
+
+	path := fmt.Sprintf(
+		"../project/include/go/hook_%s_%s_%s_gen.go",
+		// project.OutPath,
+		// project.Package,
+		method.Entity,
+		method.ID,
+		hookId,
+	)
+
+	// return
+	if _, fileErr := os.Stat(path); os.IsNotExist(fileErr) {
+
+		methodId := fmt.Sprintf(
+			"%s%s%s",
+			strings.Title(hookId),
+			method.Entity,
+			strings.Title(method.ID),
+		)
+
+		fmt.Println("\n\n\nCREATE HOOK", path, methodId)
+
+		file := G.NewFile(project.Package)
+		file.Func().Id(methodId).Call(
+			G.Id("ctx").Qual(IRIS_CTX, "Context"),
+			G.Id("resp").Interface(),
+		).Op("*").Qual(API_ERROR, "Error").Block(
+			G.Return(G.Nil()),
+		)
+
+		if err := Write(path, file); err != nil {
+			spew.Dump(err)
+		}
+	}
+}
+
+func parseMethodActions(actions []Action) string {
+	actionsCallStmt := []string{}
+	var actionStmt string
+
+	for _, action := range actions {
+
+		actionStmt = fmt.Sprintf("actions.%s", strings.Title(action.ID))
+
+		if action.Context != nil {
+			actionStmt = fmt.Sprintf(
+				"%s(%s)",
+				actionStmt,
+				fmt.Sprintf("%#v", action.Context),
+			)
+		}
+
+		actionsCallStmt = append(actionsCallStmt, actionStmt)
+	}
+
+	return strings.Join(actionsCallStmt, ",\n")
+}

+ 333 - 0
translate/got/middleware_delete.go

@@ -0,0 +1,333 @@
+package got
+
+import (
+	"text/template"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+)
+
+const ()
+
+var (
+	deleteStmtsTmpl, hasDepDeleteStmtsTmpl *template.Template
+	deleteStmtsErr                         error
+)
+
+func init() {
+
+	deleteStmtsTmpl, deleteStmtsErr = ParseTemplate(`
+
+		filter := ctx.Values().Get("$filter").(*api.Filter)
+		filter.DB = "{{.dbName}}"
+		filter.Collection = "{{.collectionName}}"
+			
+		{{if .createDepFunc}}
+		if HasDep{{.entity}}(filter) {
+			err = errs.FailedPrecondition().Details(&errs.Detail{
+				Message: "Ocorreu um conflito ao remover a entidade '{{.entity}}'",
+			})
+			return
+		}
+		{{end}}
+
+		{{if .preconditions}}
+			if _, err = executeAction(
+				ctx,
+				{{.preconditions}},
+			); err != nil {
+				return
+			}
+		{{end}}
+
+		if replacement := ctx.Values().Get("replace.default.patch"); replacement != nil  {
+			filter.Patchs = replacement.(*bson.A)
+		} else {
+			filter.Patchs = api.DeletedPatch()
+		}
+	
+		if _, err = models.Api.PatchOne(filter); err != nil {
+			err.Details(&errs.Detail{
+				Dominio:      "",
+				Reason:       "",
+				Location:     "middleware.path",
+				LocationType: "middleware.operation/Caracterization",
+			})
+		}
+
+		{{if .beforeResponse}}
+		if resp, err = executeAction(
+			ctx,
+			{{.beforeResponse}},
+		); err != nil || resp != nil {
+			return
+		}
+		{{end}}
+
+		resp = ""
+		
+		return`)
+
+	if deleteStmtsErr != nil {
+		panic(deleteStmtsErr)
+	}
+
+	hasDepDeleteStmtsTmpl, deleteStmtsErr = ParseTemplate(`	
+	//  {{.dependenceMethod}} verifica se a entidade '{{.entity}}' possui dependencias com outras entidades.
+	func {{.dependenceMethod}}(options *api.Filter) bool {
+		var (
+			foundChan = make(chan bool)
+			find = func(db, collection, attr string) {
+				exists, _ := models.Api.Exists(&api.Filter{
+					DB:         db,
+					Collection: collection,
+					Query:      &bson.M{attr: options.Id},
+				})
+				foundChan <- exists
+			}
+		)
+		
+	
+		{{range .relations}}
+			go find("{{.DB}}", "{{.Collection}}", "{{.Attr}}._id")
+		{{end}}
+	
+		for i := 0; i < {{.relationCount}}; i++ {
+			select {
+				{{range $idx, $relation := .relations}} case x{{$idx}} := <-foundChan:
+				if x{{$idx}} {
+					return true
+				}
+				{{end}}
+			}
+		}
+	
+		return false
+	}`)
+
+	if deleteStmtsErr != nil {
+		panic(deleteStmtsErr)
+	}
+
+}
+
+var (
+	GenDeleteStmts = &Middleware{
+		Id:   "delete",
+		Type: "method",
+		Fn: func(ctx *MiddlewareContext) error {
+			var (
+				project          = ctx.Project
+				method           = ctx.Method
+				dbName           = project.GetEntityDB(method.Entity)
+				collectionName   = project.GetCollection(method.Entity)
+				relations        = SR.Get(method.Entity)
+				relationCount    = len(relations)
+				dependenceMethod = BASE_HAS_DEPENDE + method.Entity
+				createDepFunc    = SR.Has(method.Entity)
+				beforeSend       = method.Hook("beforeSend")
+				beforePersist    = method.Hook("beforePersist")
+				context          = map[string]interface{}{
+					"dbName":           dbName,
+					"collectionName":   collectionName,
+					"entity":           method.Entity,
+					"createDepFunc":    createDepFunc,
+					"relationCount":    relationCount,
+					"relations":        relations,
+					"dependenceMethod": dependenceMethod,
+					"beforePersist":    beforePersist,
+					"beforeSend":       beforeSend,
+					"preconditions":    parseMethodActions(method.Preconditions),
+					"beforeResponse":   parseMethodActions(method.BeforeResponse),
+				}
+			)
+			// Nome do metodo que verifica se a entidade tem dependencias
+
+			out, _ := TemplateToString(deleteStmtsTmpl, context)
+			afterMethod := ctx.Statement.Block(G.Id(out)).Line()
+
+			if createDepFunc {
+				out, _ = TemplateToString(hasDepDeleteStmtsTmpl, context)
+				afterMethod.Id(out)
+			}
+
+			return nil
+		},
+	}
+)
+
+// afterDefineDelete.Do(func(x *G.Statement) {
+
+// 	x.Add(G.Comment(
+// 		fmt.Sprintf("%s verifica se a entidade '%s' possui dependencias com outras entidades.", dependenceMethod, method.Entity),
+// 	).Line())
+
+// 	// Cria as variaveis
+
+// 	block := G.Func().Id(dependenceMethod).Params(
+// 		G.Id("f").Op("*").Qual(API_URL, "Filter"),
+// 	).Bool().BlockFunc(func(b *G.Group) {
+
+// 		for index, relation := range relations {
+// 			// for index, _ := range relations {
+// 			indexStr = strconv.Itoa(index)
+// 			cIndex = "c" + indexStr
+// 			xIndex = "x" + indexStr
+// 			// Cria o canal para a variavel
+// 			vars = append(vars, G.Id(cIndex).Op("=").Id("make").Call(G.Chan().Bool()))
+
+// 			// Define um case para um canal
+// 			cases = append(cases, G.Case(G.Id(xIndex).Op(":=").Op("<-").Id("c"+indexStr)).Block(
+// 				G.If(G.Id(xIndex)).Block(G.Return(G.True())),
+// 			))
+
+// 			rotines = append(rotines, G.Go().Func().Params().Block(
+// 				G.Id("filter").Op(":=").Op("&").Qual(API_URL, "Filter").Values(G.Dict{
+// 					// Especifica a collection
+// 					G.Id("DB"):         G.Lit(relation.DB),
+// 					G.Id("Collection"): G.Lit(relation.Collection),
+
+// 					// Especifica a query
+// 					G.Id("Query"): G.Op("&").Qual(BSON, "M").Values(G.Dict{
+// 						G.Lit(relation.Attr + "._id"): G.Id("f").Dot("Id"),
+// 					}),
+// 				}),
+
+// 				G.Id(cIndex).Op("<-").Id("Models").Dot("Exist").Call(G.Id("filter")),
+// 			).Call().Line())
+// 		}
+
+// 		// Adiciona as variaveis de canal
+// 		b.Add(G.Var().DefsFunc(func(b2 *G.Group) {
+// 			// Gera o case para cada relacao
+// 			for _, v := range vars {
+// 				b2.Add(v)
+// 			}
+// 		}))
+
+// 		// Adiciona as chamadas das rotinas que consultam a existencia de dependencia
+// 		for _, r := range rotines {
+// 			// fmt.Printf("%#v", r)
+// 			b.Add(r)
+// 		}
+
+// 		// Geracao do for select
+// 		b.Add(G.For(
+// 			G.Id("i").Op(":=").Lit(0),
+// 			G.Id("i").Op("<").Lit(relationCount),
+// 			G.Id("i").Op("++"),
+// 		).Block(G.Select().BlockFunc(func(b1 *G.Group) {
+// 			// Gera o case para cada relacao
+// 			for _, case1 := range cases {
+// 				b1.Add(case1)
+// 			}
+
+// 		})).Line())
+
+// 		b.Return(G.False())
+// 		// fmt.Printf("%#v")
+
+// 	})
+
+// 	x.Add(block)
+
+// }).Line()
+
+// if  {
+// 				createDepFunc = true
+
+// 				do.Add(G.If(G.Id(dependenceMethod).Call(
+// 					G.Id("filter"),
+// 				)).Block(
+// 					G.Id("err").Op("=").Qual(API_URL, "Error").Call(
+// 						G.Qual(API_URL, "ERR_CONFLICT"),
+// 						G.Lit(fmt.Sprintf("Ocorreu um conflito ao remover a entidade '%s'", method.Entity)),
+// 					),
+// 					G.Return(),
+// 				).Line())
+// 			}
+
+// ctx.Statement.Block(
+// 	G.Id(`filter := ctx.Values.Get("$filter")`).Assert(G.Op("*").Qual(API_URL, "Filter")).Line(),
+// 	G.Id("filter").Dot("DB").Op("=").Lit(dbName),
+// 	G.Id("filter").Dot("Collection").Op("=").Lit(collectionName).Line(),
+
+// 	// Verifica se o tem tem dependencias
+// 	G.Do(func(do *G.Statement) {
+
+// 		if SR.Has(method.Entity) {
+// 			createDepFunc = true
+
+// 			do.Add(G.If(G.Id(dependenceMethod).Call(
+// 				G.Id("filter"),
+// 			)).Block(
+// 				G.Id("err").Op("=").Qual(API_URL, "Error").Call(
+// 					G.Qual(API_URL, "ERR_CONFLICT"),
+// 					G.Lit(fmt.Sprintf("Ocorreu um conflito ao remover a entidade '%s'", method.Entity)),
+// 				),
+// 				G.Return(),
+// 			).Line())
+// 		}
+// 		// fmt.Println("GenerateFunc" + method.Entity + "|" + dependenceMethod)
+// 	}),
+
+// 	G.Do(func(s *G.Statement) {
+// 		if method.BeforePersistAction {
+// 			fnName := fmt.Sprintf("Before%s%s", strings.Title(method.ID), method.Entity)
+// 			s.Add(G.Id(fmt.Sprintf(`
+// 			 // Chama uma função onde são descritas as ações executadas antes da entidade ser persistida.
+// 			 if err = %s(ctx, resp); err != nil {
+// 				return
+// 			 }
+// 			`, fnName)))
+
+// 			// part := s.Comment("").Line()
+// 			// part.If(
+// 			// 	G.List(G.Id("fn"), G.Id("err")).Op("=").Id(fnName).Call(G.Id("ctx"), G.Id("patchs")),
+// 			// 	G.Id("err").Op("!=").Id("nil"),
+// 			// ).Block(G.Return())
+// 		}
+// 	}),
+
+// 	// Executa a remocao do item
+// 	// G.If(
+// 	// G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("RemoveOne").Call(
+// 	// 	G.Id("filter"),
+// 	// ),
+
+// 	G.Id(`
+// 	filter.Patchs = &`).Qual(BSON, "A").Id(`{
+// 		bson.M{
+// 			"$set": bson.M{
+// 				"deleted":   true,
+// 				"deletedIn": `).Qual("time", "Now").Id(`().Unix(),
+// 			},
+// 		},
+// 	}`),
+// 	generateHookCall(project, method, "beforePersist"),
+// 	G.Id(`if _, err = models.Api.PatchOne(filter), err !`),
+
+// 	G.Return(),
+// 	// G.Return(G.Nil()),
+// ).
+// --------------------------------------------------
+// G.Id("err").Op("!=").Id("nil"),
+// ).Block(
+// G.Return(G.Id("err")),
+// G.Return(),
+// ),
+// G.Var().Defs(
+// 	G.Id("err").Op("*").Qual(API_ERROR, "Error"),
+// 	// G.Id("entity").Op("=").Op("&").Id(method.Entity).Values(),
+// ).Line(),
+
+// fmt.Println("Compont:" + method.Entity)
+
+// block.Add(
+// 	x.Var().Defs(
+// 		G.Id("err").Op("*").Qual(API_ERROR, "Error"),
+// 		G.Id("entity").Op("=").Op("&").Id(method.Entity).Values(),
+// 	),
+// )
+
+// vars.Defs(varList...)

+ 35 - 0
translate/got/middleware_filter.go

@@ -0,0 +1,35 @@
+package got
+
+import (
+	"fmt"
+	"strings"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+)
+
+var (
+	GenGetFilter = &Middleware{
+		Id:   "filter",
+		Type: "method",
+		Fn: func(ctx *MiddlewareContext) error {
+			var (
+				method = ctx.Method
+				entity = strings.ToLower(method.Entity)
+			)
+			ctx.Statement.Block(G.Id(`
+			var (
+				ok bool
+				res *`).
+				Qual(CODE_GEN_V2_COMMON, "ApiFilter").
+				Id(fmt.Sprintf(`
+			)
+			if res, ok = filtersApiReference["%s"]; !ok {
+				res = common.NewApiFilter("%s")
+			}
+			return res, nil
+			`, entity, entity)))
+			return nil
+		},
+	}
+)

+ 112 - 0
translate/got/middleware_get_list.go

@@ -0,0 +1,112 @@
+package got
+
+import (
+	// . "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/gen"
+
+	"text/template"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+)
+
+var (
+	getListStmtsTmpl *template.Template
+	getListStmtsErr  error
+)
+
+func init() {
+
+	getListStmtsTmpl, getListStmtsErr = ParseTemplate(`
+	var (
+		ntoken   string
+		entities = []*models.{{.entity}}{}
+		count    = 0
+		values = ctx.Values()
+	)
+
+	values.Set("entities", &entities)
+	
+	options := values.Get("$filter").(*api.Filter)
+	options.DB = "{{.dbName}}"
+	options.Collection = "{{.collectionName}}"
+	
+	options.Entities = &entities
+
+
+	if err = t.Fields(options); err != nil {
+		return
+	}
+	
+	{{if .preconditions}}
+		if _, err = executeAction(
+			ctx,
+			{{.preconditions}},
+		); err != nil {
+			return
+		}
+	{{end}}
+	
+	if _, err = models.Api.FindMany(options); err != nil {
+		return
+	}
+
+	{{if .beforeResponse}}
+	if resp, err = executeAction(
+		ctx,
+		{{.beforeResponse}},
+	); err != nil || resp != nil {
+		return
+	}
+	{{end}}
+
+	resp = &models.{{.responseStruct}}{
+		Itens:              entities,
+		NextPageToken:      ntoken,
+		ResultSizeEstimate: count,
+	}
+
+	return`)
+
+	if getListStmtsErr != nil {
+		panic(getListStmtsErr)
+	}
+}
+
+var (
+	GenGetStmtsList = &Middleware{
+		Id:   "get_list",
+		Type: "method",
+		Fn: func(ctx *MiddlewareContext) error {
+
+			var (
+				project        = ctx.Project
+				method         = ctx.Method
+				responseStruct = method.Response
+				response       = project.ResponseEntity(responseStruct)
+			)
+
+			if response.IsGeneric {
+				GenFromGenericModel(project, response)
+				responseStruct = response.NewName
+			}
+
+			var (
+				context = map[string]interface{}{
+					"dbName":         project.GetEntityDB(method.Entity),
+					"collectionName": project.GetCollection(method.Entity),
+					"beforeSend":     method.Hook("beforeSend"),
+					"beforeFind":     method.Hook("beforeFind"),
+					"entity":         method.Entity,
+					"responseStruct": responseStruct,
+					"preconditions":  parseMethodActions(method.Preconditions),
+					"beforeResponse": parseMethodActions(method.BeforeResponse),
+				}
+			)
+			// Nome do metodo que verifica se a entidade tem dependencias
+			out, _ := TemplateToString(getListStmtsTmpl, context)
+			ctx.Statement.Block(G.Id(out)).Line()
+
+			return nil
+		},
+	}
+)

+ 90 - 0
translate/got/middleware_get_one.go

@@ -0,0 +1,90 @@
+package got
+
+import (
+	"text/template"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+)
+
+var (
+	getStmtsTmpl *template.Template
+	getStmtsErr  error
+)
+
+func init() {
+
+	getStmtsTmpl, getStmtsErr = ParseTemplate(`
+	var (
+		entity = &models.{{.entity}}{}
+		values = ctx.Values()
+	)
+	values.Set("entity", entity)
+
+	options := values.Get("$filter").(*api.Filter)
+	options.DB = "{{.dbName}}"
+	options.Collection = "{{.collectionName}}"
+	options.Entity = entity
+
+	if err = t.Fields(options); err != nil {
+		return
+	}
+	
+	{{if .preconditions}}
+		if _, err = executeAction(
+			ctx,
+			{{.preconditions}},
+		); err != nil {
+			return
+		}
+	{{end}}
+
+	if _, err = models.Api.FindOne(options); err != nil {
+		return
+	}
+	
+	{{if .beforeResponse}}
+	if resp, err = executeAction(
+		ctx,
+		{{.beforeResponse}},
+	); err != nil || resp != nil {
+		return
+	}
+	{{end}}
+	resp = entity
+	return`)
+
+	if getStmtsErr != nil {
+		panic(getStmtsErr)
+	}
+}
+
+var (
+	GenGetStmtsOne = &Middleware{
+		Id:   "get_one",
+		Type: "method",
+		Fn: func(ctx *MiddlewareContext) error {
+			var (
+				project        = ctx.Project
+				method         = ctx.Method
+				dbName         = project.GetEntityDB(method.Entity)
+				collectionName = project.GetCollection(method.Entity)
+				context        = map[string]interface{}{
+					"dbName":         dbName,
+					"collectionName": collectionName,
+					"entity":         method.Entity,
+					"beforeFind":     method.Hook("beforeFind"),
+					"beforeSend":     method.Hook("beforeSend"),
+					"preconditions":  parseMethodActions(method.Preconditions),
+					"beforeResponse": parseMethodActions(method.BeforeResponse),
+				}
+			)
+
+			// Nome do metodo que verifica se a entidade tem dependencias
+			out, _ := TemplateToString(getStmtsTmpl, context)
+			ctx.Statement.Block(G.Id(out)).Line()
+
+			return nil
+		},
+	}
+)

+ 37 - 0
translate/got/middleware_hook_template.go

@@ -0,0 +1,37 @@
+package got
+
+import (
+	"text/template"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+)
+
+var (
+	hookStmtsTmpl *template.Template
+	hookStmtsErr  error
+)
+
+func init() {
+	hookStmtsTmpl, hookStmtsErr = ParseTemplate(`
+	import(
+		{{range $label, $path := .imports}}
+			{{$label}} "{{$path}}"
+		{{end}}
+	)
+	{{if .hasContext}}
+	func {{.function}}(options map[string]interface{}) func( context.Context) (interface{},*errs.Error) {
+		return func(ctx context.Context) (resp interface{}, err *errs.Error) {
+			return
+		}
+	}
+	{{else}}
+	func {{.function}}(ctx context.Context) (resp interface{}, err *errs.Error) {
+		return
+	}
+	{{end}}
+	`)
+
+	if hookStmtsErr != nil {
+		panic(hookStmtsErr)
+	}
+}

+ 52 - 0
translate/got/middleware_implement.go

@@ -0,0 +1,52 @@
+package got
+
+import (
+	"text/template"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+)
+
+var (
+	implementStmtsTmpl *template.Template
+	implementStmtsErr  error
+)
+
+func init() {
+
+	implementStmtsTmpl, implementStmtsErr = ParseTemplate(`
+	{{if .preconditions}}
+		if _, err = executeAction(
+			ctx,
+			{{.preconditions}},
+		); err != nil {
+			return
+		}
+	{{end}}
+	return
+	`)
+
+	if implementStmtsErr != nil {
+		panic(implementStmtsErr)
+	}
+}
+
+var (
+	GenImplement = &Middleware{
+		Id:   "implement",
+		Type: "method",
+		Fn: func(ctx *MiddlewareContext) error {
+			var (
+				method  = ctx.Method
+				context = map[string]interface{}{
+					"preconditions": parseMethodActions(method.Preconditions),
+				}
+			)
+			// Nome do metodo que verifica se a entidade tem dependencias
+			out, _ := TemplateToString(implementStmtsTmpl, context)
+			ctx.Statement.Block(G.Id(out)).Line()
+
+			return nil
+		},
+	}
+)

+ 117 - 0
translate/got/middleware_main.go

@@ -0,0 +1,117 @@
+package got
+
+import (
+	"fmt"
+	"strings"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	"git.eugeniocarvalho.dev/eugeniucarvalho/utils"
+	G "github.com/dave/jennifer/jen"
+	"github.com/davecgh/go-spew/spew"
+	// . "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/gen"
+)
+
+func CreateMainFile(project *Project) (err error) {
+
+	spew.Dump(project.Custom)
+
+	var (
+		Index               = G.NewFile("main")
+		address             = strings.Join(strings.Split(project.BaseURL, ":")[1:], ":")
+		goPackageRepository = project.Custom["go.package.repository"].(string)
+	)
+
+	address = strings.Replace(address, "//", "", -1)
+
+	Index.Id(`import (
+	
+		`).Id(project.ID).Lit(goPackageRepository+"/build/v1").Id(`
+		models `).Lit(goPackageRepository+"/build/v1/models").Id(`
+		"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api" // Pacotes do framework web
+		"os"
+		"fmt"
+		iris "github.com/kataras/iris/v12"
+		"github.com/kataras/iris/v12/middleware/recover"
+	)
+
+	func main() {
+
+		var (
+			err error
+		)
+	
+		if err = `).Id(project.ID).Id(`.InitEnvironment(); err != nil {
+			panic(err)
+		}
+
+		app := iris.New()
+		app.Logger().SetLevel("debug")
+		// Optionally, add two built'n handlers
+		// that can recover from any http-relative panics
+		// and log the requests to the terminal.
+		app.Use(recover.New())
+		app.Use(`).Qual("github.com/kataras/iris/v12/middleware/logger", "New").Id(`())
+		
+		app.Use(api.DefaultCorsHandler())
+	
+		api.BuildVersion = `).Id(fmt.Sprintf(`"%s"`, project.BuildVersion)).Id(`
+
+		// session := &api.Mongo{ Config: os.Getenv("MONGO_CONFIG") }
+		models.Api.Config = os.Getenv("MONGO_CONFIG")
+	
+		if err = models.Api.Init(); err != nil {
+			panic(err)
+		}
+		
+		if err = `).Id(fmt.Sprintf("%s.AppInitialize(app)", project.ID)).Id(`; err != nil {
+			panic(err)
+		}
+
+		`).Id(project.ID).Id(`.Register(app)
+
+		fmt.Println("API_VERSION: ", api.BuildVersion)
+		app.Run(iris.Addr(os.Getenv("APP_ADDRS")), iris.WithoutServerError(iris.ErrServerClosed))
+	}
+	`)
+
+	if err = Write(fmt.Sprintf("%s/main.go", project.OutPath), Index); err != nil {
+		return
+	}
+
+	path := fmt.Sprintf("%s/v1/app_initialize.go", project.OutPath)
+	if !utils.FileExists(path) {
+
+		AppInitialize := G.NewFile("v1")
+		AppInitialize.Id(`
+		import(
+			iris "github.com/kataras/iris/v12"
+		)
+
+		func AppInitialize(app *iris.Application) error { 
+			return nil
+		}
+		`)
+		if err = Write(path, AppInitialize); err != nil {
+			return
+		}
+	}
+
+	return nil
+}
+
+// app.RegisterView(iris.HTML("./public", ".html"))
+// app.Get("/", func(ctx iris.Context) {
+// 	ctx.View("index.html")
+// })
+// app.HandleDir("/", "./public", iris.DirOptions{
+// 	Asset:      Asset,
+// 	AssetInfo:  AssetInfo,
+// 	AssetNames: AssetNames,
+// 	// IndexName:  "index.html", // default.
+// 	// If you want to show a list of embedded files when inside a directory without an index file:
+// 	// ShowList:   true,
+// 	// DirList: func(ctx iris.Context, dirName string, f http.File) error {
+// 	// 	// [Optional, custom code to show the html list].
+// 	// }
+// })
+// app.SPA(app.StaticHandler("./public", false, false))

+ 142 - 0
translate/got/middleware_patch.go

@@ -0,0 +1,142 @@
+package got
+
+import (
+	"text/template"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+	// "github.com/davecgh/go-spew/spew"
+)
+
+var (
+	patchStmtsTmpl, patchRelationStmtsTmpl *template.Template
+	patchStmtsErr                          error
+)
+
+func init() {
+
+	patchStmtsTmpl, patchStmtsErr = ParseTemplate(`
+	var (
+		patchs = &models.{{.entity}}Patchs{Set: &models.{{.entity}}Input{}}
+		entity = patchs.Set
+	)
+
+	if err = api.ReadJson(ctx, patchs); err != nil {
+		return
+	}
+
+	entity.SetMode("patch")
+	values := ctx.Values()
+	values.Set("patchs", patchs)
+	entity.LastUpdate = api.NowUnix()
+	user := values.Get("$user.ref").(*models.UserReference)
+	entity.UpdatedBy = user
+	values.Set("entity", entity)
+	
+	{{if .preconditions}}
+		if _, err = executeAction(
+			ctx,
+			{{.preconditions}},
+		); err != nil {
+			return
+		}
+	{{end}}
+
+	filter := values.Get("$filter").(*api.Filter)
+
+	filter.DB = "{{.dbName}}"
+	filter.Collection = "{{.collectionName}}"
+	filter.Patchs = patchs.Patch()
+
+	if _, err = models.Api.PatchOne(filter); err != nil {
+
+		err.Details(&errs.Detail{
+			Dominio:      "",
+			Reason:       "",
+			Location:     "middleware.path",
+			LocationType: "middleware.operation/{{.entity}}",
+		})
+
+		return
+	}
+
+	filter.Entity = &models.{{.entity}}{}
+
+	if _, err = models.Api.FindOne(filter); err != nil {
+
+		err.Details(&errs.Detail{
+			Dominio:      "",
+			Reason:       "",
+			Location:     "middleware.findOne",
+			LocationType: "middleware.operation/{{.entity}}",
+		})
+
+		return
+	}
+	{{if .hasUpdateRelation}} // Cria uma thread que executa a atualizaca das referências.
+	go func() {
+		UpdateRelation{{.entity}}(filter)
+	}()	{{end}}
+
+	{{if .beforeResponse}}
+		if resp, err = executeAction(
+			ctx,
+			{{.beforeResponse}},
+		); err != nil || resp != nil {
+		return
+	}
+	{{end}}
+	resp = filter.Entity
+	return`)
+
+	if patchStmtsErr != nil {
+		panic(patchStmtsErr)
+	}
+
+}
+
+var (
+	GenPatchStmts = &Middleware{
+		Id:   "implement",
+		Type: "method",
+		Fn: func(ctx *MiddlewareContext) error {
+			var (
+				project          = ctx.Project
+				method           = ctx.Method
+				dbName           = project.GetEntityDB(method.Entity)
+				collectionName   = project.GetCollection(method.Entity)
+				dependenceMethod = BASE_HAS_DEPENDE + method.Entity
+				beforeSend       = method.Hook("beforeSend")
+				beforePersist    = method.Hook("beforePersist")
+				context          = map[string]interface{}{
+					"dbName":            dbName,
+					"collectionName":    collectionName,
+					"entity":            method.Entity,
+					"dependenceMethod":  dependenceMethod,
+					"beforePersist":     beforePersist,
+					"beforeSend":        beforeSend,
+					"hasUpdateRelation": SR.Has(method.Entity),
+					"preconditions":     parseMethodActions(method.Preconditions),
+					"beforeResponse":    parseMethodActions(method.BeforeResponse),
+				}
+			)
+
+			if beforePersist {
+				generateHookCall(project, method, "beforePersist")
+			}
+			if beforeSend {
+				generateHookCall(project, method, "beforeSend")
+			}
+
+			// Nome do metodo que verifica se a entidade tem dependencias
+			out, _ := TemplateToString(patchStmtsTmpl, context)
+			afterMethod := ctx.Statement.Block(G.Id(out)).Line()
+
+			if body, err := createUpdateRelationMethod(method, context); err == nil {
+				afterMethod.Id(body)
+			}
+
+			return nil
+		},
+	}
+)

+ 225 - 0
translate/got/middleware_post.go

@@ -0,0 +1,225 @@
+package got
+
+import (
+	"strings"
+	"text/template"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+)
+
+var (
+	createStmtsTmpl *template.Template
+	createStmtsErr  error
+)
+
+func init() {
+
+	createStmtsTmpl, createStmtsErr = ParseTemplate(`
+	var (
+		entity = models.New{{.entity}}()
+	)
+
+	if err = api.ReadJson(ctx, entity); err != nil {
+		return
+	}
+
+	values := ctx.Values()
+
+	user := values.Get("$user.ref").(*models.UserReference)
+	entity.CreatedBy = user
+	entity.UpdatedBy = user
+	entity.SetMode("create")
+
+	values.Set("entity", entity)
+	
+	if err = api.Validate(entity); err != nil {
+		return
+	}
+
+	{{if .preconditions}}
+		if _, err = executeAction(
+			ctx,
+			{{.preconditions}},
+		); err != nil {
+			return
+		}
+	{{end}}
+
+	filter := values.Get("$filter").(*api.Filter)
+
+	filter.DB = "{{.dbName}}"
+	filter.Collection = "{{.collectionName}}"
+	filter.Entity = entity
+	
+	if _, err = models.Api.InsertOne(filter); err != nil {
+		return
+	}
+	
+	{{if .beforeResponse}}
+	if resp, err = executeAction(
+		ctx,
+		{{.beforeResponse}},
+	); err != nil || resp != nil {
+		return
+	}
+	{{end}}
+	
+	resp = entity
+
+	return`)
+
+	if createStmtsErr != nil {
+		panic(createStmtsErr)
+	}
+}
+
+var (
+	GenCreateStmts = &Middleware{
+		Id:   "post",
+		Type: "method",
+		Fn: func(ctx *MiddlewareContext) error {
+			var (
+				project          = ctx.Project
+				method           = ctx.Method
+				dbName           = project.GetEntityDB(method.Entity)
+				collectionName   = project.GetCollection(method.Entity)
+				dependenceMethod = BASE_HAS_DEPENDE + method.Entity
+				beforeSend       = method.Hook("beforeSend")
+				beforePersist    = method.Hook("beforePersist")
+				context          = map[string]interface{}{
+					"dbName":           dbName,
+					"collectionName":   collectionName,
+					"entity":           method.Entity,
+					"dependenceMethod": dependenceMethod,
+					"beforePersist":    beforePersist,
+					"beforeSend":       beforeSend,
+					"methodName":       strings.Title(method.ID),
+					"preconditions":    parseMethodActions(method.Preconditions),
+					"beforeResponse":   parseMethodActions(method.BeforeResponse),
+				}
+			)
+			// Nome do metodo que verifica se a entidade tem dependencias
+			out, _ := TemplateToString(createStmtsTmpl, context)
+			ctx.Statement.Block(G.Id(out)).Line()
+
+			return nil
+		},
+	}
+)
+
+// var (
+// 	project        = ctx.Project
+// 	method         = ctx.Method
+// 	responseEntity = GenericPart.ReplaceAllString(method.Response, "")
+// )
+
+// if responseEntity == "" {
+// 	panic("Modelo de resposta não definido")
+// }
+
+// ctx.Statement.Block(
+// 	// Declaracao das variaveis
+// 	G.Var().Defs(
+// 		// G.Id("err").Op("*").Qual(API_ERROR, "Error"),
+// 		G.Id("entity").Op("=").Id("New"+method.Entity).Call(),
+// 	).Line(),
+
+// 	// Fazendo o parse do body
+// 	G.If(
+// 		G.Id("err").Op("=").Qual(API_URL, "ReadJson").Call(G.Id("ctx"), G.Id("entity")),
+// 		G.Id("err").Op("!=").Id("nil"),
+// 	).Block(
+// 		// G.Id("api").Dot("Falha").Call(
+// 		// 	G.Id("ctx"),
+// 		// 	G.Id("api").Dot("ErrGeneral"),
+// 		// 	G.Id("err").Dot("Error").Call(),
+// 		// 	G.Nil(),
+// 		// ),
+// 		// G.Return(G.Id("err")),
+// 		G.Return(),
+// 	).Line(),
+
+// 	G.Id("values").Op(":=").Id("ctx").Dot("Values()").Line(),
+
+// 	G.Do(func(part *G.Statement) {
+// 		entity := ctx.Project.GetSchema(method.Entity)
+// 		user := false
+// 		for _, prop := range entity.Properties {
+// 			if def, ok := prop.Autogenerate["create_action"]; ok {
+// 				switch def.Type {
+// 				case "user":
+// 					if !user {
+// 						part.Add(G.Id(`user := values.Get("$user.ref").(*UserReference)`).Line())
+// 						user = true
+// 					}
+// 					part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Id("user").Line())
+// 				case "now":
+// 					part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Qual("time", "Now").Call().Id(".Unix()").Line())
+// 				case "fn":
+// 					part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Id(def.Args[0]).Call().Line())
+// 				default:
+// 					// fmt.Println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
+// 					// spew.Dump(def)
+// 					// parts := strings.Split(def, "#")
+// 					// part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Qual("time", "Now").Call().Id(".Unix()").Line())
+// 				}
+// 			}
+// 		}
+// 	}).Line(),
+
+// 	G.Do(func(s *G.Statement) {
+// 		entity := ctx.Project.EntityDesc(method.Entity)
+// 		if entity != nil && entity.HasMode {
+// 			s.Add(G.Id(`entity.SetMode("create")`))
+// 		}
+// 	}),
+// 	G.If(
+// 		G.Id("err").Op("=").Qual(API_URL, "Validate").Call(G.Id("entity")),
+// 		G.Id("err").Op("!=").Id("nil"),
+// 	).Block(
+// 		// G.Return(G.Id("err")),
+// 		G.Return(),
+// 	).Line(),
+
+// 	// Carrega o filtro do contexto para adicionar a entidade
+// 	G.Id("filter").Op(":=").Id("values").Dot("Get").Call(
+// 		G.Lit("$filter"),
+// 	).Assert(G.Op("*").Qual(API_URL, "Filter")).Line(),
+
+// 	// Adiciona a entidade
+// 	G.Id("filter").Dot("DB").Op("=").Lit(ctx.Project.GetEntityDB(method.Entity)),
+// 	G.Id("filter").Dot("Collection").Op("=").Lit(ctx.Project.GetCollection(method.Entity)),
+// 	G.Id("filter").Dot("Entity").Op("=").Id("entity"),
+// 	generateHookCall(project, method, "beforePersist"),
+// 	// Inseri a entidade na coleção e verifica se a operação ocorreu com exito
+// 	G.If(
+// 		G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("InsertOne").Call(
+// 			G.Id("filter"),
+// 		),
+// 		G.Id("err").Op("!=").Id("nil"),
+// 	).Block(
+// 		// G.Id("api").Dot("Falha").Call(
+// 		// 	G.Id("ctx"),
+// 		// 	G.Id("api").Dot("ErrGeneral"),
+// 		// 	G.Id("err").Dot("Error").Call(),
+// 		// 	G.Nil(),
+// 		// ),
+// 		// bson.IsObjectIdHex(m.Id.Hex())
+// 		// G.Return(G.Id("err")),
+// 		G.Return(),
+// 	),
+
+// 	// Envia a resposta pro usuario em caso de sucesso
+// 	// G.Line().Id("ctx").Dot("JSON").Call(G.Qual(APIC, "Map").Values(G.Dict{
+// 	// 	G.Lit("entity"): G.Id("entity"),
+// 	// })),
+// 	// G.Line().Id("resp").Dot("Entity").Op("=").Id("entity"),
+// 	// G.Line().Id("resp").Op("=").Id(responseEntity).Values(G.Dict{
+// 	// 	G.Id("Entity"): G.Id("entity"),
+// 	// }),
+// 	G.Line().Id("resp").Op("=").Id("entity"),
+// 	generateHookCall(project, method, "beforeSend"),
+// 	// G.Return(G.Nil()),
+// 	G.Return(),
+// )

+ 431 - 0
translate/got/middleware_put.go

@@ -0,0 +1,431 @@
+package got
+
+import (
+	"text/template"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+)
+
+var (
+	updateStmtsTmpl *template.Template
+	updateStmtsErr  error
+)
+
+func init() {
+
+	updateStmtsTmpl, updateStmtsErr = ParseTemplate(`
+	var (
+		entity = &models.{{.entity}}Input{}
+	)
+
+	if err = api.ReadJson(ctx, entity); err != nil {
+		return
+	}
+	
+	entity.SetMode("update")
+
+	if err = api.Validate(entity); err != nil {
+		return
+	}
+
+	values := ctx.Values()
+	entity.LastUpdate = api.NowUnix()
+	user := values.Get("$user.ref").(*models.UserReference)
+	entity.UpdatedBy = user
+	values.Set("entity", entity)
+	
+	{{if .preconditions}}
+		if _, err = executeAction(
+			ctx,
+			{{.preconditions}},
+		); err != nil {
+			return
+		}
+	{{end}}
+
+	filter := values.Get("$filter").(*api.Filter)
+	filter.DB = "{{.dbName}}"
+	filter.Collection = "{{.collectionName}}"
+	filter.Entity = entity
+
+	{{if .beforePersist}} if err = BeforePersist{{.entity}}Update(ctx, resp); err != nil {
+		return
+	}{{end}}
+
+	if _, err = models.Api.UpdateOne(filter); err != nil {
+		return
+	}
+
+	{{if .hasUpdateRelation}} // Cria uma thread que executa a atualizaca das referências.
+	go func() {
+		UpdateRelation{{.entity}}(filter)
+	}()	{{end}}
+
+	{{if .beforeResponse}}
+	if resp, err = executeAction(
+		ctx,
+		{{.beforeResponse}},
+	); err != nil || resp != nil {
+		return
+	}
+	{{end}}
+	
+	resp = entity
+	return`)
+
+	if updateStmtsErr != nil {
+		panic(updateStmtsErr)
+	}
+}
+
+var (
+	GenUpdateStmts = &Middleware{
+		Id:   "implement",
+		Type: "method",
+		Fn: func(ctx *MiddlewareContext) error {
+			var (
+				project          = ctx.Project
+				method           = ctx.Method
+				dbName           = project.GetEntityDB(method.Entity)
+				collectionName   = project.GetCollection(method.Entity)
+				dependenceMethod = BASE_HAS_DEPENDE + method.Entity
+				beforeSend       = method.Hook("beforeSend")
+				beforePersist    = method.Hook("beforePersist")
+				relations        = []map[string]interface{}{}
+				context          = map[string]interface{}{
+					"dbName":            dbName,
+					"collectionName":    collectionName,
+					"entity":            method.Entity,
+					"dependenceMethod":  dependenceMethod,
+					"beforeSend":        beforeSend,
+					"beforePersist":     beforePersist,
+					"relations":         &relations,
+					"hasUpdateRelation": SR.Has(method.Entity),
+					"preconditions":     parseMethodActions(method.Preconditions),
+					"beforeResponse":    parseMethodActions(method.BeforeResponse),
+				}
+			)
+
+			generateHookCall(project, method, "beforePersist")
+
+			generateHookCall(project, method, "beforeSend")
+
+			// Nome do metodo que verifica se a entidade tem dependencias
+			out, _ := TemplateToString(updateStmtsTmpl, context)
+			afterMethod := ctx.Statement.Block(G.Id(out)).Line()
+
+			if body, err := createUpdateRelationMethod(method, context); err == nil {
+				afterMethod.Id(body)
+			}
+
+			return nil
+		},
+	}
+)
+
+// updateRelationStmtsTmpl, updateStmtsErr = ParseTemplate(`
+// func UpdateRelation{{.entity}}(options *api.Filter) {
+// 	var (
+// 		filter *api.Filter
+// 		err    *errs.Error
+// 		entity = &{{.entity}}Reference{}
+// 		wg     = sync.WaitGroup{}
+// 	)
+
+// 	options.Entity = entity
+
+// 	if _, err = models.Api.FindOne(options); err != nil {
+// 		return
+// 	}
+
+// 	entity.Id = entity.Id.(primitive.ObjectID).Hex()
+// 	{{range .relations}}
+// 	wg.Add(1)
+// 	go func(){
+// 		defer wg.Done()
+
+// 		filter = &api.Filter{
+// 			Collection: "{{.Collection}}",
+// 			DB:         "{{.DB}}",
+// 			Patchs:     &bson.A{bson.M{"$set": bson.M{"{{.path}}": entity}}},
+// 			Query:      &bson.M{"{{.attr}}._id": entity.Id},
+// 		}
+
+// 		if _, err = models.Api.PatchMany(filter); err != nil {
+// 			fmt.Println(err.Error())
+// 		}
+// 	}()
+// 	{{end}}
+
+// 	wg.Wait()
+// }`)
+
+// if updateStmtsErr != nil {
+// 	panic(updateStmtsErr)
+// }
+// if hasUpdateRelation && !updateRelationMethodDefined {
+// 	relationFunctionDefined[updRelation] = true
+// 	for _, relation := range SR.Get(method.Entity) {
+
+// 		if relation.DB == "" || relation.Collection == "" {
+// 			api.LogWarning(0, fmt.Sprintf("Relation DB or Collection can't be empty! %s -> %s", relation.Target, relation.Source))
+// 			continue
+// 		}
+
+// 		relations = append(relations, map[string]interface{}{
+// 			"DB":         relation.DB,
+// 			"Collection": relation.Collection,
+// 			"attr":       relation.Attr,
+// 			"path":       fmt.Sprintf("%s%s", relation.Attr, indexMap[relation.IsArray]),
+// 		})
+
+// 	}
+
+// 	out, _ = TemplateToString(updateRelationStmtsTmpl, context)
+// 	afterMethod.Id(out)
+// }
+
+// j = G.Id("filter").Op("=").Op("&").Qual(API_URL, "Filter").Values(G.Dict{
+// 	// Especifica a collection
+// 	G.Id("DB"):         G.Lit(relation.DB),
+// 	G.Id("Collection"): G.Lit(relation.Collection),
+
+// 	// Especifica a query
+// 	G.Id("Query"): G.Op("&").Qual(BSON, "M").Values(G.Dict{
+// 		G.Lit(attrId + "._id"): G.Id("f").Dot("Id").Dot("Hex").Call(),
+// 	}),
+
+// 	// G.Id("Entity"): G.Qual(BSON, "M").Values(G.Dict{
+// 	// 	G.Lit("$set"): G.Qual(BSON, "M").Values(G.Dict{
+// 	// 		G.Lit(attrId + index): G.Id("f").Dot("Entity"),
+// 	// 	}),
+// 	// }),
+// 	G.Id("Patchs"): G.Op("&").Qual(BSON, "A").Values(
+// 		G.Qual(BSON, "M").Values(G.Dict{
+// 			G.Lit("$set"): G.Qual(BSON, "M").Values(G.Dict{
+// 				G.Lit(attrId + index): G.Id("f").Dot("Entity"),
+// 			}),
+// 		}),
+// 	),
+// }).Line().If(
+// G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("PatchMany").Call(
+// G.Id("filter"),
+// ),
+// G.Id("err").Op("!=").Id("nil"),
+// ).Block(
+// G.Qual("fmt", "Println").Call(G.Id("err").Dot("Error").Call()),
+// ).Line().Line()
+
+// k.Add(j)
+// .Do(func(s *G.Statement) {
+
+// 	_, found := relationFunctionDefined[updRelation]
+// 	if !createUpdateRelation || found {
+// 		return
+// 	}
+
+// 	s.Comment(updRelation + " atualiza as referências da entidade " + method.Entity).Line()
+
+// 	s.Func().Id(updRelation).Params(
+// 		G.Id("f").Op("*").Qual(API_URL, "Filter"),
+// 	).Block(
+// 		G.Var().Defs(
+// 			G.Id("filter").Op("*").Qual(API_URL, "Filter"),
+// 			G.Id("err").Op("*").Qual(API_ERROR, "Error"),
+// 		),
+// 		// atualiza o filtro para a entidade de referencia
+// 		G.Id("f").Dot("Entity").Op("=").Op("&").Id(method.Entity+"Reference").Values(),
+
+// 		// Carrega os dados do banco para atualizar nas entidades relacionadas
+// 		G.If(
+// 			G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("FindOne").Call(
+// 				G.Id("f"),
+// 			),
+// 			G.Id("err").Op("!=").Id("nil"),
+// 		).Block(
+// 			G.Return(),
+// 		),
+// 		G.Do(func(k *G.Statement) { // Adiciona as regras de atualizacao
+// 			var (
+// 				j     *G.Statement
+// 				index string
+// 			)
+
+// 			for _, relation := range SR.Get(method.Entity) {
+// 				if relation.DB == "" || relation.Collection == "" {
+// 					api.LogWarning(0, fmt.Sprintf("Relation DB or Collection can't be empty! %s -> %s", relation.Target, relation.Source))
+// 					continue
+// 				}
+
+// 				// spew.Dump(relation)
+// 				// panic("sss")
+// 				// e := project.GetSchema(relation)
+// 				attrId := relation.Attr
+// 				if relation.IsArray {
+// 					index = ".$"
+// 				} else {
+// 					index = ""
+// 				}
+
+// 				j = G.Id("filter").Op("=").Op("&").Qual(API_URL, "Filter").Values(G.Dict{
+// 					// Especifica a collection
+// 					G.Id("DB"):         G.Lit(relation.DB),
+// 					G.Id("Collection"): G.Lit(relation.Collection),
+
+// 					// Especifica a query
+// 					G.Id("Query"): G.Op("&").Qual(BSON, "M").Values(G.Dict{
+// 						G.Lit(attrId + "._id"): G.Id("f").Dot("Id").Dot("Hex").Call(),
+// 					}),
+
+// 					// G.Id("Entity"): G.Qual(BSON, "M").Values(G.Dict{
+// 					// 	G.Lit("$set"): G.Qual(BSON, "M").Values(G.Dict{
+// 					// 		G.Lit(attrId + index): G.Id("f").Dot("Entity"),
+// 					// 	}),
+// 					// }),
+// 					G.Id("Patchs"): G.Op("&").Qual(BSON, "A").Values(
+// 						G.Qual(BSON, "M").Values(G.Dict{
+// 							G.Lit("$set"): G.Qual(BSON, "M").Values(G.Dict{
+// 								G.Lit(attrId + index): G.Id("f").Dot("Entity"),
+// 							}),
+// 						}),
+// 					),
+// 				}).Line().If(
+// 					G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("PatchMany").Call(
+// 						G.Id("filter"),
+// 					),
+// 					G.Id("err").Op("!=").Id("nil"),
+// 				).Block(
+// 					G.Qual("fmt", "Println").Call(G.Id("err").Dot("Error").Call()),
+// 				).Line().Line()
+
+// 				k.Add(j)
+// 			}
+// 		}),
+// 	)
+// })
+
+// var (
+// 	// UpdateRelationRotine *G.Statement
+// 	method               = ctx.Method
+// 	project              = ctx.Project
+// 	updRelation          = UPDATE_RELATION + method.Entity
+// 	createUpdateRelation = false
+// 	entity               = method.Request
+// 	responseEntity       = GenericPart.ReplaceAllString(method.Response, "")
+// )
+// if entity == "" {
+// 	panic("Modelo de request não definido em UpdateStmt!")
+// }
+// if responseEntity == "" {
+// 	panic("Modelo de response não definido!")
+// }
+
+// ctx.Statement.Block(
+// 	// Declaracao das variaveis
+// 	G.Var().Defs(
+// 		// G.Id("err").Op("*").Qual(API_ERROR, "Error"),
+// 		G.Id("entity").Op("=").Op("&").Id(entity).Values(),
+// 	).Line(),
+// 	// Fazendo o parse do body
+// 	G.If(
+// 		G.Id("err").Op("=").Qual(API_URL, "ReadJson").Call(G.Id("ctx"), G.Id("entity")),
+// 		G.Id("err").Op("!=").Id("nil"),
+// 	).Block(
+// 		// G.Id("api").Dot("Falha").Call(
+// 		// 	G.Id("ctx"),
+// 		// 	G.Id("api").Dot("ErrGeneral"),
+// 		// 	G.Id("err").Dot("Error").Call(),
+// 		// 	G.Nil(),
+// 		// ),
+// 		// G.Return(G.Id("err")),
+// 		G.Return(),
+// 	),
+// 	G.Do(func(s *G.Statement) {
+// 		entity := ctx.Project.EntityDesc(method.Entity)
+// 		if entity != nil && entity.HasMode {
+// 			s.Add(G.Id(`entity.SetMode("create")`))
+// 		}
+// 	}),
+// 	G.If(
+// 		G.Id("err").Op("=").Qual(API_URL, "Validate").Call(G.Id("entity")),
+// 		G.Id("err").Op("!=").Id("nil"),
+// 	).Block(
+// 		// G.Return(G.Id("err")),
+// 		G.Return(),
+// 	).Line(),
+// 	// Captura a base de values
+// 	G.Id("values").Op(":=").Id("ctx").Dot("Values").Call(),
+
+// 	// Gera as atribuicoes de variaveis que seram atualizadas (usuario e ultimo update quando existir)
+// 	G.Do(func(part *G.Statement) {
+// 		entity := ctx.Project.GetSchema(method.Entity)
+// 		user := false
+// 		for _, prop := range entity.Properties {
+// 			if def, ok := prop.Autogenerate["update"]; ok {
+// 				switch def.Type {
+// 				case "user":
+// 					if !user {
+// 						part.Add(G.Id(`user := values.Get("$user.ref").(*UserReference)`).Line())
+// 						user = true
+// 					}
+
+// 					part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Id("user").Line())
+// 				case "now":
+// 					part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Qual("time", "Now").Call().Id(".Unix()").Line())
+// 				}
+// 			}
+// 		}
+// 	}).Line(),
+
+// 	// Carrega o filtro do contexto para adicionar a entidade
+// 	G.Id("filter").Op(":=").Id("values").Dot("Get").Call(
+// 		G.Lit("$filter"),
+// 	).Assert(G.Op("*").Qual(API_URL, "Filter")).Line(),
+
+// 	// Adiciona a entidade
+// 	G.Id("filter").Dot("DB").Op("=").Lit(ctx.Project.GetEntityDB(method.Entity)),
+// 	G.Id("filter").Dot("Collection").Op("=").Lit(ctx.Project.GetCollection(method.Entity)),
+// 	G.Id("filter").Dot("Entity").Op("=").Id("entity").Line(),
+// 	generateHookCall(project, method, "beforePersist"),
+// 	// Inseri a entidade na coleção e verifica se a operação ocorreu com exito
+// 	G.If(
+// 		G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("UpdateOne").Call(
+// 			// G.Lit(ctx.Project.GetCollection(method.Entity)),
+// 			G.Id("filter"),
+// 		),
+// 		G.Id("err").Op("!=").Id("nil"),
+// 	).Block(
+// 		// G.Id("api").Dot("Falha").Call(
+// 		// 	G.Id("ctx"),
+// 		// 	G.Id("api").Dot("ErrGeneral"),
+// 		// 	G.Id("err").Dot("Error").Call(),
+// 		// 	G.Nil(),
+// 		// ),
+// 		// bson.IsObjectIdHex(m.Id.Hex())
+// 		// G.Return(G.Id("err")),
+// 		G.Return(),
+// 	),
+// 	// Cria a rotina de atualização de relacionamentos
+// 	G.Do(func(s *G.Statement) {
+
+// 		if SR.Has(method.Entity) {
+// 			createUpdateRelation = true
+
+// 			relationFunctionDefined[updRelation] = true
+// 			s.Comment("Cria uma thread que executa a atualizaca das referências.").Line().Go().Func().Params().Block(
+// 				G.Id(updRelation).Call(G.Id("filter")),
+// 			).Call()
+
+// 		}
+// 	}),
+
+// 	// Envia a resposta pro usuario em caso de sucesso
+// 	// G.Line().Id("resp").Op("=").Id(responseEntity).Values(G.Dict{
+// 	// 	G.Id("Entity"): G.Id("entity"),
+// 	// }),
+// 	G.Line().Id("resp").Op("=").Id("entity"),
+// 	generateHookCall(project, method, "beforeSend"),
+// 	// G.Return(G.Nil()),
+// 	G.Return(),
+// ).Line()

+ 290 - 0
translate/got/middleware_undelete.go

@@ -0,0 +1,290 @@
+package got
+
+import (
+	"text/template"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+)
+
+const ()
+
+var (
+	undeleteStmtsTmpl, hasDepUndeleteStmtsTmpl *template.Template
+	undeleteStmtsErr                           error
+)
+
+func init() {
+
+	undeleteStmtsTmpl, undeleteStmtsErr = ParseTemplate(`
+	var (
+		patchs = &models.{{.entity}}Patchs{Set: &models.{{.entity}}Input{}}
+		entity = patchs.Set
+	)
+
+	values := ctx.Values()
+	values.Set("patchs", patchs)
+	values.Set("entity", entity)
+
+	user := values.Get("$user.ref").(*models.UserReference)
+	
+	entity.SetDeleted(false)
+	entity.LastUpdate = api.NowUnix()
+	entity.UpdatedBy = user
+
+	{{if .preconditions}}
+		if _, err = executeAction(
+			ctx,
+			{{.preconditions}},
+		); err != nil {
+			return
+		}
+	{{end}}
+
+	options := values.Get("$filter").(*api.Filter)
+	options.DB = "{{.dbName}}"
+	options.Collection = "{{.collectionName}}"
+	options.Patchs = patchs.Patch()
+
+	if _, err = models.Api.PatchOne(options); err != nil {
+		return
+	}
+
+	{{if .beforeResponse}}
+	if resp, err = executeAction(
+		ctx,
+		{{.beforeResponse}},
+	); err != nil || resp != nil {
+		return
+	}
+	{{end}}
+
+	resp = ""
+
+	return`)
+
+	if undeleteStmtsErr != nil {
+		panic(undeleteStmtsErr)
+	}
+}
+
+var (
+	GenUndeleteStmts = &Middleware{
+		Id:   "undelete",
+		Type: "method",
+		Fn: func(ctx *MiddlewareContext) error {
+			var (
+				project          = ctx.Project
+				method           = ctx.Method
+				dbName           = project.GetEntityDB(method.Entity)
+				collectionName   = project.GetCollection(method.Entity)
+				relations        = SR.Get(method.Entity)
+				relationCount    = len(relations)
+				dependenceMethod = BASE_HAS_DEPENDE + method.Entity
+				// createDepFunc    = SR.Has(method.Entity)
+				beforeSend    = method.Hook("beforeSend")
+				beforePersist = method.Hook("beforePersist")
+				context       = map[string]interface{}{
+					"dbName":         dbName,
+					"collectionName": collectionName,
+					"entity":         method.Entity,
+					// "createDepFunc":    createDepFunc,
+					"relationCount":    relationCount,
+					"relations":        relations,
+					"dependenceMethod": dependenceMethod,
+					"beforePersist":    beforePersist,
+					"beforeSend":       beforeSend,
+					"preconditions":    parseMethodActions(method.Preconditions),
+					"beforeResponse":   parseMethodActions(method.BeforeResponse),
+				}
+			)
+			// Nome do metodo que verifica se a entidade tem dependencias
+
+			out, _ := TemplateToString(undeleteStmtsTmpl, context)
+			ctx.Statement.Block(G.Id(out)).Line()
+
+			// if createDepFunc {
+			// 	out, _ = TemplateToString(hasDepUndeleteStmtsTmpl, context)
+			// 	afterMethod.Id(out)
+			// }
+
+			return nil
+		},
+	}
+)
+
+// import (
+// 	"fmt"
+// 	"strings"
+
+// 	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+// 	G "github.com/dave/jennifer/jen"
+// 	// "github.com/davecgh/go-spew/spew"
+// )
+
+// var (
+// 	GenUndeleteStmts = &Middleware{
+// 		Id:   "undelete",
+// 		Type: "method",
+// 		Fn: func(ctx *MiddlewareContext) error {
+
+// 			var (
+// 				// UpdateRelationRotine *G.Statement
+// 				method = ctx.Method
+// 				// updRelation          = UPDATE_RELATION + method.Entity
+// 				// createUpdateRelation = false
+// 				entity         = method.Request
+// 				responseEntity = GenericPart.ReplaceAllString(method.Response, "")
+// 			)
+// 			if entity == "" {
+// 				panic("Modelo de request não definido em UpdateStmt!")
+// 			}
+// 			if responseEntity == "" {
+// 				panic("Modelo de response não definido!")
+// 			}
+
+// 			ctx.Statement.Block(
+// 				// Declaracao das variaveis
+// 				G.Var().Defs(
+// 					// G.Id("err").Op("*").Qual(API_ERROR, "Error"),
+// 					// G.Id("patchs").Op("=").Op("&").Id(entity).Values(),
+// 					G.Id("patchs").Op("=").Op("&").Id(fmt.Sprintf("%sPatchs", method.Entity)).Values(
+// 						G.Id("Set").Op(": &").Id(entity).Values(),
+// 					),
+// 					// G.Id("patchs").Op("=").Id(entity).Values(),
+// 					G.Id("entity = patchs.Set"),
+// 					G.Do(func(s *G.Statement) {
+// 						if method.BeforePersistAction {
+// 							s.Id("fn").Func().Call().Op("*").Qual(API_ERROR, "Error")
+// 						}
+// 					}),
+// 				).Line(),
+// 				// G.Id("fmt.Println(`era o undelete`)").Line(),
+// 				// Fazendo o parse do body
+// 				// G.If(
+// 				// 	G.Id("err").Op("=").Qual(API_URL, "ReadJson").Call(G.Id("ctx"), G.Id("patchs")),
+// 				// 	G.Id("err").Op("!=").Id("nil"),
+// 				// ).Block(
+// 				// 	// G.Id("api").Dot("Falha").Call(
+// 				// 	// 	G.Id("ctx"),
+// 				// 	// 	G.Id("api").Dot("ErrGeneral"),
+// 				// 	// 	G.Id("err").Dot("Error").Call(),
+// 				// 	// 	G.Nil(),
+// 				// 	// ),
+// 				// 	// G.Return(G.Id("err")),
+// 				// 	G.Return(),
+// 				// ),
+// 				// G.Do(func(s *G.Statement) {
+// 				// 	entity := ctx.Project.EntityDesc(method.Entity)
+// 				// 	if entity != nil && entity.HasMode {
+// 				// 		s.Add(G.Id(`entity.SetMode("create")`))
+// 				// 	}
+// 				// }),
+// 				// G.If(
+// 				// 	G.Id("err").Op("=").Qual(API_URL, "Validate").Call(G.Id("entity")),
+// 				// 	G.Id("err").Op("!=").Id("nil"),
+// 				// ).Block(
+// 				// 	// G.Return(G.Id("err")),
+// 				// 	G.Return(),
+// 				// ).Line(),
+// 				// Captura a base de values
+// 				G.Id("values").Op(":=").Id("ctx").Dot("Values").Call(),
+// 				G.Id("values").Dot("Set").Call(G.Lit("patchs"), G.Id("patchs")),
+// 				G.Id("entity.SetDeleted(false)"),
+
+// 				// Gera as atribuicoes de variaveis que seram atualizadas (usuario e ultimo update quando existir)
+// 				G.Do(func(part *G.Statement) {
+// 					entity := ctx.Project.GetSchema(method.Entity)
+// 					user := false
+// 					for _, prop := range entity.Properties {
+// 						if def, ok := prop.Autogenerate["update"]; ok {
+// 							switch def.Type {
+// 							case "user":
+// 								if !user {
+// 									part.Add(G.Id(`user := values.Get("$user.ref").(*UserReference)`).Line())
+// 									user = true
+// 								}
+
+// 								part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Id("user").Line())
+// 							case "now":
+// 								part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Qual("time", "Now").Call().Id(".Unix()").Line())
+// 							}
+// 						}
+// 					}
+// 				}).Line(),
+
+// 				// Carrega o filtro do contexto para adicionar a entidade
+// 				G.Id("filter").Op(":=").Id("values").Dot("Get").Call(
+// 					G.Lit("$filter"),
+// 				).Assert(G.Op("*").Qual(API_URL, "Filter")).Line(),
+
+// 				// Adiciona a entidade
+// 				G.Id("filter").Dot("DB").Op("=").Lit(ctx.Project.GetEntityDB(method.Entity)),
+// 				G.Id("filter").Dot("Collection").Op("=").Lit(ctx.Project.GetCollection(method.Entity)),
+
+// 				G.Do(func(s *G.Statement) {
+// 					if method.BeforePersistAction {
+// 						fnName := fmt.Sprintf("Before%s%s", strings.Title(method.ID), method.Entity)
+// 						part := s.Comment("Chama uma função onde são descritas as ações executadas antes da entidade ser persistida.").Line()
+// 						part.If(
+// 							G.List(G.Id("fn"), G.Id("err")).Op("=").Id(fnName).Call(G.Id("ctx"), G.Id("patchs")),
+// 							G.Id("err").Op("!=").Id("nil"),
+// 						).Block(G.Return())
+// 					}
+// 				}),
+
+// 				G.Id("filter").Dot("Patchs").Op("=").Id("patchs.Patch()").Line(),
+
+// 				// Inseri a entidade na coleção e verifica se a operação ocorreu com exito
+// 				G.If(
+// 					G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("PatchOne").Call(
+// 						// G.Lit(ctx.Project.GetCollection(method.Entity)),
+// 						G.Id("filter"),
+// 					),
+// 					G.Id("err").Op("!=").Id("nil"),
+// 				).Block(
+// 					// G.Id("api").Dot("Falha").Call(
+// 					// 	G.Id("ctx"),
+// 					// 	G.Id("api").Dot("ErrGeneral"),
+// 					// 	G.Id("err").Dot("Error").Call(),
+// 					// 	G.Nil(),
+// 					// ),
+// 					// bson.IsObjectIdHex(m.Id.Hex())
+// 					// G.Return(G.Id("err")),
+// 					G.Return(),
+// 				),
+// 				G.Do(func(s *G.Statement) {
+// 					if method.BeforePersistAction {
+// 						part := s.Comment("Chama uma função onde são descritas as ações executadas após a entidade ser persistida.").Line()
+// 						part.If(
+// 							G.Id("err").Op("=").Id("fn").Call(),
+// 							G.Id("err").Op("!=").Id("nil"),
+// 						).Block(G.Return())
+// 					}
+// 				}),
+// 				G.Id("resp").Op("=").Lit(""),
+// 				// Cria a rotina de atualização de relacionamentos
+// 				// G.Do(func(s *G.Statement) {
+
+// 				// 	if SR.Has(method.Entity) {
+// 				// 		createUpdateRelation = true
+// 				// 		s.Comment("Cria uma thread que executa a atualizaca das referências.").Line().Go().Func().Params().Block(
+// 				// 			G.Id(updRelation).Call(G.Id("filter")),
+// 				// 		).Call()
+
+// 				// 	}
+// 				// }),
+
+// 				// Envia a resposta pro usuario em caso de sucesso
+// 				// G.Line().Id("resp").Op("=").Id(responseEntity).Values(G.Dict{
+// 				// 	G.Id("Entity"): G.Id("entity"),
+// 				// }),
+// 				// G.Line().Id("resp").Op("=").Id("entity"),
+
+// 				// G.Return(G.Nil()),
+// 				G.Return(),
+// 			).Line()
+// 			return nil
+// 		},
+// 	}
+// )

+ 393 - 0
translate/got/middleware_update_relation.go

@@ -0,0 +1,393 @@
+package got
+
+import (
+	"fmt"
+	"text/template"
+
+	api "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api"
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	// "github.com/davecgh/go-spew/spew"
+)
+
+var (
+	updateRelationStmtsTmpl *template.Template
+	updateRelationStmtsErr  error
+)
+
+func init() {
+	updateRelationStmtsTmpl, updateRelationStmtsErr = ParseTemplate(`
+	func UpdateRelation{{.entity}}(options *api.Filter) {
+		var (
+			filter *api.Filter
+			err    *errs.Error
+			entity = &models.{{.entity}}Reference{}
+			wg     = sync.WaitGroup{}
+		)
+		
+		options.Entity = entity
+		
+		if _, err = models.Api.FindOne(options); err != nil {
+			return
+		}
+		
+		entity.Id = entity.Id.(primitive.ObjectID).Hex()
+		{{range .relations}}
+		wg.Add(1)
+		go func(){
+			defer wg.Done()
+			
+			filter = &api.Filter{
+				Collection: "{{.Collection}}",
+				DB:         "{{.DB}}",
+				Patchs:     &bson.A{bson.M{"$set": bson.M{"{{.path}}": entity}}},
+				Query:      &bson.M{"{{.attr}}._id": entity.Id},
+			}
+			
+			if _, err = models.Api.PatchMany(filter); err != nil {
+				fmt.Println(err.Error())
+			}
+		}()
+		{{end}}
+
+		wg.Wait()
+	}`)
+
+	if updateRelationStmtsErr != nil {
+		panic(updateRelationStmtsErr)
+	}
+}
+
+func createUpdateRelationMethod(method *Method, context map[string]interface{}) (out string, err error) {
+	var (
+		indexMap = map[bool]string{
+			true:  ".$",
+			false: "",
+		}
+		hasUpdateRelation              = SR.Has(method.Entity)
+		relations                      = []map[string]interface{}{}
+		updRelation                    = UPDATE_RELATION + method.Entity
+		_, updateRelationMethodDefined = relationFunctionDefined[updRelation]
+	)
+
+	context["hasUpdateRelation"] = hasUpdateRelation
+	context["relations"] = &relations
+
+	if hasUpdateRelation {
+		if updateRelationMethodDefined {
+			err = fmt.Errorf("function defined")
+			return
+		}
+		relationFunctionDefined[updRelation] = true
+
+		fmt.Printf("Generate update callback for entity '%s'\n", method.Entity)
+
+		for _, relation := range SR.Get(method.Entity) {
+
+			if relation.DB == "" || relation.Collection == "" {
+				api.LogWarning(0, fmt.Sprintf("DB and Collection are required! %s -> %s", relation.Target, relation.Source))
+				continue
+			}
+			relations = append(relations, map[string]interface{}{
+				"DB":         relation.DB,
+				"Collection": relation.Collection,
+				"attr":       relation.Attr,
+				"path":       fmt.Sprintf("%s%s", relation.Attr, indexMap[relation.IsArray]),
+			})
+		}
+		out, err = TemplateToString(updateRelationStmtsTmpl, context)
+	}
+	return
+}
+
+// var (
+// 	GenUpdateStmts = &Middleware{
+// 		Id:   "implement",
+// 		Type: "method",
+// 		Fn: func(ctx *MiddlewareContext) error {
+// 			var (
+// 				project          = ctx.Project
+// 				method           = ctx.Method
+// 				dbName           = project.GetEntityDB(method.Entity)
+// 				collectionName   = project.GetCollection(method.Entity)
+// 				dependenceMethod = BASE_HAS_DEPENDE + method.Entity
+// 				updRelation      = UPDATE_RELATION + method.Entity
+// 				indexMap         = map[bool]string{
+// 					true:  ".$",
+// 					false: "",
+// 				}
+// 				beforeSend                     = method.Hook("beforeSend")
+// 				beforePersist                  = method.Hook("beforePersist")
+// 				hasUpdateRelation              = SR.Has(method.Entity)
+// 				_, updateRelationMethodDefined = relationFunctionDefined[updRelation]
+// 				relations                      = []map[string]interface{}{}
+// 				context                        = map[string]interface{}{
+// 					"dbName":            dbName,
+// 					"collectionName":    collectionName,
+// 					"entity":            method.Entity,
+// 					"dependenceMethod":  dependenceMethod,
+// 					"beforeSend":        beforeSend,
+// 					"beforePersist":     beforePersist,
+// 					"hasUpdateRelation": hasUpdateRelation,
+// 					"relations":         &relations,
+// 				}
+// 			)
+
+// 			if beforePersist {
+// 				generateHookCall(project, method, "beforePersist")
+// 			}
+// 			if beforeSend {
+// 				generateHookCall(project, method, "beforeSend")
+// 			}
+
+// 			// Nome do metodo que verifica se a entidade tem dependencias
+// 			out, _ := TemplateToString(updateStmtsTmpl, context)
+// 			afterMethod := ctx.Statement.Block(G.Id(out)).Line()
+
+// 			return nil
+// 		},
+// 	}
+// )
+
+// j = G.Id("filter").Op("=").Op("&").Qual(API_URL, "Filter").Values(G.Dict{
+// 	// Especifica a collection
+// 	G.Id("DB"):         G.Lit(relation.DB),
+// 	G.Id("Collection"): G.Lit(relation.Collection),
+
+// 	// Especifica a query
+// 	G.Id("Query"): G.Op("&").Qual(BSON, "M").Values(G.Dict{
+// 		G.Lit(attrId + "._id"): G.Id("f").Dot("Id").Dot("Hex").Call(),
+// 	}),
+
+// 	// G.Id("Entity"): G.Qual(BSON, "M").Values(G.Dict{
+// 	// 	G.Lit("$set"): G.Qual(BSON, "M").Values(G.Dict{
+// 	// 		G.Lit(attrId + index): G.Id("f").Dot("Entity"),
+// 	// 	}),
+// 	// }),
+// 	G.Id("Patchs"): G.Op("&").Qual(BSON, "A").Values(
+// 		G.Qual(BSON, "M").Values(G.Dict{
+// 			G.Lit("$set"): G.Qual(BSON, "M").Values(G.Dict{
+// 				G.Lit(attrId + index): G.Id("f").Dot("Entity"),
+// 			}),
+// 		}),
+// 	),
+// }).Line().If(
+// G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("PatchMany").Call(
+// G.Id("filter"),
+// ),
+// G.Id("err").Op("!=").Id("nil"),
+// ).Block(
+// G.Qual("fmt", "Println").Call(G.Id("err").Dot("Error").Call()),
+// ).Line().Line()
+
+// k.Add(j)
+// .Do(func(s *G.Statement) {
+
+// 	_, found := relationFunctionDefined[updRelation]
+// 	if !createUpdateRelation || found {
+// 		return
+// 	}
+
+// 	s.Comment(updRelation + " atualiza as referências da entidade " + method.Entity).Line()
+
+// 	s.Func().Id(updRelation).Params(
+// 		G.Id("f").Op("*").Qual(API_URL, "Filter"),
+// 	).Block(
+// 		G.Var().Defs(
+// 			G.Id("filter").Op("*").Qual(API_URL, "Filter"),
+// 			G.Id("err").Op("*").Qual(API_ERROR, "Error"),
+// 		),
+// 		// atualiza o filtro para a entidade de referencia
+// 		G.Id("f").Dot("Entity").Op("=").Op("&").Id(method.Entity+"Reference").Values(),
+
+// 		// Carrega os dados do banco para atualizar nas entidades relacionadas
+// 		G.If(
+// 			G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("FindOne").Call(
+// 				G.Id("f"),
+// 			),
+// 			G.Id("err").Op("!=").Id("nil"),
+// 		).Block(
+// 			G.Return(),
+// 		),
+// 		G.Do(func(k *G.Statement) { // Adiciona as regras de atualizacao
+// 			var (
+// 				j     *G.Statement
+// 				index string
+// 			)
+
+// 			for _, relation := range SR.Get(method.Entity) {
+// 				if relation.DB == "" || relation.Collection == "" {
+// 					api.LogWarning(0, fmt.Sprintf("Relation DB or Collection can't be empty! %s -> %s", relation.Target, relation.Source))
+// 					continue
+// 				}
+
+// 				// spew.Dump(relation)
+// 				// panic("sss")
+// 				// e := project.GetSchema(relation)
+// 				attrId := relation.Attr
+// 				if relation.IsArray {
+// 					index = ".$"
+// 				} else {
+// 					index = ""
+// 				}
+
+// 				j = G.Id("filter").Op("=").Op("&").Qual(API_URL, "Filter").Values(G.Dict{
+// 					// Especifica a collection
+// 					G.Id("DB"):         G.Lit(relation.DB),
+// 					G.Id("Collection"): G.Lit(relation.Collection),
+
+// 					// Especifica a query
+// 					G.Id("Query"): G.Op("&").Qual(BSON, "M").Values(G.Dict{
+// 						G.Lit(attrId + "._id"): G.Id("f").Dot("Id").Dot("Hex").Call(),
+// 					}),
+
+// 					// G.Id("Entity"): G.Qual(BSON, "M").Values(G.Dict{
+// 					// 	G.Lit("$set"): G.Qual(BSON, "M").Values(G.Dict{
+// 					// 		G.Lit(attrId + index): G.Id("f").Dot("Entity"),
+// 					// 	}),
+// 					// }),
+// 					G.Id("Patchs"): G.Op("&").Qual(BSON, "A").Values(
+// 						G.Qual(BSON, "M").Values(G.Dict{
+// 							G.Lit("$set"): G.Qual(BSON, "M").Values(G.Dict{
+// 								G.Lit(attrId + index): G.Id("f").Dot("Entity"),
+// 							}),
+// 						}),
+// 					),
+// 				}).Line().If(
+// 					G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("PatchMany").Call(
+// 						G.Id("filter"),
+// 					),
+// 					G.Id("err").Op("!=").Id("nil"),
+// 				).Block(
+// 					G.Qual("fmt", "Println").Call(G.Id("err").Dot("Error").Call()),
+// 				).Line().Line()
+
+// 				k.Add(j)
+// 			}
+// 		}),
+// 	)
+// })
+
+// var (
+// 	// UpdateRelationRotine *G.Statement
+// 	method               = ctx.Method
+// 	project              = ctx.Project
+// 	updRelation          = UPDATE_RELATION + method.Entity
+// 	createUpdateRelation = false
+// 	entity               = method.Request
+// 	responseEntity       = GenericPart.ReplaceAllString(method.Response, "")
+// )
+// if entity == "" {
+// 	panic("Modelo de request não definido em UpdateStmt!")
+// }
+// if responseEntity == "" {
+// 	panic("Modelo de response não definido!")
+// }
+
+// ctx.Statement.Block(
+// 	// Declaracao das variaveis
+// 	G.Var().Defs(
+// 		// G.Id("err").Op("*").Qual(API_ERROR, "Error"),
+// 		G.Id("entity").Op("=").Op("&").Id(entity).Values(),
+// 	).Line(),
+// 	// Fazendo o parse do body
+// 	G.If(
+// 		G.Id("err").Op("=").Qual(API_URL, "ReadJson").Call(G.Id("ctx"), G.Id("entity")),
+// 		G.Id("err").Op("!=").Id("nil"),
+// 	).Block(
+// 		// G.Id("api").Dot("Falha").Call(
+// 		// 	G.Id("ctx"),
+// 		// 	G.Id("api").Dot("ErrGeneral"),
+// 		// 	G.Id("err").Dot("Error").Call(),
+// 		// 	G.Nil(),
+// 		// ),
+// 		// G.Return(G.Id("err")),
+// 		G.Return(),
+// 	),
+// 	G.Do(func(s *G.Statement) {
+// 		entity := ctx.Project.EntityDesc(method.Entity)
+// 		if entity != nil && entity.HasMode {
+// 			s.Add(G.Id(`entity.SetMode("create")`))
+// 		}
+// 	}),
+// 	G.If(
+// 		G.Id("err").Op("=").Qual(API_URL, "Validate").Call(G.Id("entity")),
+// 		G.Id("err").Op("!=").Id("nil"),
+// 	).Block(
+// 		// G.Return(G.Id("err")),
+// 		G.Return(),
+// 	).Line(),
+// 	// Captura a base de values
+// 	G.Id("values").Op(":=").Id("ctx").Dot("Values").Call(),
+
+// 	// Gera as atribuicoes de variaveis que seram atualizadas (usuario e ultimo update quando existir)
+// 	G.Do(func(part *G.Statement) {
+// 		entity := ctx.Project.GetSchema(method.Entity)
+// 		user := false
+// 		for _, prop := range entity.Properties {
+// 			if def, ok := prop.Autogenerate["update"]; ok {
+// 				switch def.Type {
+// 				case "user":
+// 					if !user {
+// 						part.Add(G.Id(`user := values.Get("$user.ref").(*UserReference)`).Line())
+// 						user = true
+// 					}
+
+// 					part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Id("user").Line())
+// 				case "now":
+// 					part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Qual("time", "Now").Call().Id(".Unix()").Line())
+// 				}
+// 			}
+// 		}
+// 	}).Line(),
+
+// 	// Carrega o filtro do contexto para adicionar a entidade
+// 	G.Id("filter").Op(":=").Id("values").Dot("Get").Call(
+// 		G.Lit("$filter"),
+// 	).Assert(G.Op("*").Qual(API_URL, "Filter")).Line(),
+
+// 	// Adiciona a entidade
+// 	G.Id("filter").Dot("DB").Op("=").Lit(ctx.Project.GetEntityDB(method.Entity)),
+// 	G.Id("filter").Dot("Collection").Op("=").Lit(ctx.Project.GetCollection(method.Entity)),
+// 	G.Id("filter").Dot("Entity").Op("=").Id("entity").Line(),
+// 	generateHookCall(project, method, "beforePersist"),
+// 	// Inseri a entidade na coleção e verifica se a operação ocorreu com exito
+// 	G.If(
+// 		G.List(G.Id("_"), G.Id("err")).Op("=").Id("Models").Dot("UpdateOne").Call(
+// 			// G.Lit(ctx.Project.GetCollection(method.Entity)),
+// 			G.Id("filter"),
+// 		),
+// 		G.Id("err").Op("!=").Id("nil"),
+// 	).Block(
+// 		// G.Id("api").Dot("Falha").Call(
+// 		// 	G.Id("ctx"),
+// 		// 	G.Id("api").Dot("ErrGeneral"),
+// 		// 	G.Id("err").Dot("Error").Call(),
+// 		// 	G.Nil(),
+// 		// ),
+// 		// bson.IsObjectIdHex(m.Id.Hex())
+// 		// G.Return(G.Id("err")),
+// 		G.Return(),
+// 	),
+// 	// Cria a rotina de atualização de relacionamentos
+// 	G.Do(func(s *G.Statement) {
+
+// 		if SR.Has(method.Entity) {
+// 			createUpdateRelation = true
+
+// 			relationFunctionDefined[updRelation] = true
+// 			s.Comment("Cria uma thread que executa a atualizaca das referências.").Line().Go().Func().Params().Block(
+// 				G.Id(updRelation).Call(G.Id("filter")),
+// 			).Call()
+
+// 		}
+// 	}),
+
+// 	// Envia a resposta pro usuario em caso de sucesso
+// 	// G.Line().Id("resp").Op("=").Id(responseEntity).Values(G.Dict{
+// 	// 	G.Id("Entity"): G.Id("entity"),
+// 	// }),
+// 	G.Line().Id("resp").Op("=").Id("entity"),
+// 	generateHookCall(project, method, "beforeSend"),
+// 	// G.Return(G.Nil()),
+// 	G.Return(),
+// ).Line()

+ 42 - 0
translate/got/middlewares.go

@@ -0,0 +1,42 @@
+package got
+
+import (
+	"fmt"
+	"strings"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+)
+
+func GenMiddlewares(p *Project) (err error) {
+	for _, middle := range Middlewares {
+		if middle.Type == "middleware" {
+
+			if err = CreateMiddleware(p, middle); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func CreateMiddleware(p *Project, middle *Middleware) error {
+
+	var (
+		err  error
+		file = G.NewFile(p.Package)
+		ctx  = &MiddlewareContext{
+			Project:    p,
+			Middleware: middle,
+			Statement: file.Func().Id(fmt.Sprintf("%sMiddleware", strings.Title(middle.Id))).Params(
+				G.Id("ctx").Qual(IRIS_CTX, "Context"),
+			),
+		}
+	)
+
+	if err = middle.Fn(ctx); err != nil {
+		return err
+	}
+
+	return Write(fmt.Sprintf("%s/%s/middleware_%s.go", p.OutPath, p.Package, middle.Id), file)
+}

+ 38 - 0
translate/got/params.go

@@ -0,0 +1,38 @@
+package got
+
+import (
+	"encoding/json"
+	"fmt"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+)
+
+func createParamsFile(p *Project) (err error) {
+	var (
+		file = G.NewFile(p.Package)
+		out  []byte
+	)
+
+	if out, err = json.Marshal(p.Resource.CommonParams); err != nil {
+		return
+	}
+
+	input := fmt.Sprintf("`%s`", string(out))
+
+	file.Id(`
+	
+	var (
+		UserRequestParams = map[string]*`).Qual(CODE_GEN_V2_COMMON, "Parameter").Id(fmt.Sprintf(`{}
+	)
+
+	func init(){
+		input := %s
+
+	`, input)).Id(`if err :=`).Qual("encoding/json", "Unmarshal").Id(`([]byte(input),&UserRequestParams); err != nil {
+			panic(err)
+		}
+	}`)
+	// `).Id(string(out)).Id(`
+	return Write(fmt.Sprintf("%s/%s/params.go", p.OutPath, p.Package), file)
+}

+ 656 - 0
translate/got/resources.go

@@ -0,0 +1,656 @@
+package got
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"strings"
+	"sync"
+	"time"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	G "github.com/dave/jennifer/jen"
+	"github.com/davecgh/go-spew/spew"
+)
+
+type Middleware struct {
+	Id   string                             `json:"id"`
+	Type string                             `json:"type"`
+	Fn   func(ctx *MiddlewareContext) error `json:"-"`
+}
+
+type MiddlewareContext struct {
+	Project    *Project
+	Method     *Method
+	Statement  *G.Statement
+	Middleware *Middleware
+	File       *G.File
+}
+
+var (
+	Middlewares = map[string]*Middleware{
+		"post":      GenCreateStmts,
+		"put":       GenUpdateStmts,
+		"patch":     GenPatchStmts,
+		"delete":    GenDeleteStmts,
+		"get_one":   GenGetStmtsOne,
+		"filter":    GenGetFilter,
+		"get_list":  GenGetStmtsList,
+		"undelete":  GenUndeleteStmts,
+		"implement": GenImplement,
+	}
+	ResourceWG = sync.WaitGroup{}
+)
+
+func InitFile(file *G.File, resource *Resource, p *Project) {
+	now := time.Now().UnixNano()
+
+	// file.ImportAlias(
+	// file.ImportName(
+	// 	fmt.Sprintf("%s/%s/models", p.Custom["go.package.repository"].(string), p.Package),
+	// 	"models",
+	// )
+
+	if _, defined := p.Custom["go.package.repository"]; !defined {
+		panic("go.package.repository not defined in project.json")
+	}
+
+	file.Var().Defs(
+		G.Id(fmt.Sprintf("_%s_bson_%d_ *", resource.Entity, now)).Qual(BSON, "M"),
+		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"),
+		G.Id(fmt.Sprintf("_%s_models_%d_ *", resource.Entity, now)).Qual(
+			fmt.Sprintf("%s/build/%s/models", p.Custom["go.package.repository"].(string), p.Package),
+			"Entity",
+		),
+		G.Id(fmt.Sprintf("_%s_actions_%d_ *", resource.Entity, now)).Qual(
+			fmt.Sprintf("%s/build/%s/actions", p.Custom["go.package.repository"].(string), p.Package),
+			"Action",
+		),
+	)
+
+	file.Func().Id("init").Params().BlockFunc(func(s *G.Group) {
+		for _, v := range resource.Formats {
+			s.Add(G.Id("FormatSelection").Index(G.Lit(resource.ID + "." + v.Id)).Op("=").Lit(v.Fields))
+		}
+	})
+}
+
+func CreateDummy(p *Project, resource *Resource, method *Method) error {
+	// Verifica se existe um arquivo na pasta de include.
+	// Caso o arquivo não exista um novo arquivo é criado.
+
+	outputfile := fmt.Sprintf("%s/include/go/%s_%s.go", CurrentDirectory, strings.ToLower(resource.ID), strings.ToLower(method.ID))
+
+	if _, err := os.Stat(outputfile); os.IsNotExist(err) {
+
+		file := G.NewFile(p.Package)
+		file.Comment(method.Description)
+
+		if o, err := json.MarshalIndent(method, "", "    "); err == nil {
+			file.Comment(string(o))
+		}
+
+		file.Func().Params(
+			G.Id("t").Op("*").Id(ResourceStructId(resource)),
+		).Id(method.ID).Params(
+			G.Id("ctx").Qual(IRIS_CTX, "Context"),
+		).Params(
+			G.Id("resp").Interface(),
+			G.Id("err").Op("*").Qual(API_ERROR, "Error"),
+		).Block(G.Return())
+
+		// if err = Write(path, file); err != nil {
+		// 	return err
+		// }
+		return Write(outputfile, file)
+	}
+	return nil
+}
+
+func GenResources(p *Project) (err error) {
+	var (
+		file *G.File
+		path string
+	)
+
+	if err = generateActionCommonFile(p); err != nil {
+		return
+	}
+
+	for _, resource := range p.Resources {
+
+		// Registra os campos retornados nos formatos do metodo list
+		// FormatMap = map[string]string{}
+		// Cria um novo arquivo para cada recurso
+		file = G.NewFile(p.Package)
+
+		InitFile(file, resource, p)
+
+		// Inseri os comentarios de cada recurso no inicio do arquivo
+		file.Comment(resource.Description).Line()
+		// Define um tipo para registrar os metodos da api
+		file.Type().Id(ResourceStructId(resource)).Struct().Line()
+
+		for _, method := range resource.Methods {
+			switch method.Template {
+			case "", "implement":
+				CreateDummy(p, resource, method)
+			default:
+				GenMethod(p, file, resource, method)
+			}
+		}
+
+		// file.Var().DefsFunc(func(s *G.Group) {
+		// 	values := G.Dict{}
+
+		// 	for k, v := range FormatMap {
+		// 		values[G.Lit(k)] = G.Lit(v)
+		// 	}
+
+		// 	s.Add(
+		// 		G.Id("Format" + resource.Entity).Op("=").Map(G.String()).String().Values(values),
+		// 	)
+		// })
+
+		file.Func().Params(
+			G.Id("t").Op("*").Id(ResourceStructId((resource))),
+		).Id("Fields").Params(
+			G.Id("filter").Op("*").Qual(API_URL, "Filter"),
+		).Params(
+			G.Id("err").Op("*").Qual(API_ERROR, "Error"),
+		).Block(
+			G.Id(`
+
+			if filter.Fields != nil {
+				return
+			}
+
+			if filter.Format == "full" {
+				filter.Fields = nil
+				return
+			}
+
+			if fields, has := FormatSelection[`).Lit(fmt.Sprintf("%s.", resource.ID)).Id(`+filter.Format]; has {
+				filter.Fields = api.MgoFields(fields)
+			} else {
+
+				err = errs.InvalidArgument().Details(&errs.Detail{
+					Message: `).Qual("fmt", "Sprintf").Id(`("Invalid value for projection '%s'",filter.Format),
+				})
+			}
+			return
+			`),
+			// G.If(
+			// 	// G.List(G.Id("fields"), G.Id("has")).Op(":=").Id("FormatSelection"+resource.Entity).Index(G.Id("filter").Dot("Format")),
+			// 	G.List(G.Id("fields"), G.Id("has")).Op(":=").Id("FormatSelection").Index(
+			// 		G.Lit(resource.ID+".").Op("+").Id("filter").Dot("Format"),
+			// 	),
+			// 	G.Id("has"),
+			// ).Block(
+			// 	G.Id("filter").Dot("Fields").Op("=").Id("api").Dot("MgoFields").Call(G.Id("fields")),
+			// ).Else().Block(
+			// 	G.Id("err").Op("=").Qual(API_URL, "Error").Call(
+			// 		G.Qual(API_URL, "ERR_INVALID_PARAM_VALUE"),
+			// 		G.Lit("Invalid value for %s"),
+			// 	),
+			// 	// .Dot("Add").Call(
+			// 	// 	G.Op("&").Qual(API_URL, "ErrDescription").Values(G.Dict{
+			// 	// 		G.Id("Message"): G.Lit(""),
+			// 	// 	}),
+			// 	// ),
+			// ),
+		)
+
+		// Fim do loop de methods
+
+		// GenCall(f, resource)
+
+		path = fmt.Sprintf("%s/%s/api_%s_gen.go", p.OutPath, p.Package, strings.ToLower(resource.ID))
+
+		if err = Write(path, file); err != nil {
+			return err
+		}
+	}
+	// Fim do loop de recursos
+	ResourceWG.Add(2)
+
+	go GenIndexApi(p)
+
+	ResourceWG.Wait()
+	return nil
+}
+
+func GenQueries(p *Project, resourcesIdMap map[string]bool) {
+	defer func() { ResourceWG.Done() }()
+
+	if len(p.Queries.Queries) == 0 {
+		return
+	}
+
+	file := G.NewFile(p.Package)
+
+	queries := func(s *G.Statement) {
+		var (
+			query string
+			found bool
+		)
+
+		for key, _ := range resourcesIdMap {
+			if query, found = p.Queries.Queries[fmt.Sprintf("go.%s", key)]; !found {
+				query = "{}"
+			}
+			s.Add(G.Qual(API_URL, "RegisterQuery(").Lit(key).Id(",").Lit(query).Id(")").Line())
+		}
+	}
+
+	file.Id(`
+
+	func init() {`).Do(queries).Id(`}`)
+
+	if err := Write(fmt.Sprintf("%s/%s/%s_gen.go", p.OutPath, p.Package, "queries"), file); err != nil {
+		panic(err)
+	}
+}
+
+func GenIndexApi(p *Project) error {
+	defer func() { ResourceWG.Done() }()
+	var (
+		stmt           *G.Statement
+		params         string
+		statments      G.Statement
+		idString       string
+		middlewares    []string
+		Index          = G.NewFile(p.Package)
+		RequestParams  = G.Id(`RequestParams := `).Qual(CODE_GEN_V2_COMMON, "RequestParams")
+		jwt            = G.Id(`JWT := `).Qual(CODE_GEN_V2_AUTHORIZATION, "Handler")
+		addJwt         = false
+		queryIndexsMap = map[string]bool{}
+		callActionId   = "apply"
+	)
+
+	statments = append(statments, G.Id(fmt.Sprintf("%s := ", callActionId)).Qual(API_URL, "CallAction"))
+	statments = append(statments, RequestParams)
+
+	// statments = append(statments)
+
+	// Inicializa o mapa de filtros da api
+	Index.Id(`
+		var (
+			filtersApiReference = map[string]*`).Qual(CODE_GEN_V2_COMMON, "ApiFilter").Id(`{}
+			FormatSelection = map[string]string{}
+		)
+		func init(){
+			var (
+				entity *common.ApiFilter
+				files []`).Qual("os", "FileInfo").Id(`
+				path  = "./filters"
+				err   error
+			)
+
+			files, _ = `).Qual(CODE_GEN_V2_COMMON, "GetFiles").Id(`(path)
+
+			for _, file := range files {
+				if !file.IsDir() {
+					entity = &common.ApiFilter{}  
+					if err = common.ParseJson(`).Qual("path/filepath", "Join").Id(`(path, file.Name()), entity); err != nil {
+						panic(err)
+					}
+					filtersApiReference[entity.Id] = entity
+				}
+			}
+		}
+
+		func executeAction(ctx context.Context, actions ...func(context.Context) (interface{}, *`).Qual(API_ERROR, "Error").Id(`)) (resp interface{},err *errs.Error){
+			for _, action := range actions {
+				if resp, err = action(ctx); err != nil || resp != nil {
+					return
+				}
+			}
+			return
+		}
+	`)
+
+	for rindex, resource := range p.Resources {
+
+		stmt = G.Line().Id(resource.ID).Op(":=").Id(ResourceStructId(resource)).Values().Line()
+
+		for k, method := range resource.Methods {
+			// fmt.Println("----------------------------")
+			args := []G.Code{
+				G.Lit(method.HttpMethod),
+				G.Lit(p.GetPath(method)),
+			}
+
+			middlewares = []string{}
+
+			if len(method.ParametersString) > 0 {
+
+				params = strings.Join(method.ParametersString, ",")
+				// fmt.Println(fmt.Sprintf(`%s|RequestParams("%s", UserRequestParams)`, API_URL, params))
+				stmt.Add(G.Line().Comment(
+					"Lista de parametros a serem validados durante a requisição",
+				).Line().Id(fmt.Sprintf(`args%d%d := "%s"`, rindex, k, params)).Line())
+				middlewares = append(middlewares, fmt.Sprintf(`RequestParams(args%d%d, UserRequestParams)`, rindex, k))
+			}
+
+			middlewares = append(middlewares, p.Middlewares...)
+			middlewares = append(middlewares, method.Middlewares...)
+
+			data := map[string]interface{}{
+				"ResourceId": resource.ID,
+				"MethodId":   method.ID,
+			}
+
+			idString = fmt.Sprintf("%s:%s", resource.ID, method.ID)
+			queryIndexsMap[idString] = true
+
+			for _, m := range middlewares {
+
+				if strings.Contains(m, "JWT") && !addJwt {
+					addJwt = true
+					RequestParams.Line().Add(jwt)
+				}
+
+				m = ResolveParams(m, data)
+
+				parts := strings.Split(m, "|")
+				// Quando parts possui tamanho maior que
+				// significa que foi especificado um middleware de outro pacote.
+				if len(parts) > 1 {
+					args = append(args, G.Line().Id(callActionId).Call(
+						G.Id(parts[1]),
+						G.Qual(parts[0], parts[1]),
+					))
+				} else {
+					args = append(args, G.Line().Id(callActionId).Call(
+						G.Lit(m),
+						G.Id(m),
+					))
+				}
+			}
+
+			args = append(args, G.Line().Id(callActionId).Call(
+				G.Lit(fmt.Sprintf("%s.%s", resource.ID, method.ID)),
+				G.Id(resource.ID).Dot(method.ID),
+			))
+
+			if len(method.Postresponse) > 0 {
+				args = append(args, G.Line().Id(method.Postresponse[0]))
+			}
+
+			stmt.Add(G.Line().Comment(method.Description).Line().Id("app").Dot("Handle").Call(args...).Line())
+		}
+		statments = append(statments, stmt)
+	}
+
+	// Cria a funcao que trata os filtros
+	// statments = append(statments, G.Line().Comment("Filter request").Line().Id("app").Dot("Handle").Call(
+	// 	G.Lit("GET"),
+	// 	G.Lit(p.BasePath+"/filters/{id:string}"),
+	// 	// G.Func().Params(G.Id("ctx").Qual(IRIS_CTX, "Context")).Block(),
+	// 	G.Id(fmt.Sprintf(`FilterHandle("../api/%s/filters")`, p.Package)),
+	// ))
+
+	// Cria a funcao que trata os metodos options
+	statments = append(statments, G.Line().Comment("Options request").Line().Id("app").Dot("Options").Call(
+		G.Lit("/{url:path}"),
+		G.Func().Params(G.Id("ctx").Qual(IRIS_CTX, "Context")).Block(
+			G.Id(`ctx.ResponseWriter().Header().Set("Access-Control-Allow-Origin", ctx.GetHeader("Origin"))`),
+		),
+	))
+
+	// Cria a funcao que registra as urls da api no arquivo api_index_gen.go
+	Index.Func().Id("Register").Params(
+		G.Id("app").Op("*").Qual(IRIS, "Application"),
+	).Block(
+		statments...,
+	).Line()
+
+	go GenQueries(p, queryIndexsMap)
+
+	return Write(fmt.Sprintf("%s/%s/api_index_gen.go", p.OutPath, p.Package), Index)
+}
+
+func GenMethod(p *Project, f *G.File, r *Resource, method *Method) error {
+	f.Comment(method.Description)
+
+	if o, err := json.MarshalIndent(method, "", "    "); err == nil {
+		f.Comment(string(o))
+	}
+
+	stmt := f.Func().Params(
+		// G.Id("t").Op("*").Id(strings.Title(r.ID)),
+		G.Id("t").Op("*").Id(ResourceStructId(r)),
+	).Id(method.ID).Params(
+		G.Id("ctx").Qual(IRIS_CTX, "Context"),
+		// G.Id("resp").Op("*").Qual(API_URL, "ApiResponse"),
+	).Params(
+		G.Id("resp").Interface(),
+		G.Id("err").Op("*").Qual(API_ERROR, "Error"),
+	)
+
+	generateActionsFiles(p, f, r, method)
+
+	if middle, found := Middlewares[method.Template]; found {
+		ctx := &MiddlewareContext{
+			Project:    p,
+			Method:     method,
+			Middleware: middle,
+			Statement:  stmt,
+			File:       f,
+		}
+
+		return middle.Fn(ctx)
+	}
+	return fmt.Errorf("Method '%s' template not defined!", method.ID)
+}
+
+func generateActionCommonFile(p *Project) (err error) {
+
+	path := fmt.Sprintf(
+		"%s/include/go/actions/index_gen.go",
+		CurrentDirectory,
+	)
+
+	if _, fileErr := os.Stat(path); os.IsNotExist(fileErr) {
+
+		file := G.NewFile("actions")
+		file.Id(`
+		type Action struct {
+			ID string
+		}
+		`).Line()
+		err = Write(path, file)
+	}
+	return
+}
+func generateActionsFiles(p *Project, f *G.File, r *Resource, method *Method) {
+
+	actions := []Action{}
+	if method.Preconditions != nil {
+		actions = append(actions, method.Preconditions...)
+	}
+	if method.BeforeResponse != nil {
+		actions = append(actions, method.BeforeResponse...)
+	}
+
+	for _, action := range actions {
+
+		path := fmt.Sprintf(
+			"%s/include/go/actions/%s_gen.go",
+			CurrentDirectory,
+			action.ID,
+			// method.Entity,
+			// method.ID,
+			// hookId,
+		)
+
+		if _, fileErr := os.Stat(path); os.IsNotExist(fileErr) {
+			// methodId := fmt.Sprintf("%s%s%s", strings.Title(hookId), method.Entity, strings.Title(method.ID))
+
+			file := G.NewFile("actions")
+			context := map[string]interface{}{
+				"imports": map[string]string{
+					"errs":    "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs",
+					"context": "github.com/kataras/iris/v12/context",
+				},
+				"function":   strings.Title(action.ID),
+				"hasContext": action.Context != nil,
+			}
+
+			spew.Dump(action.ID)
+			spew.Dump(action.Context)
+
+			out, _ := TemplateToString(hookStmtsTmpl, context)
+			file.Id(out).Line()
+			Write(path, file)
+		}
+	}
+
+	// for hookId, _ := range method.Hooks {
+
+	// 	// methodId := fmt.Sprintf("%s%s%s", strings.Title(hookId), method.Entity, strings.Title(method.ID))
+
+	// 	path := fmt.Sprintf(
+	// 		"%s/include/go/actions/%s_%s_%s_gen.go",
+	// 		CurrentDirectory,
+	// 		method.Entity,
+	// 		method.ID,
+	// 		hookId,
+	// 	)
+
+	// 	if _, fileErr := os.Stat(path); os.IsNotExist(fileErr) {
+
+	// 		file := G.NewFile(p.Package)
+
+	// 		context := map[string]interface{}{
+	// 			"imports": map[string]string{
+	// 				// "api":     "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api",
+	// 				"errs":    "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs",
+	// 				"context": "github.com/kataras/iris/v12/context",
+	// 			},
+	// 		}
+	// 		out, _ := TemplateToString(hookStmtsTmpl, context)
+	// 		file.Id(out).Line()
+	// 		Write(path, file)
+	// 	}
+	// }
+}
+
+func GenFromGenericModel(p *Project, entity *EntityInfo) {
+
+	var (
+		posfix      string
+		propertie   *G.Statement
+		tproperties G.Statement
+		// file        = G.NewFile(p.Package)
+		file        = G.NewFile("models")
+		cproperties = map[string]*G.Statement{}
+		properties  = G.Statement{}
+		values      = G.Dict{}
+		model       = p.GetSchema(entity.Name)
+		entityName  = entity.NewName
+		// filename    = "model_" + strings.ToLower(entityName)
+		filename = "models/" + CamelToUnder(entityName)
+		propName string
+	)
+
+	for _, meta := range model.Properties {
+		propName = UpFirst(meta.ID)
+		propertie = G.Id(propName)
+		meta.FillTags(p, propName)
+
+		posfix = ""
+		// Registra a relaao entre as entidades
+		if meta.Relation {
+			posfix = "Ref"
+			SR.Add(&Relation{
+				Source:     meta.GetType(),
+				Target:     model.ID,
+				Attr:       strings.Replace(meta.Tags["bson"], ",omitempty", "", 1),
+				DB:         model.DB,
+				Collection: model.Collection,
+				IsArray:    meta.Array,
+			})
+		}
+
+		if meta.Array {
+			propertie.Index()
+		}
+
+		propertie.Id(entity.TranslateType(meta.Type) + posfix)
+
+		// propertie.Id(meta.Type + posfix)
+
+		// Adiciona as tags caso sejam definidas
+		if meta.Tags != nil {
+			propertie.Tag(meta.Tags)
+			// if name, ok := meta.Tags["json"]; ok {
+			// }
+		}
+
+		// Adiciona a crescricao como comentario
+		if meta.Description != "" {
+			propertie.Comment(meta.Description)
+		}
+
+		cproperties[meta.ID] = propertie
+
+		if meta.ID == "ID" {
+			values[G.Id("ID")] = G.Qual(BSON_PRIMITIVE, "NewObjectID").Call()
+		}
+
+		// Verifica se possui valor padrão
+		if meta.Default != nil {
+			values[G.Id(meta.ID)] = G.Lit(meta.Default)
+		}
+
+		properties = append(properties, propertie)
+	}
+
+	if model.Representations != nil {
+		for posfix, rep := range model.Representations {
+			tproperties = G.Statement{}
+
+			for _, attr := range rep {
+				tproperties = append(tproperties, cproperties[attr])
+			}
+
+			file.Comment(
+				"Representação " + posfix,
+			).Line().Type().Id(
+				model.ID + posfix,
+			).Struct(tproperties...)
+		}
+
+	}
+
+	// Cria a entidade normal
+	file.Line().Comment(model.Description).Line().Comment("Representação Completa")
+	file.Type().Id(entityName).Struct(properties...)
+
+	file.Comment(fmt.Sprintf("Cria uma instancia de %s.", entityName))
+
+	// Cria a função de instanciar um novo elemento com os valores padrão determinados
+	file.Func().Id("New" + entityName).Params().Op("*").Id(entityName).Block(
+		G.Return(G.Op("&").Id(entityName).Values(values)),
+	)
+
+	// Salva o arquivo da entidade
+	// if err := f.Save(fmt.Sprintf("%s/%s/%s_gen.go", p.OutPath, p.Package, filename)); err != nil {
+	// fmt.Printf("%s/%s/%s_gen.go", p.OutPath, p.Package, filename)
+	if err := Write(fmt.Sprintf("%s/%s/%s_gen.go", p.OutPath, p.Package, filename), file); err != nil {
+		panic(err)
+	}
+}
+
+func Write(path string, file *G.File) error {
+	// fmt.Println(fmt.Sprintf("Write -> %#v", path))
+	return FilePutContents(path, fmt.Sprintf("%#v", file), 0777)
+}
+
+func ResourceStructId(resource *Resource) string {
+	return strings.Title(resource.ID) + "Resource"
+}

+ 795 - 0
translate/got/schemas.go

@@ -0,0 +1,795 @@
+package got
+
+import (
+	"fmt"
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common" // . "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/gen"
+	G "github.com/dave/jennifer/jen"
+	"regexp"
+	"strings"
+	"time" // "github.com/davecgh/go-spew/spew"
+)
+
+var (
+	filled      = map[string]*Entity{}
+	numberRegex = regexp.MustCompile("(int|float).*")
+
+	// bool
+	// string
+	// int  int8  int16  int32  int64
+	// uint uint8 uint16 uint32 uint64 uintptr
+	// byte // alias for uint8
+	// rune // alias for int32
+	//      // represents a Unicode code point
+	// float32 float64
+	// complex64 complex128
+
+	primitiveRegex = regexp.MustCompile(`^(bool|string|u?int\d{0,2}|byte|rune|float\d{0,2}|complex\d{2,3})$`)
+)
+
+func GenSchemas(p *Project) error {
+	var (
+		// requiredOnCreate bool
+		hasAddOrRemove bool
+		filename       string
+		inputName      string
+		typ            string
+		extName        string
+		f              *G.File
+		propertie      *G.Statement
+		extend         *G.Statement
+		base           = "models/"
+		posfix         = ""
+		properties     G.Statement
+		// values                  G.Dict
+		cproperties         map[string]*G.Statement
+		addUpdate           map[string]string
+		removeUpdate        map[string]map[string]string
+		entityName          string
+		entityPatchName     string
+		propName            string
+		entityInfo          *EntityInfo
+		inputProperties     G.Statement
+		patchProperties     G.Statement
+		referenceProperties G.Statement
+		entities, err       = Schemas(p)
+		// entities           = p.Schemas
+		// err error
+	)
+
+	if err != nil {
+		return err
+	}
+
+	go GenModelIndex(p)
+
+	if err = GenFilterEntityDocument(p); err != nil {
+		return err
+	}
+
+	for _, entity := range entities {
+
+		entityInfo = p.ResponseEntity(entity.ID)
+
+		if entityInfo.IsGeneric {
+			continue
+		}
+
+		inputName = ""
+		entityPatchName = ""
+		hasAddOrRemove = false
+		// requiredOnCreate = false
+		entityName = GenericPart.ReplaceAllString(entity.ID, "")
+		filename = base + strings.ToLower(entityName)
+		// f = G.NewFile(p.Package)
+		f = G.NewFile("models")
+
+		generateIndexsEntity(f, entity)
+		// fmt.Println("Gerando entity: ", entityName)
+
+		cproperties = map[string]*G.Statement{}
+		// f.Comment(entity.Description)
+		properties = G.Statement{}
+		patchProperties = G.Statement{
+			G.Qual(API_URL, "PatchHistoryRegister").Tag(map[string]string{
+				"json": "-",
+				"bson": ",inline",
+			}),
+		}
+
+		entityModel := G.Qual(API_URL, "EntityModel").Tag(map[string]string{
+			"json": "-",
+			"bson": "-",
+		})
+
+		patchProperties = append(patchProperties, entityModel)
+		// values = G.Dict{}
+
+		// entityInfo = p.IsGenericEntity(entity.ID)
+
+		inputProperties = G.Statement{
+			// G.Qual(API_URL, "DotNotation").Tag(map[string]string{
+			// 	"json": "-",
+			// 	"bson": ",inline",
+			// }),
+			G.Id("DotNotation map[string]interface{}").Tag(map[string]string{
+				"json": "-",
+				"bson": ",inline",
+			}),
+		}
+
+		// inputProperties = append(inputProperties, entityModel)
+		referenceProperties = G.Statement{}
+
+		addUpdate = map[string]string{}
+		removeUpdate = map[string]map[string]string{}
+
+		for _, meta := range entity.Properties {
+
+			// fmt.Println("Gerando ---------- ", entityName, "---------", propName, "----", meta.Array)
+
+			if meta.Targets != "" && !strings.Contains(meta.Targets, "go") {
+				continue
+			}
+
+			propName = strings.Title(meta.ID)
+			propertie = G.Id(propName)
+
+			meta.FillTags(p, propName)
+
+			posfix = ""
+			// Registra a relaao entre as entidades
+			if meta.Relation {
+				posfix = "Reference"
+				SR.Add(&Relation{
+					Source:     meta.GetType(),
+					Target:     entity.ID,
+					Attr:       strings.Replace(meta.Tags["bson"], ",omitempty", "", 1),
+					DB:         entity.DB,
+					Collection: entity.Collection,
+					IsArray:    meta.Array,
+				})
+			}
+			typ = meta.Type + posfix
+
+			if typ == "any" {
+				typ = "interface{}"
+			}
+
+			if meta.Array {
+				hasAddOrRemove = true
+				propertie.Op("*")
+				propertie.Index()
+				if !meta.Readonly {
+
+					extName = "Add" + propName
+					addUpdate[extName] = meta.ID
+
+					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),
+						"propId": meta.ID,
+					}
+					extend = G.Id(extName).Index().Interface().Tag(map[string]string{
+						"json": extName + ",omitempty",
+						"bson": extName + ",omitempty",
+					})
+
+					patchProperties = append(patchProperties, extend)
+				}
+			}
+
+			// propertie.Id(entityInfo.TranslateType(meta.Type) + posfix)
+			if strings.Contains(typ, ".") {
+				typt := strings.Split(typ, ".")
+				propertie.Qual(ImportMap(typt[0]), ConvType(typt[1]))
+			} else {
+				propertie.Id(ConvType(typ))
+			}
+
+			entity.HasMode = true
+			// Adiciona as tags caso sejam definidas
+			if meta.Tags != nil {
+				// if name, ok := meta.Tags["valid"]; ok && strings.Contains(name, "requiredOnCreate") {
+				// 	entity.HasMode = true
+				// }
+
+				propertie.Tag(meta.Tags)
+				// if name, ok := meta.Tags["json"]; ok {
+				// 	// tsPropertieName = strings.Split(name, ",")[0]
+				// }
+			} else {
+				// tsPropertieName = meta.ID
+			}
+			// Adiciona a crescricao como comentario
+
+			// tsPropertie = &ts.Group{}
+
+			if meta.Description != "" {
+				propertie.Comment(meta.Description)
+				// tsPropertie.Comment(meta.Description)
+			}
+
+			// if tsPropertieName != "" {
+
+			// 	tsPropertie.Add(ts.Public().Id(tsPropertieName).Op(":").Id(ts.ConvType(meta.Type)))
+			// 	if meta.Array {
+			// 		tsPropertie.Add(ts.Index())
+			// 	}
+			// 	tsPropertie.Endl()
+			// 	tsProperties = append(tsProperties, tsPropertie)
+			// }
+
+			cproperties[meta.ID] = propertie
+
+			// Adiciona o valor padrao de inicializacao
+			// spew.Dump(meta)
+			// if tvalue, ok := meta.Autogenerate["create"]; ok {
+			// 	switch tvalue {
+			// 	case "objectId":
+			// 		values[G.Id(propName)] = G.Qual(BSON_PRIMITIVE, "NewObjectID").Call()
+			// 	case "now":
+			// 		values[G.Id(propName)] = G.Qual("time", "Now").Call().Id(".Unix()")
+			// 	case "time:now":
+			// 	case "user:current":
+			// 	default:
+			// 		// Verifica se possui valor padrão
+			// 		if meta.Default != nil {
+			// 			values[G.Id(propName)] = G.Lit(meta.Default)
+			// 		}
+			// 	}
+			// }
+
+			// Adiciona as propriedades readonly na entidade principal
+			// e as que tem permissao de escrita na entidade input
+
+			if !meta.Readonly {
+				inputProperties = append(inputProperties, propertie)
+			} else {
+				properties = append(properties, propertie)
+			}
+			// Adiciona a propriedade que é referencia a lista de propriedades
+			// da entidade reference
+			if meta.Reference {
+				if propName == "Id" {
+					propertie = G.Id("Id").Interface().Tag(map[string]string{
+						"json": "_id",
+						"bson": "_id",
+					})
+				}
+				referenceProperties = append(referenceProperties, propertie)
+			}
+		}
+
+		extend = G.Qual(API_URL, "EntityModel").Tag(map[string]string{
+			"json": "-",
+			"bson": ",inline",
+		})
+
+		inputProperties = append(G.Statement{extend}, inputProperties...)
+
+		if len(inputProperties) > 0 {
+			// if entity.HasMode {
+			// values[G.Id("Mode")] = G.Qual(API_URL, "Mode").Values(G.Dict{
+			// 	G.Id("M"): G.Lit("create"),
+			// })
+			// }
+
+			if len(properties) == 0 {
+				properties = inputProperties
+
+				patchProperties = append(patchProperties, G.Id("Set").Op("*").Id(entityName).Tag(map[string]string{
+					"json": "$set,omitempty",
+					"bson": "$set,omitempty",
+				}))
+
+			} else {
+
+				inputName = entity.ID + "Input"
+
+				extend = G.Id(inputName).Tag(map[string]string{
+					"json": ",inline",
+					"bson": ",inline",
+				})
+
+				properties = append(G.Statement{extend}, properties...)
+
+				f.Comment("Representação Input aplicada em operações de update.")
+				f.Type().Id(inputName).Struct(inputProperties...)
+
+				patchProperties = append(patchProperties, G.Id("Set").Op("*").Id(inputName).Tag(map[string]string{
+					"json": "$set,omitempty",
+					"bson": "-",
+				}))
+			}
+		}
+
+		if len(referenceProperties) > 0 {
+			f.Comment("Representação reference aplicada quando  a entidade é associada a outra.")
+			f.Type().Id(entity.ID + "Reference").Struct(referenceProperties...)
+		}
+
+		// if entity.Representations != nil {
+		// 	for k, rep := range entity.Representations {
+		// 		tproperties = G.Statement{}
+		// 		for _, attr := range rep {
+		// 			tproperties = append(tproperties, cproperties[attr])
+		// 		}
+		// 		f.Comment("Representação " + k)
+		// 		f.Type().Id(entity.ID + k).Struct(tproperties...)
+		// 	}
+		// }
+
+		if len(patchProperties) > 1 {
+			entityPatchName = fmt.Sprintf("%sPatchs", entityName)
+			f.Comment(entity.Description).Line().Comment("Representação de atualização")
+
+			// patchProperties = append(G.Statement{*entityModel}, patchProperties)
+
+			f.Type().Id(entityPatchName).Struct(patchProperties...)
+		}
+		// Cria a entidade normal
+		f.Comment(entity.Description).Line().Comment("Representação Completa")
+		f.Type().Id(entityName).Struct(properties...)
+
+		// Cria a entidade em typescript
+		// tsModels.Line().Export().Class().Id(strings.Title(entity.ID)).Block(tsProperties...).Line()
+
+		f.Comment("Cria uma instancia de " + entity.ID + ".")
+		// Cria a função de instanciar um novo elemento com os valores padrão determinados
+		f.Func().Id("New"+entityName).Params().Op("*").Id(entityName).Block(
+			G.Id("entity").Op(":=").Op("&").Id(entityName).Values(),
+
+			G.Do(func(x *G.Statement) {
+				if entity.HasMode {
+					x.Add(G.Id(`entity.SetMode("create")`))
+				}
+			}),
+
+			G.Do(func(part *G.Statement) {
+				// user := false
+				for _, prop := range entity.Properties {
+
+					typ := ConvType(prop.Type)
+					isMap := strings.Contains(typ, "map")
+
+					if prop.Relation {
+						typ = typ + "Reference"
+					}
+
+					if def, ok := prop.Autogenerate["create"]; ok {
+						switch def.Type {
+						case "objectId":
+							part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Qual(BSON_PRIMITIVE, "NewObjectID()").Line())
+						case "now":
+							part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Qual("time", "Now").Call().Id(".Unix()").Line())
+						case "default":
+							part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Lit(prop.Default).Line())
+						}
+
+					} else if prop.Array {
+						part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("= &").Index().Id(typ).Values().Line())
+					} else if isMap {
+						part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Id(typ).Values().Line())
+					}
+				}
+			}).Line(),
+
+			G.Return(G.Id("entity")).Line(),
+		)
+
+		// update = bson.M{"$set": f.Entity}
+
+		// if data, ok = f.Entity.Push(); ok {
+		// 	update["$push"] = data
+		// }
+
+		// if data, ok = f.Entity.Pull(); ok {
+		// 	update["$pull"] = data
+		// }
+		if entityPatchName != "" {
+
+			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(
+					G.Qual(BSON, "M").Values(G.Lit("$set").Id(": t.Set")),
+				).Line(),
+				G.Do(func(y *G.Statement) {
+					if !hasAddOrRemove {
+						return
+					}
+					stmts := G.Statement{
+						G.Id("hasAdd").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(),
+					}
+
+					// add entitys
+					// spew.Dump(addUpdate)
+					for prop, value := range addUpdate {
+						stmts = append(stmts, G.If(G.Len(G.Id("t").Dot(prop)).Op(">").Lit(0)).Block(
+							G.Id("hasAdd").Op("++"),
+							G.Id("push").Index(G.Lit(value)).Op("=").Qual(BSON, "M").Values(G.Dict{
+								G.Lit("$each"): G.Id("t").Dot(prop),
+							}),
+						).Line())
+					}
+					// spew.Dump(removeUpdate)
+					// remove entitys
+					for prop, value := range removeUpdate {
+						stmts = append(stmts, G.If(G.Len(G.Id("t").Dot(prop)).Op(">").Lit(0)).Block(
+							G.Id("hasRemove").Op("++"),
+							G.Do(func(hasRemove *G.Statement) {
+								var assign G.Dict
+								// Se o array for de um atributo de tipo primitivo
+								if primitiveRegex.MatchString(value["type"]) {
+									assign = G.Dict{
+										G.Lit("$in"): G.Id("t").Dot(prop),
+									}
+								} else {
+									assign = G.Dict{
+										G.Lit("_id"): G.Qual(BSON, "M").Values(G.Dict{
+											G.Lit("$in"): G.Id("t").Dot(prop),
+										}),
+									}
+								}
+
+								hasRemove.Id("pull").Index(G.Lit(value["propId"])).Op("=").Qual(BSON, "M").Values(assign)
+							}),
+						).Line())
+					}
+					// fmt.Println("Remove:", inputName, prop, value)
+
+					// fmt.Printf("%#v", G.If(G.Len(G.Id("t").Dot(prop)).Op(">").Lit(0)).Block(
+					// 	G.Id("hasRemove").Op("++"),
+					// 	G.Id("pull").Index(G.Lit(value)).Op("=").Qual(BSON, "M").Values(G.Dict{
+					// 		G.Lit("$in"): G.Id("t").Dot(prop),
+					// 	}),
+					// ).Line())
+
+					// x.Add(G.If(G.Len(G.Id("t").Dot(prop)).Op(">").Lit(0)).Block(
+					// 	G.Id("hasRemove").Op("++"),
+					// 	G.Id("pull").Index(G.Lit(value)).Op("=").Qual(BSON, "M").Values(G.Dict{
+					// 		G.Lit("$in"): G.Id("t").Dot(prop),
+					// 	}),
+					// ).Line())
+
+					stmts = append(stmts, G.If(G.Id("hasAdd").Op(">").Lit(0)).Block(
+						// G.Id("updt").Index(G.Lit("$push")).Op("=").Id("push"),
+						G.Id("updt").Op("=").Append(
+							G.Id("updt"),
+							G.Qual(BSON, "M").Values(G.Lit("$push").Id(": push")),
+						),
+
+						// .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")),
+						),
+					).Line())
+
+					y.Add(stmts...)
+				}),
+				G.Return(G.Op("&").Id("updt")),
+			)
+
+		}
+		// Salva o arquivo da entidade
+
+		// if err := f.Save(fmt.Sprintf("%s/%s/%s_gen.go", p.OutPath, p.Package, filename)); err != nil {
+		if err := Write(fmt.Sprintf("%s/%s/%s_gen.go", p.OutPath, p.Package, filename), f); err != nil {
+			return err
+		}
+	} // Fim do for de schema
+	return nil
+}
+
+func ConvType(ntype string) string {
+	if len(ntype) < 3 {
+		fmt.Printf("Invalid type name '%s':\n", ntype)
+	}
+	if ntype[0:3] == "map" {
+		parts := strings.Split(ntype, "|")
+		ntype = fmt.Sprintf("map[%s]%s", parts[1], parts[2])
+	}
+	return ntype
+}
+
+func GenModelIndex(p *Project) error {
+	var (
+		// Index = G.NewFile(p.Package)
+		Index = G.NewFile("models")
+	)
+
+	Index.Id(`type Entity struct {}`).Line()
+
+	Index.Comment("")
+
+	Index.Var().Defs(
+		G.Id("Api").Op("=").Op("&").Qual(API_URL, "Mongo").Values(),
+	).Line()
+
+	return Write(fmt.Sprintf("%s/%s/models/index_gen.go", p.OutPath, p.Package), Index)
+}
+
+func fill(p *Project, schema *Entity) (*Entity, error) {
+	var (
+		// found, ok    bool
+		found bool
+		cls   *Entity
+		// newSchema = &(*schema)
+
+		err error
+	)
+	// Se o esquema ainda não esta completo com as classes extendidas caso possua
+	if _, found = filled[schema.ID]; !found {
+
+		// fEntity = schema
+		//
+
+		// fmt.Println("Fill ............ ", schema.ID)
+		// fmt.Println("Nao estava cheio ", schema.ID)
+		for _, entity := range schema.Extends {
+
+			if cls, err = fill(p, p.GetSchema(entity)); err != nil {
+				return nil, err
+			}
+
+			// fmt.Println("extends ", entity)
+
+			// if cls, ok = filled[entity]; !ok {
+
+			// 	cls = p.GetSchema(entity)
+
+			// 	// fmt.Println("Não tava cheio", entity)
+
+			// 	// fmt.Println("fill:", schema)
+
+			// 	// fmt.Println(cls)
+
+			// 	if err = fill(p, cls); err != nil {
+			// 		return err
+			// 	}
+			// }
+			//  else {
+			// 	fmt.Println("estava cheio ", entity)
+
+			// fmt.Println("Merge ", schema.ID, cls.ID, ok)
+
+			schema.Properties = append(schema.Properties, cls.Properties...)
+
+		}
+		// for _, x := range schema.Properties {
+		// fmt.Println("fill----------------", schema.ID, x.ID, len(schema.Properties))
+		// }
+		filled[schema.ID] = schema
+	}
+
+	return filled[schema.ID], nil
+}
+
+func Schemas(p *Project) ([]*Entity, error) {
+
+	var (
+		err      error
+		entities = []*Entity{}
+	)
+
+	for _, schema := range p.Schemas {
+
+		if schema, err = fill(p, schema); err != nil {
+			return nil, err
+		}
+
+		if schema.Type != "abstract" {
+			entities = append(entities, schema)
+		}
+
+		// for _, x := range schema.Properties {
+		// 	fmt.Printf("%s , %s, %p\n", schema.ID, x.ID, x)
+		// }
+
+	}
+
+	// for _, s := range entities {
+	// 	for _, x := range s.Properties {
+	// 		fmt.Printf("final-schemas....%s , %s, %p\n", s.ID, x.ID, x)
+	// 	}
+	// }
+
+	return entities, nil
+}
+
+func GenFilterEntityDocument(p *Project) error {
+	var (
+		content string
+		path    string
+		err     error
+	)
+	for _, s := range p.Schemas {
+
+		if s.Type == "nested.object" {
+			continue
+		}
+		// fmt.Println("gerando filtro ", s.ID)
+
+		filter := &ApiFilter{
+			Id:   strings.ToLower(s.ID),
+			Date: time.Now().Unix(),
+		}
+
+		for _, attr := range s.Properties {
+			if attr.Filter == nil {
+				continue
+			}
+			// Atualiza o tipo do filtro na estrutura  do campo
+			for _, filterItem := range attr.Filter {
+				FilterTypeMap(attr, filterItem)
+				// Atualiza o path da query
+				if filterItem.Type == "nested" {
+					FilterPath(filter, p, attr, attr.ID)
+				} else {
+
+					if filterItem.Path == "" {
+						filterItem.Path = attr.ID
+					}
+
+					if filterItem.UserEnumAsOptions {
+						filterItem.Options = []FilterOption{}
+
+						// if len(attr.Values) == 0 {
+						// 	attr.Values = make([]string, len(attr.Enum))
+						// }
+						// 	values = make()
+						// }
+
+						for key, value := range attr.Enum {
+							filterItem.Options = append(filterItem.Options, FilterOption{
+								Value: attr.Values[key],
+								Label: value,
+							})
+						}
+
+					}
+
+					filter.Fields = append(filter.Fields, filterItem)
+				}
+			}
+		}
+
+		if filter.Fields == nil {
+			continue
+		}
+
+		if content, err = JSONStringfy(filter); err != nil {
+			return err
+		}
+
+		path = fmt.Sprintf("%s/filters/%s.json", p.OutPath, strings.ToLower(s.ID))
+		// fmt.Printf("storing filter '%s'\n",path)
+		if err = FilePutContents(path, content, 0777); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func FilterPath(filter *ApiFilter, p *Project, attr *Propertie, path string) {
+	var (
+		nfil  Filter
+		npath string
+		typ   = attr.Type
+	)
+	if attr.Relation {
+		typ += "Reference"
+	}
+
+	entity := p.GetSchema(typ)
+
+	for _, x := range entity.Properties {
+
+		if x.Filter == nil {
+			continue
+		}
+
+		npath = fmt.Sprintf("%s.%s", path, x.ID)
+
+		for _, item := range x.Filter {
+
+			if item.Type != "nested" {
+				nfil = *item
+				if nfil.Path == "" {
+					nfil.Path = npath
+				}
+				FilterTypeMap(x, item)
+				filter.Fields = append(filter.Fields, &nfil)
+			} else {
+				FilterPath(filter, p, x, npath)
+			}
+		}
+	}
+}
+
+func FilterTypeMap(attr *Propertie, f *Filter) {
+	typ := f.Type
+	if typ != "" {
+		return
+	}
+	switch {
+	// Se o tipo for numerico
+	case numberRegex.Match([]byte(typ)):
+		f.Type = "number"
+	default:
+		f.Type = attr.Type
+	}
+}
+
+func generateIndexsEntity(file *G.File, entity *Entity) {
+	// keys := G.{}
+	// options := G.Dict{
+	// 	G.Id("Unique"): G.Lit(true),
+	// 	// G.Id("Key"):   G.Lit("unique"),
+	// 	// G.Id("Value"): G.Lit(true),
+	// }
+	var create = false
+	indexOptions := G.Dict{
+		G.Id("Keys"): G.Qual(BSONX, "Doc").ValuesFunc(func(keys *G.Group) {
+
+			for _, propertie := range entity.Properties {
+				if propertie.Unique {
+					keys.Add(G.Values(G.Lit(propertie.ID), G.Qual(BSONX, "Int32").Call(G.Lit(1))))
+					create = true
+					// keys[G.Lit(propertie.ID)] = G.Lit(1)
+				}
+			}
+
+		}),
+		// G.Id("Options"): G.Qual(BSON, "M").Values(options),
+		G.Id("Options"): G.Id("opts.SetUnique(true)"),
+	}
+
+	if create {
+		file.Line().Func().Id("init").Call().Block(
+			G.Id("models.Api.Ready().Subscribe").Call(
+				G.Func().Call(G.Id("value").Id("...interface{}")).Block(
+					G.Id("opts := &").Qual("go.mongodb.org/mongo-driver/mongo/options", "IndexOptions").Values(),
+					G.Id(`index := `).Qual("go.mongodb.org/mongo-driver/mongo", "IndexModel").Values(indexOptions),
+					// G.Qual("fmt", "Println").Call(G.Lit("create index for "), G.Lit(entity.ID)),
+					G.Id(`if err := models.Api.CreateIndex(`).Lit(entity.DB).Id(`, `).Lit(entity.Collection).Id(`, index); err != nil`).
+						Block(
+							G.Id("panic").Call(
+								G.Qual("fmt", "Sprintf").Call(
+									// G.Lit(fmt.Sprintf("Index %s creation error %s", entity.ID, err.Error())),
+									G.Id(`"Index`).Id(entity.ID).Id(`creation error %s", err.Error()`),
+								),
+							).Line(),
+							// (`).
+							// Call().
+							// Line().
+						),
+				),
+			),
+		)
+
+		// .BlockFunc(func(statement *G.Group) {
+		// 	statement.Add(
+		// 		,
+		// 	)
+		// 	statement.Add(G.Line().Id("})"))
+		// })
+
+	}
+
+}

+ 139 - 0
translate/got/translate.go

@@ -0,0 +1,139 @@
+package got
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+)
+
+var (
+	OutputDirectory  string
+	CurrentDirectory string
+)
+
+func Translate(p *Project) (err error) {
+
+	if err = initGlobalVars(p); err != nil {
+		fmt.Println("error initGlobalVars")
+		return
+	}
+	if err = CreateMainFile(p); err != nil {
+		fmt.Println("error CreateMainFile")
+		return
+	}
+	if err = createParamsFile(p); err != nil {
+		fmt.Println("error createParamsFile")
+		return
+	}
+
+	if err = CreateEnvironment(p); err != nil {
+		fmt.Println("error CreateEnvironment")
+		return
+	}
+
+	if err = CreateVariables(p); err != nil {
+		fmt.Println("error CreateVariables")
+		return
+	}
+
+	if err = GenSchemas(p); err != nil {
+		fmt.Println("error GenSchemas")
+		return
+	}
+
+	if err = GenResources(p); err != nil {
+		fmt.Println("error GenResources")
+		return
+	}
+
+	if err = GenMiddlewares(p); err != nil {
+		fmt.Println("error GenMiddlewares")
+		return
+	}
+
+	return include(p)
+}
+
+func createSymbolicLinks(basedir string) func(string, os.FileInfo, error) error {
+	var (
+		callback func(string, os.FileInfo, error) error
+		relative = func(path string) string {
+			return fmt.Sprintf("%s/%s%s", OutputDirectory, "v1", strings.Replace(filepath.Dir(path), basedir, "", 1))
+		}
+	)
+
+	callback = func(pathX string, infoX os.FileInfo, errX error) (err error) {
+
+		if errX != nil {
+			fmt.Printf("error 「%v」 at a path 「%q」\n", errX, pathX)
+			return errX
+		}
+
+		if infoX.IsDir() {
+			relativedir := strings.Replace(pathX, basedir, "", 1)
+			if relativedir != "" {
+
+				// fmt.Println("crinado diretorio", fmt.Sprintf("%s/%s%s", OutputDirectory, "v1", relativedir))
+
+				os.MkdirAll(fmt.Sprintf("%s/%s%s", OutputDirectory, "v1", relativedir), 0777)
+			}
+
+		} else {
+			// fmt.Println("crinado link symbolico aqui", relative(pathX))
+			os.Symlink(pathX, filepath.Join(relative(pathX), infoX.Name()))
+
+			// fmt.Printf("  dir ------ : 「%v」\n", pathX)
+			// fmt.Printf("  dir: 「%v」\n", strings.Replace(filepath.Dir(pathX), basedir, "", 1))
+			// fmt.Printf("  file name 「%v」\n", infoX.Name())
+			// fmt.Printf("  extenion: 「%v」\n", filepath.Ext(pathX))
+			// fmt.Printf("  target: 「%v」\n", relative(pathX))
+			// fmt.Sprintf("%s/%s%s", OutputDirectory, "v1", strings.Replace(filepath.Dir(pathX), basedir, "", 1)),
+		}
+
+		return nil
+	}
+	return callback
+}
+
+func include(p *Project) error {
+	var err error
+
+	if err = exec.Command("sh", "-c", fmt.Sprintf("find %s -type l -delete", OutputDirectory)).Run(); err != nil {
+		fmt.Println("include error ", err)
+	}
+
+	basedir := fmt.Sprintf("%s/include/go", CurrentDirectory)
+
+	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
+}
+
+func initGlobalVars(p *Project) error {
+	var err error
+
+	if CurrentDirectory, err = os.Getwd(); err != nil {
+		return err
+	}
+
+	if OutputDirectory, err = filepath.Abs(CurrentDirectory + "/" + p.OutPath); err != nil {
+		return err
+	}
+	return nil
+}

+ 1 - 0
translate/translate.go

@@ -0,0 +1 @@
+package translate

+ 226 - 0
translate/tst/auth.go

@@ -0,0 +1,226 @@
+package tst
+
+import (
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	TS "git.eugeniocarvalho.dev/eugeniucarvalho/gg/generators/typescript"
+)
+
+func createAuthClass(p *Project, file *TS.File) {
+	// auth := p.Auth.Oauth2
+	// scope, _ := json.Marshal(auth.Client.Scope)
+
+	file.Line().Comment(
+		"Class gerencia autenticacao e autorizacao da api.",
+	).Export().Class().Id("Auth").Block(TS.Raw(`
+
+    // public profile = new Subject<any>();
+    public profile$ = new Subject<any>();
+    public profile: any;
+    public cookieDomain: string;
+
+    public grant: any;
+    public onAuthorize$ = new BehaviorSubject(null);
+    public clientParams = {
+        redirect_uri: '',
+        client_id: '',
+        client_secret: '',
+        scope: [],
+        state: '',
+    };
+    
+    protected oauthURI: string;
+
+    // constructor(protected api: `).Id(ApiClassName(p)).Raw(`) {
+    constructor(protected api: ApiInterface) {
+        const options = api.apiOptions;
+        this.oauthURI = options.ACCOUNT_URL;
+        this.clientParams = {
+            redirect_uri: options.REDIRECT_URI,
+            client_id: options.CLIENT_ID,
+            client_secret: options.CLIENT_SECRET,
+            scope: options.SCOPES,
+            state: this.state()
+        };
+        this.cookieDomain = options.COOKIE_DOMAIN;
+        console.log("create auth instance", options);
+    }
+    protected state() {
+        // return [32, 7, 28].map(k => { return Math.random().toString(k).substr(2) }).join('+');
+        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
+            const r = Math.random() * 16 | 0;
+            const v = c === 'x' ? r : (r & 0x3 | 0x8);
+            return v.toString(16);
+        });
+    }
+    getProfile() {
+        if (this.profile) {
+            return of(this.profile).pipe(take(1));
+        }
+        return this.api.http
+            .post<any>(`).Raw("`${this.oauthURI}/api/profile`").Raw(`, {}, this.api.options())
+            .pipe(
+                take(1),
+                map(profile => this.profile = profile),
+            );
+    }
+    Authorize(): Observable<any> {
+        return this.getAuthCode().pipe(
+            take(1),
+            switchMap(code => {
+                // console.log('(get code)', code);
+                return this.getAccessToken({ code });
+            }),
+            catchError(err => {
+                console.log('(catch erro grant)', err);
+                switch (err) {
+                case '':
+                  return this.getAccessToken({
+                    request_type: 'refreshToken',
+                    refresh_token: this.grant.refresh_token
+                  });
+                case '':
+                  err = { type: err };
+                }
+                throw err;
+            }),
+            tap(grant => {
+                // console.log('(set grant )', grant);
+                this.setGrant(grant);
+                // this.api.poolReady.map(callback => callback());
+                this.onAuthorize$.next(grant);
+            }),
+        );
+    }
+    Token() {
+        const g = this.grant;
+        if (g) {
+            return `).Raw("`${g.token_type} ${g.access_token}`;").Raw(`
+        }
+        return '';
+    }
+    Unauthorize() {
+        // ${this.oauthURI}/api/signout
+        window.location.href = `).Raw("this.location('/api/signout').href").Raw(`;
+    }
+    protected getAccessToken(options: any) {
+
+        if (this.grant) {
+            return of(this.grant);
+        }
+
+        const params = Object.assign({
+            request_type: 'accessToken',
+            client_id: this.clientParams.client_id,
+            state: this.clientParams.state
+        }, options);
+
+        return this.api.http.post<any>(`).Raw("`${this.oauthURI}/oauth2/token`").Raw(`, {},
+            Object.assign({}, this.api.httpOptions, { params: params })
+        );
+    }
+    protected setGrant(grant) {
+        this.grant = grant;
+        // recupera os dados do usuario
+        setTimeout(() => this.getProfile().subscribe(data => this.profile$.next(data)));
+    }
+    protected location(path = '/oauth2/authorize'): URL {
+        // Solicita autorização par ao servidor de autenticacao caso não exista um token e nem um codigo
+        const url = new URL(`).Raw("`${this.oauthURI}${path}`").Raw(`);
+        Object.keys(this.clientParams).map((k, v) => {
+            url.searchParams.append(k, this.clientParams[k]);
+        });
+        return url;
+    }
+    protected getAuthCode() {
+        return new Observable<string>(ob => {
+            const oauth2 = document.createElement('iframe'),
+                url = this.location();
+
+            oauth2.id = `).Raw("`iframe-${Math.random().toString(36)}`").Raw(`;
+
+            url.searchParams.set('redirect_uri', oauth2.id);
+            
+            function listenAccessCode(event) {
+                // Check sender origin to be trusted
+                // if (event.origin !== "http://example.com") return;
+                const { type, code } = event.data;
+                if (type === 'access_code') {
+                  // remove o iframe do dom
+                  oauth2.remove();
+                  // submete o codigo
+                  if (code) { ob.next(code); } else { ob.error(event.data); }
+
+                  window.removeEventListener('message', listenAccessCode, false);
+                }
+              }
+              window.addEventListener('message', listenAccessCode, false);
+              
+              oauth2.src = url.href;
+              
+              document.body.appendChild(oauth2);
+
+              oauth2.onload = () => {
+                if (!oauth2.contentDocument) {
+                  window.postMessage({ 'type': 'access_code', 'error': 'unauthorized' }, '*');
+                }
+              }
+            
+        });
+    }
+	`))
+}
+
+        // return new Observable<any>(observer => {
+        //     // Verifica se existe um usuario logado
+        //     // Quando o usuario não está logado ele é redirecionado para o servidor de autenticacao
+        //     if (!(`).Raw(fmt.Sprintf("/\\s%s=([^;]+);?/gm.exec(", p.Auth.AuthTokenID)).Raw("` ${document.cookie}`").Raw(`))) {
+        //         window.location.href = this.location('/signin').href;
+        //     }
+
+        //     const res = (g, success = true) => {
+        //         let f;
+        //         if (success) {
+        //             observer.next(g);
+        //             while (f = this.api.poolReady.shift()){
+        //                 f();
+        //             }
+        //         } else {
+        //             observer.error(g);
+        //         }
+        //         observer.complete();
+        //     };
+        //     // Se existe um token de authorizacao
+        //     if (this.grant) {
+        //         this.setGrant(this.grant);
+        //         res(this.grant);
+        //         return;
+        //     }
+        //     this.getAuthCode().subscribe(
+        //         code => {
+        //             const done = (grant) => {
+        //                 this.setGrant(grant);
+        //                 res(this.grant);
+        //             };
+
+        //             this.getAccessToken({
+        //                 code: code
+        //             }).subscribe(
+        //                 grant => done(grant),
+        //                 _ => {
+        //                     // Falha ao recuperar o token ( não existe ou expirou)
+        //                     this.getAccessToken({
+        //                         request_type: 'refreshToken',
+        //                         refresh_token: this.grant.refresh_token
+        //                     }).subscribe(
+        //                         grant => done(grant),
+        //                         error => res(error, false)
+        //                     );
+        //                 });
+        //         },
+        //         // Não conseguiu obter o codigo de autenticacao
+        //         // acao tomada: redirecionar para a tela de login do sistema
+        //         error => {
+        //             // window.location.href = `).Raw("`${this.oauthURI}/signout`").Raw(`;
+        //         }
+        //     );
+        // });

+ 557 - 0
translate/tst/resources.go

@@ -0,0 +1,557 @@
+package tst
+
+import (
+	"fmt"
+	"strings"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common" // . "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/gen"
+	TS "git.eugeniocarvalho.dev/eugeniucarvalho/gg/generators/typescript"
+)
+
+var (
+	FormatMap map[string]string
+
+	// TemplatesMethods = map[string]func(p *Project, method *Method) (*G.Statement, error){
+	// 	"post":     GenCreateStmts,
+	// 	"put":      GenUpdateStmts,
+	// 	"delete":   GenDeleteStmts,
+	// 	"get_one":  GenGetStmtsOne,
+	// 	"get_list": GenGetStmtsList,
+	// }
+
+	angularServiceOption    = []TS.CodeInterface{}
+	angularServiceOptionMap = map[string]string{}
+)
+
+func GenResources(p *Project) {
+	var (
+		// angularServiceStmtName    string
+		angularResourceName string
+		ServiceStmt         = "ServiceStmt"
+		// angularServiceMethods     []TS.CodeInterface
+		angularServiceStmtMethods []TS.CodeInterface
+	)
+
+	// imports(module)
+
+	// module.Line().Export().Class().Id(ServiceStmt).Block(
+	// 	TS.Public().Id("Url").Call(TS.Raw("u: string, options:{}")).Op(":").String().Block(
+	// 		TS.Raw(""),
+	// 	),
+	// ).Line()
+	module.Line().Raw(`
+	
+	export interface HttpOptions {
+		headers?: HttpHeaders;
+		params?: any;
+		withCredentials?: boolean;
+		id?: string;
+	}
+	
+	export class ServiceStmt {
+		
+		// constructor(protected api: `).Id(ApiClassName(p)).Raw(`) {}
+		constructor(protected api: ApiInterface) {}
+	
+		public Url(u: string, opts: {}): string {
+			
+			(u.match(/{(\w+)}/gm)||[]).map(argExpression => {
+				const
+				prop = argExpression.replace(/({|})/gm,''),
+				value = opts[prop];
+				if (!value) { throw Error(`).Raw("`Url params '${prop}' is required`").Raw(`);}
+				u = u.replace(argExpression, value);
+			});
+			return u;
+		}
+	
+		_event(url, opt: HttpOptions): Observable<EventSourcePolyfill> {
+			opt = this.api.options(opt);
+			const _url = new URL(this.Url(url, opt)), { params = null } = opt;
+			if (params) {
+				params.map.forEach((values, prop) => values.map(value => _url.searchParams.append(prop, value)));
+			}
+			return new Observable(o => o.next(new EventSourcePolyfill(
+				_url.toString(),
+				{ heartbeatTimeout: 86400000, headers: { Authorization: this.api.Auth.Token() } },
+			))).pipe(take(1));
+		}
+		
+		_get(url, opt: HttpOptions): Observable<ServiceResponse> {
+			opt = this.api.options(opt);
+			return this.api.http.get<ServiceResponse>(this.Url(url, opt), opt).pipe(take(1));
+		}
+	
+		_put(url, entity: any, opt: HttpOptions): Observable<ServiceResponse> {
+			opt = this.api.options(opt);
+			return this.api.http.put<ServiceResponse>(this.Url(url, opt), entity, opt).pipe(take(1));
+		}
+		
+		_patch(url, entity: any, opt: HttpOptions): Observable<ServiceResponse> {
+			opt = this.api.options(opt);
+			return this.api.http.patch<ServiceResponse>(this.Url(url, opt), entity, opt).pipe(take(1));
+		}
+	
+		_post(url, entity: any, opt: HttpOptions): Observable<ServiceResponse> {
+			opt = this.api.options(opt);
+			return this.api.http.post<ServiceResponse>(this.Url(url, opt), entity, opt).pipe(take(1));
+		}
+	
+		_delete(url, opt: HttpOptions): Observable<ServiceResponse> {
+			opt = this.api.options(opt);
+			return this.api.http.delete<ServiceResponse>(this.Url(url, opt), opt).pipe(take(1));
+		}
+	}`).Line()
+
+	for _, resource := range p.Resources {
+		FormatMap = map[string]string{}
+		// angularServiceMethods = []TS.CodeInterface{}
+		angularServiceStmtMethods = []TS.CodeInterface{}
+		angularResourceName = strings.Title(resource.ID) + "Service"
+
+		module.Comment(resource.Description).Line()
+
+		// angularServiceMethods = append(angularServiceMethods, TS.Constructor(
+		// 	TS.Protected().Id("api").Op(":").Id(ApiClassName(p)),
+		// ).Block(
+		// // TS.This().Dot("Url").Op("="),
+		// ))
+
+		// angularServiceStmtMethods = append(angularServiceStmtMethods, TS.Public().Id("Url").Op(":").String())
+
+		// angularServiceStmtMethods = append(angularServiceStmtMethods, TS.Constructor(
+		// 	TS.Protected().Id("api").Op(":").Id(ApiClassName(p)),
+		// 	TS.Protected().Id("options").Op(":").Id("ServiceOptions"),
+		// ).Block(
+		// 	// TS.Raw("this.url").Op("=").Lit(p.UrlFromMethod()).Endl(),
+		// 	TS.Raw("super();"),
+		// 	TS.Raw("if (this.options == null)").Block(
+		// 		// TS.Raw("this.options = new ServiceOptions();"),
+		// 		TS.Raw("this.options = {};"),
+		// 	),
+		// 	// TS.Super().Call(TS.Id("options")),
+		// ))
+
+		// angularServiceStmtName = angularResourceName + "Stmt"
+
+		// angularServiceMethods = append(angularServiceMethods, TS.Id("Options").Params(
+		// 	TS.Id("options?").Op(":").Id("ServiceOptions"),
+		// ).Op(":").Id(angularServiceStmtName).Block(
+		// 	TS.Return().New().Id(angularServiceStmtName).Call(
+		// 		TS.Id("this.api"),
+		// 		TS.Id("options"),
+		// 	),
+		// ).Line())
+
+		// angularServiceStmtMethods = append(angularServiceStmtMethods, TS.Line().Raw(`
+		// options(opts?: HttpOptions): this {
+		// 	opts && Object.assign(this.opts, opts)
+		// 	return this;
+		// }`).Line())
+
+		for _, method := range resource.Methods {
+			// if method.Template == "implement" {
+			// 	continue
+			// }
+
+			GenAngularMethodStmt(
+				p,
+				resource,
+				method,
+				&angularServiceStmtMethods,
+				angularResourceName,
+			)
+
+			// Vai ser usado no metodo de construcao da classe chamado em createAngularService
+			for paramName, param := range method.Parameters {
+
+				// fmt.Println("param >>>>>>>>>", resource.ID, method.ID, paramName)
+
+				if _, find := angularServiceOptionMap[paramName]; find {
+					continue
+				}
+
+				angularServiceOptionMap[paramName] = param.Location
+				angularServiceOption = append(angularServiceOption,
+					TS.Comment(param.Description).Id(paramName+"?").Op(":").Id(TS.ConvType(param.Type)).Endl(),
+				)
+			}
+		}
+
+		module.Line().Export().Class().Id(
+			angularResourceName,
+		).Extends().Id(ServiceStmt).Block(
+			angularServiceStmtMethods...,
+		).Line()
+
+		// module.Line().Export().Class().Id(
+		// 	angularResourceName,
+		// ).Block(
+		// 	angularServiceMethods...,
+		// ).Line()
+	}
+
+	// Cria o arquivo de servido da api
+	createAngularService(p, module)
+	// Fim do laco de recurso
+	// Salva o arquivo do modulo
+
+}
+
+func ApiClassName(p *Project) string {
+	return p.Name + "Api"
+}
+
+func createAngularService(p *Project, module *TS.File) error {
+	// var (
+	// service = Ts.NewFile("api.service.ts")
+	// )
+
+	// Define a class que representa uma resposta da api
+	// "Class gerencia todos os serviços da api.",
+	module.Export().Interface().Id("ServiceResponse").Block(
+		TS.Raw("resultSizeEstimate: number;"),
+		TS.Raw("nextPageToken: string;"),
+		TS.Raw("entity?:object;"),
+		TS.Raw("itens?:object[];"),
+	)
+
+	// Define todos os parametros atribuiveis as opcoes de uma requisicao da api
+	module.Line().Export().Interface().Id("ServiceOptions").BlockFun(func(g *TS.Group) {
+		g.Stmts = angularServiceOption
+	})
+
+	//
+	createClientClass(p, module)
+	//
+	createAuthClass(p, module)
+
+	module.Line().Comment("Objeto acessivel pelo usuario para realizar requisicoes à api.").Raw(`
+	@Injectable({ providedIn: 'root' })
+	// @Injectable()
+	export class `).Raw(ApiClassName(p)).Raw(` {
+		public Client: Client;
+		// public BASE_URL = environment.BASE_URL;
+		public Auth: Auth;
+		// public queryParams: any = {};
+		public poolReady = [];
+
+		public httpOptions = {
+			headers: new HttpHeaders({
+				'Content-Type': 'application/json',
+				'Accept': 'application/json'
+			}),
+			withCredentials: true,
+		};
+
+		constructor(
+			public http: HttpClient,
+			// public route: ActivatedRoute,
+			// public router: Router,
+			// @Inject(ApiOptionsParams) public apiOptions: `).Raw(ApiClassName(p) + "Options").Raw(`,
+			public apiOptions: `).Raw(ApiClassName(p) + "Options").Raw(`,
+		) {
+			console.log('apiOptions `).Raw(ApiClassName(p)).Raw(`',apiOptions);
+			// this.route.queryParams.subscribe(params => { this.queryParams = params; });
+			this.Client = new Client(this);
+
+			if (Boolean(window[oauthWindowProp])) {
+				this.Auth = window[oauthWindowProp];
+			} else {
+				this.Auth = window[oauthWindowProp] = new Auth(this);
+			}
+		}
+
+		onready(fn: () => void) {
+			this.Auth.onAuthorize$.pipe(filter(grant => Boolean(grant))).subscribe(fn);
+			// if (this.Auth.grant) {
+			// 	fn();
+			// } else {
+			// 	this.poolReady.push(fn);
+			// }
+		}
+
+		options(opts?: HttpOptions): HttpOptions {
+
+			opts = Object.assign({
+				headers: new HttpHeaders(),
+				params: {},
+			}, opts);
+	
+	
+			const headers = {
+				'Authorization': this.Auth.Token()
+			};
+	
+			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)));
+			}
+	
+			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(hopts),
+			// 	params: {}
+			// }, opts);
+	
+			// // Converte a query para base64
+			// if (options.params.q) {
+			// 	options.params.q = window.btoa(unescape(encodeURIComponent(options.params.q)));
+			// }
+	
+			return options;
+		}
+	
+		copyHeader(headers, hopts) {
+			if (!headers) { return; }
+			headers.keys().map(key => { hopts[key] = headers.get(key); });
+		}
+	}`)
+	// .Injetable("{providedIn: 'root'}").Export().Class().Id(ApiClassName(p)).Block(
+	// 	TS.Public().Id("Client").Op(":").Id("Client").Endl(),
+	// 	TS.Public().Id("baseURL").Op("=").Lit(p.BaseURL).Endl(),
+	// 	TS.Public().Id("Auth").Op(":").Id("Auth").Endl(),
+	// 	TS.Public().Id("httpOptions").Id("=").Block(
+	// 		TS.Id("headers").Op(":").Raw("new HttpHeaders").Call(TS.Block(
+	// 			TS.Raw("'Content-Type': 'application/json',"),
+	// 			TS.Raw("'Accept': 'application/json'"),
+	// 		)),
+	// 	).Endl(),
+	// 	// TS.Public().Id("http").Op(":").Id("HttpClient").Endl(),
+	// 	TS.Constructor(
+	// 		TS.Public().Id("http").Op(":").Id("HttpClient"),
+	// 	).Block(
+	// 		TS.This().Dot("Client").Op("=").New().Id("Client").Call(TS.This()).Endl(),
+	// 		// TS.Id("this.http").Op("=").New().Id("Client").Call(TS.This()).Endl(),
+	// 	),
+	// 	TS.Id("Options").Params(
+	// 		TS.Id("opts").Op(":").Id("ServiceOptions"),
+	// 	).Op(":").Id("{headers: HttpHeaders,params: any}").Block(
+	// 		TS.Return().Block(
+	// 			TS.Raw("headers: this.httpOptions.headers,"),
+	// 			// TS.Raw("params : opts.Params()"),
+	// 			TS.Raw("params : opts"),
+	// 		),
+	// 	),
+	// ).Line()
+
+	module.Line().Raw(`
+
+	export function ApiOptionsProvider(options) {
+		console.log('ApiOptionsProvider', options);
+		return options;
+	}
+
+	@NgModule({
+		imports: [
+		  CommonModule,
+		  HttpClientModule,
+		]
+	  })
+	`).Export().
+		Class().
+		Id(p.Name + "ApiModule").
+		Block(
+			TS.Raw(`
+			static forRoot( options?: `).Raw(ApiClassName(p) + "Options").Raw(` ) : ModuleWithProviders {
+			// static forRoot( options?:any ) : ModuleWithProviders {
+				return({
+					ngModule: `).Raw(p.Name + "ApiModule").Raw(`,
+					providers: [
+						{
+							provide: ApiOptionsParams,
+							useValue: options,
+						},
+						{
+							provide: `).Raw(ApiClassName(p) + "Options").Raw(`,
+							useFactory: ApiOptionsProvider,
+							deps:[ApiOptionsParams]
+						}
+					]
+				});
+			}
+			`),
+		).
+		Line()
+
+	return nil
+}
+
+// createClientClass cria a classe de cliente que guarda a referencia para todos os servicos da api
+func createClientClass(p *Project, file *TS.File) {
+	var (
+		angularResourceName      string
+		angularClientStmts       = []TS.CodeInterface{}
+		angularClientConstructor = []TS.CodeInterface{}
+	)
+
+	for _, resource := range p.Resources {
+
+		angularResourceName = strings.Title(resource.ID) + "Service"
+
+		angularClientStmts = append(
+			angularClientStmts,
+			TS.Public().Id(strings.Title(resource.ID)).Op(":").Id(angularResourceName).Endl(),
+		)
+
+		angularClientConstructor = append(
+			angularClientConstructor,
+			TS.This().Dot(strings.Title(resource.ID)).Op("=").New().Id(angularResourceName).Call(TS.Id("api")).Endl(),
+		)
+	}
+	angularClientConstructor = append(
+		angularClientConstructor,
+		TS.Raw("this.Generic = new ServiceStmt").Call(TS.Id("api")).Endl(),
+	)
+	// angularClientStmts = append(
+	// 	angularClientStmts,
+	// 	TS.Raw(`public onready = new EventEmitter<any>();`),
+	// )
+
+	angularClientStmts = append(
+		angularClientStmts,
+		TS.Public().Id("Generic: ServiceStmt").Endl(),
+	)
+
+	angularClientStmts = append(
+		angularClientStmts,
+		TS.Constructor(
+			TS.Protected().Id("api").Op(":").Id("ApiInterface"),
+		).Block(angularClientConstructor...),
+	)
+
+	file.Line().Comment(
+		"Class gerencia todos os serviços da api.",
+	).Export().Class().Id("Client").Block(angularClientStmts...)
+
+}
+
+// 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) {
+
+	var (
+		typ        string
+		params     = []TS.CodeInterface{}
+		paramsHttp = []TS.CodeInterface{}
+		// id         = method.ID
+		// ret        = TS.Id("Observable")
+		param         = "entity"
+		template      string
+		templateValue interface{}
+		defined       bool
+	)
+
+	if templateValue, defined = method.Custom["ts.template.method"]; defined {
+		template = templateValue.(string)
+	} else {
+		template = method.HttpMethod
+	}
+
+	template = strings.ToLower(template)
+
+	switch template {
+
+	case "post":
+		typ = "post"
+		params = append(params, TS.Id(param).Op(":").Id(method.Entity))
+		// ret.TypeAliase(method.Entity)
+	case "filter":
+		typ = "get"
+		// ret.TypeAliase(method.Entity)
+		param = ""
+		// params = append(params, TS.Id(param).Op(":").String())
+	case "get":
+		typ = "get"
+		// ret.TypeAliase(method.Entity)
+		param = ""
+	case "put":
+		typ = "put"
+		// ret.TypeAliase(method.Entity)
+		params = append(params, TS.Id(param).Op(":").Id(method.Entity).Op("|").String())
+	case "delete":
+		typ = "delete"
+		// ret.TypeAliase(method.Entity)
+		// params = append(params, TS.Id(param).Op(":").Id(method.Entity).Op("|").String())
+	case "patch":
+		typ = "patch"
+		params = append(params, TS.Id(param).Op(":").Id(method.Entity).Op("|").String())
+		// ret.TypeAliase(method.Entity)
+		// params = append(params, TS.Id(param).Op(":").Id(method.Entity).Op("|").String())
+	case "sse":
+		typ = "event"
+
+	default:
+		panic(fmt.Sprintf("Method '%s' template not defined!", template))
+	}
+
+	// paramsHttp = append(paramsHttp, TS.Raw("this.Url(url, opt.params)"))
+	paramsHttp = append(paramsHttp, TS.Raw(fmt.Sprintf("`${this.api.apiOptions.BASE_URL}%s`", method.Path)))
+
+	switch typ {
+	case "post", "put", "patch":
+		paramsHttp = append(paramsHttp, TS.Id("entity"))
+	}
+	params = append(params, TS.Id("opt?: HttpOptions"))
+
+	paramsHttp = append(paramsHttp, TS.Id("opt"))
+
+	// *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(),
+	).Line())
+}

+ 521 - 0
translate/tst/schemas.go

@@ -0,0 +1,521 @@
+package tst
+
+import (
+	"fmt"
+	"strings"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common" // . "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/gen"
+	TS "git.eugeniocarvalho.dev/eugeniucarvalho/gg/generators/typescript"
+)
+
+func GenSchemas(p *Project) error {
+	var (
+		attribName      string
+		propName        string
+		attribs         []TS.CodeInterface
+		attrib          *TS.Group
+		entityInfo      *EntityInfo
+		arrayProperties = []*Propertie{}
+	)
+
+	appendBasePatchClass()
+
+	for _, entity := range p.Schemas {
+
+		attribs = []TS.CodeInterface{}
+		arrayProperties = []*Propertie{}
+
+		entityInfo = p.ResponseEntity(entity.ID)
+
+		if entityInfo.IsGeneric {
+			continue
+		}
+
+		for _, meta := range entity.Properties {
+
+			propName = strings.Title(meta.ID)
+			meta.FillTags(p, propName)
+
+			// Registra a relaao entre as entidades
+			// if meta.Relation {
+			// 	SR.Add(&Relation{
+			// 		Source:     meta.GetType(),
+			// 		Target:     entity.ID,
+			// 		Attr:       strings.Replace(meta.Tags["bson"], ",omitempty", "", 1),
+			// 		Collection: entity.Collection,
+			// 	})
+			// }
+			if meta.Array {
+				arrayProperties = append(arrayProperties, meta)
+			}
+
+			// Adiciona as tags caso sejam definidas
+			if meta.Tags != nil {
+				if name, ok := meta.Tags["json"]; ok {
+					attribName = strings.Split(name, ",")[0]
+					if attribName == "-" {
+						attribName = meta.ID
+					}
+				}
+			} else {
+				attribName = meta.ID
+			}
+
+			if attribName == "id" {
+				attribName = "_id"
+			}
+
+			meta.Name = attribName
+			// Adiciona a crescricao como comentario
+
+			attrib = &TS.Group{}
+
+			if meta.Description != "" {
+				// propertie.Comment(meta.Description)
+				attrib.Comment(meta.Description)
+			}
+
+			if attribName != "" {
+
+				// attrib.Add(TS.Public().Id(attribName).Op(":").Id(TS.ConvType(meta.Type)))
+				attrib.Add(TS.Id(attribName).Op(":").Id(TS.ConvType(meta.Type)))
+				if meta.Array {
+					attrib.Add(TS.Index())
+				}
+				attrib.Endl()
+				attribs = append(attribs, attrib)
+			}
+		} // Fim do loop dos atributos
+
+		// attribs = append(attribs, TS.Constructor(TS.Raw("opts: any")).Block(TS.Raw("opts && Object.assign(this,opts);")))
+
+		module.Line().Export().Class().Id(strings.Title(entity.ID)).Block(
+			// module.Line().Export().Interface().Id(strings.Title(entity.ID)).Block(
+			// TS.Constructor(TS.Id(`options: `)),
+			attribs...,
+		).Line()
+
+		createPathClass(entity, arrayProperties)
+
+	} // Fim do for de schema
+	return nil
+}
+
+func createPathClass(entity *Entity, arrayProperties []*Propertie) {
+
+	module.
+		Line().
+		Export().
+		Class().
+		Id(strings.Title(entity.ID + "Patch")).
+		Extends().
+		Id("BasePatch").
+		BlockFun(func(g *TS.Group) {
+			createClassProperties(g, entity, arrayProperties)
+
+			createSetInitializeMethod(g, entity, arrayProperties)
+
+			// createPatchMethod(g, entity, arrayProperties)
+
+			createAddAndRemoveMethods(g, entity, arrayProperties)
+
+			// g.Raw(`
+			// protected emit(timeout = 0) {
+			// 	super.emit(this.Patchs(), timeout);
+			// }
+			// `)
+		}).
+		Line()
+}
+
+func createClassProperties(g *TS.Group, entity *Entity, arrayProperties []*Propertie) {
+	for _, meta := range arrayProperties {
+		g.Line().Add(
+			TS.Raw(fmt.Sprintf("%s = new Map<string, { item: %s, action: string, type: string }>();", meta.ID, strings.Title(entity.ID))),
+		)
+	}
+}
+func createSetInitializeMethod(g *TS.Group, entity *Entity, arrayProperties []*Propertie) {
+	destructuring := []string{}
+
+	for _, meta := range arrayProperties {
+		destructuring = append(destructuring, fmt.Sprintf("%s=[]", meta.ID))
+	}
+
+	destructuringString := strings.Join(destructuring, ",")
+
+	g.Add(TS.
+		Line().
+		Line().
+		// Do().
+
+		Constructor(TS.Raw(fmt.Sprintf("{%s} : %s", destructuringString, entity.ID))).
+		BlockFun(func(block *TS.Group) {
+			// block.Add(TS.Raw(`this.instance = instance;`).Line())
+			block.Add(TS.Raw(`super();`).Line())
+
+			// .Raw(`const {`)
+
+			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())
+			}
+
+		}),
+	)
+}
+
+func createPatchMethod(g *TS.Group, entity *Entity, arrayProperties []*Propertie) {
+
+	g.Add(TS.Line().
+		Raw(`Patchs()`).
+		BlockFun(func(block *TS.Group) {
+
+			block.Add(TS.Raw(`const patchs = {`))
+
+			for _, meta := range arrayProperties {
+				block.Add(TS.
+					Line().
+					Raw(fmt.Sprintf("Add%s : [],", strings.Title(meta.ID))).
+					Line().
+					Raw(fmt.Sprintf("Remove%s : [],", strings.Title(meta.ID))),
+				)
+			}
+
+			block.
+				Line().
+				Raw(`};`).
+				Line().
+				Raw(`return patchs;`).
+				Line()
+		}),
+	)
+
+}
+
+func createAddAndRemoveMethods(g *TS.Group, entity *Entity, arrayProperties []*Propertie) {
+	const timeout = 50
+
+	for _, meta := range arrayProperties {
+		g.Add(TS.
+			Line().
+			Id(fmt.Sprintf("Add%s", strings.Title(meta.ID))).
+			Params(TS.Id("itens"), TS.Id(fmt.Sprintf("timeout = %d", timeout))).
+			// Params(TS.Id("itens"), TS.Id("timeout")).
+			Block(
+				TS.Raw(fmt.Sprintf("this._set(this.%s, itens, 'add', timeout);return this;", meta.ID)),
+			).
+			Line().
+			Id(fmt.Sprintf("Remove%s", strings.Title(meta.ID))).
+			Params(TS.Id("itens"), TS.Id(fmt.Sprintf("timeout = %d", timeout))).
+			// Params(TS.Id("itens"), TS.Id("timeout")).
+			Block(
+				TS.Raw(fmt.Sprintf("this._set(this.%s, itens, 'remove', timeout);return this;", meta.ID)),
+			),
+		)
+	}
+}
+
+func appendBasePatchClass() {
+	module.Line().Raw(`
+	export class BasePatch {
+		protected $set = {};
+		protected $subscription: any;
+		public locked = false;
+		public change$ = new EventEmitter<any>();
+		public $transformPatch = (patch) => patch;
+
+		Set(options, timeout = 0) {
+			this.$set = {...this.$set, ...options};
+			
+			if (timeout >= 0){
+				this.emit(this.patch(), timeout);
+			}
+		}
+		
+		protected emit(patch:any , timeout = 0) {
+			if (this.$subscription) { clearTimeout(this.$subscription); }
+			this.locked = true;
+			this.$subscription = setTimeout(() => this.change$.emit(patch), timeout);
+		}
+
+		protected __set(list, add, rem) {
+			list.forEach(element => {
+				const item = element.item;
+				switch (element.action) {
+					case 'add':
+					add.push(item);
+					break;
+					case 'remove':
+					if (element.type !== 'new') { rem.push(item._id || item); } 
+					break;
+				}
+			});
+		}
+		
+		public from(patches, timeout = 50){
+			const {$set = {}} = patches;
+			this.Set($set, timeout);
+			
+			Object.keys(patches).map(prop => {
+				if (prop.startsWith('$')) { return;}
+				if (typeof this[prop] === 'function') {
+					this[prop](patches[prop], timeout);
+				}
+			});
+		}
+
+		protected _set(_map: Map<string, any>, itens, action, timeout = 50) {
+			(itens || []).map(item => {
+		
+				const id = (item._id || item);
+				const has = _map.has(id)
+					, el = _map.get(id);
+		
+				switch (action) {
+					case 'add':
+					if (!has) {
+						_map.set(id, {
+						item: item,
+						action: 'add',
+						type: 'new',
+						});
+					} else if (el.action === 'remove') {
+						el.action = 'add';
+					}
+					break;
+					case 'remove':
+					if (has) {
+						el.action = 'remove';
+						break;
+					}
+					// tslint:disable-next-line:no-switch-case-fall-through
+					default:
+					_map.set(id, {
+						item,
+						action,
+						type: '',
+					});
+				}
+			});
+
+			if (timeout >= 0){
+				this.emit(this.patch(), timeout);
+			}
+
+		}
+
+	    patch() {
+			const $this = this;
+			const patchs = {};
+
+			if (Object.getOwnPropertyNames(this.$set).length) {
+				patchs['$set'] = this.$set;
+			}
+
+			Object.keys($this).map(prop => {
+				if (prop.startsWith('$')) {
+					return;
+				}
+				const adds = [], remove = [];
+				const captalized = prop[0].toUpperCase() + prop.slice(1);
+				
+				if ($this[prop].size) {
+
+					this.__set($this[prop], adds, remove);
+					
+					if (adds.length){ patchs[`).Raw("`Add${captalized}`").Raw(`] = adds; }
+					
+					if (remove.length){ patchs[`).Raw("`Remove${captalized}`").Raw(`] = remove; }
+				}
+			});
+			
+			return this.$transformPatch(patchs);
+		}
+	}
+	`)
+}
+
+// 	public Patchs() {
+
+// 	  const patchs = {
+// 		AddMethods: [],
+// 		AddSamples: [],
+// 		AddCaracterizations: [],
+// 		RemoveMethods: [],
+// 		RemoveSamples: [],
+// 		RemoveCaracterizations: [],
+// 		$set: this.$set,
+// 	  };
+
+// 	  console.log('paths', this);
+
+// 	  this.__set(this._samples, patchs.AddSamples, patchs.RemoveSamples);
+// 	  this.__set(this._methods, patchs.AddMethods, patchs.RemoveMethods);
+
+// 	  patchs.RemoveSamples.map(s => {
+// 		this.RemoveCaracterizations(this._form.caracterizations.filter(c => {
+// 		  return c.sample === s.id;
+// 		}));
+// 	  });
+
+// 	  patchs.RemoveMethods.map(m => {
+// 		this.RemoveCaracterizations(this._form.caracterizations.filter(c => {
+// 		  return c.method === m.id;
+// 		}));
+// 	  });
+
+// 	  this.__set(this._caracterizations, patchs.AddCaracterizations, patchs.RemoveCaracterizations);
+
+// 	  return patchs;
+// 	}
+// public Patchs(patchs) {
+// 	const patchs = {
+// 		AddMethods: [],
+// 		AddSamples: [],
+// 		AddCaracterizations: [],
+// 		RemoveMethods: [],
+// 		RemoveSamples: [],
+// 		RemoveCaracterizations: [],
+// 		$set: this.$set,
+// 	};
+
+// 	console.log('paths', this);
+
+// 	this.__set(this._samples, patchs.AddSamples, patchs.RemoveSamples);
+// 	this.__set(this._methods, patchs.AddMethods, patchs.RemoveMethods);
+
+// 	patchs.RemoveSamples.map(s => {
+// 	this.RemoveCaracterizations(this._form.caracterizations.filter(c => {
+// 		return c.sample === s.id;
+// 	}));
+// 	});
+
+// 	patchs.RemoveMethods.map(m => {
+// 	this.RemoveCaracterizations(this._form.caracterizations.filter(c => {
+// 		return c.method === m.id;
+// 	}));
+// 	});
+
+// 	this.__set(this._caracterizations, patchs.AddCaracterizations, patchs.RemoveCaracterizations);
+
+// 	return patchs;
+// }
+
+// export class FormPatch {
+
+// 	$set = {};
+// 	_form: CaracterizationForm;
+// 	_samples = new Map<string, { item: Sample, action: string, type: string }>();
+// 	_methods = new Map<string, { item: CaracterizationMethod, action: string, type: string }>();
+// 	_caracterizations = new Map<string, { item: Caracterization, action: string, type: string }>();
+
+// 	setSolicitation(form: CaracterizationForm) {
+// 	  // this._set(this._methods, form.methods.map(m => { return { id: m.id } }), '');
+// 	  this._form = form;
+// 	  this._set(this._samples, form.samples, '');
+// 	  this._set(this._methods, form.methods, '');
+// 	  this._set(this._caracterizations, form.caracterizations, '');
+// 	}
+
+// 	protected __set(list, add, rem) {
+// 	  list.forEach((i) => {
+// 		switch (i.action) {
+// 		  case 'add':
+// 			add.push(i.item);
+// 			break;
+// 		  case 'remove':
+// 			(i.type !== 'new') && rem.push(i.item);
+// 			break;
+// 		}
+// 	  });
+// 	}
+
+// 	public Patchs() {
+
+// 	  const patchs = {
+// 		AddMethods: [],
+// 		AddSamples: [],
+// 		AddCaracterizations: [],
+// 		RemoveMethods: [],
+// 		RemoveSamples: [],
+// 		RemoveCaracterizations: [],
+// 		$set: this.$set,
+// 	  };
+
+// 	  console.log('paths', this);
+
+// 	  this.__set(this._samples, patchs.AddSamples, patchs.RemoveSamples);
+// 	  this.__set(this._methods, patchs.AddMethods, patchs.RemoveMethods);
+
+// 	  patchs.RemoveSamples.map(s => {
+// 		this.RemoveCaracterizations(this._form.caracterizations.filter(c => {
+// 		  return c.sample === s.id;
+// 		}));
+// 	  });
+
+// 	  patchs.RemoveMethods.map(m => {
+// 		this.RemoveCaracterizations(this._form.caracterizations.filter(c => {
+// 		  return c.method === m.id;
+// 		}));
+// 	  });
+
+// 	  this.__set(this._caracterizations, patchs.AddCaracterizations, patchs.RemoveCaracterizations);
+
+// 	  return patchs;
+// 	}
+
+// 	protected _set(_map: Map<string, any>, itens, action) {
+// 	  (itens || []).map(item => {
+// 		const id = item.id || (item.method + item.sample);
+// 		const has = _map.has(id)
+// 		  , el = _map.get(id);
+
+// 		switch (action) {
+// 		  case 'add':
+// 			if (!has) {
+// 			  _map.set(id, {
+// 				item: item,
+// 				action: 'add',
+// 				type: 'new',
+// 			  });
+// 			} else if (el.action === 'remove') {
+// 			  el.action = 'add';
+// 			}
+// 			break;
+// 		  case 'remove':
+// 			if (has) {
+// 			  el.action = 'remove';
+// 			}
+// 			break;
+// 		  default:
+// 			_map.set(id, {
+// 			  item: item,
+// 			  action: '',
+// 			  type: '',
+// 			});
+// 		}
+// 	  });
+// 	}
+
+// 	AddMethods(itens) {
+// 	  this._set(this._methods, itens, 'add');
+// 	}
+
+// 	AddSamples(itens) {
+// 	  this._set(this._samples, itens, 'add');
+// 	}
+// 	AddCaracterizations(itens) {
+// 	  this._set(this._caracterizations, itens, 'add');
+// 	}
+// 	RemoveMethods(itens) {
+// 	  this._set(this._methods, itens, 'remove');
+// 	}
+// 	RemoveSamples(itens) {
+// 	  this._set(this._samples, itens, 'remove');
+// 	}
+// 	RemoveCaracterizations(itens) {
+// 	  this._set(this._caracterizations, itens, 'remove');
+// 	}
+
+//   }

+ 92 - 0
translate/tst/translate.go

@@ -0,0 +1,92 @@
+package tst
+
+import (
+	"fmt"
+
+	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
+	TS "git.eugeniocarvalho.dev/eugeniucarvalho/gg/generators/typescript"
+)
+
+var (
+	module *TS.File
+)
+
+func Translate(project *Project) (err error) {
+
+	ts := project.Client("angular6")
+
+	path := fmt.Sprintf("%s/%s.module.ts", ts.OutputDir, project.ID)
+
+	module = TS.NewFile(path)
+
+	// fmt.Printf("Store angular module '%s'\n", path)
+	// Adiciona os importes necessarios
+	imports(project, module)
+
+	// Cria todas as classes de entidades do projeto
+	if err = GenSchemas(project); err != nil {
+		return
+	}
+	// Define os objetos de acesso a API
+	GenResources(project)
+
+	return module.Save()
+}
+
+func imports(project *Project, file *TS.File) {
+
+	file.Raw(`
+	// import { NgModule, Injectable, EventEmitter, ModuleWithProviders } from '@angular/core';
+	import { NgModule, Injectable, EventEmitter, ModuleWithProviders, InjectionToken, Inject } from '@angular/core';
+	// import { BrowserModule } from '@angular/platform-browser';
+	import { CommonModule } from '@angular/common';
+	import { HttpClientModule, HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
+	import { Observable, of, Subject, BehaviorSubject} from 'rxjs';
+	// import { ActivatedRoute, Router } from '@angular/router';
+	import { catchError, map, switchMap, tap, take, filter } from 'rxjs/operators';
+	import { EventSourcePolyfill } from 'event-source-polyfill';
+	
+	if (!Boolean(window['oauth_ss'])) {
+		window['oauth_ss'] = Math.random().toString(30).substr(2, 11).toUpperCase();
+	}
+	
+	const oauthWindowProp = `).Raw(fmt.Sprintf("`__${window['oauth_ss']}__`")).Raw(`;
+
+	// @Injectable({
+	// 	providedIn: 'root'
+	// })
+	export class `).Raw(project.Name + "ApiOptions").Raw(` {
+		public COOKIE_DOMAIN:string;
+		public BASE_URL:string;
+		public ACCOUNT_URL:string;
+		public CONSOLE_URL:string;
+		public REDIRECT_URI:string;
+		public CLIENT_ID:string;
+		public THREAD_SOCKET:string;
+		public CLIENT_SECRET:string;
+		public SCOPES: string[];
+		
+	}
+
+	export const ApiOptionsParams = new InjectionToken<`).Raw(ApiClassName(project) + "Options").Raw(`>('ApiOptionsParams');
+
+	export interface ApiInterface {
+		Client: Client;
+		Auth: Auth;
+		poolReady: any[];
+		httpOptions: {
+			headers: HttpHeaders,
+			withCredentials: boolean,
+			};
+		http: HttpClient;
+		// queryParams: any;
+		// route: ActivatedRoute;
+		// router: Router;
+		apiOptions: any;
+		options(opts?: HttpOptions): HttpOptions;
+		onready(fn: () => void);
+		copyHeader(headers, hopts);
+	}
+	
+	`).Line()
+}