Cody Joyce 2 months ago
commit
1031656be0

+ 51 - 0
.air.toml

@@ -0,0 +1,51 @@
+root = "."
+testdata_dir = "testdata"
+tmp_dir = "tmp"
+
+[build]
+  args_bin = []
+  bin = "./tmp/main 3000"
+  cmd = "find . -type f -name '*.gen.*' -exec rm {} + & go build -o ./tmp/main ."
+  delay = 1000
+  exclude_dir = ["assets", "tmp", "vendor", "testdata"]
+  exclude_file = []
+  exclude_regex = ["gen.tpl","gen.css","gen.js"]
+  exclude_unchanged = false
+  follow_symlink = false
+  full_bin = ""
+  include_dir = []
+  include_ext = ["go", "tpl", "tmpl", "html","css","js","json"]
+  include_file = []
+  kill_delay = "0s"
+  log = "build-errors.log"
+  poll = false
+  poll_interval = 0
+  post_cmd = []
+  pre_cmd = []
+  rerun = false
+  rerun_delay = 500
+  send_interrupt = false
+  stop_on_error = false
+
+[color]
+  app = ""
+  build = "yellow"
+  main = "magenta"
+  runner = "green"
+  watcher = "cyan"
+
+[log]
+  main_only = false
+  time = false
+
+[misc]
+  clean_on_exit = false
+
+[proxy]
+  app_port = 3000
+  enabled = true
+  proxy_port = 3001
+
+[screen]
+  clear_on_rebuild = false
+  keep_scroll = true

+ 24 - 0
app/app.go

@@ -0,0 +1,24 @@
+package app
+
+import (
+	"gex/app/route/home"
+	"gex/pkg/sec"
+	"gex/pkg/sess"
+
+	"gex/pkg/gen"
+	"gex/pkg/gex"
+)
+
+func Init() {
+	gen.GeneratePages("app")
+}
+
+func Middleware() {
+	gex.AddMiddleware(sess.Middleware)
+	gex.AddMiddleware(sec.Middleware)
+}
+
+func Routes() {
+	type roles []string
+	sec.Route("/{$}", roles{"Everyone"}, home.Index)
+}

+ 12 - 0
app/layout/app.gen.tpl

@@ -0,0 +1,12 @@
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+    <link rel="stylesheet" href="/app/pub/css/app.gen.css">
+    
+</head>
+<body class="YFmXvnoP">
+    
+</body>
+</html>

+ 12 - 0
app/layout/app.tpl

@@ -0,0 +1,12 @@
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title><block id="title">Document</block></title>
+    <link rel="stylesheet" href="/app/pub/css/app.gen.css">
+    <block id="head"></block>
+</head>
+<body>
+    <block id="body"></block>
+</body>
+</html>

+ 0 - 0
app/pub/css/app.gen.css


+ 12 - 0
app/route/home/home.go

@@ -0,0 +1,12 @@
+package home
+
+import (
+	"gex/pkg/gex"
+	"gex/pkg/render"
+)
+
+func Index(req *gex.Req, res gex.Res) {
+
+	html := render.Template("./index.tpl", req)
+	res.Send(html)
+}

+ 15 - 0
app/route/home/index.gen.tpl

@@ -0,0 +1,15 @@
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Hello World</title>
+    <link rel="stylesheet" href="/app/pub/css/app.gen.css">
+    
+</head>
+<body class="YFmXvnoP">
+    
+        <h1 class="nTStyErB">Hello {{.Sess.User_name}}</h1>
+        
+    
+</body>
+</html>

+ 7 - 0
app/route/home/index.tpl

@@ -0,0 +1,7 @@
+<layout src="/app/layout/app.tpl">
+    <block id="title">Hello World</block>
+    <block id="body">
+        <h1>Hello {{.Sess.User_name}}</h1>
+        
+    </block>
+</layout>

+ 3 - 0
go.mod

@@ -0,0 +1,3 @@
+module gex
+
+go 1.23.4

+ 23 - 0
main.go

@@ -0,0 +1,23 @@
+package main
+
+import (
+	"gex/app"
+
+	"gex/pkg/gex"
+	"gex/pkg/utils"
+)
+
+func main() {
+	// check cli arguments are set and correct
+	utils.CheckArgs()
+
+	app.Init()
+
+	gex.InitRouter()
+
+	app.Middleware()
+	app.Routes()
+
+	gex.StartServer()
+
+}

+ 181 - 0
pkg/gen/css.go

