├── static ├── favicons │ ├── favicon.ico │ ├── blankfavicon.png │ ├── mstile-70x70.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── apple-touch-icon.png │ ├── favicon-160x160.png │ ├── favicon-196x196.png │ ├── apple-touch-icon-57x57.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-72x72.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon-114x114.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-144x144.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-precomposed.png │ └── browserconfig.xml ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── css │ ├── global.css │ └── bootstrap-theme.min.css └── js │ ├── global.js │ ├── underscore-min.js │ ├── underscore-min.map │ └── bootstrap.min.js ├── template ├── blank.tmpl ├── partial │ ├── footer.tmpl │ └── menu.tmpl ├── index │ ├── anon.tmpl │ └── auth.tmpl ├── about │ └── about.tmpl ├── notepad │ ├── create.tmpl │ ├── update.tmpl │ └── read.tmpl ├── login │ └── login.tmpl ├── register │ └── register.tmpl └── base.tmpl ├── vendor └── app │ ├── controller │ ├── about.go │ ├── static.go │ ├── index.go │ ├── error.go │ ├── register.go │ ├── login.go │ └── notepad.go │ ├── shared │ ├── view │ │ ├── plugin │ │ │ ├── noescape.go │ │ │ ├── prettytime.go │ │ │ └── taghelper.go │ │ └── view.go │ ├── jsonconfig │ │ └── jsonconfig.go │ ├── passhash │ │ ├── passhash.go │ │ └── passhash_test.go │ ├── recaptcha │ │ └── recaptcha.go │ ├── session │ │ └── session.go │ ├── email │ │ └── email.go │ ├── server │ │ └── server.go │ └── database │ │ └── database.go │ ├── route │ ├── middleware │ │ ├── logrequest │ │ │ └── logrequest.go │ │ ├── acl │ │ │ └── acl.go │ │ ├── httprouterwrapper │ │ │ └── httprouterwrapper.go │ │ └── pprofhandler │ │ │ └── pprofhandler.go │ └── route.go │ └── model │ ├── model.go │ ├── user.go │ └── note.go ├── LICENSE ├── config ├── config.json └── mysql.sql ├── gowebapp.go └── README.md /static/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/favicon.ico -------------------------------------------------------------------------------- /static/favicons/blankfavicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/blankfavicon.png -------------------------------------------------------------------------------- /static/favicons/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/mstile-70x70.png -------------------------------------------------------------------------------- /static/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /static/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /static/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /static/favicons/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/mstile-144x144.png -------------------------------------------------------------------------------- /static/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /static/favicons/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/mstile-310x150.png -------------------------------------------------------------------------------- /static/favicons/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/mstile-310x310.png -------------------------------------------------------------------------------- /static/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /static/favicons/favicon-160x160.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/favicon-160x160.png -------------------------------------------------------------------------------- /static/favicons/favicon-196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/favicon-196x196.png -------------------------------------------------------------------------------- /static/favicons/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /static/favicons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /static/favicons/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /static/favicons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /static/favicons/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /static/favicons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /static/favicons/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /static/favicons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /static/favicons/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephspurrier/gowebapp/HEAD/static/favicons/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /template/blank.tmpl: -------------------------------------------------------------------------------- 1 | {{define "title"}}Blank Template{{end}} 2 | {{define "head"}}{{end}} 3 | {{define "content"}} 4 | This is a blank template. 5 | {{end}} 6 | {{define "foot"}}{{end}} -------------------------------------------------------------------------------- /template/partial/footer.tmpl: -------------------------------------------------------------------------------- 1 | {{define "footer"}} 2 | 6 | {{end}} -------------------------------------------------------------------------------- /vendor/app/controller/about.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "net/http" 5 | 6 | "app/shared/view" 7 | ) 8 | 9 | // AboutGET displays the About page 10 | func AboutGET(w http.ResponseWriter, r *http.Request) { 11 | // Display the view 12 | v := view.New(r) 13 | v.Name = "about/about" 14 | v.Render(w) 15 | } 16 | -------------------------------------------------------------------------------- /template/index/anon.tmpl: -------------------------------------------------------------------------------- 1 | {{define "title"}}Go Web App{{end}} 2 | {{define "head"}}{{end}} 3 | {{define "content"}} 4 |
5 | 8 |

Click {{LINK "login" "here"}} to login.

9 | {{template "footer" .}} 10 |
11 | {{end}} 12 | {{define "foot"}}{{end}} -------------------------------------------------------------------------------- /template/partial/menu.tmpl: -------------------------------------------------------------------------------- 1 | {{if eq .AuthLevel "auth"}} 2 | 3 | 7 | 8 | {{else}} 9 | 10 | 13 | 14 | {{end}} -------------------------------------------------------------------------------- /vendor/app/controller/static.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | ) 7 | 8 | // Static maps static files 9 | func Static(w http.ResponseWriter, r *http.Request) { 10 | // Disable listing directories 11 | if strings.HasSuffix(r.URL.Path, "/") { 12 | Error404(w, r) 13 | return 14 | } 15 | http.ServeFile(w, r, r.URL.Path[1:]) 16 | } 17 | -------------------------------------------------------------------------------- /template/index/auth.tmpl: -------------------------------------------------------------------------------- 1 | {{define "title"}}Go Web App{{end}} 2 | {{define "head"}}{{end}} 3 | {{define "content"}} 4 |
5 | 8 |

You have arrived. Click {{LINK "notepad" "here"}} to view your notepad.

9 | {{template "footer" .}} 10 |
11 | {{end}} 12 | {{define "foot"}}{{end}} -------------------------------------------------------------------------------- /vendor/app/shared/view/plugin/noescape.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "html/template" 5 | ) 6 | 7 | // NoEscape returns a template.FuncMap 8 | // * NOESCAPE prevents escaping variable 9 | func NoEscape() template.FuncMap { 10 | f := make(template.FuncMap) 11 | 12 | f["NOESCAPE"] = func(name string) template.HTML { 13 | return template.HTML(name) 14 | } 15 | 16 | return f 17 | } 18 | -------------------------------------------------------------------------------- /template/about/about.tmpl: -------------------------------------------------------------------------------- 1 | {{define "title"}}About{{end}} 2 | {{define "head"}}{{end}} 3 | {{define "content"}} 4 |
5 | 8 |

This project demonstrates how to structure and build a website using 9 | the Go language without a framework.

10 | {{template "footer" .}} 11 |
12 | {{end}} 13 | {{define "foot"}}{{end}} -------------------------------------------------------------------------------- /vendor/app/shared/view/plugin/prettytime.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "html/template" 5 | "time" 6 | ) 7 | 8 | // PrettyTime returns a template.FuncMap 9 | // * PRETTYTIME outputs a nice time format 10 | func PrettyTime() template.FuncMap { 11 | f := make(template.FuncMap) 12 | 13 | f["PRETTYTIME"] = func(t time.Time) string { 14 | return t.Format("3:04 PM 01/02/2006") 15 | } 16 | 17 | return f 18 | } 19 | -------------------------------------------------------------------------------- /vendor/app/route/middleware/logrequest/logrequest.go: -------------------------------------------------------------------------------- 1 | package logrequest 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | // Handler will log the HTTP requests 10 | func Handler(next http.Handler) http.Handler { 11 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | fmt.Println(time.Now().Format("2006-01-02 03:04:05 PM"), r.RemoteAddr, r.Method, r.URL) 13 | next.ServeHTTP(w, r) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /static/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #da532c 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /static/css/global.css: -------------------------------------------------------------------------------- 1 | /* Global Styles */ 2 | body { 3 | font-family: "Open Sans", Helvetica, Arial, sans-serif !important; 4 | } 5 | 6 | h1, h2, h3, h4, h5, h6 { 7 | font-family: "Open Sans", Helvetica, Arial, sans-serif; 8 | } 9 | 10 | .page-header { 11 | border-bottom: 0; 12 | margin-top: 0; 13 | } 14 | 15 | /* Notification Styles */ 16 | #flash-container { 17 | position: fixed; 18 | bottom: 0; 19 | right: 0; 20 | z-index: 100; 21 | margin: 0; 22 | } 23 | 24 | .alert-box-fixed { 25 | margin: 0 15px 15px 0; 26 | } -------------------------------------------------------------------------------- /vendor/app/controller/index.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "net/http" 5 | 6 | "app/shared/session" 7 | "app/shared/view" 8 | ) 9 | 10 | // IndexGET displays the home page 11 | func IndexGET(w http.ResponseWriter, r *http.Request) { 12 | // Get session 13 | session := session.Instance(r) 14 | 15 | if session.Values["id"] != nil { 16 | // Display the view 17 | v := view.New(r) 18 | v.Name = "index/auth" 19 | v.Vars["first_name"] = session.Values["first_name"] 20 | v.Render(w) 21 | } else { 22 | // Display the view 23 | v := view.New(r) 24 | v.Name = "index/anon" 25 | v.Render(w) 26 | return 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /vendor/app/model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | 7 | "gopkg.in/mgo.v2" 8 | ) 9 | 10 | var ( 11 | // ErrCode is a config or an internal error 12 | ErrCode = errors.New("Case statement in code is not correct.") 13 | // ErrNoResult is a not results error 14 | ErrNoResult = errors.New("Result not found.") 15 | // ErrUnavailable is a database not available error 16 | ErrUnavailable = errors.New("Database is unavailable.") 17 | // ErrUnauthorized is a permissions violation 18 | ErrUnauthorized = errors.New("User does not have permission to perform this operation.") 19 | ) 20 | 21 | // standardizeErrors returns the same error regardless of the database used 22 | func standardizeError(err error) error { 23 | if err == sql.ErrNoRows || err == mgo.ErrNotFound { 24 | return ErrNoResult 25 | } 26 | 27 | return err 28 | } 29 | -------------------------------------------------------------------------------- /vendor/app/controller/error.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | // Error404 handles 404 - Page Not Found 9 | func Error404(w http.ResponseWriter, r *http.Request) { 10 | w.WriteHeader(http.StatusNotFound) 11 | fmt.Fprint(w, "Not Found 404") 12 | } 13 | 14 | // Error500 handles 500 - Internal Server Error 15 | func Error500(w http.ResponseWriter, r *http.Request) { 16 | w.WriteHeader(http.StatusInternalServerError) 17 | fmt.Fprint(w, "Internal Server Error 500") 18 | } 19 | 20 | // InvalidToken handles CSRF attacks 21 | func InvalidToken(w http.ResponseWriter, r *http.Request) { 22 | w.Header().Set("Content-Type", "text/html") 23 | w.WriteHeader(http.StatusForbidden) 24 | fmt.Fprint(w, `Your token expired, click here to try again.`) 25 | } 26 | -------------------------------------------------------------------------------- /vendor/app/shared/jsonconfig/jsonconfig.go: -------------------------------------------------------------------------------- 1 | package jsonconfig 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | // Parser must implement ParseJSON 12 | type Parser interface { 13 | ParseJSON([]byte) error 14 | } 15 | 16 | // Load the JSON config file 17 | func Load(configFile string, p Parser) { 18 | var err error 19 | var absPath string 20 | var input = io.ReadCloser(os.Stdin) 21 | if absPath, err = filepath.Abs(configFile); err != nil { 22 | log.Fatalln(err) 23 | } 24 | 25 | if input, err = os.Open(absPath); err != nil { 26 | log.Fatalln(err) 27 | } 28 | 29 | // Read the config file 30 | jsonBytes, err := ioutil.ReadAll(input) 31 | input.Close() 32 | if err != nil { 33 | log.Fatalln(err) 34 | } 35 | 36 | // Parse the config 37 | if err := p.ParseJSON(jsonBytes); err != nil { 38 | log.Fatalln("Could not parse %q: %v", configFile, err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /template/notepad/create.tmpl: -------------------------------------------------------------------------------- 1 | {{define "title"}}Add Note{{end}} 2 | {{define "head"}}{{end}} 3 | {{define "content"}} 4 | 5 |
6 | 9 | 10 |
11 |
12 | 13 |
14 |
15 | 16 | 17 | Save 18 | 19 | 20 | Back 21 | 22 | 23 | 24 |
25 | 26 | {{template "footer" .}} 27 |
28 | 29 | {{end}} 30 | {{define "foot"}}{{end}} -------------------------------------------------------------------------------- /template/notepad/update.tmpl: -------------------------------------------------------------------------------- 1 | {{define "title"}}Edit Note{{end}} 2 | {{define "head"}}{{end}} 3 | {{define "content"}} 4 | 5 |
6 | 9 | 10 |
11 |
12 | 13 |
14 |
15 | 16 | 17 | Save 18 | 19 | 20 | Back 21 | 22 | 23 | 24 |
25 | 26 | {{template "footer" .}} 27 |
28 | 29 | {{end}} 30 | {{define "foot"}}{{end}} -------------------------------------------------------------------------------- /template/login/login.tmpl: -------------------------------------------------------------------------------- 1 | {{define "title"}}Login{{end}} 2 | {{define "head"}}{{end}} 3 | {{define "content"}} 4 | 5 |
6 | 9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 | 17 |
18 |
19 | 20 | 21 | 22 | 23 |
24 | 25 |

26 | {{LINK "register" "Create a new account."}} 27 |

28 | 29 | {{template "footer" .}} 30 |
31 | 32 | {{end}} 33 | {{define "foot"}}{{end}} -------------------------------------------------------------------------------- /vendor/app/route/middleware/acl/acl.go: -------------------------------------------------------------------------------- 1 | package acl 2 | 3 | import ( 4 | "net/http" 5 | 6 | "app/shared/session" 7 | ) 8 | 9 | // DisallowAuth does not allow authenticated users to access the page 10 | func DisallowAuth(h http.Handler) http.Handler { 11 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | // Get session 13 | sess := session.Instance(r) 14 | 15 | // If user is authenticated, don't allow them to access the page 16 | if sess.Values["id"] != nil { 17 | http.Redirect(w, r, "/", http.StatusFound) 18 | return 19 | } 20 | 21 | h.ServeHTTP(w, r) 22 | }) 23 | } 24 | 25 | // DisallowAnon does not allow anonymous users to access the page 26 | func DisallowAnon(h http.Handler) http.Handler { 27 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 28 | // Get session 29 | sess := session.Instance(r) 30 | 31 | // If user is not authenticated, don't allow them to access the page 32 | if sess.Values["id"] == nil { 33 | http.Redirect(w, r, "/", http.StatusFound) 34 | return 35 | } 36 | 37 | h.ServeHTTP(w, r) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Joseph Spurrier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /vendor/app/route/middleware/httprouterwrapper/httprouterwrapper.go: -------------------------------------------------------------------------------- 1 | // Package httprouterwrapper allows the use of http.HandlerFunc compatible funcs with julienschmidt/httprouter 2 | package httprouterwrapper 3 | 4 | // Source: http://nicolasmerouze.com/guide-routers-golang/ 5 | 6 | import ( 7 | "net/http" 8 | 9 | "github.com/gorilla/context" 10 | "github.com/julienschmidt/httprouter" 11 | ) 12 | 13 | // HandlerFunc accepts the name of a function so you don't have to wrap it with http.HandlerFunc 14 | // Example: r.GET("/", httprouterwrapper.HandlerFunc(controller.Index)) 15 | func HandlerFunc(h http.HandlerFunc) httprouter.Handle { 16 | return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 17 | context.Set(r, "params", p) 18 | h.ServeHTTP(w, r) 19 | } 20 | } 21 | 22 | // Handler accepts a handler to make it compatible with http.HandlerFunc 23 | // Example: r.GET("/", httprouterwrapper.Handler(http.HandlerFunc(controller.Index))) 24 | func Handler(h http.Handler) httprouter.Handle { 25 | return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 26 | context.Set(r, "params", p) 27 | h.ServeHTTP(w, r) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /vendor/app/shared/passhash/passhash.go: -------------------------------------------------------------------------------- 1 | package passhash 2 | 3 | import ( 4 | "golang.org/x/crypto/bcrypt" 5 | ) 6 | 7 | // HashString returns a hashed string and an error 8 | func HashString(password string) (string, error) { 9 | key, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 10 | if err != nil { 11 | return "", err 12 | } 13 | 14 | return string(key), nil 15 | } 16 | 17 | // HashBytes returns a hashed byte array and an error 18 | func HashBytes(password []byte) ([]byte, error) { 19 | key, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | return key, nil 25 | } 26 | 27 | // MatchString returns true if the hash matches the password 28 | func MatchString(hash, password string) bool { 29 | err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) 30 | if err == nil { 31 | return true 32 | } 33 | 34 | return false 35 | } 36 | 37 | // MatchBytes returns true if the hash matches the password 38 | func MatchBytes(hash, password []byte) bool { 39 | err := bcrypt.CompareHashAndPassword(hash, []byte(password)) 40 | if err == nil { 41 | return true 42 | } 43 | 44 | return false 45 | } 46 | -------------------------------------------------------------------------------- /vendor/app/shared/view/plugin/taghelper.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "html/template" 5 | "log" 6 | 7 | "app/shared/view" 8 | ) 9 | 10 | // TagHelper returns a template.FuncMap 11 | // * JS returns JavaScript tag 12 | // * CSS returns stylesheet tag 13 | // * LINK returns hyperlink tag 14 | func TagHelper(v view.View) template.FuncMap { 15 | f := make(template.FuncMap) 16 | 17 | f["JS"] = func(s string) template.HTML { 18 | path, err := v.AssetTimePath(s) 19 | 20 | if err != nil { 21 | log.Println("JS Error:", err) 22 | return template.HTML("") 23 | } 24 | 25 | return template.HTML(``) 26 | } 27 | 28 | f["CSS"] = func(s string) template.HTML { 29 | path, err := v.AssetTimePath(s) 30 | 31 | if err != nil { 32 | log.Println("CSS Error:", err) 33 | return template.HTML("") 34 | } 35 | 36 | return template.HTML(``) 37 | } 38 | 39 | f["LINK"] = func(path, name string) template.HTML { 40 | return template.HTML(`` + name + ``) 41 | } 42 | 43 | return f 44 | } 45 | -------------------------------------------------------------------------------- /vendor/app/shared/recaptcha/recaptcha.go: -------------------------------------------------------------------------------- 1 | package recaptcha 2 | 3 | import ( 4 | "html/template" 5 | "net/http" 6 | 7 | "github.com/haisum/recaptcha" 8 | ) 9 | 10 | var ( 11 | recap Info 12 | ) 13 | 14 | // Info has the details for the Google reCAPTCHA 15 | type Info struct { 16 | Enabled bool 17 | Secret string 18 | SiteKey string 19 | } 20 | 21 | // Configure adds the settings for Google reCAPTCHA 22 | func Configure(c Info) { 23 | recap = c 24 | } 25 | 26 | // ReadConfig returns the settings for Google reCAPTCHA 27 | func ReadConfig() Info { 28 | return recap 29 | } 30 | 31 | // Verified returns whether the Google reCAPTCHA was verified or not 32 | func Verified(r *http.Request) bool { 33 | if !recap.Enabled { 34 | return true 35 | } 36 | 37 | // Check the reCaptcha 38 | re := recaptcha.R{ 39 | Secret: recap.Secret, 40 | } 41 | return re.Verify(*r) 42 | } 43 | 44 | // Plugin returns a map of functions that are usable in templates 45 | func Plugin() template.FuncMap { 46 | f := make(template.FuncMap) 47 | 48 | f["RECAPTCHA_SITEKEY"] = func() template.HTML { 49 | if ReadConfig().Enabled { 50 | return template.HTML(ReadConfig().SiteKey) 51 | } 52 | 53 | return template.HTML("") 54 | } 55 | 56 | return f 57 | } 58 | -------------------------------------------------------------------------------- /template/notepad/read.tmpl: -------------------------------------------------------------------------------- 1 | {{define "title"}}Notepad{{end}} 2 | {{define "head"}}{{end}} 3 | {{define "content"}} 4 |
5 | 8 |

9 | 10 | Add Note 11 | 12 |

13 | 14 | {{range $n := .notes}} 15 |
16 |
17 |

{{.Content}}

