Skip to content
Snippets Groups Projects
main.go 17.25 KiB
package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"sort"
	"strings"
	"unicode"
	"unicode/utf8"

	"code.vereign.com/code/restful-api/server"
)

func main() {
	newpath := filepath.Join(".", "javascript/temp")
	os.MkdirAll(newpath, os.ModePerm)

	viamapi := buildViamAPI()
	err := ioutil.WriteFile("./javascript/temp/viamapi.js", []byte(viamapi), 0644)

	if err != nil {
		fmt.Println(err.Error())
	}

	penpalMethods := buildPenpalMethods()
	err = ioutil.WriteFile("./javascript/temp/penpal-methods.js", []byte(penpalMethods), 0644)

	if err != nil {
		fmt.Println(err.Error())
	}
}

func buildPenpalMethods() string {
	prefixes := []string{}
	endPoints := server.GetEndPoints(prefixes)

	result :=
		"import Penpal from 'penpal';\n\n" +
			"export default {\n"

	methods := generatePenpalRemoteMethods(endPoints)

	result += methods

	result += "\n}"

	return result
}

func buildViamAPI() string {
	prefixes := []string{}
	endPoints := server.GetEndPoints(prefixes)

	result := "const axios = require('axios');\n"
	result += "const merge = require('lodash/merge');\n"

	var keys []string
	for k := range endPoints {
		keys = append(keys, k)
	}

	sort.Strings(keys)

	keysLen := len(keys)

	result += "function ViamAPI() {\n" +
		"    this.config = {\n" +
		"        headers: {\n" +
		"            'publicKey': '',\n" +
		"            'uuid': '',\n" +
		"            'deviceHash': '',\n" +
		"            'token': ''\n" +
		"        }\n" +
		"    }\n" +
		"}\n\n"

	result += "ViamAPI.prototype.setSessionData = function(uuid, token) {\n" +
		"    this.config.headers.uuid = uuid;\n" +
		"    this.config.headers.token = token;\n" +
		"};\n\n"

	result += "ViamAPI.prototype.setDeviceHash = function(deviceHash) {\n" +
		"    this.config.headers.deviceHash = deviceHash;\n" +
		"};\n\n"

	result += "ViamAPI.prototype.setIdentity = function(authenticationPublicKey) {\n" +
		"    this.config.headers.publicKey = window.btoa(authenticationPublicKey);\n" +
		"};\n\n"

	result += "ViamAPI.prototype.getConfig = function() {\n" +
		"    return this.config;\n" +
		"};\n\n"

	for i := 0; i < keysLen; i++ {
		url := keys[i]
		if endPoints[url].Form != nil {
			splits := strings.Split(url, "/")

			packageStr := splits[len(splits)-2]

			methodStr := splits[len(splits)-1]

			form := endPoints[url].Form
			s := reflect.ValueOf(form)

			typeOfT := s.Type()

			fields := typeFields(typeOfT)

			lenFields := len(fields)

			args := ""

			for i := 0; i < lenFields; i++ {
				if i != lenFields-1 {
					args += fmt.Sprintf("%sArg,", fields[i].name)
				} else {
					args += fmt.Sprintf("%sArg", fields[i].name)
				}
			}

			lastComma := ","

			if args == "" {
				lastComma = ""
			}

			result += "ViamAPI.prototype." + packageStr + strings.Title(methodStr) + " = function(config" + lastComma + args + ") {\n" +
				"    return axios.post(window.API_HOST + '" + packageStr + "/" + methodStr + "', {\n"

			for i := 0; i < lenFields; i++ {
				result += "        " + fields[i].name + ": " + fields[i].name + "Arg"
				if i != lenFields-1 {
					result += ","
				}
				result += "\n"
			}

			result += "    }, merge({}, this.config, config));\n" +
				"};\n\n"
		} else {
			splits := strings.Split(url, "/")

			packageStr := splits[len(splits)-2]

			/*if !packageCreated[packageStr] {
				result += "api[\"" + packageStr + "\"] = {}\n"
				packageCreated[packageStr] = true
			}*/

			methodStr := splits[len(splits)-1]

			result += "ViamAPI.prototype." + packageStr + strings.Title(methodStr) + " = function(config, data) {\n" +
				"    return axios.post(window.API_HOST + '" + packageStr + "/" + methodStr + "', data || {}, merge({}, this.config, config));\n" +
				"};\n\n"
		}
	}

	result += "module.exports = ViamAPI;\n"

	return result
}

const (
	caseMask     = ^byte(0x20) // Mask to ignore case in ASCII.
	kelvin       = '\u212a'
	smallLongEss = '\u017f'
)

