Sfoglia il codice sorgente

Adding source for github releasing

Jake Kalstad 7 anni fa
commit
c96c108791
6 ha cambiato i file con 961 aggiunte e 0 eliminazioni
  1. 15 0
      README.md
  2. 0 0
      gen_src/generated_files_appear_here.txt
  3. 556 0
      main.go
  4. 194 0
      templates/app.tmpl
  5. 99 0
      templates/data.tmpl
  6. 97 0
      testAppConfig.json

+ 15 - 0
README.md

@@ -0,0 +1,15 @@
+I realized that half of my services are more or less CRUD services with little to no customization aside from obvious differences, but I still wanted them as seperate stand alone applications, with enough espresso and liquor anything is possible.
+
+> START UP AND PRINT THEIR NAMES
+
+> START TO LISTEN TO SOME HTTP REQUESTS FOR A HEART BEAT
+
+> GIVE ME SOME ABILITY TO GET MEH CUSTOMIZABLE END POINTS TO DO STUFF
+
+> I NEED A DATABASE LAYER AND A METASTATS TABLE FOR EACH SERVICE || SQL WILL WORK FOR NOW
+
+> I NEED A WAY TO PERSIST AND SNAGGLE SOME DATA || CRUD SERVICE THAT IS FULLY FUXWITHABLE
+
+>(events via deeeez nats)
+>redis crap to r.set("deal" "with" "that" "joy");
+>graphing data layer ability

+ 0 - 0
gen_src/generated_files_appear_here.txt


+ 556 - 0
main.go

@@ -0,0 +1,556 @@
+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")
+}

+ 194 - 0
templates/app.tmpl

@@ -0,0 +1,194 @@
+package main
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"os/signal"
+	"strconv"
+	"strings"
+	"syscall"
+	"time"
+  "{{.URL}}/{{.Name}}/data"
+  
+	_ "github.com/lib/pq"
+	"github.com/nats-io/go-nats"
+)
+
+type AppError struct {
+  error
+  Action string
+  Data   string
+}
+
+type EventMessage struct {
+  Status    string
+  DataType  string
+  Data      interface{}
+}
+
+type response struct {
+	Message string `json:"message"`
+}
+
+func initializeNats() (*nats.EncodedConn, error) {
+	nc, err := nats.Connect(nats.DefaultURL)
+	if err != nil {
+		return nil, err
+	}
+	c, err := nats.NewEncodedConn(nc, nats.JSON_ENCODER)
+	if err != nil {
+		return nil, err
+	}
+	return c, nil
+}
+
+func contextualize(timeout time.Duration) func(next http.Handler) http.Handler {
+	return func(next http.Handler) http.Handler {
+		fn := func(w http.ResponseWriter, r *http.Request) {
+			ctx, cancel := context.WithTimeout(r.Context(), timeout)
+			defer func() {
+				cancel()
+				if ctx.Err() == context.DeadlineExceeded {
+					w.WriteHeader(http.StatusGatewayTimeout)
+				}
+			}()
+			r = r.WithContext(ctx)
+			next.ServeHTTP(w, r)
+		}
+		return http.HandlerFunc(fn)
+	}
+}
+
+type chanRet struct {
+	result []byte
+	err    error
+  status int
+}
+
+func (app *application) runner(w http.ResponseWriter, r *http.Request, task func() *chanRet) {
+	fin := make(chan *chanRet, 1)
+	go func() {
+		fin <- task()
+	}()
+	select {
+	case <-r.Context().Done():
+    close(fin)
+		return
+	case res := <-fin:
+		if res.err != nil {
+			fmt.Fprint(os.Stderr, res.err)
+      app.Publish("error", res.err)
+		} 
+		w.WriteHeader(res.status)
+		w.Write(res.result)
+	}
+}
+
+type application struct {
+	db *db.DB
+	events *nats.EncodedConn
+}
+
+func (a *application) Publish(action string, v interface{}) {
+	subject := strings.ToLower("{{.Name}}.") + action + "." + strconv.Itoa(int(time.Now().Unix()))
+  a.events.Flush()
+	a.events.Publish(subject, v) 
+}
+
+func execByVerb(r *http.Request, post func(body []byte) ([]byte, error), get func(key string) ([]byte, error), delete func(key string) error) *chanRet {
+	var result []byte
+	var err error
+	switch r.Method {
+	case http.MethodGet:
+		key := r.URL.Query().Get("key")
+		result, err = get(key)
+	case http.MethodPut:
+	case http.MethodPost:
+		defer r.Body.Close()
+		body, bodyReadErr := ioutil.ReadAll(r.Body)
+		if bodyReadErr != nil {
+			err = bodyReadErr
+		} else {
+		  result, err = post(body)
+    }
+  case http.MethodDelete:
+		key := r.URL.Query().Get("key")
+	  err = delete(key)
+    result = []byte{'o', 'k'}
+	}
+  status := http.StatusOK
+  if err != nil {
+    status = http.StatusInternalServerError
+  } else if err == nil && result == nil {
+    status = http.StatusNotFound
+  }
+	return &chanRet{result, err, status}
+}
+
+func (app *application) root(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) {
+				return json.Marshal(&response{Message: "{{.Name}}"})
+			},
+			func(key string) error {
+				return nil
+			})
+	})
+}
+
+{{.GetRoutes}}
+
+func main() {
+	newDb, err := db.NewDB()
+	if err != nil {
+		panic(err)
+	}
+	nEvents, err := initializeNats()
+	if err != nil {
+		panic(err)
+	}
+	app := &application{
+		db:     newDb,
+		events: nEvents,
+	}
+	mux := http.NewServeMux()
+
+	mux.Handle("/", contextualize(time.Second*1)(http.HandlerFunc(app.root)))
+  {{.MuxHandlers}}
+	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")
+}

