| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 |
- package main
- import (
- "archive/tar"
- "bytes"
- "compress/gzip"
- "encoding/json"
- "flag"
- "fmt"
- "html"
- "html/template"
- "io/ioutil"
- "net/http"
- "os"
- "os/signal"
- "path/filepath"
- "strconv"
- "strings"
- "syscall"
- "time"
- "github.com/fgrid/uuid"
- )
- const emptyUUID = "00000000-0000-0000-0000-000000000000"
- func isUpper(c byte) bool {
- return c >= 'A' && c <= 'Z'
- }
- func toLower(c byte) byte {
- return c + 32
- }
- func Underscore(s string) string {
- r := make([]byte, 0, len(s))
- for i := 0; i < len(s); i++ {
- c := s[i]
- if isUpper(c) {
- if i > 0 && i+1 < len(s) && (!isUpper(s[i-1]) || !isUpper(s[i+1])) {
- r = append(r, '_', toLower(c))
- } else {
- r = append(r, toLower(c))
- }
- } else {
- r = append(r, c)
- }
- }
- return string(r)
- }
- func (a *AppConfig) Underscore(s string) string {
- return Underscore(s)
- }
- type Column struct {
- Name string
- Type string
- Null bool
- }
- type DataConfig struct {
- Name string
- Columns []Column
- Routing Routing
- }
- type Routing map[string]string
- type AppConfig struct {
- Name string `json:"name"`
- URL string `json:"url"`
- Email string `json:"email"`
- MsTimeout int `json:"ms_timeout"`
- Data []DataConfig
- }
- func (a *AppConfig) load(path string) {
- if len(path) <= 0 {
- panic("No configuration file defined!")
- }
- content, err := ioutil.ReadFile(path)
- if err != nil {
- panic(err)
- }
- err = json.Unmarshal(content, a)
- if err != nil {
- panic(err)
- }
- }
- type routeTempData struct {
- Name string
- Lower string
- }
- func (a *AppConfig) GetRoutes() string {
- funcTemp := `func (app *application) {{.Lower}}(w http.ResponseWriter, r *http.Request) {
- app.runner(w, r, func() *chanRet {
- return execByVerb(r,
- func(body []byte) ([]byte, error) {
- data := &db.{{.Name}}{}
- err := json.Unmarshal(body, &data)
- if err != nil {
- app.Publish("error", &AppError{
- error: err,
- Action: "post request - {{.Name}}",
- Data: string(body),
- })
- }
- status := "created"
- if len(data.UUID) != 0 {
- status = "updated"
- }
- err = app.db.Insert{{.Name}}(data)
- if err == nil {
- app.Publish("{{.Lower}}." + status, &EventMessage{
- Status: status,
- DataType: "{{.Name}}",
- Data: data,
- })
- } else {
- app.Publish("error", &AppError{
- error: err,
- Action: "post request - {{.Name}}",
- Data: string(body),
- })
- return nil, err
- }
- return json.Marshal(data)
- },
- func(key string) ([]byte, error) {
- result, err := app.db.Get{{.Name}}(key)
- if err != nil {
- app.Publish("error", &AppError{
- error: err,
- Action: "get request - {{.Name}}",
- Data: key,
- })
- return nil, err
- }
- return json.Marshal(result)
- },
- func(key string) error {
- err := app.db.Delete{{.Name}}(key)
- if err == nil {
- app.Publish("{{.Lower}}.deleted", &EventMessage{
- Status: "deleted",
- DataType: "{{.Name}}",
- Data: &response{Message: key},
- })
- } else {
- app.Publish("error", &AppError{
- error: err,
- Action: "delete request - {{.Name}}",
- Data: key,
- })
- }
- return err
- })
- })
- }
- `
- specialRoute := `func (app *application) %s(w http.ResponseWriter, r *http.Request) {
- app.runner(w, r, func() *chanRet {
- return execByVerb(r,
- func(body []byte) ([]byte, error) {
- return nil, nil
- },
- func(key string) ([]byte, error) {
- result, err := app.db.Get%s(key)
- if err != nil {
- return nil, err
- }
- return json.Marshal(result)
- },
- func(key string) error {
- return nil
- })
- })
- }
- `
- tmpl, err := template.New("func").Parse(funcTemp)
- if err != nil {
- panic(err)
- }
- routes := ""
- buf := bytes.NewBuffer([]byte{})
- for _, data := range a.Data {
- rData := &routeTempData{
- Name: data.Name,
- Lower: strings.ToLower(data.Name),
- }
- buf.Reset()
- tmpl.Execute(buf, rData)
- routes += buf.String()
- for _, route := range data.Routing {
- routes += fmt.Sprintf(specialRoute, strings.ToLower(data.Name)+"By"+route, data.Name+"By"+route)
- }
- }
- return routes
- }
- func (a *AppConfig) MuxHandlers() string {
- muxTemp := `mux.Handle("/%s", contextualize(time.Millisecond*` + strconv.Itoa(a.MsTimeout) + `)(http.HandlerFunc(app.%s)))
- `
- muxes := ""
- for _, data := range a.Data {
- muxes += fmt.Sprintf(muxTemp, Underscore(data.Name), strings.ToLower(data.Name))
- for key, route := range data.Routing {
- muxes += fmt.Sprintf(muxTemp, Underscore(data.Name)+"/"+key, strings.ToLower(data.Name)+"By"+route)
- }
- }
- return muxes
- }
- func (data *DataConfig) UniqueID() string {
- return "strconv.Itoa(time.Now().Nanosecond())"
- }
- func (data *DataConfig) GetCreate() string {
- query := `
- CREATE TABLE IF NOT EXISTS %s (
- uuid UUID PRIMARY KEY,
- %s
- created TIMESTAMP WITH TIME ZONE NULL,
- updated TIMESTAMP WITH TIME ZONE NULL,
- deleted TIMESTAMP WITH TIME ZONE NULL
- );`
- columnDef := ""
- for _, c := range data.Columns {
- nullable := " NOT NULL"
- if c.Null {
- nullable = " NULL"
- }
- columnDef += Underscore(c.Name) + " " + getPsqlStringByType(c.Type) + nullable + ", "
- }
- return fmt.Sprintf(query, Underscore(data.Name), columnDef)
- }
- func (data *DataConfig) GetName() string {
- return Underscore(data.Name)
- }
- func (data *DataConfig) GetSelectParams() string {
- cont := "uuid"
- for _, c := range data.Columns {
- cont += "," + Underscore(c.Name)
- }
- return cont
- }
- func (data *DataConfig) GetInsert() string {
- query := `INSERT INTO %s ("uuid"%s
- VALUES(%s, now())
- ON CONFLICT("uuid") DO UPDATE SET
- %s`
- columnDef := ""
- valueDef := "$1"
- setDef := "updated=now()"
- valCnt := 1
- for _, c := range data.Columns {
- valCnt++
- setDef += ", " + Underscore(c.Name) + "=$" + strconv.Itoa(valCnt)
- columnDef += "," + Underscore(c.Name)
- valueDef += ",$" + strconv.Itoa(valCnt)
- }
- columnDef += ", created)"
- return fmt.Sprintf(query, Underscore(data.Name), columnDef, valueDef, setDef)
- }
- // Name Aliases Description
- // cidr IPv4 or IPv6 network address
- // double precision float8 double precision floating-point number (8 bytes)
- // inet IPv4 or IPv6 host address
- // macaddr MAC (Media Access Control) address
- // money currency amount
- // numeric [ (p, s) ] decimal [ (p, s) ] exact numeric of selectable precision
- type dataType struct {
- GoString string
- PsqlString string
- TEST float32
- }
- var dataTypes = map[string]dataType{
- "TEXT": dataType{
- GoString: "string",
- PsqlString: "TEXT",
- },
- "UUID": dataType{
- GoString: "string",
- PsqlString: "uuid",
- },
- "INTEGER": dataType{
- GoString: "int",
- PsqlString: "INTEGER",
- },
- "SMALL": dataType{
- GoString: "int16",
- PsqlString: "smallint",
- },
- "BIG": dataType{
- GoString: "int64",
- PsqlString: "bigint",
- },
- "FLOAT": dataType{
- GoString: "float32",
- PsqlString: "real",
- },
- "DOUBLE": dataType{
- GoString: "float64",
- PsqlString: "double",
- },
- "POINT": dataType{
- GoString: "string",
- PsqlString: "point",
- },
- "BOOL": dataType{
- GoString: "bool",
- PsqlString: "BOOLEAN",
- },
- "TIME": dataType{
- GoString: "int64",
- PsqlString: "TIMESTAMP WITH TIME ZONE",
- },
- }
- func getGoStringByType(t string) string {
- return dataTypes[t].GoString
- }
- func getPsqlStringByType(t string) string {
- return dataTypes[t].PsqlString
- }
- func (data *DataConfig) GetGoStruct() string {
- structDef := `type %s struct {
- UUID string ` + "`" + `json:"uuid"` + "`" + `%s
- }`
- dataDef := ""
- for _, c := range data.Columns {
- entry := c.Name + " " + getGoStringByType(c.Type) + " `" + `json:"` + Underscore(c.Name) + "\"`"
- dataDef += fmt.Sprintf(`
- %s`, entry)
- }
- return fmt.Sprintf(structDef, data.Name, dataDef)
- }
- func (data *DataConfig) CleanData() string {
- actions := ""
- for _, c := range data.Columns {
- if strings.ToUpper(c.Type) == "UUID" {
- actions += fmt.Sprintf(`if len(data.%s) == 0 {
- data.%s = "00000000-0000-0000-0000-000000000000"
- }
- `, c.Name, c.Name)
- }
- }
- return actions
- }
- func (data *DataConfig) GetGoParams() string {
- params := ""
- for _, c := range data.Columns {
- params += ", data." + c.Name
- }
- return params
- }
- func (data *DataConfig) GetGoRefParams() string {
- params := ""
- for _, c := range data.Columns {
- params += ", &data." + c.Name
- }
- return params
- }
- type source struct {
- UUID string
- Name string
- Body *bytes.Buffer
- }
- func newSource(name string, pkg string, body *bytes.Buffer) source {
- newUUID := uuid.NewV5(uuid.NewNamespaceUUID(pkg), []byte(name)).String()
- return source{
- UUID: newUUID,
- Name: name,
- Body: body,
- }
- }
- type sourceMap struct {
- sourceData map[string][]source
- }
- func createMain(config *AppConfig) source {
- buf := bytes.NewBuffer([]byte{})
- if err := template.Must(template.ParseFiles("templates/app.tmpl")).Execute(buf, config); err != nil {
- panic(err)
- }
- return newSource("main", "main", buf)
- }
- func createDb(config *AppConfig) source {
- buf := bytes.NewBuffer([]byte{})
- if err := template.Must(template.ParseFiles("templates/data.tmpl")).Execute(buf, config); err != nil {
- panic(err)
- }
- return newSource("sql", "data", buf)
- }
- func generateFromDisk() bool {
- appConfigPath := flag.String("conf", "", "where is the configuration stored?")
- flag.Parse()
- if len(*appConfigPath) == 0 {
- return false
- }
- config := &AppConfig{}
- config.load(*appConfigPath)
- sMap := sourceMap{
- sourceData: map[string][]source{},
- }
- sMap.sourceData["main"] = append(sMap.sourceData["main"], createMain(config))
- sMap.sourceData["data"] = append(sMap.sourceData["data"], createDb(config))
- err := os.Chdir("gen_src")
- if err != nil {
- panic(err)
- }
- err = os.RemoveAll("./data")
- if err != nil {
- panic(err)
- }
- for k, w := range sMap.sourceData {
- if k != "main" {
- err = os.Mkdir(k, 0755)
- if err != nil {
- panic(err)
- }
- err = os.Chdir("./" + k)
- if err != nil {
- panic(err)
- }
- }
- for _, sauce := range w {
- err = ioutil.WriteFile(sauce.Name+".go", bytes.NewBufferString(html.UnescapeString(sauce.Body.String())).Bytes(), 0755)
- if err != nil {
- panic(err)
- }
- }
- if k != "main" {
- err = os.Chdir("./..")
- if err != nil {
- panic(err)
- }
- }
- }
- return true
- }
- func main() {
- if generateFromDisk() {
- return
- }
- mux := http.NewServeMux()
- mux.Handle("/generate.tar.gz", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- config := &AppConfig{}
- err := json.Unmarshal(bytes.NewBufferString(r.URL.Query().Get("config")).Bytes(), config)
- if err != nil {
- fmt.Printf("%+v", err)
- w.WriteHeader(http.StatusBadRequest)
- _, _ = w.Write([]byte{})
- return
- }
- sMap := sourceMap{
- sourceData: map[string][]source{},
- }
- sMap.sourceData["main"] = append(sMap.sourceData["main"], createMain(config))
- sMap.sourceData["data"] = append(sMap.sourceData["data"], createDb(config))
- dirName := config.Name
- if err != nil {
- fmt.Printf("%+v", err)
- w.WriteHeader(http.StatusInternalServerError)
- _, _ = w.Write([]byte{})
- return
- }
- gzw := gzip.NewWriter(w)
- defer gzw.Close()
- tw := tar.NewWriter(gzw)
- if err := tw.Flush(); err != nil {
- fmt.Printf("%+v", err)
- }
- defer tw.Close()
- for k, sa := range sMap.sourceData {
- subdirName := dirName
- if k != "main" {
- subdirName = dirName + "/" + k
- }
- if err != nil {
- fmt.Printf("%+v", err)
- w.WriteHeader(http.StatusInternalServerError)
- _, _ = w.Write([]byte{})
- return
- }
- for _, sauce := range sa {
- file := subdirName + "/" + sauce.Name + ".go"
- contentBytes := bytes.NewBufferString(html.UnescapeString(sauce.Body.String())).Bytes()
- header := &tar.Header{
- Mode: 0777,
- Typeflag: tar.TypeReg,
- Name: strings.TrimPrefix(file, string(filepath.Separator)),
- ModTime: time.Now(),
- Size: int64(len(contentBytes)),
- }
- if err := tw.WriteHeader(header); err != nil {
- fmt.Printf("%+v", err)
- w.WriteHeader(http.StatusInternalServerError)
- _, _ = w.Write([]byte{})
- return
- }
- b, err := tw.Write(contentBytes)
- if err != nil {
- fmt.Printf("%+v", err)
- w.WriteHeader(http.StatusInternalServerError)
- _, _ = w.Write([]byte{})
- return
- }
- fmt.Println(b)
- }
- }
- }))
- server := &http.Server{
- Addr: fmt.Sprintf(
- "%s:%d",
- "0.0.0.0",
- 9111,
- ),
- Handler: mux,
- }
- serverStartup := make(chan error, 1)
- go func() {
- serverStartup <- server.ListenAndServe()
- }()
- osSignals := make(chan os.Signal, 1)
- signal.Notify(
- osSignals,
- syscall.SIGINT,
- syscall.SIGTERM,
- syscall.SIGKILL,
- syscall.SIGQUIT,
- )
- select {
- case sig := <-osSignals:
- fmt.Printf(sig.String())
- case err := <-serverStartup:
- fmt.Printf(err.Error())
- }
- fmt.Printf("\n\nADIOS! TOT ZIENS! HASTA LUEGO!\n")
- }
|