// equalFoldRight is a specialization of bytes.EqualFold when s is
// known to be all ASCII (including punctuation), but contains an 's',
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
// See comments on foldFunc.
func equalFoldRight(s, t []byte) bool {
	for _, sb := range s {
		if len(t) == 0 {
			return false
		}
		tb := t[0]
		if tb < utf8.RuneSelf {
			if sb != tb {
				sbUpper := sb & caseMask
				if 'A' <= sbUpper && sbUpper <= 'Z' {
					if sbUpper != tb&caseMask {
						return false
					}
				} else {
					return false
				}
			}
			t = t[1:]
			continue
		}
		// sb is ASCII and t is not. t must be either kelvin
		// sign or long s; sb must be s, S, k, or K.
		tr, size := utf8.DecodeRune(t)
		switch sb {
		case 's', 'S':
			if tr != smallLongEss {
				return false
			}
		case 'k', 'K':
			if tr != kelvin {
				return false
			}
		default:
			return false
		}
		t = t[size:]

	}
	if len(t) > 0 {
		return false
	}
	return true
}

// asciiEqualFold is a specialization of bytes.EqualFold for use when
// s is all ASCII (but may contain non-letters) and contains no
// special-folding letters.
// See comments on foldFunc.
func asciiEqualFold(s, t []byte) bool {
	if len(s) != len(t) {
		return false
	}
	for i, sb := range s {
		tb := t[i]
		if sb == tb {
			continue
		}
		if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
			if sb&caseMask != tb&caseMask {
				return false
			}
		} else {
			return false
		}
	}
	return true
}

// simpleLetterEqualFold is a specialization of bytes.EqualFold for
// use when s is all ASCII letters (no underscores, etc) and also
// doesn't contain 'k', 'K', 's', or 'S'.
// See comments on foldFunc.
func simpleLetterEqualFold(s, t []byte) bool {
	if len(s) != len(t) {
		return false
	}
	for i, b := range s {
		if b&caseMask != t[i]&caseMask {
			return false
		}
	}
	return true
}

func foldFunc(s []byte) func(s, t []byte) bool {
	nonLetter := false
	special := false // special letter
	for _, b := range s {
		if b >= utf8.RuneSelf {
			return bytes.EqualFold
		}
		upper := b & caseMask
		if upper < 'A' || upper > 'Z' {
			nonLetter = true
		} else if upper == 'K' || upper == 'S' {
			// See above for why these letters are special.
			special = true
		}
	}
	if special {
		return equalFoldRight
	}
	if nonLetter {
		return asciiEqualFold
	}
	return simpleLetterEqualFold
}

// A field represents a single field found in a struct.
type field struct {
	name      string
	nameBytes []byte                 // []byte(name)
	equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent

	tag       bool
	index     []int
	typ       reflect.Type
	omitEmpty bool
	quoted    bool
}

func fillField(f field) field {
	f.nameBytes = []byte(f.name)
	f.equalFold = foldFunc(f.nameBytes)
	return f
}

// byIndex sorts field by index sequence.
type byIndex []field

func (x byIndex) Len() int { return len(x) }

func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }

func (x byIndex) Less(i, j int) bool {
	for k, xik := range x[i].index {
		if k >= len(x[j].index) {
			return false
		}
		if xik != x[j].index[k] {
			return xik < x[j].index[k]
		}
	}
	return len(x[i].index) < len(x[j].index)
}

func isValidTag(s string) bool {
	if s == "" {
		return false
	}
	for _, c := range s {
		switch {
		case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
			// Backslash and quote chars are reserved, but
			// otherwise any punctuation chars are allowed
			// in a tag name.
		default:
			if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
				return false
			}
		}
	}
	return true
}

// tagOptions is the string following a comma in a struct field's "json"
// tag, or the empty string. It does not include the leading comma.
type tagOptions string

// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
	if len(o) == 0 {
		return false
	}
	s := string(o)
	for s != "" {
		var next string
		i := strings.Index(s, ",")
		if i >= 0 {
			s, next = s[:i], s[i+1:]
		}
		if s == optionName {
			return true
		}
		s = next
	}
	return false
}

// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
	if idx := strings.Index(tag, ","); idx != -1 {
		return tag[:idx], tagOptions(tag[idx+1:])
	}
	return tag, tagOptions("")
}