@@ -0,0 +1,181 @@
+package gen
+
+import (
+	"regexp"
+	"strings"
+)
+
+type cssRule struct {
+	typeOf   string
+	selector string
+	block    string
+	ruleList cssRuleList
+}
+
+type cssRuleList []cssRule
+
+// Prepare the css for parsing
+func clean(css *string) {
+	// remove spaces
+	re := regexp.MustCompile(`\s+`)
+	*css = re.ReplaceAllString(*css, " ")
+
+	// remove newlines
+	*css = strings.ReplaceAll(*css, "\n", "")
+
+	// remove comments
+	r := regexp.MustCompile(`/\*[\s\S]*?\*/`)
+	*css = r.ReplaceAllString(*css, "")
+}
+
+// Parse the css into a ruleList
+func toRuleList(css string) cssRuleList {
+	clean(&css)
+	dep := 0
+	sel := ""
+	blk := ""
+	var ruleList cssRuleList
+	for i := 0; i < len(css); i++ {
+		chr := string(css[i])
+
+		if chr == "{" && dep == 0 {
+			dep++
+			continue
+		}
+		if chr == "{" {
+			dep++
+		}
+
+		if chr == "}" {
+			dep--
+		}
+		if chr == "}" && dep == 0 {
+			sel = strings.TrimSpace(sel)
+			if strings.Contains(sel, "@media") {
+				typeOf := "media"
+				mediaRuleList := toRuleList(blk)
+				ruleList = append(ruleList, cssRule{typeOf, sel, "", mediaRuleList})
+			} else {
+				typeOf := "rule"
+				ruleList = append(ruleList, cssRule{typeOf, sel, blk, cssRuleList{}})
+			}
+
+			sel = ""
+			blk = ""
+			continue
+		}
+
+		if dep == 0 {
+			sel = sel + chr
+		} else {
+			blk = blk + chr
+		}
+
+	}
+	return ruleList
+}
+
+// scope the rules by adding a css class to each selector
+func scopeSelectors(ruleList cssRuleList, className string) cssRuleList {
+	newRuleList := cssRuleList{}
+	for i := 0; i < len(ruleList); i++ {
+		rule := &ruleList[i]
+
+		if strings.Contains(rule.selector, ":root") {
+			continue
+		}
+		if strings.Contains(rule.selector, ":global") {
+			continue
+		}
+		if strings.Contains(rule.selector, "@font-face") {
+			continue
+		}
+		if strings.Contains(rule.selector, "@import") {
+			continue
+		}
+		if rule.typeOf == "media" {
+			rule.ruleList = scopeSelectors(rule.ruleList, className)
+			continue
+		}
+
+		//
+
+		if strings.Contains(rule.selector, ":cascade") {
+
+			newRule := cssRule{}
+			newRule.selector = "." + className + " " + rule.selector
+			//newRule.selector = strings.ReplaceAll(newRule.selector, ":cascade", "")
+			newRule.block = rule.block
+			newRuleList = append(newRuleList, newRule)
+
+			rule.selector = "." + className + " " + rule.selector
+			continue
+		}
+		rule.selector = strings.ReplaceAll(rule.selector, " ", "."+className+" ")
+		rule.selector = rule.selector + "." + className
+
+	}
+	ruleList = append(ruleList, newRuleList...)
+
+	return ruleList
+}
+
+func mergeMediaRules(ruleList cssRuleList) cssRuleList {
+	mediaMap := make(map[string]cssRuleList)
+	var newRuleList cssRuleList
+
+	for i := 0; i < len(ruleList); i++ {
+		rule := ruleList[i]
+		if rule.typeOf != "media" {
+			newRuleList = append(newRuleList, rule)
+			continue
+		}
+		mediaMap[rule.selector] = append(mediaMap[rule.selector], rule.ruleList...)
+	}
+
+	for sel, rList := range mediaMap {
+		newRuleList = append(newRuleList, cssRule{"media", sel, "", rList})
+	}
+
+	return newRuleList
+}
+
+// order the appRules correctly, stip the :cascade tag too
+func orderRules(ruleList cssRuleList) cssRuleList {
+	var newRuleList cssRuleList
+
+	for i := 0; i < len(ruleList); i++ {
+		rule := ruleList[i]
+		if rule.typeOf == "media" {
+			rule.ruleList = orderRules(rule.ruleList)
+		}
+
+		if strings.Contains(rule.selector, ":cascade") {
+			rule.selector = strings.ReplaceAll(rule.selector, ":cascade", "")
+			newRuleList = append(cssRuleList{rule}, newRuleList...)
+			continue
+		}
+
+		if strings.Contains(rule.selector, ":global") {
+			rule.selector = strings.ReplaceAll(rule.selector, ":global", "")
+			newRuleList = append(cssRuleList{rule}, newRuleList...)
+			continue
+		}
+
+		newRuleList = append(newRuleList, rule)
+	}
+	return newRuleList
+}
+
+func rulesToCSSstr(ruleList cssRuleList) string {
+	cssStr := ""
+
+	for i := 0; i < len(ruleList); i++ {
+		rule := ruleList[i]
+		if rule.typeOf == "media" {
+			rule.block = rulesToCSSstr(rule.ruleList)
+		}
+		cssStr += rule.selector + "{" + rule.block + "}"
+	}
+	return cssStr
+}

+ 239 - 0
pkg/gen/domquery/htmlquery.go

@@ -0,0 +1,239 @@
+package domquery
+
+import (
+	"fmt"
+	"strings"
+)
+
+/* End Node Functions */
+func LoadHTML(htmlStr string) *Node {
+	tokList := getTokenList(htmlStr)
+	bt := buildTree(tokList)
+	return bt
+}
+
+func getTokenList(htmlStr string) []string {
+	var tokList []string
+	var tok string
+
+	var isPhp bool
+
+	for i := 0; i < len(htmlStr); i++ {
+		chr := string(htmlStr[i])
+
+		if strings.Contains(tok, "</style") {
+			tok = strings.Replace(tok, "</style", "", 1)
+			tokList = append(tokList, tok)
+			tok = "</style>"
+			tokList = append(tokList, tok)
+			tok = ""
+			continue
+		}
+
+		if len(tokList) > 0 {
+			if strings.Contains(tokList[len(tokList)-1], "<style") {
+				tok += chr
+				continue
+			}
+		}
+
+		if strings.Contains(tok, "</script") {
+			tok = strings.Replace(tok, "</script", "", 1)
+			tokList = append(tokList, tok)
+			tok = "</script>"
+			tokList = append(tokList, tok)
+			tok = ""
+			continue
+		}
+
+		if len(tokList) > 0 {
+			if strings.Contains(tokList[len(tokList)-1], "<script") {
+				tok += chr
+				continue
+			}
+		}
+
+		if chr == "<" {
+			if string(htmlStr[i+1]) == "?" {
+				isPhp = true
+				tok += chr
+				continue
+			}
+
+			if isPhp == true {
+				tok += chr
+				continue
+			}
+
+			tokList = append(tokList, tok)
+			tok = chr
+			continue
+		}
+
+		if chr == ">" {
+			if string(htmlStr[i-1]) == "?" {
+				isPhp = false
+				tok += chr
+				continue
+			}
+			if isPhp == true {
+				tok += chr
+				continue
+			}
+
+			tok += chr
+			tokList = append(tokList, tok)
+			tok = ""
+			continue
+		}
+
+		tok += chr
+
+	}
+	tokList = append(tokList, tok)
+	return tokList
+}
+
+func buildTree(tokList []string) *Node {
+	root := &Node{}
+
+	node := &Node{}
+	node.Parent = root
+
+	for _, tok := range tokList {
+		prev := node
+		prev.token = tok
+		node = &Node{}
+
+		if getTokType(tok) == "open" {
+			node.Parent = prev
+		}
+
+		if getTokType(tok) == "text" {
+			node.Parent = prev.Parent
+		}
+
+		if getTokType(tok) == "selfclosing" {
+			node.Parent = prev.Parent
+		}
+
+		if getTokType(tok) == "comment" {
+			node.Parent = prev.Parent
+		}
+
+		if getTokType(tok) == "close" {
+			prev.Parent = prev.Parent.Parent
+			node.Parent = prev.Parent
+		}
+
+		prev.Parent.Children = append(prev.Parent.Children, prev)
+	}
+
+	return root
+}
+
+func getTokType(tok string) string {
+	if len(tok) < 2 {
+		return "text"
+	}
+	fc := string(tok[0])
+	sc := string(tok[1])
+
+	if sc == "/" {
+		return "close"
+	}
+
+	if fc == "<" && sc == "!" {
+		return "comment"
+	}
+
+	if fc == "<" && sc == "?" {
+		return "text"
+	}
+
+	if fc == "<" {
+		if isSelfClosing(tok) {
+			return "selfclosing"
+		}
+		return "open"
+	}
+
+	return "text"
+}
+
+func isSelfClosing(tok string) bool {
+	tags := map[string]bool{
+		"area":   true,
+		"base":   true,
+		"br":     true,
+		"col":    true,
+		"embed":  true,
+		"hr":     true,
+		"img":    true,
+		"input":  true,
+		"link":   true,
+		"meta":   true,
+		"param":  true,
+		"source": true,
+		"track":  true,
+		"wbr":    true,
+	}
+
+	return tags[getTagName(tok)]
+}
+
+func getTagName(tok string) string {
+	tName := ""
+	for i := 1; i < len(tok); i++ {
+		chr := string(tok[i])
+		if chr == " " || chr == ">" {
+			break
+		}
+		tName += chr
+	}
+	return tName
+}
+
+func getCloseNode(node *Node) *Node {
+
+	if getTokType(node.token) != "open" {
+		return &Node{}
+	}
+
+	idx := 0
+	for i, child := range node.Parent.Children {
+		if child == node {
+			idx = i
+
+			break
+		}
+	}
+	idx = idx + 1
+	if idx > len(node.Parent.Children)-1 {
+		fmt.Println("Parse Error: Unclosed tag in " + node.token)
+		idx--
+	}
+
+	return node.Parent.Children[idx]
+}
+
+func matchSelector(node *Node, sel string) bool {
+	if getTokType(node.token) == "close" {
+		return false
+	}
+
+	if getTagName(node.token) == sel {
+		return true
+	}
+
+	if "#"+node.GetAttribute("id") == sel {
+		return true
+	}
+
+	for _, class := range node.ClassList() {
+		if "."+class == sel {
+			return true
+		}
+	}
+	return false
+}

