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") }