// typeFields returns a list of fields that JSON should recognize for the given type.
// The algorithm is breadth-first search over the set of structs to include - the top struct
// and then any reachable anonymous structs.
func typeFields(t reflect.Type) []field {
	// Anonymous fields to explore at the current level and the next.
	current := []field{}
	next := []field{{typ: t}}

	// Count of queued names for current level and the next.
	count := map[reflect.Type]int{}
	nextCount := map[reflect.Type]int{}

	// Types already visited at an earlier level.
	visited := map[reflect.Type]bool{}

	// Fields found.
	var fields []field

	for len(next) > 0 {
		current, next = next, current[:0]
		count, nextCount = nextCount, map[reflect.Type]int{}

		for _, f := range current {
			if visited[f.typ] {
				continue
			}
			visited[f.typ] = true

			// Scan f.typ for fields to include.
			for i := 0; i < f.typ.NumField(); i++ {
				sf := f.typ.Field(i)
				isUnexported := sf.PkgPath != ""
				if sf.Anonymous {
					t := sf.Type
					if t.Kind() == reflect.Ptr {
						t = t.Elem()
					}
					if isUnexported && t.Kind() != reflect.Struct {
						// Ignore embedded fields of unexported non-struct types.
						continue
					}
					// Do not ignore embedded fields of unexported struct types
					// since they may have exported fields.
				} else if isUnexported {
					// Ignore unexported non-embedded fields.
					continue
				}
				tag := sf.Tag.Get("json")
				if tag == "-" {
					continue
				}
				name, opts := parseTag(tag)
				if !isValidTag(name) {
					name = ""
				}
				index := make([]int, len(f.index)+1)
				copy(index, f.index)
				index[len(f.index)] = i

				ft := sf.Type
				if ft.Name() == "" && ft.Kind() == reflect.Ptr {
					// Follow pointer.
					ft = ft.Elem()
				}

				// Only strings, floats, integers, and booleans can be quoted.
				quoted := false
				if opts.Contains("string") {
					switch ft.Kind() {
					case reflect.Bool,
						reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
						reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
						reflect.Float32, reflect.Float64,
						reflect.String:
						quoted = true
					}
				}

				// Record found field and index sequence.
				if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
					tagged := name != ""
					if name == "" {
						name = sf.Name
					}
					fields = append(fields, fillField(field{
						name:      name,
						tag:       tagged,
						index:     index,
						typ:       ft,
						omitEmpty: opts.Contains("omitempty"),
						quoted:    quoted,
					}))
					if count[f.typ] > 1 {
						// If there were multiple instances, add a second,
						// so that the annihilation code will see a duplicate.
						// It only cares about the distinction between 1 or 2,
						// so don't bother generating any more copies.
						fields = append(fields, fields[len(fields)-1])
					}
					continue
				}

				// Record new anonymous struct to explore in next round.
				nextCount[ft]++
				if nextCount[ft] == 1 {
					next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft}))
				}
			}
		}
	}

	sort.Slice(fields, func(i, j int) bool {
		x := fields
		// sort field by name, breaking ties with depth, then
		// breaking ties with "name came from json tag", then
		// breaking ties with index sequence.
		if x[i].name != x[j].name {
			return x[i].name < x[j].name
		}
		if len(x[i].index) != len(x[j].index) {
			return len(x[i].index) < len(x[j].index)
		}
		if x[i].tag != x[j].tag {
			return x[i].tag
		}
		return byIndex(x).Less(i, j)
	})

	// Delete all fields that are hidden by the Go rules for embedded fields,
	// except that fields with JSON tags are promoted.

	// The fields are sorted in primary order of name, secondary order
	// of field index length. Loop over names; for each name, delete
	// hidden fields by choosing the one dominant field that survives.
	out := fields[:0]
	for advance, i := 0, 0; i < len(fields); i += advance {
		// One iteration per name.
		// Find the sequence of fields with the name of this first field.
		fi := fields[i]
		name := fi.name
		for advance = 1; i+advance < len(fields); advance++ {
			fj := fields[i+advance]
			if fj.name != name {
				break
			}
		}
		if advance == 1 { // Only one field with this name
			out = append(out, fi)
			continue
		}
		dominant, ok := dominantField(fields[i : i+advance])
		if ok {
			out = append(out, dominant)
		}
	}

	fields = out
	sort.Sort(byIndex(fields))

	return fields
}