+ 278 - 0
pkg/gen/domquery/node.go

@@ -0,0 +1,278 @@
+package domquery
+
+import (
+	"strings"
+)
+
+type Node struct {
+	token    string
+	Children []*Node
+	Parent   *Node
+}
+
+func (node *Node) TagName() string {
+	return getTagName(node.token)
+}
+
+func (node *Node) OuterHTML() string {
+	str := node.token
+	str += node.InnerHTML()
+	str += getCloseNode(node).token
+	return str
+}
+
+func (node *Node) SetOuterHTML(str string) {
+	closeTag := getCloseNode(node)
+	closeTag.token = ""
+
+	//use the token to insert str
+	node.token = str
+	node.Children = []*Node{}
+
+	// regen parent
+	tokList := getTokenList(node.Parent.InnerHTML())
+	tree := buildTree(tokList)
+
+	node.Parent.Children = tree.Children
+}
+
+func (node *Node) InnerHTML() string {
+
+	str := ""
+
+	for _, child := range node.Children {
+		str += child.token
+		str += child.InnerHTML()
+		//fmt.Println(child.token)
+	}
+
+	return str
+}
+
+func (node *Node) SetInnerHTML(str string) {
+	tokList := getTokenList(str)
+	document := buildTree(tokList)
+	node.Children = document.Children
+}
+
+func (node *Node) QuerySelector(str string) *Node {
+	found := &Node{}
+
+	for _, child := range node.Children {
+		if matchSelector(child, str) {
+			return child
+		}
+		found := child.QuerySelector(str)
+
+		if found.token != "" {
+			return found
+		}
+	}
+
+	return found
+}
+
+func (node *Node) QuerySelectorAll(str string) []*Node {
+	found := []*Node{}
+
+	for _, child := range node.Children {
+		if getTagName(child.token) == str && (getTokType(child.token) == "open" || getTokType(child.token) == "selfclosing") {
+			found = append(found, child)
+		}
+		if str == "*" && (getTokType(child.token) == "open" || getTokType(child.token) == "selfclosing") {
+			found = append(found, child)
+		}
+		found = append(found, child.QuerySelectorAll(str)...)
+	}
+
+	return found
+}
+
+func (node *Node) GetAttribute(str string) string {
+
+	for key, val := range node.AttributeList() {
+		if key == str {
+			return val
+		}
+	}
+	return ""
+}
+
+func (node *Node) HasAttribute(str string) bool {
+
+	for key := range node.AttributeList() {
+		if key == str {
+			return true
+		}
+	}
+	return false
+}
+
+func (node *Node) SetAttribute(attr string, val string) {
+	attributes := node.AttributeList()
+	attributes[attr] = val
+
+	attrStr := ""
+	for key, val := range attributes {
+		attrStr += key
+		if val != "" {
+			attrStr += "=" + "\"" + val + "\""
+		}
+		attrStr += " "
+
+	}
+	attrStr = strings.Trim(attrStr, " ")
+	node.token = "<" + getTagName(node.token) + " " + attrStr + ">"
+}
+
+func (node *Node) RemoveAttribute(attr string) {
+	attributes := node.AttributeList()
+
+	attrStr := ""
+	for key, val := range attributes {
+		if key == attr {
+			continue
+		}
+		attrStr += key
+		if val != "" {
+			attrStr += "=" + "\"" + val + "\""
+		}
+		attrStr += " "
+
+	}
+	attrStr = strings.Trim(attrStr, " ")
+	node.token = "<" + getTagName(node.token) + " " + attrStr + ">"
+}
+
+func (node *Node) AttributeList() map[string]string {
+	attributesList := make(map[string]string)
+
+	if getTokType(node.token) != "open" && getTokType(node.token) != "selfclosing" {
+		return attributesList
+	}
+
+	attrStr := strings.Replace(node.token, "<"+getTagName(node.token), "", 1)
+	attrStr = strings.Trim(attrStr, "<> ")
+
+	tok := ""
+	state := "key"
+	strKey := ""
+
+	for i := 0; i < len(attrStr); i++ {
+		chr := string(attrStr[i])
+
+		if state == "key" {
+			if i == len(attrStr)-1 {
+				tok += chr
+				strKey = strings.Trim(tok, " ")
+				attributesList[strKey] = ""
+				continue
+			}
+			if chr == "=" {
+				strKey = strings.Trim(tok, " ")
+				state = "qual"
+				tok = ""
+				continue
+			}
+			if chr == " " {
+				strKey = strings.Trim(tok, " ")
+				if strKey != "" {
+					attributesList[strKey] = ""
+				}
+				strKey = ""
+				tok = ""
+				continue
+			}
+		}
+
+		if state == "qual" {
+			if chr == "\"" {
+				state = "val"
+				tok = ""
+				continue
+			}
+		}
+
+		if state == "val" {
+			if chr == "\"" {
+				attributesList[strKey] = strings.Trim(tok, " ")
+				state = "key"
+				tok = ""
+				continue
+			}
+		}
+
+		tok += chr
+	}
+
+	return attributesList
+}
+
+func (node *Node) ClassList() []string {
+	classStr := node.GetAttribute("class")
+	classList := strings.Split(classStr, " ")
+
+	return classList
+}
+
+func (node *Node) ClassListAdd(classStr string) {
+	classList := append(node.ClassList(), classStr)
+
+	str := strings.Join(classList, " ")
+	str = strings.Trim(str, " ")
+	node.SetAttribute("class", str)
+}
+
+func (node *Node) ClassListRemove(classStr string) {
+	classList := []string{}
+
+	for _, class := range node.ClassList() {
+		if class != classStr {
+			classList = append(classList, class)
+		}
+	}
+
+	str := strings.Join(classList, " ")
+
+	str = strings.Trim(str, " ")
+	node.SetAttribute("class", str)
+}
+
+func (node *Node) Remove() {
+	closeNode := getCloseNode(node)
+
+	i := 0
+	for _, n := range node.Parent.Children {
+		if n == closeNode {
+			node.Parent.Children = append(node.Parent.Children[:i], node.Parent.Children[i+1:]...)
+			break
+		}
+		i++
+	}
+
+	i = 0
+	for _, n := range node.Parent.Children {
+		if n == node {
+			node.Parent.Children = append(node.Parent.Children[:i], node.Parent.Children[i+1:]...)
+			break
+		}
+		i++
+	}
+}
+
+func (node *Node) CreateElement(tagName string) *Node {
+	newNode := &Node{}
+	newNode.token = "<" + tagName + ">"
+	return newNode
+}
+
+func (node *Node) Append(newNode *Node) {
+	newNode.Parent = node
+	node.Children = append(node.Children, newNode)
+	if getTokType(newNode.token) != "selfclosing" {
+		closeNode := &Node{}
+		closeNode.token = "</" + getTagName(newNode.token) + ">"
+		node.Children = append(node.Children, closeNode)
+	}
+
+}

