main.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. package main
  2. import (
  3. "archive/tar"
  4. "bytes"
  5. "compress/gzip"
  6. "encoding/json"
  7. "flag"
  8. "fmt"
  9. "html"
  10. "html/template"
  11. "io/ioutil"
  12. "net/http"
  13. "os"
  14. "os/signal"
  15. "path/filepath"
  16. "strconv"
  17. "strings"
  18. "syscall"
  19. "time"
  20. "github.com/fgrid/uuid"
  21. )
  22. const emptyUUID = "00000000-0000-0000-0000-000000000000"
  23. func isUpper(c byte) bool {
  24. return c >= 'A' && c <= 'Z'
  25. }
  26. func toLower(c byte) byte {
  27. return c + 32
  28. }
  29. func Underscore(s string) string {
  30. r := make([]byte, 0, len(s))
  31. for i := 0; i < len(s); i++ {
  32. c := s[i]
  33. if isUpper(c) {
  34. if i > 0 && i+1 < len(s) && (!isUpper(s[i-1]) || !isUpper(s[i+1])) {
  35. r = append(r, '_', toLower(c))
  36. } else {
  37. r = append(r, toLower(c))
  38. }
  39. } else {
  40. r = append(r, c)
  41. }
  42. }
  43. return string(r)
  44. }
  45. func (a *AppConfig) Underscore(s string) string {
  46. return Underscore(s)
  47. }
  48. type Column struct {
  49. Name string
  50. Type string
  51. Null bool
  52. }
  53. type DataConfig struct {
  54. Name string
  55. Columns []Column
  56. Routing Routing
  57. }
  58. type Routing map[string]string
  59. type AppConfig struct {
  60. Name string `json:"name"`
  61. URL string `json:"url"`
  62. Email string `json:"email"`
  63. MsTimeout int `json:"ms_timeout"`
  64. Data []DataConfig
  65. }
  66. func (a *AppConfig) load(path string) {
  67. if len(path) <= 0 {
  68. panic("No configuration file defined!")
  69. }
  70. content, err := ioutil.ReadFile(path)
  71. if err != nil {
  72. panic(err)
  73. }
  74. err = json.Unmarshal(content, a)
  75. if err != nil {
  76. panic(err)
  77. }
  78. }
  79. type routeTempData struct {
  80. Name string
  81. Lower string
  82. }
  83. func (a *AppConfig) GetRoutes() string {
  84. funcTemp := `func (app *application) {{.Lower}}(w http.ResponseWriter, r *http.Request) {
  85. app.runner(w, r, func() *chanRet {
  86. return execByVerb(r,
  87. func(body []byte) ([]byte, error) {
  88. data := &db.{{.Name}}{}
  89. err := json.Unmarshal(body, &data)
  90. if err != nil {
  91. app.Publish("error", &AppError{
  92. error: err,
  93. Action: "post request - {{.Name}}",
  94. Data: string(body),
  95. })
  96. }
  97. status := "created"
  98. if len(data.UUID) != 0 {
  99. status = "updated"
  100. }
  101. err = app.db.Insert{{.Name}}(data)
  102. if err == nil {
  103. app.Publish("{{.Lower}}." + status, &EventMessage{
  104. Status: status,
  105. DataType: "{{.Name}}",
  106. Data: data,
  107. })
  108. } else {
  109. app.Publish("error", &AppError{
  110. error: err,
  111. Action: "post request - {{.Name}}",
  112. Data: string(body),
  113. })
  114. return nil, err
  115. }
  116. return json.Marshal(data)
  117. },
  118. func(key string) ([]byte, error) {
  119. result, err := app.db.Get{{.Name}}(key)
  120. if err != nil {
  121. app.Publish("error", &AppError{
  122. error: err,
  123. Action: "get request - {{.Name}}",
  124. Data: key,
  125. })
  126. return nil, err
  127. }
  128. return json.Marshal(result)
  129. },
  130. func(key string) error {
  131. err := app.db.Delete{{.Name}}(key)
  132. if err == nil {
  133. app.Publish("{{.Lower}}.deleted", &EventMessage{
  134. Status: "deleted",
  135. DataType: "{{.Name}}",
  136. Data: &response{Message: key},
  137. })
  138. } else {
  139. app.Publish("error", &AppError{
  140. error: err,
  141. Action: "delete request - {{.Name}}",
  142. Data: key,
  143. })
  144. }
  145. return err
  146. })
  147. })
  148. }
  149. `
  150. specialRoute := `func (app *application) %s(w http.ResponseWriter, r *http.Request) {
  151. app.runner(w, r, func() *chanRet {
  152. return execByVerb(r,
  153. func(body []byte) ([]byte, error) {
  154. return nil, nil
  155. },
  156. func(key string) ([]byte, error) {
  157. result, err := app.db.Get%s(key)
  158. if err != nil {
  159. return nil, err
  160. }
  161. return json.Marshal(result)
  162. },
  163. func(key string) error {
  164. return nil
  165. })
  166. })
  167. }
  168. `
  169. tmpl, err := template.New("func").Parse(funcTemp)
  170. if err != nil {
  171. panic(err)
  172. }
  173. routes := ""
  174. buf := bytes.NewBuffer([]byte{})
  175. for _, data := range a.Data {
  176. rData := &routeTempData{
  177. Name: data.Name,
  178. Lower: strings.ToLower(data.Name),
  179. }
  180. buf.Reset()
  181. tmpl.Execute(buf, rData)
  182. routes += buf.String()
  183. for _, route := range data.Routing {
  184. routes += fmt.Sprintf(specialRoute, strings.ToLower(data.Name)+"By"+route, data.Name+"By"+route)
  185. }
  186. }
  187. return routes
  188. }
  189. func (a *AppConfig) MuxHandlers() string {
  190. muxTemp := `mux.Handle("/%s", contextualize(time.Millisecond*` + strconv.Itoa(a.MsTimeout) + `)(http.HandlerFunc(app.%s)))
  191. `
  192. muxes := ""
  193. for _, data := range a.Data {
  194. muxes += fmt.Sprintf(muxTemp, Underscore(data.Name), strings.ToLower(data.Name))
  195. for key, route := range data.Routing {
  196. muxes += fmt.Sprintf(muxTemp, Underscore(data.Name)+"/"+key, strings.ToLower(data.Name)+"By"+route)
  197. }
  198. }
  199. return muxes
  200. }
  201. func (data *DataConfig) UniqueID() string {
  202. return "strconv.Itoa(time.Now().Nanosecond())"
  203. }
  204. func (data *DataConfig) GetCreate() string {
  205. query := `
  206. CREATE TABLE IF NOT EXISTS %s (
  207. uuid UUID PRIMARY KEY,
  208. %s
  209. created TIMESTAMP WITH TIME ZONE NULL,
  210. updated TIMESTAMP WITH TIME ZONE NULL,
  211. deleted TIMESTAMP WITH TIME ZONE NULL
  212. );`
  213. columnDef := ""
  214. for _, c := range data.Columns {
  215. nullable := " NOT NULL"
  216. if c.Null {
  217. nullable = " NULL"
  218. }
  219. columnDef += Underscore(c.Name) + " " + getPsqlStringByType(c.Type) + nullable + ", "
  220. }
  221. return fmt.Sprintf(query, Underscore(data.Name), columnDef)
  222. }
  223. func (data *DataConfig) GetName() string {
  224. return Underscore(data.Name)
  225. }
  226. func (data *DataConfig) GetSelectParams() string {
  227. cont := "uuid"
  228. for _, c := range data.Columns {
  229. cont += "," + Underscore(c.Name)
  230. }
  231. return cont
  232. }
  233. func (data *DataConfig) GetInsert() string {
  234. query := `INSERT INTO %s ("uuid"%s
  235. VALUES(%s, now())
  236. ON CONFLICT("uuid") DO UPDATE SET
  237. %s`
  238. columnDef := ""
  239. valueDef := "$1"
  240. setDef := "updated=now()"
  241. valCnt := 1
  242. for _, c := range data.Columns {
  243. valCnt++
  244. setDef += ", " + Underscore(c.Name) + "=$" + strconv.Itoa(valCnt)
  245. columnDef += "," + Underscore(c.Name)
  246. valueDef += ",$" + strconv.Itoa(valCnt)
  247. }
  248. columnDef += ", created)"
  249. return fmt.Sprintf(query, Underscore(data.Name), columnDef, valueDef, setDef)
  250. }
  251. // Name Aliases Description
  252. // cidr IPv4 or IPv6 network address
  253. // double precision float8 double precision floating-point number (8 bytes)
  254. // inet IPv4 or IPv6 host address
  255. // macaddr MAC (Media Access Control) address
  256. // money currency amount
  257. // numeric [ (p, s) ] decimal [ (p, s) ] exact numeric of selectable precision
  258. type dataType struct {
  259. GoString string
  260. PsqlString string
  261. TEST float32
  262. }
  263. var dataTypes = map[string]dataType{
  264. "TEXT": dataType{
  265. GoString: "string",
  266. PsqlString: "TEXT",
  267. },
  268. "UUID": dataType{
  269. GoString: "string",
  270. PsqlString: "uuid",
  271. },
  272. "INTEGER": dataType{
  273. GoString: "int",
  274. PsqlString: "INTEGER",
  275. },
  276. "SMALL": dataType{
  277. GoString: "int16",
  278. PsqlString: "smallint",
  279. },
  280. "BIG": dataType{
  281. GoString: "int64",
  282. PsqlString: "bigint",
  283. },
  284. "FLOAT": dataType{
  285. GoString: "float32",
  286. PsqlString: "real",
  287. },
  288. "DOUBLE": dataType{
  289. GoString: "float64",
  290. PsqlString: "double",
  291. },
  292. "POINT": dataType{
  293. GoString: "string",
  294. PsqlString: "point",
  295. },
  296. "BOOL": dataType{
  297. GoString: "bool",
  298. PsqlString: "BOOLEAN",
  299. },
  300. "TIME": dataType{
  301. GoString: "int64",
  302. PsqlString: "TIMESTAMP WITH TIME ZONE",
  303. },
  304. }
  305. func getGoStringByType(t string) string {
  306. return dataTypes[t].GoString
  307. }
  308. func getPsqlStringByType(t string) string {
  309. return dataTypes[t].PsqlString
  310. }
  311. func (data *DataConfig) GetGoStruct() string {
  312. structDef := `type %s struct {
  313. UUID string ` + "`" + `json:"uuid"` + "`" + `%s
  314. }`
  315. dataDef := ""
  316. for _, c := range data.Columns {
  317. entry := c.Name + " " + getGoStringByType(c.Type) + " `" + `json:"` + Underscore(c.Name) + "\"`"
  318. dataDef += fmt.Sprintf(`
  319. %s`, entry)
  320. }
  321. return fmt.Sprintf(structDef, data.Name, dataDef)
  322. }
  323. func (data *DataConfig) CleanData() string {
  324. actions := ""
  325. for _, c := range data.Columns {
  326. if strings.ToUpper(c.Type) == "UUID" {
  327. actions += fmt.Sprintf(`if len(data.%s) == 0 {
  328. data.%s = "00000000-0000-0000-0000-000000000000"
  329. }
  330. `, c.Name, c.Name)
  331. }
  332. }
  333. return actions
  334. }
  335. func (data *DataConfig) GetGoParams() string {
  336. params := ""
  337. for _, c := range data.Columns {
  338. params += ", data." + c.Name
  339. }
  340. return params
  341. }
  342. func (data *DataConfig) GetGoRefParams() string {
  343. params := ""
  344. for _, c := range data.Columns {
  345. params += ", &data." + c.Name
  346. }
  347. return params
  348. }
  349. type source struct {
  350. UUID string
  351. Name string
  352. Body *bytes.Buffer
  353. }
  354. func newSource(name string, pkg string, body *bytes.Buffer) source {
  355. newUUID := uuid.NewV5(uuid.NewNamespaceUUID(pkg), []byte(name)).String()
  356. return source{
  357. UUID: newUUID,
  358. Name: name,
  359. Body: body,
  360. }
  361. }
  362. type sourceMap struct {
  363. sourceData map[string][]source
  364. }
  365. func createMain(config *AppConfig) source {
  366. buf := bytes.NewBuffer([]byte{})
  367. if err := template.Must(template.ParseFiles("templates/app.tmpl")).Execute(buf, config); err != nil {
  368. panic(err)
  369. }
  370. return newSource("main", "main", buf)
  371. }
  372. func createDb(config *AppConfig) source {
  373. buf := bytes.NewBuffer([]byte{})
  374. if err := template.Must(template.ParseFiles("templates/data.tmpl")).Execute(buf, config); err != nil {
  375. panic(err)
  376. }
  377. return newSource("sql", "data", buf)
  378. }
  379. func generateFromDisk() bool {
  380. appConfigPath := flag.String("conf", "", "where is the configuration stored?")
  381. flag.Parse()
  382. if len(*appConfigPath) == 0 {
  383. return false
  384. }
  385. config := &AppConfig{}
  386. config.load(*appConfigPath)
  387. sMap := sourceMap{
  388. sourceData: map[string][]source{},
  389. }
  390. sMap.sourceData["main"] = append(sMap.sourceData["main"], createMain(config))
  391. sMap.sourceData["data"] = append(sMap.sourceData["data"], createDb(config))
  392. err := os.Chdir("gen_src")
  393. if err != nil {
  394. panic(err)
  395. }
  396. err = os.RemoveAll("./data")
  397. if err != nil {
  398. panic(err)
  399. }
  400. for k, w := range sMap.sourceData {
  401. if k != "main" {
  402. err = os.Mkdir(k, 0755)
  403. if err != nil {
  404. panic(err)
  405. }
  406. err = os.Chdir("./" + k)
  407. if err != nil {
  408. panic(err)
  409. }
  410. }
  411. for _, sauce := range w {
  412. err = ioutil.WriteFile(sauce.Name+".go", bytes.NewBufferString(html.UnescapeString(sauce.Body.String())).Bytes(), 0755)
  413. if err != nil {
  414. panic(err)
  415. }
  416. }
  417. if k != "main" {
  418. err = os.Chdir("./..")
  419. if err != nil {
  420. panic(err)
  421. }
  422. }
  423. }
  424. return true
  425. }
  426. func main() {
  427. if generateFromDisk() {
  428. return
  429. }
  430. mux := http.NewServeMux()
  431. mux.Handle("/generate.tar.gz", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  432. config := &AppConfig{}
  433. err := json.Unmarshal(bytes.NewBufferString(r.URL.Query().Get("config")).Bytes(), config)
  434. if err != nil {
  435. fmt.Printf("%+v", err)
  436. w.WriteHeader(http.StatusBadRequest)
  437. _, _ = w.Write([]byte{})
  438. return
  439. }
  440. sMap := sourceMap{
  441. sourceData: map[string][]source{},
  442. }
  443. sMap.sourceData["main"] = append(sMap.sourceData["main"], createMain(config))
  444. sMap.sourceData["data"] = append(sMap.sourceData["data"], createDb(config))
  445. dirName := config.Name
  446. if err != nil {
  447. fmt.Printf("%+v", err)
  448. w.WriteHeader(http.StatusInternalServerError)
  449. _, _ = w.Write([]byte{})
  450. return
  451. }
  452. gzw := gzip.NewWriter(w)
  453. defer gzw.Close()
  454. tw := tar.NewWriter(gzw)
  455. if err := tw.Flush(); err != nil {
  456. fmt.Printf("%+v", err)
  457. }
  458. defer tw.Close()
  459. for k, sa := range sMap.sourceData {
  460. subdirName := dirName
  461. if k != "main" {
  462. subdirName = dirName + "/" + k
  463. }
  464. if err != nil {
  465. fmt.Printf("%+v", err)
  466. w.WriteHeader(http.StatusInternalServerError)
  467. _, _ = w.Write([]byte{})
  468. return
  469. }
  470. for _, sauce := range sa {
  471. file := subdirName + "/" + sauce.Name + ".go"
  472. contentBytes := bytes.NewBufferString(html.UnescapeString(sauce.Body.String())).Bytes()
  473. header := &tar.Header{
  474. Mode: 0777,
  475. Typeflag: tar.TypeReg,
  476. Name: strings.TrimPrefix(file, string(filepath.Separator)),
  477. ModTime: time.Now(),
  478. Size: int64(len(contentBytes)),
  479. }
  480. if err := tw.WriteHeader(header); err != nil {
  481. fmt.Printf("%+v", err)
  482. w.WriteHeader(http.StatusInternalServerError)
  483. _, _ = w.Write([]byte{})
  484. return
  485. }
  486. b, err := tw.Write(contentBytes)
  487. if err != nil {
  488. fmt.Printf("%+v", err)
  489. w.WriteHeader(http.StatusInternalServerError)
  490. _, _ = w.Write([]byte{})
  491. return
  492. }
  493. fmt.Println(b)
  494. }
  495. }
  496. }))
  497. server := &http.Server{
  498. Addr: fmt.Sprintf(
  499. "%s:%d",
  500. "0.0.0.0",
  501. 9111,
  502. ),
  503. Handler: mux,
  504. }
  505. serverStartup := make(chan error, 1)
  506. go func() {
  507. serverStartup <- server.ListenAndServe()
  508. }()
  509. osSignals := make(chan os.Signal, 1)
  510. signal.Notify(
  511. osSignals,
  512. syscall.SIGINT,
  513. syscall.SIGTERM,
  514. syscall.SIGKILL,
  515. syscall.SIGQUIT,
  516. )
  517. select {
  518. case sig := <-osSignals:
  519. fmt.Printf(sig.String())
  520. case err := <-serverStartup:
  521. fmt.Printf(err.Error())
  522. }
  523. fmt.Printf("\n\nADIOS! TOT ZIENS! HASTA LUEGO!\n")
  524. }