// dominantField looks through the fields, all of which are known to
// have the same name, to find the single field that dominates the
// others using Go's embedding rules, modified by the presence of
// JSON tags. If there are multiple top-level fields, the boolean
// will be false: This condition is an error in Go and we skip all
// the fields.
func dominantField(fields []field) (field, bool) {
	// The fields are sorted in increasing index-length order. The winner
	// must therefore be one with the shortest index length. Drop all
	// longer entries, which is easy: just truncate the slice.
	length := len(fields[0].index)
	tagged := -1 // Index of first tagged field.
	for i, f := range fields {
		if len(f.index) > length {
			fields = fields[:i]
			break
		}
		if f.tag {
			if tagged >= 0 {
				// Multiple tagged fields at the same level: conflict.
				// Return no field.
				return field{}, false
			}
			tagged = i
		}
	}
	if tagged >= 0 {
		return fields[tagged], true
	}
	// All remaining fields have the same length. If there's more than one,
	// we have a conflict (two fields named "X" at the same level) and we
	// return no field.
	if len(fields) > 1 {
		return field{}, false
	}
	return fields[0], true
}

func generatePenpalRemoteMethods(endPoints map[string]*server.EndPoint) string {
	var keys []string
	for k := range endPoints {
		keys = append(keys, k)
	}

	sort.Strings(keys)

	keysLen := len(keys)

	methods := ""

	privateCheckSnippet := `
        const authenticationPublicKey = localStorage.getItem("authenticatedIdentity");

	    if (authenticationPublicKey === null) {
		    result({
			    "data" : "",
			    "code" : "400",
			    "status" : "Identity not authenticated"
		    });
	    }

	    if (loadedIdentities[authenticationPublicKey] === null) {
		    result({
			    "data" : "",
			    "code" : "400",
			    "status" : "Identity not authenticated"
		    });
 	    }

	    const success = extendPinCodeTtl(authenticationPublicKey);

	    if(success === false) {
		    result({"data" : "",
			    "code" : "400",
			    "status" : "Identity not authenticated"
		    })
		}

	`

	for i := 0; i < keysLen; i++ {
		url := keys[i]

		if endPoints[url].ManuallyWritten == true {
			continue
		}

		if url == "/identity/getIdentityProfileData" {
			privateCheckSnippet = `
				const authenticationPublicKey = localStorage.getItem("authenticatedIdentity");
				if (authenticationPublicKey === null) {
					result({
						"data" : "",
						"code" : "400",
						"status" : "Identity not authenticated"
					});
				}

				if (loadedIdentities[authenticationPublicKey] === null) {
					result({
						"data" : "",
						"code" : "400",
						"status" : "Identity not authenticated"
					});
				}

			`
		}

		if endPoints[url].Form != nil {
			splits := strings.Split(url, "/")

			packageStr := splits[len(splits)-2]

			methodStr := splits[len(splits)-1]

			form := endPoints[url].Form
			s := reflect.ValueOf(form)

			typeOfT := s.Type()

			fields := typeFields(typeOfT)

			lenFields := len(fields)

			args := ""

			for i := 0; i < lenFields; i++ {
				if i != lenFields-1 {
					args += fmt.Sprintf("%sArg,", fields[i].name)
				} else {
					args += fmt.Sprintf("%sArg", fields[i].name)
				}
			}

			snippet := ""

			if endPoints[url].HandlerType == "private" {
				snippet = privateCheckSnippet
			}

			lastComma := ","

			if args == "" {
				lastComma = ""
			}

			method := packageStr + strings.Title(methodStr) + ": function(" + args + ") {\n" +
				"    return new Penpal.Promise(function(result) {\n" + snippet +
				"    executeRestfulFunction(\"" + endPoints[url].HandlerType + "\", viamApi, viamApi." + packageStr + strings.Title(methodStr) + ", null" + lastComma + args + ").then(function(executeResult) {\n" +
				"            result(executeResult);\n" +
				"        });\n" +
				"    });\n" +
				"}"

			methods += method

			if i != keysLen-1 {
				methods += ","
			}

			methods += "\n"
		} else {
			splits := strings.Split(url, "/")

			packageStr := splits[len(splits)-2]

			methodStr := splits[len(splits)-1]

			snippet := ""

			if endPoints[url].HandlerType == "private" {
				snippet = privateCheckSnippet
			}

			method := packageStr + strings.Title(methodStr) + ": function() {\n" +
				"    return new Penpal.Promise(function(result) {\n" + snippet +
				"    executeRestfulFunction(\"" + endPoints[url].HandlerType + "\", viamApi, viamApi." + packageStr + strings.Title(methodStr) + ", null).then(function(executeResult) {\n" +
				"            result(executeResult);\n" +
				"        });\n" +
				"    });\n" +
				"}"

			methods += method

			if i != keysLen-1 {
				methods += ","
			}

			methods += "\n"

		}
	}

	return methods
}