+ 158 - 0
pkg/gen/html.go

@@ -0,0 +1,158 @@
+package gen
+
+import (
+	"gex/pkg/gen/domquery"
+)
+
+func AddClassAttr(htmlStr string, className string) string {
+	dom := domquery.LoadHTML(htmlStr)
+
+	tags := dom.QuerySelectorAll("*")
+
+	for _, tag := range tags {
+		if isExcludedTag(tag.TagName()) {
+			continue
+		}
+		tag.ClassListAdd(className)
+
+	}
+	return dom.InnerHTML()
+}
+
+func isExcludedTag(tagName string) bool {
+
+	validTags := map[string]bool{
+		"html":   true,
+		"head":   true,
+		"style":  true,
+		"script": true,
+		"link":   true,
+		"meta":   true,
+		"title":  true,
+		"slot":   true,
+		"block":  true,
+		"layout": true,
+		"part":   true,
+	}
+
+	return validTags[tagName]
+}
+
+// func isTargetTag(tagName string) bool {
+
+// 	validTags := map[string]bool{
+// 		"a":          true,
+// 		"abbr":       true,
+// 		"address":    true,
+// 		"area":       true,
+// 		"article":    true,
+// 		"aside":      true,
+// 		"audio":      true,
+// 		"b":          true,
+// 		"blockquote": true,
+// 		"body":       true,
+// 		"br":         true,
+// 		"button":     true,
+// 		"canvas":     true,
+// 		"caption":    true,
+// 		"cite":       true,
+// 		"code":       true,
+// 		"col":        true,
+// 		"colgroup":   true,
+// 		"data":       true,
+// 		"datalist":   true,
+// 		"dd":         true,
+// 		"del":        true,
+// 		"details":    true,
+// 		"dialog":     true,
+// 		"div":        true,
+// 		"dl":         true,
+// 		"dt":         true,
+// 		"em":         true,
+// 		"fieldset":   true,
+// 		"figcaption": true,
+// 		"figure":     true,
+// 		"footer":     true,
+// 		"form":       true,
+// 		"h1":         true,
+// 		"h2":         true,
+// 		"h3":         true,
+// 		"h4":         true,
+// 		"h5":         true,
+// 		"h6":         true,
+// 		"header":     true,
+// 		"hr":         true,
+// 		"i":          true,
+// 		"iframe":     true,
+// 		"img":        true,
+// 		"input":      true,
+// 		"ins":        true,
+// 		"kbd":        true,
+// 		"label":      true,
+// 		"legend":     true,
+// 		"li":         true,
+// 		"main":       true,
+// 		"map":        true,
+// 		"mark":       true,
+// 		"meter":      true,
+// 		"nav":        true,
+// 		"noscript":   true,
+// 		"object":     true,
+// 		"ol":         true,
+// 		"option":     true,
+// 		"optgroup":   true,
+// 		"p":          true,
+// 		"picture":    true,
+// 		"pre":        true,
+// 		"progress":   true,
+// 		"q":          true,
+// 		"samp":       true,
+// 		"section":    true,
+// 		"select":     true,
+// 		"small":      true,
+// 		"source":     true,
+// 		"span":       true,
+// 		"strong":     true,
+// 		"sub":        true,
+// 		"summary":    true,
+// 		"sup":        true,
+// 		"svg":        true,
+// 		"table":      true,
+// 		"tbody":      true,
+// 		"td":         true,
+// 		"textarea":   true,
+// 		"tfoot":      true,
+// 		"th":         true,
+// 		"thead":      true,
+// 		"time":       true,
+// 		"tr":         true,
+// 		"track":      true,
+// 		"u":          true,
+// 		"ul":         true,
+// 		"var":        true,
+// 		"video":      true,
+// 		"wbr":        true,
+
+// 		"label-wc":    true,
+// 		"textarea-wc": true,
+// 		"fieldset-wc": true,
+// 		"input-wc":    true,
+// 		"select-wc":   true,
+// 		"button-wc":   true,
+// 	}
+
+// 	return validTags[tagName]
+// }
+
+func ExtractCSS(htmlStr *string) string {
+	document := domquery.LoadHTML(*htmlStr)
+	styleTag := document.QuerySelector("style")
+
+	css := ""
+	if styleTag.InnerHTML() != "" {
+		css = styleTag.InnerHTML()
+		styleTag.SetOuterHTML("")
+		*htmlStr = document.InnerHTML()
+	}
+	return css
+}

+ 94 - 0
pkg/gen/img/img.go