18 |
19 | 20 | Edit 21 | 22 | 23 | Delete 24 | 25 |
26 | {{.UpdatedAt | PRETTYTIME}} 27 |
28 |
29 | {{end}} 30 | 31 | {{template "footer" .}} 32 |
33 | {{end}} 34 | {{define "foot"}}{{end}} -------------------------------------------------------------------------------- /vendor/app/shared/passhash/passhash_test.go: -------------------------------------------------------------------------------- 1 | package passhash 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestStringString(t *testing.T) { 8 | plainText := "This is a test." 9 | 10 | hash, err := HashString(plainText) 11 | 12 | if err != nil { 13 | t.Error(err) 14 | } 15 | 16 | if !MatchString(hash, plainText) { 17 | t.Error("Password does not match") 18 | } 19 | } 20 | 21 | func TestByteByte(t *testing.T) { 22 | plainText := []byte("This is a test.") 23 | 24 | hash, err := HashBytes(plainText) 25 | 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | 30 | if !MatchBytes(hash, plainText) { 31 | t.Error("Password does not match") 32 | } 33 | } 34 | 35 | func TestStringByte(t *testing.T) { 36 | plainText := "This is a test." 37 | 38 | hash, err := HashString(plainText) 39 | 40 | if err != nil { 41 | t.Error(err) 42 | } 43 | 44 | if !MatchBytes([]byte(hash), []byte(plainText)) { 45 | t.Error("Password does not match") 46 | } 47 | } 48 | 49 | func TestByteString(t *testing.T) { 50 | plainText := []byte("This is a test.") 51 | 52 | hash, err := HashBytes(plainText) 53 | 54 | if err != nil { 55 | t.Error(err) 56 | } 57 | 58 | if !MatchString(string(hash), string(plainText)) { 59 | t.Error("Password does not match") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /vendor/app/route/middleware/pprofhandler/pprofhandler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package pprofhandler 15 | 16 | import ( 17 | "net/http" 18 | "net/http/pprof" 19 | 20 | "github.com/gorilla/context" 21 | "github.com/julienschmidt/httprouter" 22 | ) 23 | 24 | // Handler routes the pprof pages using httprouter 25 | func Handler(w http.ResponseWriter, r *http.Request) { 26 | 27 | p := context.Get(r, "params").(httprouter.Params) 28 | 29 | switch p.ByName("pprof") { 30 | case "/cmdline": 31 | pprof.Cmdline(w, r) 32 | case "/profile": 33 | pprof.Profile(w, r) 34 | case "/symbol": 35 | pprof.Symbol(w, r) 36 | default: 37 | pprof.Index(w, r) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /vendor/app/shared/session/session.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/sessions" 7 | ) 8 | 9 | var ( 10 | // Store is the cookie store 11 | Store *sessions.CookieStore 12 | // Name is the session name 13 | Name string 14 | ) 15 | 16 | // Session stores session level information 17 | type Session struct { 18 | Options sessions.Options `json:"Options"` // Pulled from: http://www.gorillatoolkit.org/pkg/sessions#Options 19 | Name string `json:"Name"` // Name for: http://www.gorillatoolkit.org/pkg/sessions#CookieStore.Get 20 | SecretKey string `json:"SecretKey"` // Key for: http://www.gorillatoolkit.org/pkg/sessions#CookieStore.New 21 | } 22 | 23 | // Configure the session cookie store 24 | func Configure(s Session) { 25 | Store = sessions.NewCookieStore([]byte(s.SecretKey)) 26 | Store.Options = &s.Options 27 | Name = s.Name 28 | } 29 | 30 | // Instance returns a new session, never returns an error 31 | func Instance(r *http.Request) *sessions.Session { 32 | session, _ := Store.Get(r, Name) 33 | return session 34 | } 35 | 36 | // Empty deletes all the current session values 37 | func Empty(sess *sessions.Session) { 38 | // Clear out all stored values in the cookie 39 | for k := range sess.Values { 40 | delete(sess.Values, k) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Database": { 3 | "Type": "Bolt", 4 | "Bolt": { 5 | "Path": "gowebapp.db" 6 | }, 7 | "MongoDB": { 8 | "URL": "127.0.0.1", 9 | "Database": "gowebapp" 10 | }, 11 | "MySQL": { 12 | "Username": "root", 13 | "Password": "", 14 | "Name": "gowebapp", 15 | "Hostname": "127.0.0.1", 16 | "Port": 3306, 17 | "Parameter": "?parseTime=true" 18 | } 19 | }, 20 | "Email": { 21 | "Username": "", 22 | "Password": "", 23 | "Hostname": "", 24 | "Port": 25, 25 | "From": "" 26 | }, 27 | "Recaptcha": { 28 | "Enabled": false, 29 | "Secret": "", 30 | "SiteKey": "" 31 | }, 32 | "Server": { 33 | "Hostname": "", 34 | "UseHTTP": true, 35 | "UseHTTPS": false, 36 | "HTTPPort": 80, 37 | "HTTPSPort": 443, 38 | "CertFile": "tls/server.crt", 39 | "KeyFile": "tls/server.key" 40 | }, 41 | "Session": { 42 | "SecretKey": "@r4B?EThaSEh_drudR7P_hub=s#s2Pah", 43 | "Name": "gosess", 44 | "Options": { 45 | "Path": "/", 46 | "Domain": "", 47 | "MaxAge": 28800, 48 | "Secure": false, 49 | "HttpOnly": true 50 | } 51 | }, 52 | "Template": { 53 | "Root": "base", 54 | "Children": [ 55 | "partial/menu", 56 | "partial/footer" 57 | ] 58 | }, 59 | "View": { 60 | "BaseURI": "/", 61 | "Extension": "tmpl", 62 | "Folder": "template", 63 | "Name": "blank", 64 | "Caching": true 65 | } 66 | } -------------------------------------------------------------------------------- /vendor/app/shared/email/email.go: -------------------------------------------------------------------------------- 1 | package email 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "net/smtp" 7 | ) 8 | 9 | var ( 10 | e SMTPInfo 11 | ) 12 | 13 | // SMTPInfo is the details for the SMTP server 14 | type SMTPInfo struct { 15 | Username string 16 | Password string 17 | Hostname string 18 | Port int 19 | From string 20 | } 21 | 22 | // Configure adds the settings for the SMTP server 23 | func Configure(c SMTPInfo) { 24 | e = c 25 | } 26 | 27 | // ReadConfig returns the SMTP information 28 | func ReadConfig() SMTPInfo { 29 | return e 30 | } 31 | 32 | // SendEmail sends an email 33 | func SendEmail(to, subject, body string) error { 34 | auth := smtp.PlainAuth("", e.Username, e.Password, e.Hostname) 35 | 36 | header := make(map[string]string) 37 | header["From"] = e.From 38 | header["To"] = to 39 | header["Subject"] = subject 40 | header["MIME-Version"] = "1.0" 41 | header["Content-Type"] = `text/plain; charset="utf-8"` 42 | header["Content-Transfer-Encoding"] = "base64" 43 | 44 | message := "" 45 | for k, v := range header { 46 | message += fmt.Sprintf("%s: %s\r\n", k, v) 47 | } 48 | message += "\r\n" + base64.StdEncoding.EncodeToString([]byte(body)) 49 | 50 | // Send the email 51 | err := smtp.SendMail( 52 | fmt.Sprintf("%s:%d", e.Hostname, e.Port), 53 | auth, 54 | e.From, 55 | []string{to}, 56 | []byte(message), 57 | ) 58 | 59 | return err 60 | } 61 | -------------------------------------------------------------------------------- /static/js/global.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | //$(document).foundation(); 3 | 4 | // Hide any messages after a few seconds 5 | hideFlash(); 6 | }); 7 | 8 | function hideFlash(rnum) 9 | { 10 | if (!rnum) rnum = '0'; 11 | 12 | _.delay(function() { 13 | $('.alert-box-fixed' + rnum).fadeOut(300, function() { 14 | $(this).css({"visibility":"hidden",display:'block'}).slideUp(); 15 | 16 | var that = this; 17 | 18 | _.delay(function() { that.remove(); }, 400); 19 | }); 20 | }, 4000); 21 | } 22 | 23 | function showFlash(obj) 24 | { 25 | $('#flash-container').html(); 26 | $(obj).each(function(i, v) { 27 | var rnum = _.random(0, 100000); 28 | var message = '
' 30 | + '' 31 | + v.message + '
'; 32 | $('#flash-container').prepend(message); 33 | hideFlash(rnum); 34 | }); 35 | } 36 | 37 | function flashError(message) { 38 | var flash = [{Class: "alert-danger", Message: message}]; 39 | showFlash(flash); 40 | } 41 | 42 | function flashSuccess(message) { 43 | var flash = [{Class: "alert-success", Message: message}]; 44 | showFlash(flash); 45 | } 46 | 47 | function flashNotice(message) { 48 | var flash = [{Class: "alert-info", Message: message}]; 49 | showFlash(flash); 50 | } 51 | 52 | function flashWarning(message) { 53 | var flash = [{Class: "alert-warning", Message: message}]; 54 | showFlash(flash); 55 | } -------------------------------------------------------------------------------- /template/register/register.tmpl: -------------------------------------------------------------------------------- 1 | {{define "title"}}Create an Account{{end}} 2 | {{define "head"}}{{JS "//www.google.com/recaptcha/api.js"}}{{end}} 3 | {{define "content"}} 4 | 5 |
6 | 9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 | 17 |
18 |
19 | 20 |
21 | 22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 | 33 | {{if RECAPTCHA_SITEKEY}} 34 |
35 | {{end}} 36 | 37 | 38 | 39 | 40 |
41 | 42 | {{template "footer" .}} 43 |
44 | 45 | {{end}} 46 | {{define "foot"}}{{end}} -------------------------------------------------------------------------------- /vendor/app/shared/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | // Server stores the hostname and port number 11 | type Server struct { 12 | Hostname string `json:"Hostname"` // Server name 13 | UseHTTP bool `json:"UseHTTP"` // Listen on HTTP 14 | UseHTTPS bool `json:"UseHTTPS"` // Listen on HTTPS 15 | HTTPPort int `json:"HTTPPort"` // HTTP port 16 | HTTPSPort int `json:"HTTPSPort"` // HTTPS port 17 | CertFile string `json:"CertFile"` // HTTPS certificate 18 | KeyFile string `json:"KeyFile"` // HTTPS private key 19 | } 20 | 21 | // Run starts the HTTP and/or HTTPS listener 22 | func Run(httpHandlers http.Handler, httpsHandlers http.Handler, s Server) { 23 | if s.UseHTTP && s.UseHTTPS { 24 | go func() { 25 | startHTTPS(httpsHandlers, s) 26 | }() 27 | 28 | startHTTP(httpHandlers, s) 29 | } else if s.UseHTTP { 30 | startHTTP(httpHandlers, s) 31 | } else if s.UseHTTPS { 32 | startHTTPS(httpsHandlers, s) 33 | } else { 34 | log.Println("Config file does not specify a listener to start") 35 | } 36 | } 37 | 38 | // startHTTP starts the HTTP listener 39 | func startHTTP(handlers http.Handler, s Server) { 40 | fmt.Println(time.Now().Format("2006-01-02 03:04:05 PM"), "Running HTTP "+httpAddress(s)) 41 | 42 | // Start the HTTP listener 43 | log.Fatal(http.ListenAndServe(httpAddress(s), handlers)) 44 | } 45 | 46 | // startHTTPs starts the HTTPS listener 47 | func startHTTPS(handlers http.Handler, s Server) { 48 | fmt.Println(time.Now().Format("2006-01-02 03:04:05 PM"), "Running HTTPS "+httpsAddress(s)) 49 | 50 | // Start the HTTPS listener 51 | log.Fatal(http.ListenAndServeTLS(httpsAddress(s), s.CertFile, s.KeyFile, handlers)) 52 | } 53 | 54 | // httpAddress returns the HTTP address 55 | func httpAddress(s Server) string { 56 | return s.Hostname + ":" + fmt.Sprintf("%d", s.HTTPPort) 57 | } 58 | 59 | // httpsAddress returns the HTTPS address 60 | func httpsAddress(s Server) string { 61 | return s.Hostname + ":" + fmt.Sprintf("%d", s.HTTPSPort) 62 | } 63 | -------------------------------------------------------------------------------- /gowebapp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "os" 7 | "runtime" 8 | 9 | "app/route" 10 | "app/shared/database" 11 | "app/shared/email" 12 | "app/shared/jsonconfig" 13 | "app/shared/recaptcha" 14 | "app/shared/server" 15 | "app/shared/session" 16 | "app/shared/view" 17 | "app/shared/view/plugin" 18 | ) 19 | 20 | // ***************************************************************************** 21 | // Application Logic 22 | // ***************************************************************************** 23 | 24 | func init() { 25 | // Verbose logging with file name and line number 26 | log.SetFlags(log.Lshortfile) 27 | 28 | // Use all CPU cores 29 | runtime.GOMAXPROCS(runtime.NumCPU()) 30 | } 31 | 32 | func main() { 33 | // Load the configuration file 34 | jsonconfig.Load("config"+string(os.PathSeparator)+"config.json", config) 35 | 36 | // Configure the session cookie store 37 | session.Configure(config.Session) 38 | 39 | // Connect to database 40 | database.Connect(config.Database) 41 | 42 | // Configure the Google reCAPTCHA prior to loading view plugins 43 | recaptcha.Configure(config.Recaptcha) 44 | 45 | // Setup the views 46 | view.Configure(config.View) 47 | view.LoadTemplates(config.Template.Root, config.Template.Children) 48 | view.LoadPlugins( 49 | plugin.TagHelper(config.View), 50 | plugin.NoEscape(), 51 | plugin.PrettyTime(), 52 | recaptcha.Plugin()) 53 | 54 | // Start the listener 55 | server.Run(route.LoadHTTP(), route.LoadHTTPS(), config.Server) 56 | } 57 | 58 | // ***************************************************************************** 59 | // Application Settings 60 | // ***************************************************************************** 61 | 62 | // config the settings variable 63 | var config = &configuration{} 64 | 65 | // configuration contains the application settings 66 | type configuration struct { 67 | Database database.Info `json:"Database"` 68 | Email email.SMTPInfo `json:"Email"` 69 | Recaptcha recaptcha.Info `json:"Recaptcha"` 70 | Server server.Server `json:"Server"` 71 | Session session.Session `json:"Session"` 72 | Template view.Template `json:"Template"` 73 | View view.View `json:"View"` 74 | } 75 | 76 | // ParseJSON unmarshals bytes to structs 77 | func (c *configuration) ParseJSON(b []byte) error { 78 | return json.Unmarshal(b, &c) 79 | } 80 | -------------------------------------------------------------------------------- /config/mysql.sql: -------------------------------------------------------------------------------- 1 | /* ***************************************************************************** 2 | // Setup the preferences 3 | // ****************************************************************************/ 4 | SET NAMES utf8 COLLATE 'utf8_unicode_ci'; 5 | SET foreign_key_checks = 1; 6 | SET time_zone = '+00:00'; 7 | SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; 8 | SET default_storage_engine = InnoDB; 9 | SET CHARACTER SET utf8; 10 | 11 | /* ***************************************************************************** 12 | // Remove old database 13 | // ****************************************************************************/ 14 | DROP DATABASE IF EXISTS gowebapp; 15 | 16 | /* ***************************************************************************** 17 | // Create new database 18 | // ****************************************************************************/ 19 | CREATE DATABASE gowebapp DEFAULT CHARSET = utf8 COLLATE = utf8_unicode_ci; 20 | USE gowebapp; 21 | 22 | /* ***************************************************************************** 23 | // Create the tables 24 | // ****************************************************************************/ 25 | CREATE TABLE user_status ( 26 | id TINYINT(1) UNSIGNED NOT NULL AUTO_INCREMENT, 27 | 28 | status VARCHAR(25) NOT NULL, 29 | 30 | created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 31 | updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 32 | deleted TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, 33 | 34 | PRIMARY KEY (id) 35 | ); 36 | 37 | CREATE TABLE user ( 38 | id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, 39 | 40 | first_name VARCHAR(50) NOT NULL, 41 | last_name VARCHAR(50) NOT NULL, 42 | email VARCHAR(100) NOT NULL, 43 | password CHAR(60) NOT NULL, 44 | 45 | status_id TINYINT(1) UNSIGNED NOT NULL DEFAULT 1, 46 | 47 | created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 48 | updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 49 | deleted TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, 50 | 51 | UNIQUE KEY (email), 52 | CONSTRAINT `f_user_status` FOREIGN KEY (`status_id`) REFERENCES `user_status` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 53 | 54 | PRIMARY KEY (id) 55 | ); 56 | 57 | INSERT INTO `user_status` (`id`, `status`, `created_at`, `updated_at`, `deleted`) VALUES 58 | (1, 'active', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0), 59 | (2, 'inactive', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0); 60 | 61 | CREATE TABLE note ( 62 | id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, 63 | 64 | content TEXT NOT NULL, 65 | 66 | user_id INT(10) UNSIGNED NOT NULL, 67 | 68 | created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 69 | updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 70 | deleted TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, 71 | 72 | CONSTRAINT `f_note_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 73 | 74 | PRIMARY KEY (id) 75 | ); -------------------------------------------------------------------------------- /vendor/app/controller/register.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "app/model" 8 | "app/shared/passhash" 9 | "app/shared/recaptcha" 10 | "app/shared/session" 11 | "app/shared/view" 12 | 13 | "github.com/josephspurrier/csrfbanana" 14 | ) 15 | 16 | // RegisterGET displays the register page 17 | func RegisterGET(w http.ResponseWriter, r *http.Request) { 18 | // Get session 19 | sess := session.Instance(r) 20 | 21 | // Display the view 22 | v := view.New(r) 23 | v.Name = "register/register" 24 | v.Vars["token"] = csrfbanana.Token(w, r, sess) 25 | // Refill any form fields 26 | view.Repopulate([]string{"first_name", "last_name", "email"}, r.Form, v.Vars) 27 | v.Render(w) 28 | } 29 | 30 | // RegisterPOST handles the registration form submission 31 | func RegisterPOST(w http.ResponseWriter, r *http.Request) { 32 | // Get session 33 | sess := session.Instance(r) 34 | 35 | // Prevent brute force login attempts by not hitting MySQL and pretending like it was invalid :-) 36 | if sess.Values["register_attempt"] != nil && sess.Values["register_attempt"].(int) >= 5 { 37 | log.Println("Brute force register prevented") 38 | http.Redirect(w, r, "/register", http.StatusFound) 39 | return 40 | } 41 | 42 | // Validate with required fields 43 | if validate, missingField := view.Validate(r, []string{"first_name", "last_name", "email", "password"}); !validate { 44 | sess.AddFlash(view.Flash{"Field missing: " + missingField, view.FlashError}) 45 | sess.Save(r, w) 46 | RegisterGET(w, r) 47 | return 48 | } 49 | 50 | // Validate with Google reCAPTCHA 51 | if !recaptcha.Verified(r) { 52 | sess.AddFlash(view.Flash{"reCAPTCHA invalid!", view.FlashError}) 53 | sess.Save(r, w) 54 | RegisterGET(w, r) 55 | return 56 | } 57 | 58 | // Get form values 59 | firstName := r.FormValue("first_name") 60 | lastName := r.FormValue("last_name") 61 | email := r.FormValue("email") 62 | password, errp := passhash.HashString(r.FormValue("password")) 63 | 64 | // If password hashing failed 65 | if errp != nil { 66 | log.Println(errp) 67 | sess.AddFlash(view.Flash{"An error occurred on the server. Please try again later.", view.FlashError}) 68 | sess.Save(r, w) 69 | http.Redirect(w, r, "/register", http.StatusFound) 70 | return 71 | } 72 | 73 | // Get database result 74 | _, err := model.UserByEmail(email) 75 | 76 | if err == model.ErrNoResult { // If success (no user exists with that email) 77 | ex := model.UserCreate(firstName, lastName, email, password) 78 | // Will only error if there is a problem with the query 79 | if ex != nil { 80 | log.Println(ex) 81 | sess.AddFlash(view.Flash{"An error occurred on the server. Please try again later.", view.FlashError}) 82 | sess.Save(r, w) 83 | } else { 84 | sess.AddFlash(view.Flash{"Account created successfully for: " + email, view.FlashSuccess}) 85 | sess.Save(r, w) 86 | http.Redirect(w, r, "/login", http.StatusFound) 87 | return 88 | } 89 | } else if err != nil { // Catch all other errors 90 | log.Println(err) 91 | sess.AddFlash(view.Flash{"An error occurred on the server. Please try again later.", view.FlashError}) 92 | sess.Save(r, w) 93 | } else { // Else the user already exists 94 | sess.AddFlash(view.Flash{"Account already exists for: " + email, view.FlashError}) 95 | sess.Save(r, w) 96 | } 97 | 98 | // Display the page 99 | RegisterGET(w, r) 100 | } 101 | -------------------------------------------------------------------------------- /vendor/app/controller/login.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "app/model" 9 | "app/shared/passhash" 10 | "app/shared/session" 11 | "app/shared/view" 12 | 13 | "github.com/gorilla/sessions" 14 | "github.com/josephspurrier/csrfbanana" 15 | ) 16 | 17 | const ( 18 | // Name of the session variable that tracks login attempts 19 | sessLoginAttempt = "login_attempt" 20 | ) 21 | 22 | // loginAttempt increments the number of login attempts in sessions variable 23 | func loginAttempt(sess *sessions.Session) { 24 | // Log the attempt 25 | if sess.Values[sessLoginAttempt] == nil { 26 | sess.Values[sessLoginAttempt] = 1 27 | } else { 28 | sess.Values[sessLoginAttempt] = sess.Values[sessLoginAttempt].(int) + 1 29 | } 30 | } 31 | 32 | // LoginGET displays the login page 33 | func LoginGET(w http.ResponseWriter, r *http.Request) { 34 | // Get session 35 | sess := session.Instance(r) 36 | 37 | // Display the view 38 | v := view.New(r) 39 | v.Name = "login/login" 40 | v.Vars["token"] = csrfbanana.Token(w, r, sess) 41 | // Refill any form fields 42 | view.Repopulate([]string{"email"}, r.Form, v.Vars) 43 | v.Render(w) 44 | } 45 | 46 | // LoginPOST handles the login form submission 47 | func LoginPOST(w http.ResponseWriter, r *http.Request) { 48 | // Get session 49 | sess := session.Instance(r) 50 | 51 | // Prevent brute force login attempts by not hitting MySQL and pretending like it was invalid :-) 52 | if sess.Values[sessLoginAttempt] != nil && sess.Values[sessLoginAttempt].(int) >= 5 { 53 | log.Println("Brute force login prevented") 54 | sess.AddFlash(view.Flash{"Sorry, no brute force :-)", view.FlashNotice}) 55 | sess.Save(r, w) 56 | LoginGET(w, r) 57 | return 58 | } 59 | 60 | // Validate with required fields 61 | if validate, missingField := view.Validate(r, []string{"email", "password"}); !validate { 62 | sess.AddFlash(view.Flash{"Field missing: " + missingField, view.FlashError}) 63 | sess.Save(r, w) 64 | LoginGET(w, r) 65 | return 66 | } 67 | 68 | // Form values 69 | email := r.FormValue("email") 70 | password := r.FormValue("password") 71 | 72 | // Get database result 73 | result, err := model.UserByEmail(email) 74 | 75 | // Determine if user exists 76 | if err == model.ErrNoResult { 77 | loginAttempt(sess) 78 | sess.AddFlash(view.Flash{"Password is incorrect - Attempt: " + fmt.Sprintf("%v", sess.Values[sessLoginAttempt]), view.FlashWarning}) 79 | sess.Save(r, w) 80 | } else if err != nil { 81 | // Display error message 82 | log.Println(err) 83 | sess.AddFlash(view.Flash{"There was an error. Please try again later.", view.FlashError}) 84 | sess.Save(r, w) 85 | } else if passhash.MatchString(result.Password, password) { 86 | if result.StatusID != 1 { 87 | // User inactive and display inactive message 88 | sess.AddFlash(view.Flash{"Account is inactive so login is disabled.", view.FlashNotice}) 89 | sess.Save(r, w) 90 | } else { 91 | // Login successfully 92 | session.Empty(sess) 93 | sess.AddFlash(view.Flash{"Login successful!", view.FlashSuccess}) 94 | sess.Values["id"] = result.UserID() 95 | sess.Values["email"] = email 96 | sess.Values["first_name"] = result.FirstName 97 | sess.Save(r, w) 98 | http.Redirect(w, r, "/", http.StatusFound) 99 | return 100 | } 101 | } else { 102 | loginAttempt(sess) 103 | sess.AddFlash(view.Flash{"Password is incorrect - Attempt: " + fmt.Sprintf("%v", sess.Values[sessLoginAttempt]), view.FlashWarning}) 104 | sess.Save(r, w) 105 | } 106 | 107 | // Show the login page again 108 | LoginGET(w, r) 109 | } 110 | 111 | // LogoutGET clears the session and logs the user out 112 | func LogoutGET(w http.ResponseWriter, r *http.Request) { 113 | // Get session 114 | sess := session.Instance(r) 115 | 116 | // If user is authenticated 117 | if sess.Values["id"] != nil { 118 | session.Empty(sess) 119 | sess.AddFlash(view.Flash{"Goodbye!", view.FlashNotice}) 120 | sess.Save(r, w) 121 | } 122 | 123 | http.Redirect(w, r, "/", http.StatusFound) 124 | } 125 | -------------------------------------------------------------------------------- /template/base.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{template "title" .}} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {{CSS "static/css/bootstrap.min.css"}} 33 | {{CSS "//fonts.googleapis.com/css?family=Open+Sans:300,400,bold,italic"}} 34 | {{CSS "static/css/global.css"}} 35 | 36 | 37 | 38 | 42 | 43 | {{template "head" .}} 44 | 45 | 46 | 47 | 63 | 64 | 65 |
66 | {{range $fm := .flashes}} 67 | 70 | 71 | {{end}} 72 |
73 | 74 | {{template "content" .}} 75 | 76 | {{JS "static/js/jquery1.11.0.min.js"}} 77 | {{JS "static/js/underscore-min.js"}} 78 | {{JS "static/js/bootstrap.min.js"}} 79 | {{JS "static/js/global.js"}} 80 | 81 | {{template "foot" .}} 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /vendor/app/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "app/shared/database" 8 | 9 | "gopkg.in/mgo.v2/bson" 10 | ) 11 | 12 | // ***************************************************************************** 13 | // User 14 | // ***************************************************************************** 15 | 16 | // User table contains the information for each user 17 | type User struct { 18 | ObjectID bson.ObjectId `bson:"_id"` 19 | ID uint32 `db:"id" bson:"id,omitempty"` // Don't use Id, use UserID() instead for consistency with MongoDB 20 | FirstName string `db:"first_name" bson:"first_name"` 21 | LastName string `db:"last_name" bson:"last_name"` 22 | Email string `db:"email" bson:"email"` 23 | Password string `db:"password" bson:"password"` 24 | StatusID uint8 `db:"status_id" bson:"status_id"` 25 | CreatedAt time.Time `db:"created_at" bson:"created_at"` 26 | UpdatedAt time.Time `db:"updated_at" bson:"updated_at"` 27 | Deleted uint8 `db:"deleted" bson:"deleted"` 28 | } 29 | 30 | // UserStatus table contains every possible user status (active/inactive) 31 | type UserStatus struct { 32 | ID uint8 `db:"id" bson:"id"` 33 | Status string `db:"status" bson:"status"` 34 | CreatedAt time.Time `db:"created_at" bson:"created_at"` 35 | UpdatedAt time.Time `db:"updated_at" bson:"updated_at"` 36 | Deleted uint8 `db:"deleted" bson:"deleted"` 37 | } 38 | 39 | // UserID returns the user id 40 | func (u *User) UserID() string { 41 | r := "" 42 | 43 | switch database.ReadConfig().Type { 44 | case database.TypeMySQL: 45 | r = fmt.Sprintf("%v", u.ID) 46 | case database.TypeMongoDB: 47 | r = u.ObjectID.Hex() 48 | case database.TypeBolt: 49 | r = u.ObjectID.Hex() 50 | } 51 | 52 | return r 53 | } 54 | 55 | // UserByEmail gets user information from email 56 | func UserByEmail(email string) (User, error) { 57 | var err error 58 | 59 | result := User{} 60 | 61 | switch database.ReadConfig().Type { 62 | case database.TypeMySQL: 63 | err = database.SQL.Get(&result, "SELECT id, password, status_id, first_name FROM user WHERE email = ? LIMIT 1", email) 64 | case database.TypeMongoDB: 65 | if database.CheckConnection() { 66 | session := database.Mongo.Copy() 67 | defer session.Close() 68 | c := session.DB(database.ReadConfig().MongoDB.Database).C("user") 69 | err = c.Find(bson.M{"email": email}).One(&result) 70 | } else { 71 | err = ErrUnavailable 72 | } 73 | case database.TypeBolt: 74 | err = database.View("user", email, &result) 75 | if err != nil { 76 | err = ErrNoResult 77 | } 78 | default: 79 | err = ErrCode 80 | } 81 | 82 | return result, standardizeError(err) 83 | } 84 | 85 | // UserCreate creates user 86 | func UserCreate(firstName, lastName, email, password string) error { 87 | var err error 88 | 89 | now := time.Now() 90 | 91 | switch database.ReadConfig().Type { 92 | case database.TypeMySQL: 93 | _, err = database.SQL.Exec("INSERT INTO user (first_name, last_name, email, password) VALUES (?,?,?,?)", firstName, 94 | lastName, email, password) 95 | case database.TypeMongoDB: 96 | if database.CheckConnection() { 97 | session := database.Mongo.Copy() 98 | defer session.Close() 99 | c := session.DB(database.ReadConfig().MongoDB.Database).C("user") 100 | 101 | user := &User{ 102 | ObjectID: bson.NewObjectId(), 103 | FirstName: firstName, 104 | LastName: lastName, 105 | Email: email, 106 | Password: password, 107 | StatusID: 1, 108 | CreatedAt: now, 109 | UpdatedAt: now, 110 | Deleted: 0, 111 | } 112 | err = c.Insert(user) 113 | } else { 114 | err = ErrUnavailable 115 | } 116 | case database.TypeBolt: 117 | user := &User{ 118 | ObjectID: bson.NewObjectId(), 119 | FirstName: firstName, 120 | LastName: lastName, 121 | Email: email, 122 | Password: password, 123 | StatusID: 1, 124 | CreatedAt: now, 125 | UpdatedAt: now, 126 | Deleted: 0, 127 | } 128 | 129 | err = database.Update("user", user.Email, &user) 130 | default: 131 | err = ErrCode 132 | } 133 | 134 | return standardizeError(err) 135 | } 136 | -------------------------------------------------------------------------------- /vendor/app/route/route.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "net/http" 5 | 6 | "app/controller" 7 | "app/route/middleware/acl" 8 | hr "app/route/middleware/httprouterwrapper" 9 | "app/route/middleware/logrequest" 10 | "app/route/middleware/pprofhandler" 11 | "app/shared/session" 12 | 13 | "github.com/gorilla/context" 14 | "github.com/josephspurrier/csrfbanana" 15 | "github.com/julienschmidt/httprouter" 16 | "github.com/justinas/alice" 17 | ) 18 | 19 | // Load returns the routes and middleware 20 | func Load() http.Handler { 21 | return middleware(routes()) 22 | } 23 | 24 | // LoadHTTPS returns the HTTP routes and middleware 25 | func LoadHTTPS() http.Handler { 26 | return middleware(routes()) 27 | } 28 | 29 | // LoadHTTP returns the HTTPS routes and middleware 30 | func LoadHTTP() http.Handler { 31 | return middleware(routes()) 32 | 33 | // Uncomment this and comment out the line above to always redirect to HTTPS 34 | //return http.HandlerFunc(redirectToHTTPS) 35 | } 36 | 37 | // Optional method to make it easy to redirect from HTTP to HTTPS 38 | func redirectToHTTPS(w http.ResponseWriter, req *http.Request) { 39 | http.Redirect(w, req, "https://"+req.Host, http.StatusMovedPermanently) 40 | } 41 | 42 | // ***************************************************************************** 43 | // Routes 44 | // ***************************************************************************** 45 | 46 | func routes() *httprouter.Router { 47 | r := httprouter.New() 48 | 49 | // Set 404 handler 50 | r.NotFound = alice. 51 | New(). 52 | ThenFunc(controller.Error404) 53 | 54 | // Serve static files, no directory browsing 55 | r.GET("/static/*filepath", hr.Handler(alice. 56 | New(). 57 | ThenFunc(controller.Static))) 58 | 59 | // Home page 60 | r.GET("/", hr.Handler(alice. 61 | New(). 62 | ThenFunc(controller.IndexGET))) 63 | 64 | // Login 65 | r.GET("/login", hr.Handler(alice. 66 | New(acl.DisallowAuth). 67 | ThenFunc(controller.LoginGET))) 68 | r.POST("/login", hr.Handler(alice. 69 | New(acl.DisallowAuth). 70 | ThenFunc(controller.LoginPOST))) 71 | r.GET("/logout", hr.Handler(alice. 72 | New(). 73 | ThenFunc(controller.LogoutGET))) 74 | 75 | // Register 76 | r.GET("/register", hr.Handler(alice. 77 | New(acl.DisallowAuth). 78 | ThenFunc(controller.RegisterGET))) 79 | r.POST("/register", hr.Handler(alice. 80 | New(acl.DisallowAuth). 81 | ThenFunc(controller.RegisterPOST))) 82 | 83 | // About 84 | r.GET("/about", hr.Handler(alice. 85 | New(). 86 | ThenFunc(controller.AboutGET))) 87 | 88 | // Notepad 89 | r.GET("/notepad", hr.Handler(alice. 90 | New(acl.DisallowAnon). 91 | ThenFunc(controller.NotepadReadGET))) 92 | r.GET("/notepad/create", hr.Handler(alice. 93 | New(acl.DisallowAnon). 94 | ThenFunc(controller.NotepadCreateGET))) 95 | r.POST("/notepad/create", hr.Handler(alice. 96 | New(acl.DisallowAnon). 97 | ThenFunc(controller.NotepadCreatePOST))) 98 | r.GET("/notepad/update/:id", hr.Handler(alice. 99 | New(acl.DisallowAnon). 100 | ThenFunc(controller.NotepadUpdateGET))) 101 | r.POST("/notepad/update/:id", hr.Handler(alice. 102 | New(acl.DisallowAnon). 103 | ThenFunc(controller.NotepadUpdatePOST))) 104 | r.GET("/notepad/delete/:id", hr.Handler(alice. 105 | New(acl.DisallowAnon). 106 | ThenFunc(controller.NotepadDeleteGET))) 107 | 108 | // Enable Pprof 109 | r.GET("/debug/pprof/*pprof", hr.Handler(alice. 110 | New(acl.DisallowAnon). 111 | ThenFunc(pprofhandler.Handler))) 112 | 113 | return r 114 | } 115 | 116 | // ***************************************************************************** 117 | // Middleware 118 | // ***************************************************************************** 119 | 120 | func middleware(h http.Handler) http.Handler { 121 | // Prevents CSRF and Double Submits 122 | cs := csrfbanana.New(h, session.Store, session.Name) 123 | cs.FailureHandler(http.HandlerFunc(controller.InvalidToken)) 124 | cs.ClearAfterUsage(true) 125 | cs.ExcludeRegexPaths([]string{"/static(.*)"}) 126 | csrfbanana.TokenLength = 32 127 | csrfbanana.TokenName = "token" 128 | csrfbanana.SingleToken = false 129 | h = cs 130 | 131 | // Log every request 132 | h = logrequest.Handler(h) 133 | 134 | // Clear handler for Gorilla Context 135 | h = context.ClearHandler(h) 136 | 137 | return h 138 | } 139 | -------------------------------------------------------------------------------- /vendor/app/shared/database/database.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/boltdb/bolt" 10 | _ "github.com/go-sql-driver/mysql" // MySQL driver 11 | "github.com/jmoiron/sqlx" 12 | "gopkg.in/mgo.v2" 13 | ) 14 | 15 | var ( 16 | // BoltDB wrapper 17 | BoltDB *bolt.DB 18 | // Mongo wrapper 19 | Mongo *mgo.Session 20 | // SQL wrapper 21 | SQL *sqlx.DB 22 | // Database info 23 | databases Info 24 | ) 25 | 26 | // Type is the type of database from a Type* constant 27 | type Type string 28 | 29 | const ( 30 | // TypeBolt is BoltDB 31 | TypeBolt Type = "Bolt" 32 | // TypeMongoDB is MongoDB 33 | TypeMongoDB Type = "MongoDB" 34 | // TypeMySQL is MySQL 35 | TypeMySQL Type = "MySQL" 36 | ) 37 | 38 | // Info contains the database configurations 39 | type Info struct { 40 | // Database type 41 | Type Type 42 | // MySQL info if used 43 | MySQL MySQLInfo 44 | // Bolt info if used 45 | Bolt BoltInfo 46 | // MongoDB info if used 47 | MongoDB MongoDBInfo 48 | } 49 | 50 | // MySQLInfo is the details for the database connection 51 | type MySQLInfo struct { 52 | Username string 53 | Password string 54 | Name string 55 | Hostname string 56 | Port int 57 | Parameter string 58 | } 59 | 60 | // BoltInfo is the details for the database connection 61 | type BoltInfo struct { 62 | Path string 63 | } 64 | 65 | // MongoDBInfo is the details for the database connection 66 | type MongoDBInfo struct { 67 | URL string 68 | Database string 69 | } 70 | 71 | // DSN returns the Data Source Name 72 | func DSN(ci MySQLInfo) string { 73 | // Example: root:@tcp(localhost:3306)/test 74 | return ci.Username + 75 | ":" + 76 | ci.Password + 77 | "@tcp(" + 78 | ci.Hostname + 79 | ":" + 80 | fmt.Sprintf("%d", ci.Port) + 81 | ")/" + 82 | ci.Name + ci.Parameter 83 | } 84 | 85 | // Connect to the database 86 | func Connect(d Info) { 87 | var err error 88 | 89 | // Store the config 90 | databases = d 91 | 92 | switch d.Type { 93 | case TypeMySQL: 94 | // Connect to MySQL 95 | if SQL, err = sqlx.Connect("mysql", DSN(d.MySQL)); err != nil { 96 | log.Println("SQL Driver Error", err) 97 | } 98 | 99 | // Check if is alive 100 | if err = SQL.Ping(); err != nil { 101 | log.Println("Database Error", err) 102 | } 103 | case TypeBolt: 104 | // Connect to Bolt 105 | if BoltDB, err = bolt.Open(d.Bolt.Path, 0600, nil); err != nil { 106 | log.Println("Bolt Driver Error", err) 107 | } 108 | case TypeMongoDB: 109 | // Connect to MongoDB 110 | if Mongo, err = mgo.DialWithTimeout(d.MongoDB.URL, 5*time.Second); err != nil { 111 | log.Println("MongoDB Driver Error", err) 112 | return 113 | } 114 | 115 | // Prevents these errors: read tcp 127.0.0.1:27017: i/o timeout 116 | Mongo.SetSocketTimeout(1 * time.Second) 117 | 118 | // Check if is alive 119 | if err = Mongo.Ping(); err != nil { 120 | log.Println("Database Error", err) 121 | } 122 | default: 123 | log.Println("No registered database in config") 124 | } 125 | } 126 | 127 | // Update makes a modification to Bolt 128 | func Update(bucketName string, key string, dataStruct interface{}) error { 129 | err := BoltDB.Update(func(tx *bolt.Tx) error { 130 | // Create the bucket 131 | bucket, e := tx.CreateBucketIfNotExists([]byte(bucketName)) 132 | if e != nil { 133 | return e 134 | } 135 | 136 | // Encode the record 137 | encodedRecord, e := json.Marshal(dataStruct) 138 | if e != nil { 139 | return e 140 | } 141 | 142 | // Store the record 143 | if e = bucket.Put([]byte(key), encodedRecord); e != nil { 144 | return e 145 | } 146 | return nil 147 | }) 148 | return err 149 | } 150 | 151 | // View retrieves a record in Bolt 152 | func View(bucketName string, key string, dataStruct interface{}) error { 153 | err := BoltDB.View(func(tx *bolt.Tx) error { 154 | // Get the bucket 155 | b := tx.Bucket([]byte(bucketName)) 156 | if b == nil { 157 | return bolt.ErrBucketNotFound 158 | } 159 | 160 | // Retrieve the record 161 | v := b.Get([]byte(key)) 162 | if len(v) < 1 { 163 | return bolt.ErrInvalid 164 | } 165 | 166 | // Decode the record 167 | e := json.Unmarshal(v, &dataStruct) 168 | if e != nil { 169 | return e 170 | } 171 | 172 | return nil 173 | }) 174 | 175 | return err 176 | } 177 | 178 | // Delete removes a record from Bolt 179 | func Delete(bucketName string, key string) error { 180 | err := BoltDB.Update(func(tx *bolt.Tx) error { 181 | // Get the bucket 182 | b := tx.Bucket([]byte(bucketName)) 183 | if b == nil { 184 | return bolt.ErrBucketNotFound 185 | } 186 | 187 | return b.Delete([]byte(key)) 188 | }) 189 | return err 190 | } 191 | 192 | // CheckConnection returns true if MongoDB is available 193 | func CheckConnection() bool { 194 | if Mongo == nil { 195 | Connect(databases) 196 | } 197 | 198 | if Mongo != nil { 199 | return true 200 | } 201 | 202 | return false 203 | } 204 | 205 | // ReadConfig returns the database information 206 | func ReadConfig() Info { 207 | return databases 208 | } 209 | -------------------------------------------------------------------------------- /vendor/app/controller/notepad.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "app/model" 9 | "app/shared/session" 10 | "app/shared/view" 11 | 12 | "github.com/gorilla/context" 13 | "github.com/josephspurrier/csrfbanana" 14 | "github.com/julienschmidt/httprouter" 15 | ) 16 | 17 | // NotepadReadGET displays the notes in the notepad 18 | func NotepadReadGET(w http.ResponseWriter, r *http.Request) { 19 | // Get session 20 | sess := session.Instance(r) 21 | 22 | userID := fmt.Sprintf("%s", sess.Values["id"]) 23 | 24 | notes, err := model.NotesByUserID(userID) 25 | if err != nil { 26 | log.Println(err) 27 | notes = []model.Note{} 28 | } 29 | 30 | // Display the view 31 | v := view.New(r) 32 | v.Name = "notepad/read" 33 | v.Vars["first_name"] = sess.Values["first_name"] 34 | v.Vars["notes"] = notes 35 | v.Render(w) 36 | } 37 | 38 | // NotepadCreateGET displays the note creation page 39 | func NotepadCreateGET(w http.ResponseWriter, r *http.Request) { 40 | // Get session 41 | sess := session.Instance(r) 42 | 43 | // Display the view 44 | v := view.New(r) 45 | v.Name = "notepad/create" 46 | v.Vars["token"] = csrfbanana.Token(w, r, sess) 47 | v.Render(w) 48 | } 49 | 50 | // NotepadCreatePOST handles the note creation form submission 51 | func NotepadCreatePOST(w http.ResponseWriter, r *http.Request) { 52 | // Get session 53 | sess := session.Instance(r) 54 | 55 | // Validate with required fields 56 | if validate, missingField := view.Validate(r, []string{"note"}); !validate { 57 | sess.AddFlash(view.Flash{"Field missing: " + missingField, view.FlashError}) 58 | sess.Save(r, w) 59 | NotepadCreateGET(w, r) 60 | return 61 | } 62 | 63 | // Get form values 64 | content := r.FormValue("note") 65 | 66 | userID := fmt.Sprintf("%s", sess.Values["id"]) 67 | 68 | // Get database result 69 | err := model.NoteCreate(content, userID) 70 | // Will only error if there is a problem with the query 71 | if err != nil { 72 | log.Println(err) 73 | sess.AddFlash(view.Flash{"An error occurred on the server. Please try again later.", view.FlashError}) 74 | sess.Save(r, w) 75 | } else { 76 | sess.AddFlash(view.Flash{"Note added!", view.FlashSuccess}) 77 | sess.Save(r, w) 78 | http.Redirect(w, r, "/notepad", http.StatusFound) 79 | return 80 | } 81 | 82 | // Display the same page 83 | NotepadCreateGET(w, r) 84 | } 85 | 86 | // NotepadUpdateGET displays the note update page 87 | func NotepadUpdateGET(w http.ResponseWriter, r *http.Request) { 88 | // Get session 89 | sess := session.Instance(r) 90 | 91 | // Get the note id 92 | var params httprouter.Params 93 | params = context.Get(r, "params").(httprouter.Params) 94 | noteID := params.ByName("id") 95 | 96 | userID := fmt.Sprintf("%s", sess.Values["id"]) 97 | 98 | // Get the note 99 | note, err := model.NoteByID(userID, noteID) 100 | if err != nil { // If the note doesn't exist 101 | log.Println(err) 102 | sess.AddFlash(view.Flash{"An error occurred on the server. Please try again later.", view.FlashError}) 103 | sess.Save(r, w) 104 | http.Redirect(w, r, "/notepad", http.StatusFound) 105 | return 106 | } 107 | 108 | // Display the view 109 | v := view.New(r) 110 | v.Name = "notepad/update" 111 | v.Vars["token"] = csrfbanana.Token(w, r, sess) 112 | v.Vars["note"] = note.Content 113 | v.Render(w) 114 | } 115 | 116 | // NotepadUpdatePOST handles the note update form submission 117 | func NotepadUpdatePOST(w http.ResponseWriter, r *http.Request) { 118 | // Get session 119 | sess := session.Instance(r) 120 | 121 | // Validate with required fields 122 | if validate, missingField := view.Validate(r, []string{"note"}); !validate { 123 | sess.AddFlash(view.Flash{"Field missing: " + missingField, view.FlashError}) 124 | sess.Save(r, w) 125 | NotepadUpdateGET(w, r) 126 | return 127 | } 128 | 129 | // Get form values 130 | content := r.FormValue("note") 131 | 132 | userID := fmt.Sprintf("%s", sess.Values["id"]) 133 | 134 | var params httprouter.Params 135 | params = context.Get(r, "params").(httprouter.Params) 136 | noteID := params.ByName("id") 137 | 138 | // Get database result 139 | err := model.NoteUpdate(content, userID, noteID) 140 | // Will only error if there is a problem with the query 141 | if err != nil { 142 | log.Println(err) 143 | sess.AddFlash(view.Flash{"An error occurred on the server. Please try again later.", view.FlashError}) 144 | sess.Save(r, w) 145 | } else { 146 | sess.AddFlash(view.Flash{"Note updated!", view.FlashSuccess}) 147 | sess.Save(r, w) 148 | http.Redirect(w, r, "/notepad", http.StatusFound) 149 | return 150 | } 151 | 152 | // Display the same page 153 | NotepadUpdateGET(w, r) 154 | } 155 | 156 | // NotepadDeleteGET handles the note deletion 157 | func NotepadDeleteGET(w http.ResponseWriter, r *http.Request) { 158 | // Get session 159 | sess := session.Instance(r) 160 | 161 | userID := fmt.Sprintf("%s", sess.Values["id"]) 162 | 163 | var params httprouter.Params 164 | params = context.Get(r, "params").(httprouter.Params) 165 | noteID := params.ByName("id") 166 | 167 | // Get database result 168 | err := model.NoteDelete(userID, noteID) 169 | // Will only error if there is a problem with the query 170 | if err != nil { 171 | log.Println(err) 172 | sess.AddFlash(view.Flash{"An error occurred on the server. Please try again later.", view.FlashError}) 173 | sess.Save(r, w) 174 | } else { 175 | sess.AddFlash(view.Flash{"Note deleted!", view.FlashSuccess}) 176 | sess.Save(r, w) 177 | } 178 | 179 | http.Redirect(w, r, "/notepad", http.StatusFound) 180 | return 181 | } 182 | -------------------------------------------------------------------------------- /vendor/app/model/note.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "time" 9 | 10 | "app/shared/database" 11 | 12 | "github.com/boltdb/bolt" 13 | "gopkg.in/mgo.v2/bson" 14 | ) 15 | 16 | // ***************************************************************************** 17 | // Note 18 | // ***************************************************************************** 19 | 20 | // Note table contains the information for each note 21 | type Note struct { 22 | ObjectID bson.ObjectId `bson:"_id"` 23 | ID uint32 `db:"id" bson:"id,omitempty"` // Don't use Id, use NoteID() instead for consistency with MongoDB 24 | Content string `db:"content" bson:"content"` 25 | UserID bson.ObjectId `bson:"user_id"` 26 | UID uint32 `db:"user_id" bson:"userid,omitempty"` 27 | CreatedAt time.Time `db:"created_at" bson:"created_at"` 28 | UpdatedAt time.Time `db:"updated_at" bson:"updated_at"` 29 | Deleted uint8 `db:"deleted" bson:"deleted"` 30 | } 31 | 32 | // NoteID returns the note id 33 | func (u *Note) NoteID() string { 34 | r := "" 35 | 36 | switch database.ReadConfig().Type { 37 | case database.TypeMySQL: 38 | r = fmt.Sprintf("%v", u.ID) 39 | case database.TypeMongoDB: 40 | r = u.ObjectID.Hex() 41 | case database.TypeBolt: 42 | r = u.ObjectID.Hex() 43 | } 44 | 45 | return r 46 | } 47 | 48 | // NoteByID gets note by ID 49 | func NoteByID(userID string, noteID string) (Note, error) { 50 | var err error 51 | 52 | result := Note{} 53 | 54 | switch database.ReadConfig().Type { 55 | case database.TypeMySQL: 56 | err = database.SQL.Get(&result, "SELECT id, content, user_id, created_at, updated_at, deleted FROM note WHERE id = ? AND user_id = ? LIMIT 1", noteID, userID) 57 | case database.TypeMongoDB: 58 | if database.CheckConnection() { 59 | // Create a copy of mongo 60 | session := database.Mongo.Copy() 61 | defer session.Close() 62 | c := session.DB(database.ReadConfig().MongoDB.Database).C("note") 63 | 64 | // Validate the object id 65 | if bson.IsObjectIdHex(noteID) { 66 | err = c.FindId(bson.ObjectIdHex(noteID)).One(&result) 67 | if result.UserID != bson.ObjectIdHex(userID) { 68 | result = Note{} 69 | err = ErrUnauthorized 70 | } 71 | } else { 72 | err = ErrNoResult 73 | } 74 | } else { 75 | err = ErrUnavailable 76 | } 77 | case database.TypeBolt: 78 | err = database.View("note", userID+noteID, &result) 79 | if err != nil { 80 | err = ErrNoResult 81 | } 82 | if result.UserID != bson.ObjectIdHex(userID) { 83 | result = Note{} 84 | err = ErrUnauthorized 85 | } 86 | default: 87 | err = ErrCode 88 | } 89 | 90 | return result, standardizeError(err) 91 | } 92 | 93 | // NotesByUserID gets all notes for a user 94 | func NotesByUserID(userID string) ([]Note, error) { 95 | var err error 96 | 97 | var result []Note 98 | 99 | switch database.ReadConfig().Type { 100 | case database.TypeMySQL: 101 | err = database.SQL.Select(&result, "SELECT id, content, user_id, created_at, updated_at, deleted FROM note WHERE user_id = ?", userID) 102 | case database.TypeMongoDB: 103 | if database.CheckConnection() { 104 | // Create a copy of mongo 105 | session := database.Mongo.Copy() 106 | defer session.Close() 107 | c := session.DB(database.ReadConfig().MongoDB.Database).C("note") 108 | 109 | // Validate the object id 110 | if bson.IsObjectIdHex(userID) { 111 | err = c.Find(bson.M{"user_id": bson.ObjectIdHex(userID)}).All(&result) 112 | } else { 113 | err = ErrNoResult 114 | } 115 | } else { 116 | err = ErrUnavailable 117 | } 118 | case database.TypeBolt: 119 | // View retrieves a record set in Bolt 120 | err = database.BoltDB.View(func(tx *bolt.Tx) error { 121 | // Get the bucket 122 | b := tx.Bucket([]byte("note")) 123 | if b == nil { 124 | return bolt.ErrBucketNotFound 125 | } 126 | 127 | // Get the iterator 128 | c := b.Cursor() 129 | 130 | prefix := []byte(userID) 131 | for k, v := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, v = c.Next() { 132 | var single Note 133 | 134 | // Decode the record 135 | err := json.Unmarshal(v, &single) 136 | if err != nil { 137 | log.Println(err) 138 | continue 139 | } 140 | 141 | result = append(result, single) 142 | } 143 | 144 | return nil 145 | }) 146 | default: 147 | err = ErrCode 148 | } 149 | 150 | return result, standardizeError(err) 151 | } 152 | 153 | // NoteCreate creates a note 154 | func NoteCreate(content string, userID string) error { 155 | var err error 156 | 157 | now := time.Now() 158 | 159 | switch database.ReadConfig().Type { 160 | case database.TypeMySQL: 161 | _, err = database.SQL.Exec("INSERT INTO note (content, user_id) VALUES (?,?)", content, userID) 162 | case database.TypeMongoDB: 163 | if database.CheckConnection() { 164 | // Create a copy of mongo 165 | session := database.Mongo.Copy() 166 | defer session.Close() 167 | c := session.DB(database.ReadConfig().MongoDB.Database).C("note") 168 | 169 | note := &Note{ 170 | ObjectID: bson.NewObjectId(), 171 | Content: content, 172 | UserID: bson.ObjectIdHex(userID), 173 | CreatedAt: now, 174 | UpdatedAt: now, 175 | Deleted: 0, 176 | } 177 | err = c.Insert(note) 178 | } else { 179 | err = ErrUnavailable 180 | } 181 | case database.TypeBolt: 182 | note := &Note{ 183 | ObjectID: bson.NewObjectId(), 184 | Content: content, 185 | UserID: bson.ObjectIdHex(userID), 186 | CreatedAt: now, 187 | UpdatedAt: now, 188 | Deleted: 0, 189 | } 190 | 191 | err = database.Update("note", userID+note.ObjectID.Hex(), ¬e) 192 | default: 193 | err = ErrCode 194 | } 195 | 196 | return standardizeError(err) 197 | } 198 | 199 | // NoteUpdate updates a note 200 | func NoteUpdate(content string, userID string, noteID string) error { 201 | var err error 202 | 203 | now := time.Now() 204 | 205 | switch database.ReadConfig().Type { 206 | case database.TypeMySQL: 207 | _, err = database.SQL.Exec("UPDATE note SET content=? WHERE id = ? AND user_id = ? LIMIT 1", content, noteID, userID) 208 | case database.TypeMongoDB: 209 | if database.CheckConnection() { 210 | // Create a copy of mongo 211 | session := database.Mongo.Copy() 212 | defer session.Close() 213 | c := session.DB(database.ReadConfig().MongoDB.Database).C("note") 214 | var note Note 215 | note, err = NoteByID(userID, noteID) 216 | if err == nil { 217 | // Confirm the owner is attempting to modify the note 218 | if note.UserID.Hex() == userID { 219 | note.UpdatedAt = now 220 | note.Content = content 221 | err = c.UpdateId(bson.ObjectIdHex(noteID), ¬e) 222 | } else { 223 | err = ErrUnauthorized 224 | } 225 | } 226 | } else { 227 | err = ErrUnavailable 228 | } 229 | case database.TypeBolt: 230 | var note Note 231 | note, err = NoteByID(userID, noteID) 232 | if err == nil { 233 | // Confirm the owner is attempting to modify the note 234 | if note.UserID.Hex() == userID { 235 | note.UpdatedAt = now 236 | note.Content = content 237 | err = database.Update("note", userID+note.ObjectID.Hex(), ¬e) 238 | } else { 239 | err = ErrUnauthorized 240 | } 241 | } 242 | default: 243 | err = ErrCode 244 | } 245 | 246 | return standardizeError(err) 247 | } 248 | 249 | // NoteDelete deletes a note 250 | func NoteDelete(userID string, noteID string) error { 251 | var err error 252 | 253 | switch database.ReadConfig().Type { 254 | case database.TypeMySQL: 255 | _, err = database.SQL.Exec("DELETE FROM note WHERE id = ? AND user_id = ?", noteID, userID) 256 | case database.TypeMongoDB: 257 | if database.CheckConnection() { 258 | // Create a copy of mongo 259 | session := database.Mongo.Copy() 260 | defer session.Close() 261 | c := session.DB(database.ReadConfig().MongoDB.Database).C("note") 262 | 263 | var note Note 264 | note, err = NoteByID(userID, noteID) 265 | if err == nil { 266 | // Confirm the owner is attempting to modify the note 267 | if note.UserID.Hex() == userID { 268 | err = c.RemoveId(bson.ObjectIdHex(noteID)) 269 | } else { 270 | err = ErrUnauthorized 271 | } 272 | } 273 | } else { 274 | err = ErrUnavailable 275 | } 276 | case database.TypeBolt: 277 | var note Note 278 | note, err = NoteByID(userID, noteID) 279 | if err == nil { 280 | // Confirm the owner is attempting to modify the note 281 | if note.UserID.Hex() == userID { 282 | err = database.Delete("note", userID+note.ObjectID.Hex()) 283 | } else { 284 | err = ErrUnauthorized 285 | } 286 | } 287 | default: 288 | err = ErrCode 289 | } 290 | 291 | return standardizeError(err) 292 | } 293 | -------------------------------------------------------------------------------- /vendor/app/shared/view/view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "encoding/gob" 5 | "encoding/json" 6 | "fmt" 7 | "html/template" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | "sync" 14 | 15 | "app/shared/session" 16 | ) 17 | 18 | func init() { 19 | // Magic goes here to allow serializing maps in securecookie 20 | // http://golang.org/pkg/encoding/gob/#Register 21 | // Source: http://stackoverflow.com/questions/21934730/gob-type-not-registered-for-interface-mapstringinterface 22 | gob.Register(Flash{}) 23 | } 24 | 25 | var ( 26 | // FlashError is a bootstrap class 27 | FlashError = "alert-danger" 28 | // FlashSuccess is a bootstrap class 29 | FlashSuccess = "alert-success" 30 | // FlashNotice is a bootstrap class 31 | FlashNotice = "alert-info" 32 | // FlashWarning is a bootstrap class 33 | FlashWarning = "alert-warning" 34 | 35 | childTemplates []string 36 | rootTemplate string 37 | templateCollection = make(map[string]*template.Template) 38 | pluginCollection = make(template.FuncMap) 39 | mutex sync.RWMutex 40 | mutexPlugins sync.RWMutex 41 | sessionName string 42 | viewInfo View 43 | ) 44 | 45 | // Template root and children 46 | type Template struct { 47 | Root string `json:"Root"` 48 | Children []string `json:"Children"` 49 | } 50 | 51 | // View attributes 52 | type View struct { 53 | BaseURI string 54 | Extension string 55 | Folder string 56 | Name string 57 | Caching bool 58 | Vars map[string]interface{} 59 | request *http.Request 60 | } 61 | 62 | // Flash Message 63 | type Flash struct { 64 | Message string 65 | Class string 66 | } 67 | 68 | // Configure sets the view information 69 | func Configure(vi View) { 70 | viewInfo = vi 71 | } 72 | 73 | // ReadConfig returns the configuration 74 | func ReadConfig() View { 75 | return viewInfo 76 | } 77 | 78 | // LoadTemplates will set the root and child templates 79 | func LoadTemplates(rootTemp string, childTemps []string) { 80 | rootTemplate = rootTemp 81 | childTemplates = childTemps 82 | } 83 | 84 | // LoadPlugins will combine all template.FuncMaps into one map and then set the 85 | // plugins for the templates 86 | // If a func already exists, it is rewritten, there is no error 87 | func LoadPlugins(fms ...template.FuncMap) { 88 | // Final FuncMap 89 | fm := make(template.FuncMap) 90 | 91 | // Loop through the maps 92 | for _, m := range fms { 93 | // Loop through each key and value 94 | for k, v := range m { 95 | fm[k] = v 96 | } 97 | } 98 | 99 | // Load the plugins 100 | mutexPlugins.Lock() 101 | pluginCollection = fm 102 | mutexPlugins.Unlock() 103 | } 104 | 105 | // PrependBaseURI prepends the base URI to the string 106 | func (v *View) PrependBaseURI(s string) string { 107 | return v.BaseURI + s 108 | } 109 | 110 | // New returns a new view 111 | func New(req *http.Request) *View { 112 | v := &View{} 113 | v.Vars = make(map[string]interface{}) 114 | v.Vars["AuthLevel"] = "anon" 115 | 116 | v.BaseURI = viewInfo.BaseURI 117 | v.Extension = viewInfo.Extension 118 | v.Folder = viewInfo.Folder 119 | v.Name = viewInfo.Name 120 | 121 | // Make sure BaseURI is available in the templates 122 | v.Vars["BaseURI"] = v.BaseURI 123 | 124 | // This is required for the view to access the request 125 | v.request = req 126 | 127 | // Get session 128 | sess := session.Instance(v.request) 129 | 130 | // Set the AuthLevel to auth if the user is logged in 131 | if sess.Values["id"] != nil { 132 | v.Vars["AuthLevel"] = "auth" 133 | } 134 | 135 | return v 136 | } 137 | 138 | // AssetTimePath returns a URL with the proper base uri and timestamp appended. 139 | // Works for CSS and JS assets 140 | // Determines if local or on the web 141 | func (v *View) AssetTimePath(s string) (string, error) { 142 | if strings.HasPrefix(s, "//") { 143 | return s, nil 144 | } 145 | 146 | s = strings.TrimLeft(s, "/") 147 | abs, err := filepath.Abs(s) 148 | 149 | if err != nil { 150 | return "", err 151 | } 152 | 153 | time, err2 := FileTime(abs) 154 | if err2 != nil { 155 | return "", err2 156 | } 157 | 158 | return v.PrependBaseURI(s + "?" + time), nil 159 | } 160 | 161 | // RenderSingle renders a template to the writer 162 | func (v *View) RenderSingle(w http.ResponseWriter) { 163 | 164 | // Get the template collection from cache 165 | /*mutex.RLock() 166 | tc, ok := templateCollection[v.Name] 167 | mutex.RUnlock()*/ 168 | 169 | // Get the plugin collection 170 | mutexPlugins.RLock() 171 | pc := pluginCollection 172 | mutexPlugins.RUnlock() 173 | 174 | templateList := []string{v.Name} 175 | 176 | // List of template names 177 | /*templateList := make([]string, 0) 178 | templateList = append(templateList, rootTemplate) 179 | templateList = append(templateList, v.Name) 180 | templateList = append(templateList, childTemplates...)*/ 181 | 182 | // Loop through each template and test the full path 183 | for i, name := range templateList { 184 | // Get the absolute path of the root template 185 | path, err := filepath.Abs(v.Folder + string(os.PathSeparator) + name + "." + v.Extension) 186 | if err != nil { 187 | http.Error(w, "Template Path Error: "+err.Error(), http.StatusInternalServerError) 188 | return 189 | } 190 | templateList[i] = path 191 | } 192 | 193 | // Determine if there is an error in the template syntax 194 | templates, err := template.New(v.Name).Funcs(pc).ParseFiles(templateList...) 195 | 196 | if err != nil { 197 | http.Error(w, "Template Parse Error: "+err.Error(), http.StatusInternalServerError) 198 | return 199 | } 200 | 201 | // Cache the template collection 202 | /*mutex.Lock() 203 | templateCollection[v.Name] = templates 204 | mutex.Unlock()*/ 205 | 206 | // Save the template collection 207 | tc := templates 208 | 209 | // Get session 210 | sess := session.Instance(v.request) 211 | 212 | // Get the flashes for the template 213 | if flashes := sess.Flashes(); len(flashes) > 0 { 214 | v.Vars["flashes"] = make([]Flash, len(flashes)) 215 | for i, f := range flashes { 216 | switch f.(type) { 217 | case Flash: 218 | v.Vars["flashes"].([]Flash)[i] = f.(Flash) 219 | default: 220 | v.Vars["flashes"].([]Flash)[i] = Flash{f.(string), "alert-box"} 221 | } 222 | 223 | } 224 | sess.Save(v.request, w) 225 | } 226 | 227 | // Display the content to the screen 228 | err = tc.Funcs(pc).ExecuteTemplate(w, v.Name+"."+v.Extension, v.Vars) 229 | 230 | if err != nil { 231 | http.Error(w, "Template File Error: "+err.Error(), http.StatusInternalServerError) 232 | } 233 | } 234 | 235 | // Render renders a template to the writer 236 | func (v *View) Render(w http.ResponseWriter) { 237 | 238 | // Get the template collection from cache 239 | mutex.RLock() 240 | tc, ok := templateCollection[v.Name] 241 | mutex.RUnlock() 242 | 243 | // Get the plugin collection 244 | mutexPlugins.RLock() 245 | pc := pluginCollection 246 | mutexPlugins.RUnlock() 247 | 248 | // If the template collection is not cached or caching is disabled 249 | if !ok || !viewInfo.Caching { 250 | 251 | // List of template names 252 | var templateList []string 253 | templateList = append(templateList, rootTemplate) 254 | templateList = append(templateList, v.Name) 255 | templateList = append(templateList, childTemplates...) 256 | 257 | // Loop through each template and test the full path 258 | for i, name := range templateList { 259 | // Get the absolute path of the root template 260 | path, err := filepath.Abs(v.Folder + string(os.PathSeparator) + name + "." + v.Extension) 261 | if err != nil { 262 | http.Error(w, "Template Path Error: "+err.Error(), http.StatusInternalServerError) 263 | return 264 | } 265 | templateList[i] = path 266 | } 267 | 268 | // Determine if there is an error in the template syntax 269 | templates, err := template.New(v.Name).Funcs(pc).ParseFiles(templateList...) 270 | 271 | if err != nil { 272 | http.Error(w, "Template Parse Error: "+err.Error(), http.StatusInternalServerError) 273 | return 274 | } 275 | 276 | // Cache the template collection 277 | mutex.Lock() 278 | templateCollection[v.Name] = templates 279 | mutex.Unlock() 280 | 281 | // Save the template collection 282 | tc = templates 283 | } 284 | 285 | // Get session 286 | sess := session.Instance(v.request) 287 | 288 | // Get the flashes for the template 289 | if flashes := sess.Flashes(); len(flashes) > 0 { 290 | v.Vars["flashes"] = make([]Flash, len(flashes)) 291 | for i, f := range flashes { 292 | switch f.(type) { 293 | case Flash: 294 | v.Vars["flashes"].([]Flash)[i] = f.(Flash) 295 | default: 296 | v.Vars["flashes"].([]Flash)[i] = Flash{f.(string), "alert-box"} 297 | } 298 | 299 | } 300 | sess.Save(v.request, w) 301 | } 302 | 303 | // Display the content to the screen 304 | err := tc.Funcs(pc).ExecuteTemplate(w, rootTemplate+"."+v.Extension, v.Vars) 305 | 306 | if err != nil { 307 | http.Error(w, "Template File Error: "+err.Error(), http.StatusInternalServerError) 308 | } 309 | } 310 | 311 | // Validate returns true if all the required form values are passed 312 | func Validate(req *http.Request, required []string) (bool, string) { 313 | for _, v := range required { 314 | if req.FormValue(v) == "" { 315 | return false, v 316 | } 317 | } 318 | 319 | return true, "" 320 | } 321 | 322 | // SendFlashes allows retrieval of flash messages for using with Ajax 323 | func (v *View) SendFlashes(w http.ResponseWriter) { 324 | // Get session 325 | sess := session.Instance(v.request) 326 | 327 | flashes := peekFlashes(w, v.request) 328 | sess.Save(v.request, w) 329 | 330 | js, err := json.Marshal(flashes) 331 | if err != nil { 332 | http.Error(w, "JSON Error: "+err.Error(), http.StatusInternalServerError) 333 | return 334 | } 335 | 336 | w.Header().Set("Content-Type", "application/json") 337 | w.Write(js) 338 | } 339 | 340 | func peekFlashes(w http.ResponseWriter, r *http.Request) []Flash { 341 | // Get session 342 | sess := session.Instance(r) 343 | 344 | var v []Flash 345 | 346 | // Get the flashes for the template 347 | if flashes := sess.Flashes(); len(flashes) > 0 { 348 | v = make([]Flash, len(flashes)) 349 | for i, f := range flashes { 350 | switch f.(type) { 351 | case Flash: 352 | v[i] = f.(Flash) 353 | default: 354 | v[i] = Flash{f.(string), "alert-box"} 355 | } 356 | 357 | } 358 | } 359 | 360 | return v 361 | } 362 | 363 | // Repopulate updates the dst map so the form fields can be refilled 364 | func Repopulate(list []string, src url.Values, dst map[string]interface{}) { 365 | for _, v := range list { 366 | dst[v] = src.Get(v) 367 | } 368 | } 369 | 370 | // FileTime returns the modification time of the file 371 | func FileTime(name string) (string, error) { 372 | fi, err := os.Stat(name) 373 | if err != nil { 374 | return "", err 375 | } 376 | mtime := fi.ModTime().Unix() 377 | return fmt.Sprintf("%v", mtime), nil 378 | } 379 | -------------------------------------------------------------------------------- /static/js/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.7.0 2 | // http://underscorejs.org 3 | // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | (function(){var n=this,t=n._,r=Array.prototype,e=Object.prototype,u=Function.prototype,i=r.push,a=r.slice,o=r.concat,l=e.toString,c=e.hasOwnProperty,f=Array.isArray,s=Object.keys,p=u.bind,h=function(n){return n instanceof h?n:this instanceof h?void(this._wrapped=n):new h(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=h),exports._=h):n._=h,h.VERSION="1.7.0";var g=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}};h.iteratee=function(n,t,r){return null==n?h.identity:h.isFunction(n)?g(n,t,r):h.isObject(n)?h.matches(n):h.property(n)},h.each=h.forEach=function(n,t,r){if(null==n)return n;t=g(t,r);var e,u=n.length;if(u===+u)for(e=0;u>e;e++)t(n[e],e,n);else{var i=h.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},h.map=h.collect=function(n,t,r){if(null==n)return[];t=h.iteratee(t,r);for(var e,u=n.length!==+n.length&&h.keys(n),i=(u||n).length,a=Array(i),o=0;i>o;o++)e=u?u[o]:o,a[o]=t(n[e],e,n);return a};var v="Reduce of empty array with no initial value";h.reduce=h.foldl=h.inject=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length,o=0;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[o++]:o++]}for(;a>o;o++)u=i?i[o]:o,r=t(r,n[u],u,n);return r},h.reduceRight=h.foldr=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[--a]:--a]}for(;a--;)u=i?i[a]:a,r=t(r,n[u],u,n);return r},h.find=h.detect=function(n,t,r){var e;return t=h.iteratee(t,r),h.some(n,function(n,r,u){return t(n,r,u)?(e=n,!0):void 0}),e},h.filter=h.select=function(n,t,r){var e=[];return null==n?e:(t=h.iteratee(t,r),h.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e)},h.reject=function(n,t,r){return h.filter(n,h.negate(h.iteratee(t)),r)},h.every=h.all=function(n,t,r){if(null==n)return!0;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,!t(n[u],u,n))return!1;return!0},h.some=h.any=function(n,t,r){if(null==n)return!1;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,t(n[u],u,n))return!0;return!1},h.contains=h.include=function(n,t){return null==n?!1:(n.length!==+n.length&&(n=h.values(n)),h.indexOf(n,t)>=0)},h.invoke=function(n,t){var r=a.call(arguments,2),e=h.isFunction(t);return h.map(n,function(n){return(e?t:n[t]).apply(n,r)})},h.pluck=function(n,t){return h.map(n,h.property(t))},h.where=function(n,t){return h.filter(n,h.matches(t))},h.findWhere=function(n,t){return h.find(n,h.matches(t))},h.max=function(n,t,r){var e,u,i=-1/0,a=-1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],e>i&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(u>a||u===-1/0&&i===-1/0)&&(i=n,a=u)});return i},h.min=function(n,t,r){var e,u,i=1/0,a=1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],i>e&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(a>u||1/0===u&&1/0===i)&&(i=n,a=u)});return i},h.shuffle=function(n){for(var t,r=n&&n.length===+n.length?n:h.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=h.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},h.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=h.values(n)),n[h.random(n.length-1)]):h.shuffle(n).slice(0,Math.max(0,t))},h.sortBy=function(n,t,r){return t=h.iteratee(t,r),h.pluck(h.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var m=function(n){return function(t,r,e){var u={};return r=h.iteratee(r,e),h.each(t,function(e,i){var a=r(e,i,t);n(u,e,a)}),u}};h.groupBy=m(function(n,t,r){h.has(n,r)?n[r].push(t):n[r]=[t]}),h.indexBy=m(function(n,t,r){n[r]=t}),h.countBy=m(function(n,t,r){h.has(n,r)?n[r]++:n[r]=1}),h.sortedIndex=function(n,t,r,e){r=h.iteratee(r,e,1);for(var u=r(t),i=0,a=n.length;a>i;){var o=i+a>>>1;r(n[o])t?[]:a.call(n,0,t)},h.initial=function(n,t,r){return a.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},h.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:a.call(n,Math.max(n.length-t,0))},h.rest=h.tail=h.drop=function(n,t,r){return a.call(n,null==t||r?1:t)},h.compact=function(n){return h.filter(n,h.identity)};var y=function(n,t,r,e){if(t&&h.every(n,h.isArray))return o.apply(e,n);for(var u=0,a=n.length;a>u;u++){var l=n[u];h.isArray(l)||h.isArguments(l)?t?i.apply(e,l):y(l,t,r,e):r||e.push(l)}return e};h.flatten=function(n,t){return y(n,t,!1,[])},h.without=function(n){return h.difference(n,a.call(arguments,1))},h.uniq=h.unique=function(n,t,r,e){if(null==n)return[];h.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=h.iteratee(r,e));for(var u=[],i=[],a=0,o=n.length;o>a;a++){var l=n[a];if(t)a&&i===l||u.push(l),i=l;else if(r){var c=r(l,a,n);h.indexOf(i,c)<0&&(i.push(c),u.push(l))}else h.indexOf(u,l)<0&&u.push(l)}return u},h.union=function(){return h.uniq(y(arguments,!0,!0,[]))},h.intersection=function(n){if(null==n)return[];for(var t=[],r=arguments.length,e=0,u=n.length;u>e;e++){var i=n[e];if(!h.contains(t,i)){for(var a=1;r>a&&h.contains(arguments[a],i);a++);a===r&&t.push(i)}}return t},h.difference=function(n){var t=y(a.call(arguments,1),!0,!0,[]);return h.filter(n,function(n){return!h.contains(t,n)})},h.zip=function(n){if(null==n)return[];for(var t=h.max(arguments,"length").length,r=Array(t),e=0;t>e;e++)r[e]=h.pluck(arguments,e);return r},h.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},h.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=h.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}for(;u>e;e++)if(n[e]===t)return e;return-1},h.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=n.length;for("number"==typeof r&&(e=0>r?e+r+1:Math.min(e,r+1));--e>=0;)if(n[e]===t)return e;return-1},h.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=r||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=Array(e),i=0;e>i;i++,n+=r)u[i]=n;return u};var d=function(){};h.bind=function(n,t){var r,e;if(p&&n.bind===p)return p.apply(n,a.call(arguments,1));if(!h.isFunction(n))throw new TypeError("Bind must be called on a function");return r=a.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(a.call(arguments)));d.prototype=n.prototype;var u=new d;d.prototype=null;var i=n.apply(u,r.concat(a.call(arguments)));return h.isObject(i)?i:u}},h.partial=function(n){var t=a.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===h&&(e[u]=arguments[r++]);for(;r=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=h.bind(n[r],n);return n},h.memoize=function(n,t){var r=function(e){var u=r.cache,i=t?t.apply(this,arguments):e;return h.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},h.delay=function(n,t){var r=a.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},h.defer=function(n){return h.delay.apply(h,[n,1].concat(a.call(arguments,1)))},h.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var l=function(){o=r.leading===!1?0:h.now(),a=null,i=n.apply(e,u),a||(e=u=null)};return function(){var c=h.now();o||r.leading!==!1||(o=c);var f=t-(c-o);return e=this,u=arguments,0>=f||f>t?(clearTimeout(a),a=null,o=c,i=n.apply(e,u),a||(e=u=null)):a||r.trailing===!1||(a=setTimeout(l,f)),i}},h.debounce=function(n,t,r){var e,u,i,a,o,l=function(){var c=h.now()-a;t>c&&c>0?e=setTimeout(l,t-c):(e=null,r||(o=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,a=h.now();var c=r&&!e;return e||(e=setTimeout(l,t)),c&&(o=n.apply(i,u),i=u=null),o}},h.wrap=function(n,t){return h.partial(t,n)},h.negate=function(n){return function(){return!n.apply(this,arguments)}},h.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},h.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},h.before=function(n,t){var r;return function(){return--n>0?r=t.apply(this,arguments):t=null,r}},h.once=h.partial(h.before,2),h.keys=function(n){if(!h.isObject(n))return[];if(s)return s(n);var t=[];for(var r in n)h.has(n,r)&&t.push(r);return t},h.values=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},h.pairs=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},h.invert=function(n){for(var t={},r=h.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},h.functions=h.methods=function(n){var t=[];for(var r in n)h.isFunction(n[r])&&t.push(r);return t.sort()},h.extend=function(n){if(!h.isObject(n))return n;for(var t,r,e=1,u=arguments.length;u>e;e++){t=arguments[e];for(r in t)c.call(t,r)&&(n[r]=t[r])}return n},h.pick=function(n,t,r){var e,u={};if(null==n)return u;if(h.isFunction(t)){t=g(t,r);for(e in n){var i=n[e];t(i,e,n)&&(u[e]=i)}}else{var l=o.apply([],a.call(arguments,1));n=new Object(n);for(var c=0,f=l.length;f>c;c++)e=l[c],e in n&&(u[e]=n[e])}return u},h.omit=function(n,t,r){if(h.isFunction(t))t=h.negate(t);else{var e=h.map(o.apply([],a.call(arguments,1)),String);t=function(n,t){return!h.contains(e,t)}}return h.pick(n,t,r)},h.defaults=function(n){if(!h.isObject(n))return n;for(var t=1,r=arguments.length;r>t;t++){var e=arguments[t];for(var u in e)n[u]===void 0&&(n[u]=e[u])}return n},h.clone=function(n){return h.isObject(n)?h.isArray(n)?n.slice():h.extend({},n):n},h.tap=function(n,t){return t(n),n};var b=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof h&&(n=n._wrapped),t instanceof h&&(t=t._wrapped);var u=l.call(n);if(u!==l.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]===n)return e[i]===t;var a=n.constructor,o=t.constructor;if(a!==o&&"constructor"in n&&"constructor"in t&&!(h.isFunction(a)&&a instanceof a&&h.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c,f;if("[object Array]"===u){if(c=n.length,f=c===t.length)for(;c--&&(f=b(n[c],t[c],r,e)););}else{var s,p=h.keys(n);if(c=p.length,f=h.keys(t).length===c)for(;c--&&(s=p[c],f=h.has(t,s)&&b(n[s],t[s],r,e)););}return r.pop(),e.pop(),f};h.isEqual=function(n,t){return b(n,t,[],[])},h.isEmpty=function(n){if(null==n)return!0;if(h.isArray(n)||h.isString(n)||h.isArguments(n))return 0===n.length;for(var t in n)if(h.has(n,t))return!1;return!0},h.isElement=function(n){return!(!n||1!==n.nodeType)},h.isArray=f||function(n){return"[object Array]"===l.call(n)},h.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},h.each(["Arguments","Function","String","Number","Date","RegExp"],function(n){h["is"+n]=function(t){return l.call(t)==="[object "+n+"]"}}),h.isArguments(arguments)||(h.isArguments=function(n){return h.has(n,"callee")}),"function"!=typeof/./&&(h.isFunction=function(n){return"function"==typeof n||!1}),h.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},h.isNaN=function(n){return h.isNumber(n)&&n!==+n},h.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===l.call(n)},h.isNull=function(n){return null===n},h.isUndefined=function(n){return n===void 0},h.has=function(n,t){return null!=n&&c.call(n,t)},h.noConflict=function(){return n._=t,this},h.identity=function(n){return n},h.constant=function(n){return function(){return n}},h.noop=function(){},h.property=function(n){return function(t){return t[n]}},h.matches=function(n){var t=h.pairs(n),r=t.length;return function(n){if(null==n)return!r;n=new Object(n);for(var e=0;r>e;e++){var u=t[e],i=u[0];if(u[1]!==n[i]||!(i in n))return!1}return!0}},h.times=function(n,t,r){var e=Array(Math.max(0,n));t=g(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},h.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},h.now=Date.now||function(){return(new Date).getTime()};var _={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},w=h.invert(_),j=function(n){var t=function(t){return n[t]},r="(?:"+h.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};h.escape=j(_),h.unescape=j(w),h.result=function(n,t){if(null==n)return void 0;var r=n[t];return h.isFunction(r)?n[t]():r};var x=0;h.uniqueId=function(n){var t=++x+"";return n?n+t:t},h.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var A=/(.)^/,k={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},O=/\\|'|\r|\n|\u2028|\u2029/g,F=function(n){return"\\"+k[n]};h.template=function(n,t,r){!t&&r&&(t=r),t=h.defaults({},t,h.templateSettings);var e=RegExp([(t.escape||A).source,(t.interpolate||A).source,(t.evaluate||A).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(O,F),u=o+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":a&&(i+="';\n"+a+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=new Function(t.variable||"obj","_",i)}catch(o){throw o.source=i,o}var l=function(n){return a.call(this,n,h)},c=t.variable||"obj";return l.source="function("+c+"){\n"+i+"}",l},h.chain=function(n){var t=h(n);return t._chain=!0,t};var E=function(n){return this._chain?h(n).chain():n};h.mixin=function(n){h.each(h.functions(n),function(t){var r=h[t]=n[t];h.prototype[t]=function(){var n=[this._wrapped];return i.apply(n,arguments),E.call(this,r.apply(h,n))}})},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=r[n];h.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],E.call(this,r)}}),h.each(["concat","join","slice"],function(n){var t=r[n];h.prototype[n]=function(){return E.call(this,t.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return h})}).call(this); 6 | //# sourceMappingURL=underscore-min.map -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoWebApp 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/josephspurrier/gowebapp)](https://goreportcard.com/report/github.com/josephspurrier/gowebapp) 4 | [![GoDoc](https://godoc.org/github.com/josephspurrier/gowebapp?status.svg)](https://godoc.org/github.com/josephspurrier/gowebapp) 5 | 6 | Basic MVC Web Application in Go 7 | 8 | #### I recommend you use Blue Jay which is the latest version of this project: [https://github.com/blue-jay/blueprint](https://github.com/blue-jay/blueprint). 9 | 10 | This project demonstrates how to structure and build a website using the Go language without a framework. There is a blog article you can read at [http://www.josephspurrier.com/go-web-app-example/](http://www.josephspurrier.com/go-web-app-example/). There is a full application I built with an earlier version of the project at [https://github.com/verifiedninja/webapp](https://github.com/verifiedninja/webapp). There is an API version of this project at [https://github.com/josephspurrier/gowebapi](https://github.com/josephspurrier/gowebapi). 11 | 12 | To download, run the following command: 13 | 14 | ~~~ 15 | go get github.com/josephspurrier/gowebapp 16 | ~~~ 17 | 18 | If you are on Go 1.5, you need to set GOVENDOREXPERIMENT to 1. If you are on Go 1.4 or earlier, the code will not work because it uses the vendor folder. 19 | 20 | ### Other Branches 21 | 22 | - 2021-02-17: There is a branch using Go modules (requires Go 1.13) available here: https://github.com/josephspurrier/gowebapp/tree/modules. 23 | - 2021-05-23: There is a branch using Go embedded assets (requires Go 1.16) available here: https://github.com/josephspurrier/gowebapp/tree/embedded-templates. 24 | 25 | ## Quick Start with Bolt 26 | 27 | The gowebapp.db file will be created once you start the application. 28 | 29 | Build and run from the root directory. Open your web browser to: http://localhost. You should see the welcome page. 30 | 31 | Navigate to the login page, and then to the register page. Create a new user and you should be able to login. That's it. 32 | 33 | ## Quick Start with MongoDB 34 | 35 | Start MongoDB. 36 | 37 | Open config/config.json and edit the Database section so the connection information matches your MongoDB instance. Also, change Type from Bolt to MongoDB. 38 | 39 | Build and run from the root directory. Open your web browser to: http://localhost. You should see the welcome page. 40 | 41 | Navigate to the login page, and then to the register page. Create a new user and you should be able to login. That's it. 42 | 43 | ## Quick Start with MySQL 44 | 45 | Start MySQL and import config/mysql.sql to create the database and tables. 46 | 47 | Open config/config.json and edit the Database section so the connection information matches your MySQL instance. Also, change Type from Bolt to MySQL. 48 | 49 | Build and run from the root directory. Open your web browser to: http://localhost. You should see the welcome page. 50 | 51 | Navigate to the login page, and then to the register page. Create a new user and you should be able to login. That's it. 52 | 53 | ## Overview 54 | 55 | The web app has a public home page, authenticated home page, login page, register page, 56 | about page, and a simple notepad to demonstrate the CRUD operations. 57 | 58 | The entrypoint for the web app is gowebapp.go. The file loads the application settings, 59 | starts the session, connects to the database, sets up the templates, loads 60 | the routes, attaches the middleware, and starts the web server. 61 | 62 | The front end is built using Bootstrap with a few small changes to fonts and spacing. The flash 63 | messages are customized so they show up at the bottom right of the screen. 64 | 65 | All of the error and warning messages should be either displayed either to the 66 | user or in the console. Informational messages are displayed to the user via 67 | flash messages that disappear after 4 seconds. The flash messages are controlled 68 | by JavaScript in the static folder. 69 | 70 | ## Structure 71 | 72 | Recently, the folder structure changed. After looking at all the forks 73 | and reusing my project in different places, I decided to move the Go code to the 74 | **app** folder inside the **vendor** folder so the github path is not littered 75 | throughout the many imports. I did not want to use relative paths so the vendor 76 | folder seemed like the best option. 77 | 78 | The project is organized into the following folders: 79 | 80 | ~~~ 81 | config - application settings and database schema 82 | static - location of statically served files like CSS and JS 83 | template - HTML templates 84 | 85 | vendor/app/controller - page logic organized by HTTP methods (GET, POST) 86 | vendor/app/shared - packages for templates, MySQL, cryptography, sessions, and json 87 | vendor/app/model - database queries 88 | vendor/app/route - route information and middleware 89 | ~~~ 90 | 91 | There are a few external packages: 92 | 93 | ~~~ 94 | github.com/gorilla/context - registry for global request variables 95 | github.com/gorilla/sessions - cookie and filesystem sessions 96 | github.com/go-sql-driver/mysql - MySQL driver 97 | github.com/haisum/recaptcha - Google reCAPTCHA support 98 | github.com/jmoiron/sqlx - MySQL general purpose extensions 99 | github.com/josephspurrier/csrfbanana - CSRF protection for gorilla sessions 100 | github.com/julienschmidt/httprouter - high performance HTTP request router 101 | github.com/justinas/alice - middleware chaining 102 | github.com/mattn/go-sqlite3 - SQLite driver 103 | golang.org/x/crypto/bcrypt - password hashing algorithm 104 | ~~~ 105 | 106 | The templates are organized into folders under the **template** folder: 107 | 108 | ~~~ 109 | about/about.tmpl - quick info about the app 110 | index/anon.tmpl - public home page 111 | index/auth.tmpl - home page once you login 112 | login/login.tmpl - login page 113 | notepad/create.tmpl - create note 114 | notepad/read.tmpl - read a note 115 | notepad/update.tmpl - update a note 116 | partial/footer.tmpl - footer 117 | partial/menu.tmpl - menu at the top of all the pages 118 | register/register.tmpl - register page 119 | base.tmpl - base template for all the pages 120 | ~~~ 121 | 122 | ## Templates 123 | 124 | There are a few template funcs that are available to make working with the templates 125 | and static files easier: 126 | 127 | ~~~ html 128 | 129 | {{CSS "static/css/normalize3.0.0.min.css"}} 130 | parses to 131 | 132 | 133 | 134 | {{JS "static/js/jquery1.11.0.min.js"}} 135 | parses to 136 | 137 | 138 | 139 | {{LINK "register" "Create a new account."}} 140 | parses to 141 | Create a new account. 142 | 143 | 144 | {{.SomeVariable | NOESCAPE}} 145 | 146 | 147 | {{.SomeTime | PRETTYTIME}} 148 | parses to format 149 | 3:04 PM 01/02/2006 150 | ~~~ 151 | 152 | There are a few variables you can use in templates as well: 153 | 154 | ~~~ html 155 | 156 | {{if eq .AuthLevel "auth"}} 157 | You are logged in. 158 | {{else}} 159 | You are not logged in. 160 | {{end}} 161 | 162 | 163 |
  • About
  • 164 | 165 | 166 | 167 | ~~~ 168 | 169 | It's also easy to add template-specific code before the closing and tags: 170 | 171 | ~~~ html 172 | 173 | {{define "head"}}{{end}} 174 | 175 | ... 176 | 177 | 178 | {{define "foot"}}{{JS "//www.google.com/recaptcha/api.js"}}{{end}} 179 | ~~~ 180 | 181 | ## JavaScript 182 | 183 | You can trigger a flash notification using JavaScript. 184 | 185 | ~~~ javascript 186 | flashError("You must type in a username."); 187 | 188 | flashSuccess("Record created!"); 189 | 190 | flashNotice("There seems to be a piece missing."); 191 | 192 | flashWarning("Something does not seem right..."); 193 | ~~~ 194 | 195 | ## Controllers 196 | 197 | The controller files all share the same package name. This cuts down on the 198 | number of packages when you are mapping the routes. It also forces you to use 199 | a good naming convention for each of the funcs so you know where each of the 200 | funcs are located and what type of HTTP request they each are mapped to. 201 | 202 | ### These are a few things you can do with controllers. 203 | 204 | Access a gorilla session: 205 | 206 | ~~~ go 207 | // Get the current session 208 | sess := session.Instance(r) 209 | ... 210 | // Close the session after you are finished making changes 211 | sess.Save(r, w) 212 | ~~~ 213 | 214 | Trigger 1 of 4 different types of flash messages on the next page load (no other code needed): 215 | 216 | ~~~ go 217 | sess.AddFlash(view.Flash{"Sorry, no brute force :-)", view.FlashNotice}) 218 | sess.Save(r, w) // Ensure you save the session after making a change to it 219 | ~~~ 220 | 221 | Validate form fields are not empty: 222 | 223 | ~~~ go 224 | // Ensure a user submitted all the required form fields 225 | if validate, missingField := view.Validate(r, []string{"email", "password"}); !validate { 226 | sess.AddFlash(view.Flash{"Field missing: " + missingField, view.FlashError}) 227 | sess.Save(r, w) 228 | LoginGET(w, r) 229 | return 230 | } 231 | ~~~ 232 | 233 | Render a template: 234 | 235 | ~~~ go 236 | // Create a new view 237 | v := view.New(r) 238 | 239 | // Set the template name 240 | v.Name = "login/login" 241 | 242 | // Assign a variable that is accessible in the form 243 | v.Vars["token"] = csrfbanana.Token(w, r, sess) 244 | 245 | // Refill any form fields from a POST operation 246 | view.Repopulate([]string{"email"}, r.Form, v.Vars) 247 | 248 | // Render the template 249 | v.Render(w) 250 | ~~~ 251 | 252 | Return the flash messages during an Ajax request: 253 | 254 | ~~~ go 255 | // Get session 256 | sess := session.Instance(r) 257 | 258 | // Set the flash message 259 | sess.AddFlash(view.Flash{"An error occurred on the server. Please try again later.", view.FlashError}) 260 | sess.Save(r, w) 261 | 262 | // Display the flash messages as JSON 263 | v := view.New(r) 264 | v.SendFlashes(w) 265 | ~~~ 266 | 267 | Handle the database query: 268 | 269 | ~~~ go 270 | // Get database result 271 | result, err := model.UserByEmail(email) 272 | 273 | if err == sql.ErrNoRows { 274 | // User does not exist 275 | } else if err != nil { 276 | // Display error message 277 | } else if passhash.MatchString(result.Password, password) { 278 | // Password matches! 279 | } else { 280 | // Password does not match 281 | } 282 | ~~~ 283 | 284 | Send an email: 285 | 286 | ~~~ go 287 | // Email a user 288 | err := email.SendEmail(email.ReadConfig().From, "This is the subject", "This is the body!") 289 | if err != nil { 290 | log.Println(err) 291 | sess.AddFlash(view.Flash{"An error occurred on the server. Please try again later.", view.FlashError}) 292 | sess.Save(r, w) 293 | return 294 | } 295 | ~~~ 296 | 297 | Validate a form if the Google reCAPTCHA is enabled in the config: 298 | 299 | ~~~ go 300 | // Validate with Google reCAPTCHA 301 | if !recaptcha.Verified(r) { 302 | sess.AddFlash(view.Flash{"reCAPTCHA invalid!", view.FlashError}) 303 | sess.Save(r, w) 304 | RegisterGET(w, r) 305 | return 306 | } 307 | ~~~ 308 | 309 | ## Database 310 | 311 | It's a good idea to abstract the database layer out so if you need to make 312 | changes, you don't have to look through business logic to find the queries. All 313 | the queries are stored in the models folder. 314 | 315 | This project supports BoltDB, MongoDB, and MySQL. All the queries are stored in 316 | the same files so you can easily change the database without modifying anything 317 | but the config file. 318 | 319 | The user.go and note.go files are at the root of the model directory and are a 320 | compliation of all the queries for each database type. There are a few hacks in 321 | the models to get the structs to work with all the supported databases. 322 | 323 | Connect to the database (only once needed in your application): 324 | 325 | ~~~ go 326 | // Connect to database 327 | database.Connect(config.Database) 328 | ~~~ 329 | 330 | Read from the database: 331 | 332 | ~~~ go 333 | result := User{} 334 | err := database.DB.Get(&result, "SELECT id, password, status_id, first_name FROM user WHERE email = ? LIMIT 1", email) 335 | return result, err 336 | ~~~ 337 | 338 | Write to the database: 339 | 340 | ~~~ go 341 | _, err := database.DB.Exec("INSERT INTO user (first_name, last_name, email, password) VALUES (?,?,?,?)", firstName, lastName, email, password) 342 | return err 343 | ~~~ 344 | 345 | ## Middleware 346 | 347 | There are a few pieces of middleware included. The package called csrfbanana 348 | protects against Cross-Site Request Forgery attacks and prevents double submits. 349 | The package httprouterwrapper provides helper functions to make funcs compatible 350 | with httprouter. The package logrequest will log every request made against the 351 | website to the console. The package pprofhandler enables pprof so it will work 352 | with httprouter. In route.go, all the individual routes use alice to make 353 | chaining very easy. 354 | 355 | ## Configuration 356 | 357 | To make the web app a little more flexible, you can make changes to different 358 | components in one place through the config.json file. If you want to add any 359 | of your own settings, you can add them to config.json and update the structs 360 | in gowebapp.go and the individual files so you can reference them in your code. 361 | This is config.json: 362 | 363 | ~~~ json 364 | { 365 | "Database": { 366 | "Type": "Bolt", 367 | "Bolt": { 368 | "Path": "gowebapp.db" 369 | }, 370 | "MongoDB": { 371 | "URL": "127.0.0.1", 372 | "Database": "gowebapp" 373 | }, 374 | "MySQL": { 375 | "Username": "root", 376 | "Password": "", 377 | "Name": "gowebapp", 378 | "Hostname": "127.0.0.1", 379 | "Port": 3306, 380 | "Parameter": "?parseTime=true" 381 | } 382 | }, 383 | "Email": { 384 | "Username": "", 385 | "Password": "", 386 | "Hostname": "", 387 | "Port": 25, 388 | "From": "" 389 | }, 390 | "Recaptcha": { 391 | "Enabled": false, 392 | "Secret": "", 393 | "SiteKey": "" 394 | }, 395 | "Server": { 396 | "Hostname": "", 397 | "UseHTTP": true, 398 | "UseHTTPS": false, 399 | "HTTPPort": 80, 400 | "HTTPSPort": 443, 401 | "CertFile": "tls/server.crt", 402 | "KeyFile": "tls/server.key" 403 | }, 404 | "Session": { 405 | "SecretKey": "@r4B?EThaSEh_drudR7P_hub=s#s2Pah", 406 | "Name": "gosess", 407 | "Options": { 408 | "Path": "/", 409 | "Domain": "", 410 | "MaxAge": 28800, 411 | "Secure": false, 412 | "HttpOnly": true 413 | } 414 | }, 415 | "Template": { 416 | "Root": "base", 417 | "Children": [ 418 | "partial/menu", 419 | "partial/footer" 420 | ] 421 | }, 422 | "View": { 423 | "BaseURI": "/", 424 | "Extension": "tmpl", 425 | "Folder": "template", 426 | "Name": "blank", 427 | "Caching": true 428 | } 429 | } 430 | ~~~ 431 | 432 | To enable HTTPS, set UseHTTPS to true, create a folder called tls in the root, 433 | and then place the certificate and key files in that folder. 434 | 435 | ## Screenshots 436 | 437 | Public Home: 438 | 439 | ![Image of Public Home](https://cloud.githubusercontent.com/assets/2394539/11319464/e2cd0eac-9045-11e5-9b24-5e480240cd69.jpg) 440 | 441 | About: 442 | 443 | ![Image of About](https://cloud.githubusercontent.com/assets/2394539/11319462/e2c4d2d2-9045-11e5-805f-8b40598c92c3.jpg) 444 | 445 | Register: 446 | 447 | ![Image of Register](https://cloud.githubusercontent.com/assets/2394539/11319466/e2d03500-9045-11e5-9c8e-c28fe663ed0f.jpg) 448 | 449 | Login: 450 | 451 | ![Image of Login](https://cloud.githubusercontent.com/assets/2394539/11319463/e2cd1a00-9045-11e5-8b8e-68030d870cbe.jpg) 452 | 453 | Authenticated Home: 454 | 455 | ![Image of Auth Home](https://cloud.githubusercontent.com/assets/2394539/14809208/75f340d2-0b59-11e6-8d2a-cd26ee872281.PNG) 456 | 457 | View Notes: 458 | 459 | ![Image of Notepad View](https://cloud.githubusercontent.com/assets/2394539/14809205/75f08432-0b59-11e6-8737-84ee796bd82e.PNG) 460 | 461 | Add Note: 462 | 463 | ![Image of Notepad Add](https://cloud.githubusercontent.com/assets/2394539/14809207/75f338f8-0b59-11e6-9719-61355957996c.PNG) 464 | 465 | Edit Note: 466 | 467 | ![Image of Notepad Edit](https://cloud.githubusercontent.com/assets/2394539/14809206/75f33970-0b59-11e6-8acf-b3d533477aac.PNG) 468 | 469 | ## Feedback 470 | 471 | All feedback is welcome. Let me know if you have any suggestions, questions, or criticisms. 472 | If something is not idiomatic to Go, please let me know know so we can make it better. 473 | -------------------------------------------------------------------------------- /static/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.5 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /static/js/underscore-min.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"underscore-min.js","sources":["underscore.js"],"names":["root","this","previousUnderscore","_","ArrayProto","Array","prototype","ObjProto","Object","FuncProto","Function","push","slice","concat","toString","hasOwnProperty","nativeIsArray","isArray","nativeKeys","keys","nativeBind","bind","obj","_wrapped","exports","module","VERSION","createCallback","func","context","argCount","value","call","other","index","collection","accumulator","apply","arguments","iteratee","identity","isFunction","isObject","matches","property","each","forEach","i","length","map","collect","currentKey","results","reduceError","reduce","foldl","inject","memo","TypeError","reduceRight","foldr","find","detect","predicate","result","some","list","filter","select","reject","negate","every","all","any","contains","include","target","values","indexOf","invoke","method","args","isFunc","pluck","key","where","attrs","findWhere","max","computed","Infinity","lastComputed","min","shuffle","rand","set","shuffled","random","sample","n","guard","Math","sortBy","criteria","sort","left","right","a","b","group","behavior","groupBy","has","indexBy","countBy","sortedIndex","array","low","high","mid","toArray","size","partition","pass","fail","first","head","take","initial","last","rest","tail","drop","compact","flatten","input","shallow","strict","output","isArguments","without","difference","uniq","unique","isSorted","isBoolean","seen","union","intersection","argsLength","item","j","zip","object","lastIndexOf","from","idx","range","start","stop","step","ceil","Ctor","bound","self","partial","boundArgs","position","bindAll","Error","memoize","hasher","cache","address","delay","wait","setTimeout","defer","throttle","options","timeout","previous","later","leading","now","remaining","clearTimeout","trailing","debounce","immediate","timestamp","callNow","wrap","wrapper","compose","after","times","before","once","pairs","invert","functions","methods","names","extend","source","prop","pick","omit","String","defaults","clone","tap","interceptor","eq","aStack","bStack","className","aCtor","constructor","bCtor","pop","isEqual","isEmpty","isString","isElement","nodeType","type","name","isFinite","isNaN","parseFloat","isNumber","isNull","isUndefined","noConflict","constant","noop","pair","accum","floor","Date","getTime","escapeMap","&","<",">","\"","'","`","unescapeMap","createEscaper","escaper","match","join","testRegexp","RegExp","replaceRegexp","string","test","replace","escape","unescape","idCounter","uniqueId","prefix","id","templateSettings","evaluate","interpolate","noMatch","escapes","\\","\r","\n","
","
","escapeChar","template","text","settings","oldSettings","matcher","offset","variable","render","e","data","argument","chain","instance","_chain","mixin","define","amd"],"mappings":";;;;CAKC,WAMC,GAAIA,GAAOC,KAGPC,EAAqBF,EAAKG,EAG1BC,EAAaC,MAAMC,UAAWC,EAAWC,OAAOF,UAAWG,EAAYC,SAASJ,UAIlFK,EAAmBP,EAAWO,KAC9BC,EAAmBR,EAAWQ,MAC9BC,EAAmBT,EAAWS,OAC9BC,EAAmBP,EAASO,SAC5BC,EAAmBR,EAASQ,eAK5BC,EAAqBX,MAAMY,QAC3BC,EAAqBV,OAAOW,KAC5BC,EAAqBX,EAAUY,KAG7BlB,EAAI,SAASmB,GACf,MAAIA,aAAenB,GAAUmB,EACvBrB,eAAgBE,QACtBF,KAAKsB,SAAWD,GADiB,GAAInB,GAAEmB,GAOlB,oBAAZE,UACa,mBAAXC,SAA0BA,OAAOD,UAC1CA,QAAUC,OAAOD,QAAUrB,GAE7BqB,QAAQrB,EAAIA,GAEZH,EAAKG,EAAIA,EAIXA,EAAEuB,QAAU,OAKZ,IAAIC,GAAiB,SAASC,EAAMC,EAASC,GAC3C,GAAID,QAAiB,GAAG,MAAOD,EAC/B,QAAoB,MAAZE,EAAmB,EAAIA,GAC7B,IAAK,GAAG,MAAO,UAASC,GACtB,MAAOH,GAAKI,KAAKH,EAASE,GAE5B,KAAK,GAAG,MAAO,UAASA,EAAOE,GAC7B,MAAOL,GAAKI,KAAKH,EAASE,EAAOE,GAEnC,KAAK,GAAG,MAAO,UAASF,EAAOG,EAAOC,GACpC,MAAOP,GAAKI,KAAKH,EAASE,EAAOG,EAAOC,GAE1C,KAAK,GAAG,MAAO,UAASC,EAAaL,EAAOG,EAAOC,GACjD,MAAOP,GAAKI,KAAKH,EAASO,EAAaL,EAAOG,EAAOC,IAGzD,MAAO,YACL,MAAOP,GAAKS,MAAMR,EAASS,YAO/BnC,GAAEoC,SAAW,SAASR,EAAOF,EAASC,GACpC,MAAa,OAATC,EAAsB5B,EAAEqC,SACxBrC,EAAEsC,WAAWV,GAAeJ,EAAeI,EAAOF,EAASC,GAC3D3B,EAAEuC,SAASX,GAAe5B,EAAEwC,QAAQZ,GACjC5B,EAAEyC,SAASb,IASpB5B,EAAE0C,KAAO1C,EAAE2C,QAAU,SAASxB,EAAKiB,EAAUV,GAC3C,GAAW,MAAPP,EAAa,MAAOA,EACxBiB,GAAWZ,EAAeY,EAAUV,EACpC,IAAIkB,GAAGC,EAAS1B,EAAI0B,MACpB,IAAIA,KAAYA,EACd,IAAKD,EAAI,EAAOC,EAAJD,EAAYA,IACtBR,EAASjB,EAAIyB,GAAIA,EAAGzB,OAEjB,CACL,GAAIH,GAAOhB,EAAEgB,KAAKG,EAClB,KAAKyB,EAAI,EAAGC,EAAS7B,EAAK6B,OAAYA,EAAJD,EAAYA,IAC5CR,EAASjB,EAAIH,EAAK4B,IAAK5B,EAAK4B,GAAIzB,GAGpC,MAAOA,IAITnB,EAAE8C,IAAM9C,EAAE+C,QAAU,SAAS5B,EAAKiB,EAAUV,GAC1C,GAAW,MAAPP,EAAa,QACjBiB,GAAWpC,EAAEoC,SAASA,EAAUV,EAKhC,KAAK,GADDsB,GAHAhC,EAAOG,EAAI0B,UAAY1B,EAAI0B,QAAU7C,EAAEgB,KAAKG,GAC5C0B,GAAU7B,GAAQG,GAAK0B,OACvBI,EAAU/C,MAAM2C,GAEXd,EAAQ,EAAWc,EAARd,EAAgBA,IAClCiB,EAAahC,EAAOA,EAAKe,GAASA,EAClCkB,EAAQlB,GAASK,EAASjB,EAAI6B,GAAaA,EAAY7B,EAEzD,OAAO8B,GAGT,IAAIC,GAAc,6CAIlBlD,GAAEmD,OAASnD,EAAEoD,MAAQpD,EAAEqD,OAAS,SAASlC,EAAKiB,EAAUkB,EAAM5B,GACjD,MAAPP,IAAaA,MACjBiB,EAAWZ,EAAeY,EAAUV,EAAS,EAC7C,IAEesB,GAFXhC,EAAOG,EAAI0B,UAAY1B,EAAI0B,QAAU7C,EAAEgB,KAAKG,GAC5C0B,GAAU7B,GAAQG,GAAK0B,OACvBd,EAAQ,CACZ,IAAII,UAAUU,OAAS,EAAG,CACxB,IAAKA,EAAQ,KAAM,IAAIU,WAAUL,EACjCI,GAAOnC,EAAIH,EAAOA,EAAKe,KAAWA,KAEpC,KAAec,EAARd,EAAgBA,IACrBiB,EAAahC,EAAOA,EAAKe,GAASA,EAClCuB,EAAOlB,EAASkB,EAAMnC,EAAI6B,GAAaA,EAAY7B,EAErD,OAAOmC,IAITtD,EAAEwD,YAAcxD,EAAEyD,MAAQ,SAAStC,EAAKiB,EAAUkB,EAAM5B,GAC3C,MAAPP,IAAaA,MACjBiB,EAAWZ,EAAeY,EAAUV,EAAS,EAC7C,IAEIsB,GAFAhC,EAAOG,EAAI0B,UAAa1B,EAAI0B,QAAU7C,EAAEgB,KAAKG,GAC7CY,GAASf,GAAQG,GAAK0B,MAE1B,IAAIV,UAAUU,OAAS,EAAG,CACxB,IAAKd,EAAO,KAAM,IAAIwB,WAAUL,EAChCI,GAAOnC,EAAIH,EAAOA,IAAOe,KAAWA,GAEtC,KAAOA,KACLiB,EAAahC,EAAOA,EAAKe,GAASA,EAClCuB,EAAOlB,EAASkB,EAAMnC,EAAI6B,GAAaA,EAAY7B,EAErD,OAAOmC,IAITtD,EAAE0D,KAAO1D,EAAE2D,OAAS,SAASxC,EAAKyC,EAAWlC,GAC3C,GAAImC,EAQJ,OAPAD,GAAY5D,EAAEoC,SAASwB,EAAWlC,GAClC1B,EAAE8D,KAAK3C,EAAK,SAASS,EAAOG,EAAOgC,GACjC,MAAIH,GAAUhC,EAAOG,EAAOgC,IAC1BF,EAASjC,GACF,GAFT,SAKKiC,GAKT7D,EAAEgE,OAAShE,EAAEiE,OAAS,SAAS9C,EAAKyC,EAAWlC,GAC7C,GAAIuB,KACJ,OAAW,OAAP9B,EAAoB8B,GACxBW,EAAY5D,EAAEoC,SAASwB,EAAWlC,GAClC1B,EAAE0C,KAAKvB,EAAK,SAASS,EAAOG,EAAOgC,GAC7BH,EAAUhC,EAAOG,EAAOgC,IAAOd,EAAQzC,KAAKoB,KAE3CqB,IAITjD,EAAEkE,OAAS,SAAS/C,EAAKyC,EAAWlC,GAClC,MAAO1B,GAAEgE,OAAO7C,EAAKnB,EAAEmE,OAAOnE,EAAEoC,SAASwB,IAAalC,IAKxD1B,EAAEoE,MAAQpE,EAAEqE,IAAM,SAASlD,EAAKyC,EAAWlC,GACzC,GAAW,MAAPP,EAAa,OAAO,CACxByC,GAAY5D,EAAEoC,SAASwB,EAAWlC,EAClC,IAEIK,GAAOiB,EAFPhC,EAAOG,EAAI0B,UAAY1B,EAAI0B,QAAU7C,EAAEgB,KAAKG,GAC5C0B,GAAU7B,GAAQG,GAAK0B,MAE3B,KAAKd,EAAQ,EAAWc,EAARd,EAAgBA,IAE9B,GADAiB,EAAahC,EAAOA,EAAKe,GAASA,GAC7B6B,EAAUzC,EAAI6B,GAAaA,EAAY7B,GAAM,OAAO,CAE3D,QAAO,GAKTnB,EAAE8D,KAAO9D,EAAEsE,IAAM,SAASnD,EAAKyC,EAAWlC,GACxC,GAAW,MAAPP,EAAa,OAAO,CACxByC,GAAY5D,EAAEoC,SAASwB,EAAWlC,EAClC,IAEIK,GAAOiB,EAFPhC,EAAOG,EAAI0B,UAAY1B,EAAI0B,QAAU7C,EAAEgB,KAAKG,GAC5C0B,GAAU7B,GAAQG,GAAK0B,MAE3B,KAAKd,EAAQ,EAAWc,EAARd,EAAgBA,IAE9B,GADAiB,EAAahC,EAAOA,EAAKe,GAASA,EAC9B6B,EAAUzC,EAAI6B,GAAaA,EAAY7B,GAAM,OAAO,CAE1D,QAAO,GAKTnB,EAAEuE,SAAWvE,EAAEwE,QAAU,SAASrD,EAAKsD,GACrC,MAAW,OAAPtD,GAAoB,GACpBA,EAAI0B,UAAY1B,EAAI0B,SAAQ1B,EAAMnB,EAAE0E,OAAOvD,IACxCnB,EAAE2E,QAAQxD,EAAKsD,IAAW,IAInCzE,EAAE4E,OAAS,SAASzD,EAAK0D,GACvB,GAAIC,GAAOrE,EAAMoB,KAAKM,UAAW,GAC7B4C,EAAS/E,EAAEsC,WAAWuC,EAC1B,OAAO7E,GAAE8C,IAAI3B,EAAK,SAASS,GACzB,OAAQmD,EAASF,EAASjD,EAAMiD,IAAS3C,MAAMN,EAAOkD,MAK1D9E,EAAEgF,MAAQ,SAAS7D,EAAK8D,GACtB,MAAOjF,GAAE8C,IAAI3B,EAAKnB,EAAEyC,SAASwC,KAK/BjF,EAAEkF,MAAQ,SAAS/D,EAAKgE,GACtB,MAAOnF,GAAEgE,OAAO7C,EAAKnB,EAAEwC,QAAQ2C,KAKjCnF,EAAEoF,UAAY,SAASjE,EAAKgE,GAC1B,MAAOnF,GAAE0D,KAAKvC,EAAKnB,EAAEwC,QAAQ2C,KAI/BnF,EAAEqF,IAAM,SAASlE,EAAKiB,EAAUV,GAC9B,GACIE,GAAO0D,EADPzB,GAAU0B,IAAUC,GAAgBD,GAExC,IAAgB,MAAZnD,GAA2B,MAAPjB,EAAa,CACnCA,EAAMA,EAAI0B,UAAY1B,EAAI0B,OAAS1B,EAAMnB,EAAE0E,OAAOvD,EAClD,KAAK,GAAIyB,GAAI,EAAGC,EAAS1B,EAAI0B,OAAYA,EAAJD,EAAYA,IAC/ChB,EAAQT,EAAIyB,GACRhB,EAAQiC,IACVA,EAASjC,OAIbQ,GAAWpC,EAAEoC,SAASA,EAAUV,GAChC1B,EAAE0C,KAAKvB,EAAK,SAASS,EAAOG,EAAOgC,GACjCuB,EAAWlD,EAASR,EAAOG,EAAOgC,IAC9BuB,EAAWE,GAAgBF,KAAcC,KAAY1B,KAAY0B,OACnE1B,EAASjC,EACT4D,EAAeF,IAIrB,OAAOzB,IAIT7D,EAAEyF,IAAM,SAAStE,EAAKiB,EAAUV,GAC9B,GACIE,GAAO0D,EADPzB,EAAS0B,IAAUC,EAAeD,GAEtC,IAAgB,MAAZnD,GAA2B,MAAPjB,EAAa,CACnCA,EAAMA,EAAI0B,UAAY1B,EAAI0B,OAAS1B,EAAMnB,EAAE0E,OAAOvD,EAClD,KAAK,GAAIyB,GAAI,EAAGC,EAAS1B,EAAI0B,OAAYA,EAAJD,EAAYA,IAC/ChB,EAAQT,EAAIyB,GACAiB,EAARjC,IACFiC,EAASjC,OAIbQ,GAAWpC,EAAEoC,SAASA,EAAUV,GAChC1B,EAAE0C,KAAKvB,EAAK,SAASS,EAAOG,EAAOgC,GACjCuB,EAAWlD,EAASR,EAAOG,EAAOgC,IACnByB,EAAXF,GAAwCC,MAAbD,GAAoCC,MAAX1B,KACtDA,EAASjC,EACT4D,EAAeF,IAIrB,OAAOzB,IAKT7D,EAAE0F,QAAU,SAASvE,GAInB,IAAK,GAAewE,GAHhBC,EAAMzE,GAAOA,EAAI0B,UAAY1B,EAAI0B,OAAS1B,EAAMnB,EAAE0E,OAAOvD,GACzD0B,EAAS+C,EAAI/C,OACbgD,EAAW3F,MAAM2C,GACZd,EAAQ,EAAiBc,EAARd,EAAgBA,IACxC4D,EAAO3F,EAAE8F,OAAO,EAAG/D,GACf4D,IAAS5D,IAAO8D,EAAS9D,GAAS8D,EAASF,IAC/CE,EAASF,GAAQC,EAAI7D,EAEvB,OAAO8D,IAMT7F,EAAE+F,OAAS,SAAS5E,EAAK6E,EAAGC,GAC1B,MAAS,OAALD,GAAaC,GACX9E,EAAI0B,UAAY1B,EAAI0B,SAAQ1B,EAAMnB,EAAE0E,OAAOvD,IACxCA,EAAInB,EAAE8F,OAAO3E,EAAI0B,OAAS,KAE5B7C,EAAE0F,QAAQvE,GAAKV,MAAM,EAAGyF,KAAKb,IAAI,EAAGW,KAI7ChG,EAAEmG,OAAS,SAAShF,EAAKiB,EAAUV,GAEjC,MADAU,GAAWpC,EAAEoC,SAASA,EAAUV,GACzB1B,EAAEgF,MAAMhF,EAAE8C,IAAI3B,EAAK,SAASS,EAAOG,EAAOgC,GAC/C,OACEnC,MAAOA,EACPG,MAAOA,EACPqE,SAAUhE,EAASR,EAAOG,EAAOgC,MAElCsC,KAAK,SAASC,EAAMC,GACrB,GAAIC,GAAIF,EAAKF,SACTK,EAAIF,EAAMH,QACd,IAAII,IAAMC,EAAG,CACX,GAAID,EAAIC,GAAKD,QAAW,GAAG,MAAO,EAClC,IAAQC,EAAJD,GAASC,QAAW,GAAG,OAAQ,EAErC,MAAOH,GAAKvE,MAAQwE,EAAMxE,QACxB,SAIN,IAAI2E,GAAQ,SAASC,GACnB,MAAO,UAASxF,EAAKiB,EAAUV,GAC7B,GAAImC,KAMJ,OALAzB,GAAWpC,EAAEoC,SAASA,EAAUV,GAChC1B,EAAE0C,KAAKvB,EAAK,SAASS,EAAOG,GAC1B,GAAIkD,GAAM7C,EAASR,EAAOG,EAAOZ,EACjCwF,GAAS9C,EAAQjC,EAAOqD,KAEnBpB,GAMX7D,GAAE4G,QAAUF,EAAM,SAAS7C,EAAQjC,EAAOqD,GACpCjF,EAAE6G,IAAIhD,EAAQoB,GAAMpB,EAAOoB,GAAKzE,KAAKoB,GAAaiC,EAAOoB,IAAQrD,KAKvE5B,EAAE8G,QAAUJ,EAAM,SAAS7C,EAAQjC,EAAOqD,GACxCpB,EAAOoB,GAAOrD,IAMhB5B,EAAE+G,QAAUL,EAAM,SAAS7C,EAAQjC,EAAOqD,GACpCjF,EAAE6G,IAAIhD,EAAQoB,GAAMpB,EAAOoB,KAAapB,EAAOoB,GAAO,IAK5DjF,EAAEgH,YAAc,SAASC,EAAO9F,EAAKiB,EAAUV,GAC7CU,EAAWpC,EAAEoC,SAASA,EAAUV,EAAS,EAGzC,KAFA,GAAIE,GAAQQ,EAASjB,GACjB+F,EAAM,EAAGC,EAAOF,EAAMpE,OACbsE,EAAND,GAAY,CACjB,GAAIE,GAAMF,EAAMC,IAAS,CACrB/E,GAAS6E,EAAMG,IAAQxF,EAAOsF,EAAME,EAAM,EAAQD,EAAOC,EAE/D,MAAOF,IAITlH,EAAEqH,QAAU,SAASlG,GACnB,MAAKA,GACDnB,EAAEc,QAAQK,GAAaV,EAAMoB,KAAKV,GAClCA,EAAI0B,UAAY1B,EAAI0B,OAAe7C,EAAE8C,IAAI3B,EAAKnB,EAAEqC,UAC7CrC,EAAE0E,OAAOvD,OAIlBnB,EAAEsH,KAAO,SAASnG,GAChB,MAAW,OAAPA,EAAoB,EACjBA,EAAI0B,UAAY1B,EAAI0B,OAAS1B,EAAI0B,OAAS7C,EAAEgB,KAAKG,GAAK0B,QAK/D7C,EAAEuH,UAAY,SAASpG,EAAKyC,EAAWlC,GACrCkC,EAAY5D,EAAEoC,SAASwB,EAAWlC,EAClC,IAAI8F,MAAWC,IAIf,OAHAzH,GAAE0C,KAAKvB,EAAK,SAASS,EAAOqD,EAAK9D,IAC9ByC,EAAUhC,EAAOqD,EAAK9D,GAAOqG,EAAOC,GAAMjH,KAAKoB,MAE1C4F,EAAMC,IAShBzH,EAAE0H,MAAQ1H,EAAE2H,KAAO3H,EAAE4H,KAAO,SAASX,EAAOjB,EAAGC,GAC7C,MAAa,OAATgB,MAA2B,GACtB,MAALjB,GAAaC,EAAcgB,EAAM,GAC7B,EAAJjB,KACGvF,EAAMoB,KAAKoF,EAAO,EAAGjB,IAO9BhG,EAAE6H,QAAU,SAASZ,EAAOjB,EAAGC,GAC7B,MAAOxF,GAAMoB,KAAKoF,EAAO,EAAGf,KAAKb,IAAI,EAAG4B,EAAMpE,QAAe,MAALmD,GAAaC,EAAQ,EAAID,MAKnFhG,EAAE8H,KAAO,SAASb,EAAOjB,EAAGC,GAC1B,MAAa,OAATgB,MAA2B,GACtB,MAALjB,GAAaC,EAAcgB,EAAMA,EAAMpE,OAAS,GAC7CpC,EAAMoB,KAAKoF,EAAOf,KAAKb,IAAI4B,EAAMpE,OAASmD,EAAG,KAOtDhG,EAAE+H,KAAO/H,EAAEgI,KAAOhI,EAAEiI,KAAO,SAAShB,EAAOjB,EAAGC,GAC5C,MAAOxF,GAAMoB,KAAKoF,EAAY,MAALjB,GAAaC,EAAQ,EAAID,IAIpDhG,EAAEkI,QAAU,SAASjB,GACnB,MAAOjH,GAAEgE,OAAOiD,EAAOjH,EAAEqC,UAI3B,IAAI8F,GAAU,SAASC,EAAOC,EAASC,EAAQC,GAC7C,GAAIF,GAAWrI,EAAEoE,MAAMgE,EAAOpI,EAAEc,SAC9B,MAAOJ,GAAOwB,MAAMqG,EAAQH,EAE9B,KAAK,GAAIxF,GAAI,EAAGC,EAASuF,EAAMvF,OAAYA,EAAJD,EAAYA,IAAK,CACtD,GAAIhB,GAAQwG,EAAMxF,EACb5C,GAAEc,QAAQc,IAAW5B,EAAEwI,YAAY5G,GAE7ByG,EACT7H,EAAK0B,MAAMqG,EAAQ3G,GAEnBuG,EAAQvG,EAAOyG,EAASC,EAAQC,GAJ3BD,GAAQC,EAAO/H,KAAKoB,GAO7B,MAAO2G,GAITvI,GAAEmI,QAAU,SAASlB,EAAOoB,GAC1B,MAAOF,GAAQlB,EAAOoB,GAAS,OAIjCrI,EAAEyI,QAAU,SAASxB,GACnB,MAAOjH,GAAE0I,WAAWzB,EAAOxG,EAAMoB,KAAKM,UAAW,KAMnDnC,EAAE2I,KAAO3I,EAAE4I,OAAS,SAAS3B,EAAO4B,EAAUzG,EAAUV,GACtD,GAAa,MAATuF,EAAe,QACdjH,GAAE8I,UAAUD,KACfnH,EAAUU,EACVA,EAAWyG,EACXA,GAAW,GAEG,MAAZzG,IAAkBA,EAAWpC,EAAEoC,SAASA,EAAUV,GAGtD,KAAK,GAFDmC,MACAkF,KACKnG,EAAI,EAAGC,EAASoE,EAAMpE,OAAYA,EAAJD,EAAYA,IAAK,CACtD,GAAIhB,GAAQqF,EAAMrE,EAClB,IAAIiG,EACGjG,GAAKmG,IAASnH,GAAOiC,EAAOrD,KAAKoB,GACtCmH,EAAOnH,MACF,IAAIQ,EAAU,CACnB,GAAIkD,GAAWlD,EAASR,EAAOgB,EAAGqE,EAC9BjH,GAAE2E,QAAQoE,EAAMzD,GAAY,IAC9ByD,EAAKvI,KAAK8E,GACVzB,EAAOrD,KAAKoB,QAEL5B,GAAE2E,QAAQd,EAAQjC,GAAS,GACpCiC,EAAOrD,KAAKoB,GAGhB,MAAOiC,IAKT7D,EAAEgJ,MAAQ,WACR,MAAOhJ,GAAE2I,KAAKR,EAAQhG,WAAW,GAAM,QAKzCnC,EAAEiJ,aAAe,SAAShC,GACxB,GAAa,MAATA,EAAe,QAGnB,KAAK,GAFDpD,MACAqF,EAAa/G,UAAUU,OAClBD,EAAI,EAAGC,EAASoE,EAAMpE,OAAYA,EAAJD,EAAYA,IAAK,CACtD,GAAIuG,GAAOlC,EAAMrE,EACjB,KAAI5C,EAAEuE,SAASV,EAAQsF,GAAvB,CACA,IAAK,GAAIC,GAAI,EAAOF,EAAJE,GACTpJ,EAAEuE,SAASpC,UAAUiH,GAAID,GADAC,KAG5BA,IAAMF,GAAYrF,EAAOrD,KAAK2I,IAEpC,MAAOtF,IAKT7D,EAAE0I,WAAa,SAASzB,GACtB,GAAIc,GAAOI,EAAQ1H,EAAMoB,KAAKM,UAAW,IAAI,GAAM,KACnD,OAAOnC,GAAEgE,OAAOiD,EAAO,SAASrF,GAC9B,OAAQ5B,EAAEuE,SAASwD,EAAMnG,MAM7B5B,EAAEqJ,IAAM,SAASpC,GACf,GAAa,MAATA,EAAe,QAGnB,KAAK,GAFDpE,GAAS7C,EAAEqF,IAAIlD,UAAW,UAAUU,OACpCI,EAAU/C,MAAM2C,GACXD,EAAI,EAAOC,EAAJD,EAAYA,IAC1BK,EAAQL,GAAK5C,EAAEgF,MAAM7C,UAAWS,EAElC,OAAOK,IAMTjD,EAAEsJ,OAAS,SAASvF,EAAMW,GACxB,GAAY,MAARX,EAAc,QAElB,KAAK,GADDF,MACKjB,EAAI,EAAGC,EAASkB,EAAKlB,OAAYA,EAAJD,EAAYA,IAC5C8B,EACFb,EAAOE,EAAKnB,IAAM8B,EAAO9B,GAEzBiB,EAAOE,EAAKnB,GAAG,IAAMmB,EAAKnB,GAAG,EAGjC,OAAOiB,IAOT7D,EAAE2E,QAAU,SAASsC,EAAOkC,EAAMN,GAChC,GAAa,MAAT5B,EAAe,OAAQ,CAC3B,IAAIrE,GAAI,EAAGC,EAASoE,EAAMpE,MAC1B,IAAIgG,EAAU,CACZ,GAAuB,gBAAZA,GAIT,MADAjG,GAAI5C,EAAEgH,YAAYC,EAAOkC,GAClBlC,EAAMrE,KAAOuG,EAAOvG,GAAK,CAHhCA,GAAe,EAAXiG,EAAe3C,KAAKb,IAAI,EAAGxC,EAASgG,GAAYA,EAMxD,KAAWhG,EAAJD,EAAYA,IAAK,GAAIqE,EAAMrE,KAAOuG,EAAM,MAAOvG,EACtD,QAAQ,GAGV5C,EAAEuJ,YAAc,SAAStC,EAAOkC,EAAMK,GACpC,GAAa,MAATvC,EAAe,OAAQ,CAC3B,IAAIwC,GAAMxC,EAAMpE,MAIhB,KAHmB,gBAAR2G,KACTC,EAAa,EAAPD,EAAWC,EAAMD,EAAO,EAAItD,KAAKT,IAAIgE,EAAKD,EAAO,MAEhDC,GAAO,GAAG,GAAIxC,EAAMwC,KAASN,EAAM,MAAOM,EACnD,QAAQ,GAMVzJ,EAAE0J,MAAQ,SAASC,EAAOC,EAAMC,GAC1B1H,UAAUU,QAAU,IACtB+G,EAAOD,GAAS,EAChBA,EAAQ,GAEVE,EAAOA,GAAQ,CAKf,KAAK,GAHDhH,GAASqD,KAAKb,IAAIa,KAAK4D,MAAMF,EAAOD,GAASE,GAAO,GACpDH,EAAQxJ,MAAM2C,GAET4G,EAAM,EAAS5G,EAAN4G,EAAcA,IAAOE,GAASE,EAC9CH,EAAMD,GAAOE,CAGf,OAAOD,GAOT,IAAIK,GAAO,YAKX/J,GAAEkB,KAAO,SAASO,EAAMC,GACtB,GAAIoD,GAAMkF,CACV,IAAI/I,GAAcQ,EAAKP,OAASD,EAAY,MAAOA,GAAWiB,MAAMT,EAAMhB,EAAMoB,KAAKM,UAAW,GAChG,KAAKnC,EAAEsC,WAAWb,GAAO,KAAM,IAAI8B,WAAU,oCAW7C,OAVAuB,GAAOrE,EAAMoB,KAAKM,UAAW,GAC7B6H,EAAQ,WACN,KAAMlK,eAAgBkK,IAAQ,MAAOvI,GAAKS,MAAMR,EAASoD,EAAKpE,OAAOD,EAAMoB,KAAKM,YAChF4H,GAAK5J,UAAYsB,EAAKtB,SACtB,IAAI8J,GAAO,GAAIF,EACfA,GAAK5J,UAAY,IACjB,IAAI0D,GAASpC,EAAKS,MAAM+H,EAAMnF,EAAKpE,OAAOD,EAAMoB,KAAKM,YACrD,OAAInC,GAAEuC,SAASsB,GAAgBA,EACxBoG,IAQXjK,EAAEkK,QAAU,SAASzI,GACnB,GAAI0I,GAAY1J,EAAMoB,KAAKM,UAAW,EACtC,OAAO,YAGL,IAAK,GAFDiI,GAAW,EACXtF,EAAOqF,EAAU1J,QACZmC,EAAI,EAAGC,EAASiC,EAAKjC,OAAYA,EAAJD,EAAYA,IAC5CkC,EAAKlC,KAAO5C,IAAG8E,EAAKlC,GAAKT,UAAUiI,KAEzC,MAAOA,EAAWjI,UAAUU,QAAQiC,EAAKtE,KAAK2B,UAAUiI,KACxD,OAAO3I,GAAKS,MAAMpC,KAAMgF,KAO5B9E,EAAEqK,QAAU,SAASlJ,GACnB,GAAIyB,GAA8BqC,EAA3BpC,EAASV,UAAUU,MAC1B,IAAc,GAAVA,EAAa,KAAM,IAAIyH,OAAM,wCACjC,KAAK1H,EAAI,EAAOC,EAAJD,EAAYA,IACtBqC,EAAM9C,UAAUS,GAChBzB,EAAI8D,GAAOjF,EAAEkB,KAAKC,EAAI8D,GAAM9D,EAE9B,OAAOA,IAITnB,EAAEuK,QAAU,SAAS9I,EAAM+I,GACzB,GAAID,GAAU,SAAStF,GACrB,GAAIwF,GAAQF,EAAQE,MAChBC,EAAUF,EAASA,EAAOtI,MAAMpC,KAAMqC,WAAa8C,CAEvD,OADKjF,GAAE6G,IAAI4D,EAAOC,KAAUD,EAAMC,GAAWjJ,EAAKS,MAAMpC,KAAMqC,YACvDsI,EAAMC,GAGf,OADAH,GAAQE,SACDF,GAKTvK,EAAE2K,MAAQ,SAASlJ,EAAMmJ,GACvB,GAAI9F,GAAOrE,EAAMoB,KAAKM,UAAW,EACjC,OAAO0I,YAAW,WAChB,MAAOpJ,GAAKS,MAAM,KAAM4C,IACvB8F,IAKL5K,EAAE8K,MAAQ,SAASrJ,GACjB,MAAOzB,GAAE2K,MAAMzI,MAAMlC,GAAIyB,EAAM,GAAGf,OAAOD,EAAMoB,KAAKM,UAAW,MAQjEnC,EAAE+K,SAAW,SAAStJ,EAAMmJ,EAAMI,GAChC,GAAItJ,GAASoD,EAAMjB,EACfoH,EAAU,KACVC,EAAW,CACVF,KAASA,KACd,IAAIG,GAAQ,WACVD,EAAWF,EAAQI,WAAY,EAAQ,EAAIpL,EAAEqL,MAC7CJ,EAAU,KACVpH,EAASpC,EAAKS,MAAMR,EAASoD,GACxBmG,IAASvJ,EAAUoD,EAAO,MAEjC,OAAO,YACL,GAAIuG,GAAMrL,EAAEqL,KACPH,IAAYF,EAAQI,WAAY,IAAOF,EAAWG,EACvD,IAAIC,GAAYV,GAAQS,EAAMH,EAY9B,OAXAxJ,GAAU5B,KACVgF,EAAO3C,UACU,GAAbmJ,GAAkBA,EAAYV,GAChCW,aAAaN,GACbA,EAAU,KACVC,EAAWG,EACXxH,EAASpC,EAAKS,MAAMR,EAASoD,GACxBmG,IAASvJ,EAAUoD,EAAO,OACrBmG,GAAWD,EAAQQ,YAAa,IAC1CP,EAAUJ,WAAWM,EAAOG,IAEvBzH,IAQX7D,EAAEyL,SAAW,SAAShK,EAAMmJ,EAAMc,GAChC,GAAIT,GAASnG,EAAMpD,EAASiK,EAAW9H,EAEnCsH,EAAQ,WACV,GAAIrD,GAAO9H,EAAEqL,MAAQM,CAEVf,GAAP9C,GAAeA,EAAO,EACxBmD,EAAUJ,WAAWM,EAAOP,EAAO9C,IAEnCmD,EAAU,KACLS,IACH7H,EAASpC,EAAKS,MAAMR,EAASoD,GACxBmG,IAASvJ,EAAUoD,EAAO,QAKrC,OAAO,YACLpD,EAAU5B,KACVgF,EAAO3C,UACPwJ,EAAY3L,EAAEqL,KACd,IAAIO,GAAUF,IAAcT,CAO5B,OANKA,KAASA,EAAUJ,WAAWM,EAAOP,IACtCgB,IACF/H,EAASpC,EAAKS,MAAMR,EAASoD,GAC7BpD,EAAUoD,EAAO,MAGZjB,IAOX7D,EAAE6L,KAAO,SAASpK,EAAMqK,GACtB,MAAO9L,GAAEkK,QAAQ4B,EAASrK,IAI5BzB,EAAEmE,OAAS,SAASP,GAClB,MAAO,YACL,OAAQA,EAAU1B,MAAMpC,KAAMqC,aAMlCnC,EAAE+L,QAAU,WACV,GAAIjH,GAAO3C,UACPwH,EAAQ7E,EAAKjC,OAAS,CAC1B,OAAO,YAGL,IAFA,GAAID,GAAI+G,EACJ9F,EAASiB,EAAK6E,GAAOzH,MAAMpC,KAAMqC,WAC9BS,KAAKiB,EAASiB,EAAKlC,GAAGf,KAAK/B,KAAM+D,EACxC,OAAOA,KAKX7D,EAAEgM,MAAQ,SAASC,EAAOxK,GACxB,MAAO,YACL,QAAMwK,EAAQ,EACLxK,EAAKS,MAAMpC,KAAMqC,WAD1B,SAOJnC,EAAEkM,OAAS,SAASD,EAAOxK,GACzB,GAAI6B,EACJ,OAAO,YAML,QALM2I,EAAQ,EACZ3I,EAAO7B,EAAKS,MAAMpC,KAAMqC,WAExBV,EAAO,KAEF6B,IAMXtD,EAAEmM,KAAOnM,EAAEkK,QAAQlK,EAAEkM,OAAQ,GAO7BlM,EAAEgB,KAAO,SAASG,GAChB,IAAKnB,EAAEuC,SAASpB,GAAM,QACtB,IAAIJ,EAAY,MAAOA,GAAWI,EAClC,IAAIH,KACJ,KAAK,GAAIiE,KAAO9D,GAASnB,EAAE6G,IAAI1F,EAAK8D,IAAMjE,EAAKR,KAAKyE,EACpD,OAAOjE,IAIThB,EAAE0E,OAAS,SAASvD,GAIlB,IAAK,GAHDH,GAAOhB,EAAEgB,KAAKG,GACd0B,EAAS7B,EAAK6B,OACd6B,EAASxE,MAAM2C,GACVD,EAAI,EAAOC,EAAJD,EAAYA,IAC1B8B,EAAO9B,GAAKzB,EAAIH,EAAK4B,GAEvB,OAAO8B,IAIT1E,EAAEoM,MAAQ,SAASjL,GAIjB,IAAK,GAHDH,GAAOhB,EAAEgB,KAAKG,GACd0B,EAAS7B,EAAK6B,OACduJ,EAAQlM,MAAM2C,GACTD,EAAI,EAAOC,EAAJD,EAAYA,IAC1BwJ,EAAMxJ,IAAM5B,EAAK4B,GAAIzB,EAAIH,EAAK4B,IAEhC,OAAOwJ,IAITpM,EAAEqM,OAAS,SAASlL,GAGlB,IAAK,GAFD0C,MACA7C,EAAOhB,EAAEgB,KAAKG,GACTyB,EAAI,EAAGC,EAAS7B,EAAK6B,OAAYA,EAAJD,EAAYA,IAChDiB,EAAO1C,EAAIH,EAAK4B,KAAO5B,EAAK4B,EAE9B,OAAOiB,IAKT7D,EAAEsM,UAAYtM,EAAEuM,QAAU,SAASpL,GACjC,GAAIqL,KACJ,KAAK,GAAIvH,KAAO9D,GACVnB,EAAEsC,WAAWnB,EAAI8D,KAAOuH,EAAMhM,KAAKyE,EAEzC,OAAOuH,GAAMnG,QAIfrG,EAAEyM,OAAS,SAAStL,GAClB,IAAKnB,EAAEuC,SAASpB,GAAM,MAAOA,EAE7B,KAAK,GADDuL,GAAQC,EACH/J,EAAI,EAAGC,EAASV,UAAUU,OAAYA,EAAJD,EAAYA,IAAK,CAC1D8J,EAASvK,UAAUS,EACnB,KAAK+J,IAAQD,GACP9L,EAAeiB,KAAK6K,EAAQC,KAC5BxL,EAAIwL,GAAQD,EAAOC,IAI3B,MAAOxL,IAITnB,EAAE4M,KAAO,SAASzL,EAAKiB,EAAUV,GAC/B,GAAiBuD,GAAbpB,IACJ,IAAW,MAAP1C,EAAa,MAAO0C,EACxB,IAAI7D,EAAEsC,WAAWF,GAAW,CAC1BA,EAAWZ,EAAeY,EAAUV,EACpC,KAAKuD,IAAO9D,GAAK,CACf,GAAIS,GAAQT,EAAI8D,EACZ7C,GAASR,EAAOqD,EAAK9D,KAAM0C,EAAOoB,GAAOrD,QAE1C,CACL,GAAIZ,GAAON,EAAOwB,SAAUzB,EAAMoB,KAAKM,UAAW,GAClDhB,GAAM,GAAId,QAAOc,EACjB,KAAK,GAAIyB,GAAI,EAAGC,EAAS7B,EAAK6B,OAAYA,EAAJD,EAAYA,IAChDqC,EAAMjE,EAAK4B,GACPqC,IAAO9D,KAAK0C,EAAOoB,GAAO9D,EAAI8D,IAGtC,MAAOpB,IAIT7D,EAAE6M,KAAO,SAAS1L,EAAKiB,EAAUV,GAC/B,GAAI1B,EAAEsC,WAAWF,GACfA,EAAWpC,EAAEmE,OAAO/B,OACf,CACL,GAAIpB,GAAOhB,EAAE8C,IAAIpC,EAAOwB,SAAUzB,EAAMoB,KAAKM,UAAW,IAAK2K,OAC7D1K,GAAW,SAASR,EAAOqD,GACzB,OAAQjF,EAAEuE,SAASvD,EAAMiE,IAG7B,MAAOjF,GAAE4M,KAAKzL,EAAKiB,EAAUV,IAI/B1B,EAAE+M,SAAW,SAAS5L,GACpB,IAAKnB,EAAEuC,SAASpB,GAAM,MAAOA,EAC7B,KAAK,GAAIyB,GAAI,EAAGC,EAASV,UAAUU,OAAYA,EAAJD,EAAYA,IAAK,CAC1D,GAAI8J,GAASvK,UAAUS,EACvB,KAAK,GAAI+J,KAAQD,GACXvL,EAAIwL,SAAe,KAAGxL,EAAIwL,GAAQD,EAAOC,IAGjD,MAAOxL,IAITnB,EAAEgN,MAAQ,SAAS7L,GACjB,MAAKnB,GAAEuC,SAASpB,GACTnB,EAAEc,QAAQK,GAAOA,EAAIV,QAAUT,EAAEyM,UAAWtL,GADtBA,GAO/BnB,EAAEiN,IAAM,SAAS9L,EAAK+L,GAEpB,MADAA,GAAY/L,GACLA,EAIT,IAAIgM,GAAK,SAAS3G,EAAGC,EAAG2G,EAAQC,GAG9B,GAAI7G,IAAMC,EAAG,MAAa,KAAND,GAAW,EAAIA,IAAM,EAAIC,CAE7C,IAAS,MAALD,GAAkB,MAALC,EAAW,MAAOD,KAAMC,CAErCD,aAAaxG,KAAGwG,EAAIA,EAAEpF,UACtBqF,YAAazG,KAAGyG,EAAIA,EAAErF,SAE1B,IAAIkM,GAAY3M,EAASkB,KAAK2E,EAC9B,IAAI8G,IAAc3M,EAASkB,KAAK4E,GAAI,OAAO,CAC3C,QAAQ6G,GAEN,IAAK,kBAEL,IAAK,kBAGH,MAAO,GAAK9G,GAAM,GAAKC,CACzB,KAAK,kBAGH,OAAKD,KAAOA,GAAWC,KAAOA,EAEhB,KAAND,EAAU,GAAKA,IAAM,EAAIC,GAAKD,KAAOC,CAC/C,KAAK,gBACL,IAAK,mBAIH,OAAQD,KAAOC,EAEnB,GAAgB,gBAALD,IAA6B,gBAALC,GAAe,OAAO,CAIzD,KADA,GAAI5D,GAASuK,EAAOvK,OACbA,KAGL,GAAIuK,EAAOvK,KAAY2D,EAAG,MAAO6G,GAAOxK,KAAY4D,CAItD,IAAI8G,GAAQ/G,EAAEgH,YAAaC,EAAQhH,EAAE+G,WACrC,IACED,IAAUE,GAEV,eAAiBjH,IAAK,eAAiBC,MACrCzG,EAAEsC,WAAWiL,IAAUA,YAAiBA,IACxCvN,EAAEsC,WAAWmL,IAAUA,YAAiBA,IAE1C,OAAO,CAGTL,GAAO5M,KAAKgG,GACZ6G,EAAO7M,KAAKiG,EACZ,IAAIa,GAAMzD,CAEV,IAAkB,mBAAdyJ,GAIF,GAFAhG,EAAOd,EAAE3D,OACTgB,EAASyD,IAASb,EAAE5D,OAGlB,KAAOyE,MACCzD,EAASsJ,EAAG3G,EAAEc,GAAOb,EAAEa,GAAO8F,EAAQC,WAG3C,CAEL,GAAsBpI,GAAlBjE,EAAOhB,EAAEgB,KAAKwF,EAIlB,IAHAc,EAAOtG,EAAK6B,OAEZgB,EAAS7D,EAAEgB,KAAKyF,GAAG5D,SAAWyE,EAE5B,KAAOA,MAELrC,EAAMjE,EAAKsG,GACLzD,EAAS7D,EAAE6G,IAAIJ,EAAGxB,IAAQkI,EAAG3G,EAAEvB,GAAMwB,EAAExB,GAAMmI,EAAQC,OAOjE,MAFAD,GAAOM,MACPL,EAAOK,MACA7J,EAIT7D,GAAE2N,QAAU,SAASnH,EAAGC,GACtB,MAAO0G,GAAG3G,EAAGC,UAKfzG,EAAE4N,QAAU,SAASzM,GACnB,GAAW,MAAPA,EAAa,OAAO,CACxB,IAAInB,EAAEc,QAAQK,IAAQnB,EAAE6N,SAAS1M,IAAQnB,EAAEwI,YAAYrH,GAAM,MAAsB,KAAfA,EAAI0B,MACxE,KAAK,GAAIoC,KAAO9D,GAAK,GAAInB,EAAE6G,IAAI1F,EAAK8D,GAAM,OAAO,CACjD,QAAO,GAITjF,EAAE8N,UAAY,SAAS3M,GACrB,SAAUA,GAAwB,IAAjBA,EAAI4M,WAKvB/N,EAAEc,QAAUD,GAAiB,SAASM,GACpC,MAA8B,mBAAvBR,EAASkB,KAAKV,IAIvBnB,EAAEuC,SAAW,SAASpB,GACpB,GAAI6M,SAAc7M,EAClB,OAAgB,aAAT6M,GAAgC,WAATA,KAAuB7M,GAIvDnB,EAAE0C,MAAM,YAAa,WAAY,SAAU,SAAU,OAAQ,UAAW,SAASuL,GAC/EjO,EAAE,KAAOiO,GAAQ,SAAS9M,GACxB,MAAOR,GAASkB,KAAKV,KAAS,WAAa8M,EAAO,OAMjDjO,EAAEwI,YAAYrG,aACjBnC,EAAEwI,YAAc,SAASrH,GACvB,MAAOnB,GAAE6G,IAAI1F,EAAK,YAKH,kBAAR,MACTnB,EAAEsC,WAAa,SAASnB,GACtB,MAAqB,kBAAPA,KAAqB,IAKvCnB,EAAEkO,SAAW,SAAS/M,GACpB,MAAO+M,UAAS/M,KAASgN,MAAMC,WAAWjN,KAI5CnB,EAAEmO,MAAQ,SAAShN,GACjB,MAAOnB,GAAEqO,SAASlN,IAAQA,KAASA,GAIrCnB,EAAE8I,UAAY,SAAS3H,GACrB,MAAOA,MAAQ,GAAQA,KAAQ,GAAgC,qBAAvBR,EAASkB,KAAKV,IAIxDnB,EAAEsO,OAAS,SAASnN,GAClB,MAAe,QAARA,GAITnB,EAAEuO,YAAc,SAASpN,GACvB,MAAOA,SAAa,IAKtBnB,EAAE6G,IAAM,SAAS1F,EAAK8D,GACpB,MAAc,OAAP9D,GAAeP,EAAeiB,KAAKV,EAAK8D,IAQjDjF,EAAEwO,WAAa,WAEb,MADA3O,GAAKG,EAAID,EACFD,MAITE,EAAEqC,SAAW,SAAST,GACpB,MAAOA,IAGT5B,EAAEyO,SAAW,SAAS7M,GACpB,MAAO,YACL,MAAOA,KAIX5B,EAAE0O,KAAO,aAET1O,EAAEyC,SAAW,SAASwC,GACpB,MAAO,UAAS9D,GACd,MAAOA,GAAI8D,KAKfjF,EAAEwC,QAAU,SAAS2C,GACnB,GAAIiH,GAAQpM,EAAEoM,MAAMjH,GAAQtC,EAASuJ,EAAMvJ,MAC3C,OAAO,UAAS1B,GACd,GAAW,MAAPA,EAAa,OAAQ0B,CACzB1B,GAAM,GAAId,QAAOc,EACjB,KAAK,GAAIyB,GAAI,EAAOC,EAAJD,EAAYA,IAAK,CAC/B,GAAI+L,GAAOvC,EAAMxJ,GAAIqC,EAAM0J,EAAK,EAChC,IAAIA,EAAK,KAAOxN,EAAI8D,MAAUA,IAAO9D,IAAM,OAAO,EAEpD,OAAO,IAKXnB,EAAEiM,MAAQ,SAASjG,EAAG5D,EAAUV,GAC9B,GAAIkN,GAAQ1O,MAAMgG,KAAKb,IAAI,EAAGW,GAC9B5D,GAAWZ,EAAeY,EAAUV,EAAS,EAC7C,KAAK,GAAIkB,GAAI,EAAOoD,EAAJpD,EAAOA,IAAKgM,EAAMhM,GAAKR,EAASQ,EAChD,OAAOgM,IAIT5O,EAAE8F,OAAS,SAASL,EAAKJ,GAKvB,MAJW,OAAPA,IACFA,EAAMI,EACNA,EAAM,GAEDA,EAAMS,KAAK2I,MAAM3I,KAAKJ,UAAYT,EAAMI,EAAM,KAIvDzF,EAAEqL,IAAMyD,KAAKzD,KAAO,WAClB,OAAO,GAAIyD,OAAOC,UAIpB,IAAIC,IACFC,IAAK,QACLC,IAAK,OACLC,IAAK,OACLC,IAAK,SACLC,IAAK,SACLC,IAAK,UAEHC,EAAcvP,EAAEqM,OAAO2C,GAGvBQ,EAAgB,SAAS1M,GAC3B,GAAI2M,GAAU,SAASC,GACrB,MAAO5M,GAAI4M,IAGThD,EAAS,MAAQ1M,EAAEgB,KAAK8B,GAAK6M,KAAK,KAAO,IACzCC,EAAaC,OAAOnD,GACpBoD,EAAgBD,OAAOnD,EAAQ,IACnC,OAAO,UAASqD,GAEd,MADAA,GAAmB,MAAVA,EAAiB,GAAK,GAAKA,EAC7BH,EAAWI,KAAKD,GAAUA,EAAOE,QAAQH,EAAeL,GAAWM,GAG9E/P,GAAEkQ,OAASV,EAAcR,GACzBhP,EAAEmQ,SAAWX,EAAcD,GAI3BvP,EAAE6D,OAAS,SAASyF,EAAQ7G,GAC1B,GAAc,MAAV6G,EAAgB,WAAY,EAChC,IAAI1H,GAAQ0H,EAAO7G,EACnB,OAAOzC,GAAEsC,WAAWV,GAAS0H,EAAO7G,KAAcb,EAKpD,IAAIwO,GAAY,CAChBpQ,GAAEqQ,SAAW,SAASC,GACpB,GAAIC,KAAOH,EAAY,EACvB,OAAOE,GAASA,EAASC,EAAKA,GAKhCvQ,EAAEwQ,kBACAC,SAAc,kBACdC,YAAc,mBACdR,OAAc,mBAMhB,IAAIS,GAAU,OAIVC,GACFvB,IAAU,IACVwB,KAAU,KACVC,KAAU,IACVC,KAAU,IACVC,SAAU,QACVC,SAAU,SAGRxB,EAAU,4BAEVyB,EAAa,SAASxB,GACxB,MAAO,KAAOkB,EAAQlB,GAOxB1P,GAAEmR,SAAW,SAASC,EAAMC,EAAUC,IAC/BD,GAAYC,IAAaD,EAAWC,GACzCD,EAAWrR,EAAE+M,YAAasE,EAAUrR,EAAEwQ,iBAGtC,IAAIe,GAAU1B,SACXwB,EAASnB,QAAUS,GAASjE,QAC5B2E,EAASX,aAAeC,GAASjE,QACjC2E,EAASZ,UAAYE,GAASjE,QAC/BiD,KAAK,KAAO,KAAM,KAGhB5N,EAAQ,EACR2K,EAAS,QACb0E,GAAKnB,QAAQsB,EAAS,SAAS7B,EAAOQ,EAAQQ,EAAaD,EAAUe,GAanE,MAZA9E,IAAU0E,EAAK3Q,MAAMsB,EAAOyP,GAAQvB,QAAQR,EAASyB,GACrDnP,EAAQyP,EAAS9B,EAAM7M,OAEnBqN,EACFxD,GAAU,cAAgBwD,EAAS,iCAC1BQ,EACThE,GAAU,cAAgBgE,EAAc,uBAC/BD,IACT/D,GAAU,OAAS+D,EAAW,YAIzBf,IAEThD,GAAU,OAGL2E,EAASI,WAAU/E,EAAS,mBAAqBA,EAAS,OAE/DA,EAAS,2CACP,oDACAA,EAAS,eAEX,KACE,GAAIgF,GAAS,GAAInR,UAAS8Q,EAASI,UAAY,MAAO,IAAK/E,GAC3D,MAAOiF,GAEP,KADAA,GAAEjF,OAASA,EACLiF,EAGR,GAAIR,GAAW,SAASS,GACtB,MAAOF,GAAO7P,KAAK/B,KAAM8R,EAAM5R,IAI7B6R,EAAWR,EAASI,UAAY,KAGpC,OAFAN,GAASzE,OAAS,YAAcmF,EAAW,OAASnF,EAAS,IAEtDyE,GAITnR,EAAE8R,MAAQ,SAAS3Q,GACjB,GAAI4Q,GAAW/R,EAAEmB,EAEjB,OADA4Q,GAASC,QAAS,EACXD,EAUT,IAAIlO,GAAS,SAAS1C,GACpB,MAAOrB,MAAKkS,OAAShS,EAAEmB,GAAK2Q,QAAU3Q,EAIxCnB,GAAEiS,MAAQ,SAAS9Q,GACjBnB,EAAE0C,KAAK1C,EAAEsM,UAAUnL,GAAM,SAAS8M,GAChC,GAAIxM,GAAOzB,EAAEiO,GAAQ9M,EAAI8M,EACzBjO,GAAEG,UAAU8N,GAAQ,WAClB,GAAInJ,IAAQhF,KAAKsB,SAEjB,OADAZ,GAAK0B,MAAM4C,EAAM3C,WACV0B,EAAOhC,KAAK/B,KAAM2B,EAAKS,MAAMlC,EAAG8E,QAM7C9E,EAAEiS,MAAMjS,GAGRA,EAAE0C,MAAM,MAAO,OAAQ,UAAW,QAAS,OAAQ,SAAU,WAAY,SAASuL,GAChF,GAAIpJ,GAAS5E,EAAWgO,EACxBjO,GAAEG,UAAU8N,GAAQ,WAClB,GAAI9M,GAAMrB,KAAKsB,QAGf,OAFAyD,GAAO3C,MAAMf,EAAKgB,WACJ,UAAT8L,GAA6B,WAATA,GAAqC,IAAf9M,EAAI0B,cAAqB1B,GAAI,GACrE0C,EAAOhC,KAAK/B,KAAMqB,MAK7BnB,EAAE0C,MAAM,SAAU,OAAQ,SAAU,SAASuL,GAC3C,GAAIpJ,GAAS5E,EAAWgO,EACxBjO,GAAEG,UAAU8N,GAAQ,WAClB,MAAOpK,GAAOhC,KAAK/B,KAAM+E,EAAO3C,MAAMpC,KAAKsB,SAAUe,eAKzDnC,EAAEG,UAAUyB,MAAQ,WAClB,MAAO9B,MAAKsB,UAUQ,kBAAX8Q,SAAyBA,OAAOC,KACzCD,OAAO,gBAAkB,WACvB,MAAOlS,OAGX6B,KAAK/B"} -------------------------------------------------------------------------------- /static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.5 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.5",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.5",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.5",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.5",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.5",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); --------------------------------------------------------------------------------