+ 99 - 0
templates/data.tmpl

@@ -0,0 +1,99 @@
+package db
+
+import (
+	"database/sql"
+	"fmt"
+	"os"
+  
+	"strconv"
+	"time"
+  "github.com/fgrid/uuid" // replace when go is go-ing to give me sha5 uuids
+)
+
+const scaffold = `{{ range $value := .Data }}{{ $value.GetCreate }}{{ end }}
+`
+
+type DB struct {
+	*sql.DB
+}
+
+func NewDB() (*DB, error) {
+	db, err := sql.Open("postgres", fmt.Sprintf("host=%s dbname=%s sslmode=disable user=%s password=%s", os.Getenv("DB_HOST"), os.Getenv("DB_NAME"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD")))
+	if err != nil {
+		return nil, err
+	}
+	if err = db.Ping(); err != nil {
+		return nil, err
+	}
+	retDb := &DB{db}
+	return retDb, retDb.scaffold()
+}
+
+func (db *DB) scaffold() error {
+	_, err := db.Exec(scaffold)
+	return err
+}
+
+type Point struct {
+	X string `json:"x"`
+	Y string `json:"y"`
+}
+
+{{ range $value := .Data }}
+{{ $value.GetGoStruct }}
+const insert{{ $value.Name }} = `
+  {{ $value.GetInsert }}
+`
+func (db *DB) Insert{{$value.Name}}(data *{{$value.Name}}) error {
+  if len(data.UUID) == 0 {
+	  data.UUID = uuid.NewV5(uuid.NewNamespaceUUID("{{.Name}}"), []byte( {{.UniqueID}} )).String()
+  }
+  {{$value.CleanData}}
+	_, err := db.Exec(insert{{$value.Name}},data.UUID{{$value.GetGoParams}})
+	return err
+}
+
+const delete{{$value.Name}} = `UPDATE {{$value.GetName}} SET deleted=now() WHERE uuid = $1`
+func (db *DB) Delete{{$value.Name}}(uuid string) error {
+  _, err := db.Query(delete{{$value.Name}}, uuid)
+  return err
+}
+
+const get{{$value.Name}} = `SELECT {{$value.GetSelectParams}} FROM {{$value.GetName}} WHERE uuid = $1 AND deleted IS NULL`
+const get{{$value.Name}}s = `SELECT {{$value.GetSelectParams}} FROM {{$value.GetName}} WHERE deleted IS NULL`
+func (db *DB) Get{{$value.Name}}(uuid string) ([]*{{$value.Name}}, error) {
+	var rows *sql.Rows
+	var err error
+	if len(uuid) != 0 {
+		rows, err = db.Query(get{{$value.Name}}, uuid)
+	} else {
+		rows, err = db.Query(get{{$value.Name}}s)
+	}
+	if err != nil {
+		return nil, err
+	}
+	records := []*{{$value.Name}}{}
+	for rows.Next() {
+		data := &{{$value.Name}}{}
+		rows.Scan(&data.UUID{{.GetGoRefParams}})
+		records = append(records, data)
+	}
+	return records, nil
+}
+{{ range $key, $val := .Routing }}
+const get{{$value.Name}}sBy{{$val}} = `SELECT {{$value.GetSelectParams}} FROM {{$value.GetName}} WHERE deleted IS NULL AND {{$.Underscore $val}} = $1`
+func (db *DB) Get{{$value.Name}}By{{$val}}(uuid string) ([]*{{$value.Name}}, error) {
+	rows, err := db.Query(get{{$value.Name}}sBy{{$val}}, uuid)
+	if err != nil {
+		return nil, err
+	}
+	records := []*{{$value.Name}}{}
+	for rows.Next() {
+		data := &{{$value.Name}}{}
+		rows.Scan(&data.UUID{{$value.GetGoRefParams}})
+		records = append(records, data)
+	}
+	return records, nil
+}
+{{ end }}
+{{ end }}

+ 97 - 0
testAppConfig.json

@@ -0,0 +1,97 @@
+{
+	"name":"LaFamiglia",
+  "url": "lafamiglia.com/jakekalstad",
+  "email": "jake@jakemail.net",
+  "ms_timeout": 1000,
+  "data": [{
+    "name": "UserInfo",
+    "columns": [
+      {"name": "UserName",  "type": "TEXT", "null": false},
+      {"name": "Password",    "type": "TEXT", "null": false},
+      {"name": "Email",       "type": "TEXT", "null": false}
+    ],
+    "routing": {
+      "by_login": "Password"
+    } 
+  },
+  {
+    "name": "Character",
+    "columns": [
+      {"name": "UserUUID", "type": "UUID", "null": false},
+      {"name": "Name",    "type": "TEXT", "null": false},
+      {"name": "Health", "type": "INTEGER", "null": false},
+      {"name": "Level", "type": "INTEGER", "null": false},
+      {"name": "Actions", "type": "INTEGER", "null": false},
+      {"name": "Money", "type": "INTEGER", "null": false},
+      {"name": "Notoriety", "type": "INTEGER", "null": false},
+      {"name": "Experience", "type": "INTEGER", "null": false},
+      {"name": "Stamina", "type": "INTEGER", "null": false},
+      {"name": "Luck", "type": "INTEGER", "null": false},
+      {"name": "Strength", "type": "INTEGER", "null": false}
+    ],
+    "routing": {
+      "by_user": "UserUUID"
+    }
+  },
+  {
+    "name": "Skills",
+    "columns": [
+      {"name": "CharacterUUID", "type": "UUID", "null": false},
+      {"name": "Theft", "type": "INTEGER", "null": false},
+      {"name": "TheftXp", "type": "INTEGER", "null": false},
+      {"name": "Assault", "type": "INTEGER", "null": false},
+      {"name": "AssaultXp", "type": "INTEGER", "null": false},
+      {"name": "Fraud", "type": "INTEGER", "null": false},
+      {"name": "FraudXp", "type": "INTEGER", "null": false},
+      {"name": "Murder", "type": "INTEGER", "null": false},
+      {"name": "MurderXp", "type": "INTEGER", "null": false},
+      {"name": "Tech", "type": "INTEGER", "null": false},
+      {"name": "TechXp", "type": "INTEGER", "null": false}
+    ],
+    "routing": {
+      "by_character": "CharacterUUID"
+    }
+  },
+  {
+    "name": "Items",
+    "columns": [
+      {"name": "CharacterUUID", "type": "UUID", "null": false},
+      {"name": "Name", "type": "TEXT", "null": false},
+      {"name": "Description", "type": "TEXT", "null": false},
+      {"name": "Worth", "type": "INTEGER", "null": false},
+      {"name": "Theft", "type": "INTEGER", "null": false},
+      {"name": "Assault", "type": "INTEGER", "null": false},
+      {"name": "Fraud", "type": "INTEGER", "null": false},
+      {"name": "Murder", "type": "INTEGER", "null": false},
+      {"name": "Tech", "type": "INTEGER", "null": false},
+      {"name": "Charisma", "type": "INTEGER", "null": false},
+      {"name": "Stamina", "type": "INTEGER", "null": false},
+      {"name": "Strength", "type": "INTEGER", "null": false}
+    ], 
+    "routing": {
+      "by_character": "CharacterUUID"
+    }
+  },
+  {
+    "name": "Job",
+    "columns": [
+      {"name": "Name", "type": "TEXT", "null": false},
+      {"name": "Description", "type": "TEXT", "null": false},
+      {"name": "PayOut", "type": "INTEGER", "null": false},
+      {"name": "MinLevel", "type": "INTEGER", "null": false},
+      {"name": "MinWager", "type": "INTEGER", "null": false}
+    ]
+  },
+  {
+    "name": "Contract",
+    "columns": [
+      {"name": "CharacterUUID", "type": "UUID", "null": false},
+      {"name": "JobUUID", "type": "UUID", "null": false},
+      {"name": "Wager", "type": "INTEGER", "null": false}
+    ], 
+    "routing": {
+      "by_character": "CharacterUUID",
+      "by_job": "JobUUID"
+    }
+  }]
+}