@@ -0,0 +1,94 @@
+package img
+
+import (
+	"fmt"
+	"gex/pkg/gen/domquery"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+)
+
+func Process(htmlDir string, tag *domquery.Node) string {
+
+	imgSrc := tag.GetAttribute("src")
+	fileName := filepath.Base(imgSrc)
+
+	if !strings.Contains(imgSrc, "app/") {
+		imgSrc = htmlDir + "/" + imgSrc
+	}
+
+	imgSrc = strings.Replace(imgSrc, "/app/", "app/", 1)
+	_, err := os.ReadFile(imgSrc)
+	if err != nil {
+		fmt.Println(err)
+		return ""
+	}
+
+	w := tag.GetAttribute("w")
+	h := tag.GetAttribute("h")
+
+	cx := tag.GetAttribute("cx")
+	if cx == "" {
+		cx = "0"
+	}
+
+	cy := tag.GetAttribute("cy")
+	if cy == "" {
+		cy = "0"
+	}
+
+	cw := tag.GetAttribute("cw")
+	if cw == "" {
+		cw = "0"
+	}
+
+	ch := tag.GetAttribute("ch")
+	if ch == "" {
+		ch = "0"
+	}
+
+	q := tag.GetAttribute("q")
+
+	command := "convert " + imgSrc + " -auto-orient"
+	if w != "" || h != "" {
+		command += " -resize " + w + "x" + h
+	}
+
+	if cw != "" && ch != "" {
+		command += " -crop " + cw + "x" + ch + "+" + cx + "+" + cy
+	}
+
+	if q != "" {
+		command += " -quality " + q
+	}
+
+	newImgSrc := "app/pub/gen/" + strings.Split(fileName, ".")[0] + w + h + cx + cy + cw + ch + q + ".webp"
+	_, err = os.ReadFile(newImgSrc)
+	if err == nil {
+		tag.SetAttribute("src", "/"+newImgSrc)
+		return ""
+	} else {
+		fmt.Println(err)
+	}
+
+	command += " " + newImgSrc
+
+	cmd := exec.Command("bash", "-c", command)
+	_, err = cmd.Output()
+
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	tag.SetAttribute("src", "/"+newImgSrc)
+	tag.RemoveAttribute("webp")
+	tag.RemoveAttribute("w")
+	tag.RemoveAttribute("h")
+	tag.RemoveAttribute("cx")
+	tag.RemoveAttribute("cy")
+	tag.RemoveAttribute("cw")
+	tag.RemoveAttribute("ch")
+
+	return ""
+}

+ 55 - 0
pkg/gen/layout/main.go

@@ -0,0 +1,55 @@
+package layout
+
+import (
+	"fmt"
+	"gex/pkg/gen/domquery"
+	"os"
+	"strings"
+)
+
+func ProcessHTML(htmlStr string) (string, error) {
+	dom := domquery.LoadHTML(htmlStr)
+	layoutTag := dom.QuerySelector("layout")
+
+	if layoutTag.TagName() == "" {
+		return dom.OuterHTML(), nil
+	}
+
+	attr := layoutTag.GetAttribute("src")
+
+	attr = strings.TrimPrefix(attr, "/")
+
+	attr = strings.Replace(attr, ".tpl", ".gen.tpl", 1)
+
+	layoutFileByt, err := os.ReadFile(attr)
+	if err != nil {
+		fmt.Printf("ERROR: %s", err)
+		return "", err
+	}
+
+	// get the block values
+	blocks := layoutTag.QuerySelectorAll("block")
+
+	layoutFileHtml := string(layoutFileByt)
+	layoutFileHtml, err = ProcessHTML(layoutFileHtml)
+	if err != nil {
+		fmt.Printf("ERROR: %s\n", err)
+	}
+
+	layoutFileDom := domquery.LoadHTML(layoutFileHtml)
+
+	layoutFileBlocks := layoutFileDom.QuerySelectorAll("block")
+
+	for _, block := range blocks {
+		for _, layoutFileBlock := range layoutFileBlocks {
+			if block.GetAttribute("id") == layoutFileBlock.GetAttribute("id") {
+				layoutFileBlock.SetInnerHTML(block.InnerHTML())
+			}
+		}
+	}
+
+	layoutTag.SetOuterHTML(layoutFileDom.InnerHTML())
+	htmlStr = dom.InnerHTML()
+	//fmt.Println(htmlStr + "\n---------------------------")
+	return htmlStr, nil
+}

+ 361 - 0
pkg/gen/main.go

@@ -0,0 +1,361 @@
+package gen
+
+import (
+	"fmt"
+	"gex/pkg/gen/domquery"
+	"gex/pkg/gen/img"
+	"gex/pkg/gen/layout"
+	"gex/pkg/gen/partial"
+	"math/rand"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+var appRuleList cssRuleList
+
+//var appJsStr string
+
+func GeneratePages(dir string) error {
+	err := processTemplate(dir)
+	if err != nil {
+		fmt.Println(err)
+		return err
+	}
+
+	// Process Partials
+	err = processPartials(dir)
+	if err != nil {
+		fmt.Println(err)
+		return err
+	}
+
+	// Build the layout & blocks
+	err = processLayout(dir)
+	if err != nil {
+		fmt.Println(err)
+		return err
+	}
+
+	// Process IMG on the generated files
+	err = ProcessImg(dir)
+	if err != nil {
+		fmt.Println(err)
+		return err
+	}
+
+	// Clean block tags
+	err = cleanBlocks(dir)
+	if err != nil {
+		fmt.Println(err)
+		return err
+	}
+
+	appRuleList = mergeMediaRules(appRuleList)
+	appRuleList = orderRules(appRuleList)
+	cssStr := rulesToCSSstr(appRuleList)
+
+	os.WriteFile("app/pub/css/app.gen.css", []byte(cssStr), 0655)
+
+	return nil
+}
+
+func processTemplate(dir string) error {
+	processFile := func(fp string) error {
+		if strings.Contains(fp, ".gen.tpl") {
+			return nil
+		}
+
+		if !strings.Contains(fp, ".tpl") {
+			return nil
+		}
+
+		// get the html string
+		data, err := os.ReadFile(fp)
+		if err != nil {
+			return err
+		}
+		htmlStr := string(data)
+
+		// add the scope class to attributes
+		className := genRandName(8)
+		htmlStr = AddClassAttr(htmlStr, className)
+
+		// get & strip the css rules
+		cssStr := ExtractCSS(&htmlStr)
+
+		ruleList := toRuleList(cssStr)
+
+		// scope the rules with css class
+		ruleList = scopeSelectors(ruleList, className)
+
+		appRuleList = append(appRuleList, ruleList...)
+
+		// Process JS on the generated file
+		htmlStr = ProcessJs(htmlStr, className)
+
+		// write the .gen.html file as
+		newPath := strings.Replace(fp, ".tpl", ".gen.tpl", 1)
+		return os.WriteFile(newPath, []byte(htmlStr), 0655)
+
+	}
+
+	walk := func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if info.IsDir() {
+			return nil
+		}
+
+		return processFile(path)
+
+	}
+	return filepath.Walk(dir, walk)
+}
+
+func processPartials(dir string) error {
+	processFile := func(fp string) error {
+		if !strings.Contains(fp, ".gen.tpl") {
+			return nil
+		}
+
+		data, err := os.ReadFile(fp)
+		if err != nil {
+			return err
+		}
+		htmlStr := string(data)
+
+		htmlStr, err = partial.ProcessHTML(htmlStr)
+		if err != nil {
+			fmt.Println("\n" + fp)
+			return err
+		}
+
+		return os.WriteFile(fp, []byte(htmlStr), 0655)
+	}
+
+	walk := func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if info.IsDir() {
+			return nil
+		}
+
+		return processFile(path)
+
+	}
+	err := filepath.Walk(dir, walk)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func processLayout(dir string) error {
+	processFile := func(fp string) error {
+		if !strings.Contains(fp, ".gen.tpl") {
+			return nil
+		}
+
+		data, err := os.ReadFile(fp)
+		if err != nil {
+			return err
+		}
+		htmlStr := string(data)
+
+		htmlStr, err = layout.ProcessHTML(htmlStr)
+		if err != nil {
+			fmt.Println("\n" + fp)
+			return err
+		}
+
+		os.WriteFile(fp, []byte(htmlStr), 0655)
+		return nil
+	}
+
+	walk := func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if info.IsDir() {
+			return nil
+		}
+
+		return processFile(path)
+	}
+
+	filepath.Walk(dir, walk)
+
+	return nil
+}
+
+func ProcessJs(htmlStr string, className string) string {
+
+	dom := domquery.LoadHTML(htmlStr)
+
+	scripts := dom.QuerySelectorAll("script")
+
+	i := 0
+	jsTag := ""
+	for _, script := range scripts {
+		scriptName := className + fmt.Sprintf("%d", i)
+
+		if script.GetAttribute("inline") != "" {
+			continue
+		}
+
+		if script.GetAttribute("src") != "" {
+			continue
+		}
+
+		jsStr := script.InnerHTML()
+
+		funcStr := `if (typeof ` + scriptName + ` === 'undefined') {`
+		funcStr += `window.` + scriptName + ` = () => {`
+		funcStr += jsStr
+		funcStr += `
+				if(typeof unload == "undefined")
+	                return
+
+	            const watchInt = setInterval(() => {
+	                    let watch = document.querySelector(".` + className + `");
+	                    if (!watch) {
+	                        unload();
+	                        clearInterval(watchInt);
+	                    }
+	            },100);
+	        }
+		}
+			` + scriptName + `()
+		`
+
+		srcPath := "app/pub/gen/" + scriptName + ".gen.js"
+		os.WriteFile(srcPath, []byte(funcStr), 0655)
+
+		jsTag += `<script defer src="/` + srcPath + `"></script>`
+
+		if script.GetAttribute("type") == "module" {
+			jsTag = `<script type="module" defer src="/` + srcPath + `"></script>`
+		}
+
+		script.Remove()
+
+		i++
+	}
+	insDiv := dom.QuerySelector("." + className)
+	insDiv.SetInnerHTML(jsTag + insDiv.InnerHTML())
+
+	htmlStr = dom.InnerHTML()
+
+	return htmlStr
+}
+
+func ProcessImg(dir string) error {
+
+	processFile := func(fp string) error {
+		htmlDir := filepath.Dir(fp)
+
+		if !strings.Contains(fp, ".gen.tpl") {
+			return nil
+		}
+
+		// get the html string
+		data, err := os.ReadFile(fp)
+		if err != nil {
+			return err
+		}
+		htmlStr := string(data)
+
+		document := domquery.LoadHTML(htmlStr)
+
+		imgTags := document.QuerySelectorAll("img")
+
+		for _, tag := range imgTags {
+			if !(tag.HasAttribute("webp") && tag.HasAttribute("src")) {
+				continue
+			}
+
+			img.Process(htmlDir, tag)
+		}
+
+		htmlStr = document.InnerHTML()
+		os.WriteFile(fp, []byte(htmlStr), 0655)
+		return nil
+	}
+
+	walk := func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if info.IsDir() {
+			return nil
+		}
+
+		return processFile(path)
+
+	}
+
+	return filepath.Walk(dir, walk)
+}
+
+func cleanBlocks(dir string) error {
+	processFile := func(fp string) error {
+		if !strings.Contains(fp, ".gen.tpl") {
+			return nil
+		}
+
+		// get the html string
+		data, err := os.ReadFile(fp)
+		if err != nil {
+			return err
+		}
+		htmlStr := string(data)
+		dom := domquery.LoadHTML(htmlStr)
+
+		var clean func()
+		clean = func() {
+			blocks := dom.QuerySelectorAll("block")
+
+			// grab the top block only as blocks is invalid after a SetOuterHTML event
+			for _, block := range blocks {
+				block.SetOuterHTML(block.InnerHTML())
+				clean()
+				return
+			}
+		}
+		clean()
+
+		htmlStr = dom.InnerHTML()
+		os.WriteFile(fp, []byte(htmlStr), 0655)
+		return nil
+	}
+
+	walk := func(dir string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if info.IsDir() {
+			return nil
+		}
+
+		return processFile(dir)
+	}
+	return filepath.Walk(dir, walk)
+}
+
+// generate a random string to use as a class name
+func genRandName(length int) string {
+	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	b := make([]byte, length)
+	for i := range b {
+		b[i] = charset[rand.Intn(len(charset))]
+	}
+	return string(b)
+}

+ 43 - 0
pkg/gen/partial/partial.go

@@ -0,0 +1,43 @@
+package partial
+
+import (
+	"fmt"
+	"gex/pkg/gen/domquery"
+	"os"
+	"strings"
+)
+
+func ProcessHTML(htmlStr string) (string, error) {
+	dom := domquery.LoadHTML(htmlStr)
+
+	partTag := dom.QuerySelector("part")
+
+	if partTag.TagName() == "" {
+		return dom.OuterHTML(), nil
+	}
+
+	attr := partTag.GetAttribute("src")
+	attr = strings.TrimPrefix(attr, "/")
+	attr = strings.Replace(attr, ".tpl", ".gen.tpl", 1)
+
+	partFileByt, err := os.ReadFile(attr)
+	if err != nil {
+		fmt.Printf("ERROR: %s", err)
+		return "", err
+	}
+	partFileHtml := string(partFileByt)
+	partFileDom := domquery.LoadHTML(partFileHtml)
+
+	slot := partFileDom.QuerySelector("slot")
+	if slot.TagName() != "" {
+		slot.SetOuterHTML(partTag.InnerHTML())
+	}
+	partTag.SetOuterHTML(partFileDom.InnerHTML())
+
+	htmlStr, err = ProcessHTML(dom.InnerHTML())
+	if err != nil {
+		fmt.Printf("ERROR: %s", err)
+		return "", err
+	}
+	return htmlStr, nil
+}

+ 9 - 0
pkg/gex/middleware.go

@@ -0,0 +1,9 @@
+package gex
+
+type middlewareHandler func(req *Req, res Res) bool
+
+var middleware []middlewareHandler
+
+func AddMiddleware(fun middlewareHandler) {
+	middleware = append(middleware, fun)
+}

+ 51 - 0
pkg/gex/request.go

@@ -0,0 +1,51 @@
+package gex
+
+import (
+	"net/http"
+	"strings"
+)
+
+type Req struct {
+	r          *http.Request
+	AppURL     string
+	Path       string
+	ParentPath string
+	Pattern    string
+	Ctx        map[string]any
+}
+
+func (req *Req) Construct(r *http.Request) {
+	req.r = r
+	req.AppURL = "/" + r.Host
+	req.Path = r.RequestURI
+	req.ParentPath = req.getParentPath()
+	req.Pattern = req.getPattern()
+	req.Ctx = make(map[string]any)
+}
+
+// The route pattern eg: /user
+func (req *Req) getPattern() string {
+	_, pattern := router.Handler(req.r)
+	return pattern
+}
+
+func (req *Req) Cookie(name string) (string, error) {
+	cookie, err := req.r.Cookie(name)
+	if err != nil {
+		return "", err
+	}
+
+	return cookie.Value, nil
+}
+
+func (req *Req) getParentPath() string {
+	parentPath := ""
+	arr := strings.Split(req.Path, "/")
+	for i := 0; i < len(arr)-1; i++ {
+		if arr[i] == "" {
+			continue
+		}
+		parentPath += "/" + arr[i]
+	}
+	return parentPath
+}

+ 39 - 0
pkg/gex/response.go

@@ -0,0 +1,39 @@
+package gex
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+)
+
+type Res struct {
+	w http.ResponseWriter
+	r *http.Request
+}
+
+func (res *Res) Construct(w http.ResponseWriter, r *http.Request) {
+	res.w = w
+	res.r = r
+}
+
+func (res *Res) Send(txt string) {
+	fmt.Fprint(res.w, txt)
+}
+
+func (res *Res) Redirect(url string) {
+	http.Redirect(res.w, res.r, url, http.StatusTemporaryRedirect)
+}
+
+func (res *Res) Cookie(name string, val string) {
+	cookie := &http.Cookie{
+		Name:     name,
+		Value:    val,
+		Expires:  time.Now().Add(365 * 24 * time.Hour),
+		Path:     "/",
+		Secure:   true,
+		HttpOnly: true,
+		SameSite: http.SameSiteStrictMode,
+	}
+
+	http.SetCookie(res.w, cookie)
+}

+ 43 - 0
pkg/gex/router.go

@@ -0,0 +1,43 @@
+package gex
+
+import (
+	"net/http"
+)
+
+var router *http.ServeMux
+
+/*
+Our Special HandleFunc
+*/
+func Route(pattern string, handler func(req *Req, res Res)) {
+
+	realHandler := func(w http.ResponseWriter, r *http.Request) {
+
+		var req Req
+		var res Res
+
+		req.Construct(r)
+		res.Construct(w, r)
+
+		// run middleware on each request
+		for _, fun := range middleware {
+			cont := fun(&req, res)
+			if !cont {
+				return
+			}
+		}
+
+		handler(&req, res)
+
+	}
+
+	router.HandleFunc(pattern, realHandler)
+}
+
+func InitRouter() {
+	router = http.NewServeMux()
+
+	// Public Files
+	fs := http.FileServer(http.Dir("app/pub"))
+	router.Handle("GET /app/pub/", http.StripPrefix("/app/pub/", fs))
+}

+ 25 - 0
pkg/gex/server.go

@@ -0,0 +1,25 @@
+package gex
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+	"os"
+)
+
+func StartServer() {
+	var PORT string = os.Args[1]
+
+	server := http.Server{
+		Addr:    fmt.Sprintf(":%v", PORT),
+		Handler: router,
+	}
+
+	fmt.Println("Server Running")
+
+	err := server.ListenAndServe()
+	if err != nil {
+		log.Fatalf("Server error: %v", err)
+		os.Exit(1)
+	}
+}

+ 15 - 0
pkg/render/global.go

@@ -0,0 +1,15 @@
+package render
+
+import (
+	"gex/pkg/gex"
+	"gex/pkg/sess"
+)
+
+func global(req *gex.Req, props Props, funcs Funcs) {
+	sess := req.Ctx["Sess"].(sess.Sess)
+
+	props["Sess"] = &sess
+	props["Req"] = &req
+
+	funcs["HasRole"] = sess.HasRole
+}

+ 53 - 0
pkg/render/render.go

@@ -0,0 +1,53 @@
+package render
+
+import (
+	"bytes"
+	"gex/pkg/gex"
+	"gex/pkg/utils"
+	"html/template"
+	"path/filepath"
+	"strings"
+)
+
+type Props map[string]any
+type Funcs = template.FuncMap
+
+func Template(fp string, req *gex.Req, propFunc ...interface{}) string {
+	if !strings.Contains(fp, "app/") {
+		fp = utils.Cwd(2) + "/" + fp
+	}
+
+	fp = strings.Replace(fp, ".tpl", ".gen.tpl", 1)
+
+	var props = Props{}
+	var funcs = Funcs{}
+
+	if len(propFunc) > 0 {
+		props = propFunc[0].(Props)
+	}
+	if len(propFunc) > 1 {
+		funcs = propFunc[1].(Funcs)
+	}
+
+	global(req, props, funcs)
+
+	tmpl, err := template.
+		New(filepath.Base(fp)).
+		Funcs(funcs).
+		ParseFiles(fp)
+
+	if err != nil {
+		return err.Error()
+	}
+
+	var buf bytes.Buffer
+	err = tmpl.Execute(&buf, props)
+
+	if err != nil {
+		return err.Error()
+	}
+
+	html := buf.String()
+
+	return html
+}

+ 27 - 0
pkg/sec/middleware.go

@@ -0,0 +1,27 @@
+package sec
+
+import (
+	"gex/pkg/gex"
+	"gex/pkg/sess"
+)
+
+func Middleware(req *gex.Req, res gex.Res) bool {
+	pattern := req.Pattern
+
+	// Cancel the security check as there are no permissions for this route
+	if len(permissions[pattern]) == 0 {
+		return true
+	}
+
+	handlerRoles := permissions[pattern]
+	sess := req.Ctx["Sess"].(sess.Sess)
+
+	for _, role := range handlerRoles {
+		if sess.HasRole(role) {
+			return true
+		}
+	}
+
+	res.Send("No Access")
+	return false
+}

+ 10 - 0
pkg/sec/secure.go

@@ -0,0 +1,10 @@
+package sec
+
+import "gex/pkg/gex"
+
+var permissions = make(map[string][]string)
+
+func Route(pattern string, roles []string, handler func(req *gex.Req, res gex.Res)) {
+	permissions[pattern] = roles
+	gex.Route(pattern, handler)
+}

+ 33 - 0
pkg/sess/base64/base64url.go

@@ -0,0 +1,33 @@
+// base64url provides base64url encoding/decoding functions
+package base64
+
+import (
+	"encoding/base64"
+	"strings"
+)
+
+// decodes base64url string to bytes
+func DecodeURL(data string) ([]byte, error) {
+	data = strings.Replace(data, "-", "+", -1)
+	data = strings.Replace(data, "_", "/", -1)
+
+	switch len(data) % 4 {
+	case 0:
+	case 2:
+		data += "=="
+	case 3:
+		data += "="
+	}
+
+	return base64.StdEncoding.DecodeString(data)
+}
+
+// encodes given bytes to base64url string
+func EncodeURL(data []byte) string {
+	result := base64.StdEncoding.EncodeToString(data)
+	result = strings.Replace(result, "+", "-", -1)
+	result = strings.Replace(result, "/", "_", -1)
+	result = strings.Replace(result, "=", "", -1)
+
+	return result
+}

+ 7 - 0
pkg/sess/config.go

@@ -0,0 +1,7 @@
+package sess
+
+import "time"
+
+var TokenName string = "GexToken"
+var Expires time.Time = time.Now().Add(24 * time.Hour)
+var Secret string = "secret"

+ 52 - 0
pkg/sess/jwt/jwt.go

@@ -0,0 +1,52 @@
+// provides jwt encode and decode functions
+package jwt
+
+import (
+	"crypto/hmac"
+	"crypto/sha256"
+	"errors"
+	"fmt"
+	"gex/pkg/sess/base64"
+	"strings"
+)
+
+// encode json bytes to a jwt token string
+func Encode(jsonStr []byte, secret string) (string, error) {
+
+	header := base64.EncodeURL([]byte("{\"alg\":\"HS256\",\"typ\":\"JWT\"}"))
+	payload := base64.EncodeURL(jsonStr)
+	mac := hmac.New(sha256.New, []byte(secret))
+	mac.Write([]byte(header + "." + payload))
+	sig := base64.EncodeURL(mac.Sum(nil))
+
+	return header + "." + payload + "." + sig, nil
+}
+
+// decode a jwt token string to a json string to be processed
+func Decode(tokenStr string, secret string) ([]byte, error) {
+	parts := strings.Split(tokenStr, ".")
+	header := parts[0]
+	payload := parts[1]
+	sig, err := base64.DecodeURL(parts[2])
+	if err != nil {
+		fmt.Printf("ERROR: %s", err)
+		return []byte(""), err
+	}
+
+	mac := hmac.New(sha256.New, []byte(secret))
+	mac.Write([]byte(header + "." + payload))
+	expectedSig := mac.Sum(nil)
+
+	if !hmac.Equal([]byte(sig), expectedSig) {
+		return []byte(""), errors.New("signature don't match")
+	}
+
+	jsonStr, err := base64.DecodeURL(payload)
+
+	if err != nil {
+		fmt.Printf("ERROR: %s", err)
+		return []byte(""), err
+	}
+
+	return jsonStr, nil
+}

+ 11 - 0
pkg/sess/middleware.go

@@ -0,0 +1,11 @@
+package sess
+
+import "gex/pkg/gex"
+
+func Middleware(req *gex.Req, res gex.Res) bool {
+	var sess Sess
+	sess.Construct(req, res)
+	sess.Save()
+	req.Ctx["Sess"] = sess
+	return true
+}

+ 101 - 0
pkg/sess/session.go

@@ -0,0 +1,101 @@
+package sess
+
+import (
+	"encoding/json"
+	"fmt"
+	"gex/pkg/gex"
+	"gex/pkg/sess/jwt"
+
+	"time"
+)
+
+type Sess struct {
+	req       *gex.Req
+	res       gex.Res
+	User_id   int
+	User_name string
+	Roles     []string
+	Expires   time.Time
+}
+
+func (sess *Sess) HasRole(roleName string) bool {
+	for _, role := range sess.Roles {
+		if role == roleName {
+			return true
+		}
+	}
+	return false
+}
+
+func (sess *Sess) setDefaults() {
+	sess.User_id = 0
+	sess.User_name = "Guest"
+	sess.Roles = []string{"Guest", "Everyone"}
+	sess.Expires = Expires
+}
+
+func (sess *Sess) Construct(req *gex.Req, res gex.Res) {
+	sess.req = req
+	sess.res = res
+	sess.setDefaults()
+
+	// check cookie is valid (not expired too)
+	cookie, err := req.Cookie(TokenName)
+
+	if err != nil {
+		//fmt.Println("cookie error")
+		return
+	}
+
+	// decode jwt to json bytes
+	jsonByt, err := jwt.Decode(cookie, Secret)
+	if err != nil {
+		fmt.Println("jwt decode error")
+		return
+	}
+
+	// decode json bytes to session
+	err = json.Unmarshal(jsonByt, &sess)
+	if err != nil {
+		fmt.Println("jwt to session error")
+		return
+	}
+
+	// if session token has expired, return default session
+	if time.Now().After(sess.Expires) {
+		fmt.Println("session expired")
+		sess.setDefaults()
+	}
+
+	sess.Expires = Expires
+
+}
+
+func (sess *Sess) Token() (string, error) {
+	jsonStr, err := json.Marshal(sess)
+	if err != nil {
+		return "", err
+	}
+
+	// encode the json to jwt and set the cookie
+	token, err := jwt.Encode(jsonStr, Secret)
+	if err != nil {
+		return "", err
+	}
+	return token, nil
+}
+
+// Saves token to cookie
+func (sess *Sess) Save() {
+	// get existing session or create new one
+
+	// get the session token
+	token, err := sess.Token()
+	if err != nil {
+		sess.res.Send(err.Error())
+		return
+	}
+
+	// set the token cookie
+	sess.res.Cookie(TokenName, token)
+}

+ 28 - 0
pkg/utils/utils.go

@@ -0,0 +1,28 @@
+package utils
+
+import (
+	"log"
+	"os"
+	"runtime"
+	"strconv"
+	"strings"
+)
+
+func Cwd(i int) string {
+	_, fp, _, _ := runtime.Caller(i)
+	slice := strings.Split(fp, "/")
+	slice = slice[:len(slice)-1]
+	path := strings.Join(slice, "/")
+	return path
+}
+
+func CheckArgs() {
+	if len(os.Args) < 2 {
+		log.Fatalf("ERROR: port number required: eg ./bin 3000")
+	}
+
+	_, err := strconv.Atoi(os.Args[1])
+	if err != nil {
+		log.Fatalf("ERROR: port must be a number")
+	}
+}

+ 1 - 0
tmp/build-errors.log

@@ -0,0 +1 @@
+exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1

BIN
tmp/main