├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── admin ├── .gitignore ├── candidate │ └── candidate.go ├── company │ └── company.go ├── mail │ └── mail.go ├── question │ └── question.go ├── quiz │ └── quiz.go ├── report │ └── report.go ├── server │ └── server.go ├── tag │ └── tag.go └── webUI │ ├── .jsbeautifyrc │ ├── app │ ├── app.module.js │ ├── app.route.js │ ├── components │ │ ├── candidate │ │ │ ├── candidateController.js │ │ │ ├── candidateService.js │ │ │ ├── index.html │ │ │ ├── quizLandingController.js │ │ │ ├── quizLandingService.js │ │ │ └── views │ │ │ │ ├── quiz-landing.html │ │ │ │ └── quiz.html │ │ ├── home │ │ │ ├── home.html │ │ │ └── homeController.js │ │ ├── invite │ │ │ ├── index.html │ │ │ ├── inviteController.js │ │ │ ├── inviteService.js │ │ │ └── views │ │ │ │ ├── candidate-report.html │ │ │ │ ├── edit-invite.html │ │ │ │ ├── invite-dashboard.html │ │ │ │ ├── invite-user.html │ │ │ │ └── quiz-selection.html │ │ ├── login │ │ │ ├── index.html │ │ │ ├── loginController.js │ │ │ └── loginService.js │ │ ├── profile │ │ │ ├── edit.html │ │ │ ├── index.html │ │ │ ├── profileController.js │ │ │ └── profileService.js │ │ ├── question │ │ │ ├── add-question.html │ │ │ ├── addQuestionController.js │ │ │ ├── all-question.html │ │ │ ├── edit-question.html │ │ │ ├── editQuestionController.js │ │ │ ├── index.html │ │ │ ├── questionController.js │ │ │ └── questionService.js │ │ └── quiz │ │ │ ├── all-quiz.html │ │ │ ├── edit-quiz.html │ │ │ ├── index.html │ │ │ ├── question-selection.html │ │ │ ├── quizController.js │ │ │ ├── quizServices.js │ │ │ └── views │ │ │ ├── edit-invite.html │ │ │ ├── invite-dashboard.html │ │ │ └── quiz-selection.html │ └── shared │ │ ├── _footer.html │ │ ├── _modal_template.html │ │ ├── _not_authorized.html │ │ └── _server_crash.html │ ├── assets │ ├── css │ │ ├── _base.css │ │ ├── _colors.css │ │ ├── _layout.css │ │ ├── _utility.css │ │ ├── custom.css │ │ └── main.css │ ├── img │ │ ├── home-cover.jpg │ │ ├── question-cover.png │ │ └── quiz-cover.png │ ├── js │ │ └── gru.js │ └── lib │ │ ├── css │ │ ├── angular-select.min.css │ │ ├── codemirror.css │ │ ├── github.css │ │ ├── material.indigo-pink.min.css │ │ ├── select2.css │ │ ├── select2.min.css │ │ └── select2x2.png │ │ └── js │ │ ├── angular-css.min.js │ │ ├── angular-route.min.js │ │ ├── angular-sanitize.min.js │ │ ├── angular-select.min.js │ │ ├── angular-ui-router.min.js │ │ ├── angular.min.js │ │ ├── codemirror.js │ │ ├── duration.js │ │ ├── highlight.pack.js │ │ ├── javascript.js │ │ ├── jquery-2.1.1.min.js │ │ ├── marked.min.js │ │ ├── material.min.js │ │ ├── ocLazyLoad.min.js │ │ ├── select2.min.js │ │ └── ui-codemirror.min.js │ └── index.html ├── auth └── auth.go ├── dgraph ├── .gitignore ├── certbot-webroot │ └── .gitfolder ├── compose-local.yml ├── dgraph.go ├── docker-compose.yml ├── gru-dev │ ├── .gitignore │ ├── nginx-local.conf │ └── run-gru.sh ├── init.sh ├── nginx.conf ├── restart-local.sh └── schema.txt ├── main.go ├── migrations ├── cscore │ └── 16112016_candidate_score.go └── quizdur │ └── 16112016_quiz_duration.go ├── quiz ├── answer.go ├── answer_test.go ├── feedback.go ├── name.go ├── ping.go ├── question.go ├── quiz.go ├── report.html ├── util.go ├── util_test.go ├── validate.go └── validate_test.go ├── vendor ├── github.com │ ├── auth0 │ │ └── go-jwt-middleware │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── jwtmiddleware.go │ ├── davecgh │ │ └── go-spew │ │ │ ├── LICENSE │ │ │ └── spew │ │ │ ├── bypass.go │ │ │ ├── bypasssafe.go │ │ │ ├── common.go │ │ │ ├── config.go │ │ │ ├── doc.go │ │ │ ├── dump.go │ │ │ ├── format.go │ │ │ └── spew.go │ ├── dgrijalva │ │ └── jwt-go │ │ │ ├── LICENSE │ │ │ ├── MIGRATION_GUIDE.md │ │ │ ├── README.md │ │ │ ├── VERSION_HISTORY.md │ │ │ ├── claims.go │ │ │ ├── doc.go │ │ │ ├── ecdsa.go │ │ │ ├── ecdsa_utils.go │ │ │ ├── errors.go │ │ │ ├── hmac.go │ │ │ ├── map_claims.go │ │ │ ├── none.go │ │ │ ├── parser.go │ │ │ ├── rsa.go │ │ │ ├── rsa_pss.go │ │ │ ├── rsa_utils.go │ │ │ ├── signing_method.go │ │ │ └── token.go │ ├── gorilla │ │ ├── context │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── context.go │ │ │ └── doc.go │ │ └── mux │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── context_gorilla.go │ │ │ ├── context_native.go │ │ │ ├── doc.go │ │ │ ├── mux.go │ │ │ ├── regexp.go │ │ │ └── route.go │ ├── pkg │ │ └── errors │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── appveyor.yml │ │ │ ├── errors.go │ │ │ └── stack.go │ ├── pmezard │ │ └── go-difflib │ │ │ ├── LICENSE │ │ │ └── difflib │ │ │ └── difflib.go │ ├── russross │ │ └── blackfriday │ │ │ ├── LICENSE.txt │ │ │ ├── README.md │ │ │ ├── block.go │ │ │ ├── html.go │ │ │ ├── inline.go │ │ │ ├── latex.go │ │ │ ├── markdown.go │ │ │ └── smartypants.go │ ├── sendgrid │ │ ├── rest │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE.txt │ │ │ ├── README.md │ │ │ └── rest.go │ │ └── sendgrid-go │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE.txt │ │ │ ├── README.md │ │ │ ├── TROUBLESHOOTING.md │ │ │ ├── USAGE.md │ │ │ ├── USE_CASES.md │ │ │ ├── helpers │ │ │ └── mail │ │ │ │ ├── README.md │ │ │ │ └── mail_v3.go │ │ │ ├── prism.sh │ │ │ └── sendgrid.go │ ├── shurcooL │ │ └── sanitized_anchor_name │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── main.go │ ├── stretchr │ │ └── testify │ │ │ ├── LICENSE │ │ │ ├── assert │ │ │ ├── assertion_forward.go │ │ │ ├── assertion_forward.go.tmpl │ │ │ ├── assertions.go │ │ │ ├── doc.go │ │ │ ├── errors.go │ │ │ ├── forward_assertions.go │ │ │ └── http_assertions.go │ │ │ └── require │ │ │ ├── doc.go │ │ │ ├── forward_requirements.go │ │ │ ├── require.go │ │ │ ├── require.go.tmpl │ │ │ ├── require_forward.go │ │ │ ├── require_forward.go.tmpl │ │ │ └── requirements.go │ └── urfave │ │ └── negroni │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── doc.go │ │ ├── logger.go │ │ ├── negroni.go │ │ ├── recovery.go │ │ ├── response_writer.go │ │ └── static.go └── vendor.json └── x └── x.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | gru 3 | .idea/ 4 | /*.iml 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.7.3 5 | 6 | script: 7 | - go test $(go list ./... | grep -v /vendor/) 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gru 2 | 3 | Gru is an open source adaptive test system for screening candidates for software engineering roles. It helps us identify and recruit the right minions. 4 | 5 | You can read more about why we built Gru on our [blog](https://open.dgraph.io/post/gru/). Gru uses Dgraph as the database. 6 | 7 | ## Running 8 | 9 | Gru has three components, Gru server, Dgraph(v0.7.5) as the database and Caddy as a web server. 10 | 11 | ### Gru server 12 | 13 | ``` 14 | # Make sure you have Go installed on the server (https://golang.org/doc/install). 15 | go get github.com/dgraph-io/gru 16 | cd $GOPATH/src/github.com/dgraph-io/gru 17 | go build . 18 | ./gru --user= --pass= --secret="" --sendgrid="" -ip "https://gru.dgraph.io" -debug=true 2>&1 | tee -a gru.log 19 | ``` 20 | 21 | * Note we use sendgrid for sending invite mails to the candidates. That won't work without the sendgrid 22 | key. For development purposes when the `--sendgrid` flag is empty, we just print out the invite link for taking 23 | the quiz to the console. 24 | * The `-ip` flag is used to specify the ip address of the Gru server. 25 | 26 | 27 | ### Dgraph 28 | 29 | ``` 30 | wget https://github.com/dgraph-io/dgraph/releases/download/v0.7.5/dgraph-linux-amd64-v0.7.5.tar.gz 31 | tar -xzvf dgraph-linux-amd64-v0.7.5.tar.gz 32 | ./dgraph/dgraph 33 | ``` 34 | In case you are reloading data into Dgraph from an export, you can use dgraphloader to load the `rdf.gz` exported file. 35 | 36 | Dgraph runs on port 8080 by default. 37 | 38 | ### Caddy 39 | 40 | Note, you should modify the the address on the first line of the `admin/webUI/Caddyfile` and also the 41 | value of `hostname` in `admin/webUI/app/app.module.js` to either `http://localhost:2020` for the purposes 42 | of local development or to the address of your production server before running Caddy web server. 43 | 44 | ``` 45 | mkdir caddy 46 | wget https://github.com/mholt/caddy/releases/download/v0.10.8/caddy_v0.10.8_linux_amd64.tar.gz 47 | tar -xzvf caddy_v0.10.8_linux_amd64.tar.gz 48 | ./caddy --conf ../admin/webUI/Caddyfile 49 | ``` 50 | 51 | After this Gru should be up and running for you. You can visit http://localhost:2020 (if running 52 | locally) and login. Go ahead and add some questions, create some quizzes and invite some candidates. 53 | 54 | # [![Coverage Status](https://coveralls.io/repos/github/dgraph-io/gru/badge.svg?branch=develop)](https://coveralls.io/github/dgraph-io/gru?branch=develop) 55 | -------------------------------------------------------------------------------- /admin/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgraph-io/gru/c95cca154cc01ebf99744c6149e24314876dc503/admin/.gitignore -------------------------------------------------------------------------------- /admin/company/company.go: -------------------------------------------------------------------------------- 1 | package company 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/dgraph-io/gru/dgraph" 7 | ) 8 | 9 | type Company struct { 10 | Name string `json:"company.name"` 11 | Email string `json:"company.email"` 12 | Backup int `json:"backup,string"` 13 | BackupDays int `json:"backup_days,string"` 14 | Invite string `json:"company.invite_email"` 15 | RejectEmail string `json:"company.reject_email"` 16 | Reject bool `json:"company.reject,string"` 17 | } 18 | 19 | type info struct { 20 | Data struct { 21 | Companies []Company `json:"info"` 22 | } 23 | } 24 | 25 | func Info() (Company, error) { 26 | q := `{ 27 | info(func: has(is_company_info)) { 28 | company.name 29 | company.email 30 | company.reject 31 | company.invite_email 32 | company.reject_email 33 | backup 34 | backup_days 35 | } 36 | }` 37 | 38 | var companies info 39 | if err := dgraph.QueryAndUnmarshal(q, &companies); err != nil { 40 | return Company{}, err 41 | } 42 | 43 | if len(companies.Data.Companies) != 1 { 44 | return Company{}, fmt.Errorf("No company information found.") 45 | } 46 | 47 | return companies.Data.Companies[0], nil 48 | } 49 | -------------------------------------------------------------------------------- /admin/mail/mail.go: -------------------------------------------------------------------------------- 1 | package mail 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/url" 7 | 8 | "github.com/dgraph-io/gru/admin/company" 9 | "github.com/dgraph-io/gru/x" 10 | "github.com/russross/blackfriday" 11 | sendgrid "github.com/sendgrid/sendgrid-go" 12 | "github.com/sendgrid/sendgrid-go/helpers/mail" 13 | ) 14 | 15 | var SENDGRID_API_KEY = flag.String("sendgrid", "", "Sendgrid API Key") 16 | 17 | // TODO - Later just have one IP address with port info. 18 | var Ip = flag.String("ip", "https://gru.dgraph.io", "Public IP address of server") 19 | 20 | func GetInviteUrl(token string) string { 21 | return fmt.Sprintf("%v/#/quiz/%v", *Ip, token) 22 | } 23 | 24 | func Send(email, validity, token string) { 25 | if *SENDGRID_API_KEY == "" { 26 | fmt.Println(*Ip + "/#/quiz/" + token) 27 | return 28 | } 29 | fmt.Println("Sending invite: " + *Ip + "/#/quiz/" + token) 30 | 31 | c, err := company.Info() 32 | if err != nil { 33 | fmt.Println("Invite not sent", err) 34 | return 35 | } 36 | 37 | from := mail.NewEmail(c.Name, c.Email) 38 | subject := fmt.Sprintf("Invitation for screening quiz from %v", c.Name) 39 | to := mail.NewEmail("", email) 40 | URL := GetInviteUrl(token) 41 | // Lets unescape it first. 42 | invite, err := url.QueryUnescape(c.Invite) 43 | if err != nil { 44 | fmt.Println("Invite not sent", err) 45 | return 46 | } 47 | 48 | hr := blackfriday.HtmlRenderer(0, "", "") 49 | o := blackfriday.Options{} 50 | o.Extensions = blackfriday.EXTENSION_HARD_LINE_BREAK 51 | invite = string(blackfriday.MarkdownOptions([]byte(invite), hr, o)) 52 | 53 | body := ` 54 | 55 | 56 | Hello! 57 |

58 | You have been invited to take the screening quiz by ` + c.Name + `. 59 |

60 | You can take the quiz any time till ` + validity + ` by visiting ` + URL + `. 61 |
62 | ` + invite + ` 63 | 64 | 65 | ` 66 | content := mail.NewContent("text/html", body) 67 | m := mail.NewV3MailInit(from, subject, to, content) 68 | request := sendgrid.GetRequest(*SENDGRID_API_KEY, "/v3/mail/send", "https://api.sendgrid.com") 69 | request.Method = "POST" 70 | request.Body = mail.GetRequestBody(m) 71 | 72 | x.Debug("Sending invite to " + email + "\n Invite URL is " + URL) 73 | 74 | _, err = sendgrid.API(request) 75 | if err != nil { 76 | fmt.Println("Invite not sent", err) 77 | return 78 | } 79 | x.Debug("Mail sent") 80 | } 81 | 82 | func SendReport(name string, quiz string, score, maxScore float64, body string) { 83 | if *SENDGRID_API_KEY == "" { 84 | return 85 | } 86 | 87 | c, err := company.Info() 88 | if err != nil { 89 | fmt.Println(err) 90 | return 91 | } 92 | 93 | from := mail.NewEmail("Gru", c.Email) 94 | subject := fmt.Sprintf("%v scored %.2f/%.2f in the %v quiz", name, 95 | score, maxScore, quiz) 96 | to := mail.NewEmail(c.Name, c.Email) 97 | 98 | content := mail.NewContent("text/html", body) 99 | m := mail.NewV3MailInit(from, subject, to, content) 100 | request := sendgrid.GetRequest(*SENDGRID_API_KEY, "/v3/mail/send", "https://api.sendgrid.com") 101 | request.Method = "POST" 102 | request.Body = mail.GetRequestBody(m) 103 | _, err = sendgrid.API(request) 104 | if err != nil { 105 | fmt.Println(err) 106 | } 107 | x.Debug("Mail sent") 108 | } 109 | -------------------------------------------------------------------------------- /admin/quiz/quiz.go: -------------------------------------------------------------------------------- 1 | package quiz 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/dgraph-io/gru/admin/server" 9 | "github.com/dgraph-io/gru/dgraph" 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | type Quiz struct { 14 | Uid string `json:"uid"` 15 | Name string 16 | Duration int 17 | Cutoff float64 `json:"cut_off"` 18 | Threshold float64 `json:"threshold"` 19 | Questions []Question `json:"questions"` 20 | } 21 | 22 | type Question struct { 23 | Uid string `json:"uid"` 24 | Is_delete bool 25 | } 26 | 27 | func buildQuizMutation(q Quiz) *dgraph.Mutation { 28 | m := new(dgraph.Mutation) 29 | 30 | uid := "_:quiz" 31 | if (q.Uid != "") { 32 | uid = q.Uid 33 | } 34 | 35 | m.SetString(uid, "is_quiz", "") 36 | // TODO - Error if Name is empty. 37 | m.SetString(uid, "name", q.Name) 38 | m.SetString(uid, "threshold", strconv.FormatFloat(q.Threshold, 'g', -1, 64)) 39 | m.SetString(uid, "cut_off", strconv.FormatFloat(q.Cutoff, 'g', -1, 64)) 40 | m.SetString(uid, "duration", strconv.Itoa(q.Duration)) 41 | for _, q := range q.Questions { 42 | m.SetLink(uid, "quiz.question", q.Uid) 43 | } 44 | 45 | for _, q := range q.Questions { 46 | if q.Is_delete { 47 | m.DelLink(uid, "quiz.question", q.Uid) 48 | } else { 49 | m.SetLink(uid, "quiz.question", q.Uid) 50 | } 51 | } 52 | 53 | return m 54 | } 55 | 56 | func Add(w http.ResponseWriter, r *http.Request) { 57 | var q Quiz 58 | sr := server.Response{} 59 | err := json.NewDecoder(r.Body).Decode(&q) 60 | if err != nil { 61 | sr.Write(w, "Couldn't decode JSON", "", http.StatusBadRequest) 62 | return 63 | } 64 | 65 | mr, err := dgraph.SendMutation(buildQuizMutation(q)) 66 | if err != nil { 67 | sr.Write(w, "", err.Error(), http.StatusInternalServerError) 68 | return 69 | } 70 | if mr.Code != dgraph.Success { 71 | sr.Write(w, mr.Message, "", http.StatusInternalServerError) 72 | return 73 | } 74 | 75 | sr.Success = true 76 | sr.Message = "Quiz Successfully Saved!" 77 | w.Write(server.MarshalResponse(sr)) 78 | } 79 | 80 | func Index(w http.ResponseWriter, r *http.Request) { 81 | q := `{ 82 | quizzes(func: has(is_quiz)) { 83 | uid 84 | name 85 | duration 86 | quiz.question { 87 | uid 88 | text 89 | } 90 | } 91 | }` 92 | res, err := dgraph.Query(q) 93 | if err != nil { 94 | sr := server.Response{} 95 | sr.Write(w, "", err.Error(), http.StatusInternalServerError) 96 | return 97 | } 98 | w.Write(res) 99 | } 100 | 101 | func getQuizQuery(quizId string) string { 102 | return `{ 103 | quiz(func: uid(` + quizId + `)) { 104 | uid 105 | name 106 | duration 107 | cut_off 108 | threshold 109 | quiz.question { 110 | uid 111 | name 112 | text 113 | positive 114 | negative 115 | tags: question.tag { 116 | uid 117 | name 118 | } 119 | correct: question.correct { 120 | uid 121 | name 122 | } 123 | } 124 | } 125 | }` 126 | } 127 | 128 | func Get(w http.ResponseWriter, r *http.Request) { 129 | vars := mux.Vars(r) 130 | res, err := dgraph.Query(getQuizQuery(vars["id"])) 131 | if err != nil { 132 | sr := server.Response{} 133 | sr.Write(w, "", err.Error(), http.StatusInternalServerError) 134 | return 135 | } 136 | w.Write(res) 137 | } 138 | 139 | func Edit(w http.ResponseWriter, r *http.Request) { 140 | var q Quiz 141 | vars := mux.Vars(r) 142 | qid := vars["id"] 143 | sr := server.Response{} 144 | err := json.NewDecoder(r.Body).Decode(&q) 145 | if err != nil { 146 | sr.Write(w, "Couldn't decode JSON", "", http.StatusBadRequest) 147 | return 148 | } 149 | q.Uid = qid 150 | _, err = dgraph.SendMutation(buildQuizMutation(q)) 151 | if err != nil { 152 | sr.Write(w, "", err.Error(), http.StatusInternalServerError) 153 | return 154 | } 155 | 156 | sr.Success = true 157 | sr.Message = "Quiz info updated successfully." 158 | w.Write(server.MarshalResponse(sr)) 159 | } 160 | -------------------------------------------------------------------------------- /admin/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | type Response struct { 9 | Success bool 10 | // Message to display to the user. 11 | Message string 12 | // Actual error. 13 | Error string 14 | } 15 | 16 | func AddCorsHeaders(w http.ResponseWriter) { 17 | w.Header().Set("Access-Control-Allow-Origin", "*") 18 | w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS") 19 | w.Header().Set("Access-Control-Allow-Headers", 20 | "Authorization,Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token,"+ 21 | "X-Auth-Token, Cache-Control, X-Requested-With") 22 | w.Header().Set("Access-Control-Allow-Credentials", "true") 23 | w.Header().Set("Connection", "close") 24 | 25 | w.Header().Set("Content-Type", "application/json") 26 | } 27 | 28 | func MarshalResponse(r Response) []byte { 29 | if r.Message == "" { 30 | r.Message = "Unknown error" 31 | } 32 | b, err := json.Marshal(r) 33 | if err != nil { 34 | b, _ = json.Marshal(Response{ 35 | Success: false, 36 | Message: "Failed to serialize response", 37 | Error: err.Error(), 38 | }) 39 | } 40 | return b 41 | } 42 | 43 | func (r Response) Write(w http.ResponseWriter, err string, msg string, status int) { 44 | r.Error = err 45 | r.Message = msg 46 | w.WriteHeader(status) 47 | w.Write(MarshalResponse(r)) 48 | } 49 | 50 | func MarshalAndWrite(w http.ResponseWriter, i interface{}) { 51 | b, err := json.Marshal(i) 52 | if err != nil { 53 | r := Response{} 54 | r.Write(w, err.Error(), "", http.StatusInternalServerError) 55 | return 56 | } 57 | w.Write(b) 58 | } 59 | -------------------------------------------------------------------------------- /admin/tag/tag.go: -------------------------------------------------------------------------------- 1 | package tag 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/dgraph-io/gru/admin/server" 7 | "github.com/dgraph-io/gru/dgraph" 8 | ) 9 | 10 | type Tag struct { 11 | Uid string `json:"uid"` 12 | Name string `json:"name"` 13 | Is_delete bool 14 | } 15 | 16 | // fetch all the tags 17 | func Index(w http.ResponseWriter, r *http.Request) { 18 | q := `{ 19 | tags(func: has(is_tag)) { 20 | name 21 | uid 22 | } 23 | }` 24 | 25 | res, err := dgraph.Query(q) 26 | if err != nil { 27 | sr := server.Response{} 28 | sr.Write(w, "", err.Error(), http.StatusInternalServerError) 29 | return 30 | } 31 | w.Write(res) 32 | } 33 | -------------------------------------------------------------------------------- /admin/webUI/.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "html": { 3 | "indent_char": " ", 4 | "indent_size": 2 5 | }, 6 | "js": { 7 | "indent_char": " ", 8 | "indent_size": 2 9 | }, 10 | "css": { 11 | "allowed_file_extensions": ["css", "scss", "sass", "less"], 12 | "indent_char": " ", 13 | "indent_size": 2 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /admin/webUI/app/components/candidate/candidateService.js: -------------------------------------------------------------------------------- 1 | angular.module('GruiApp').service('candidateService', [ 2 | "$http", 3 | "MainService", 4 | function candidateService($http, MainService) { 5 | return { 6 | getQuestion: function() { 7 | return MainService.post('/quiz/question').then(function(question) { 8 | return mainVm.fixQuestionUnescape(question); 9 | }); 10 | }, 11 | 12 | sendFeedback: function(data) { 13 | mainVm.showAjaxLoader = true; 14 | return $http({ 15 | method: 'POST', 16 | url: mainVm.candidate_url + '/quiz/feedback', 17 | data: $.param(data), 18 | headers: { 19 | 'Content-Type': 'application/x-www-form-urlencoded' 20 | } 21 | }) 22 | .then(function(data) { 23 | mainVm.showAjaxLoader = false; 24 | return data; 25 | }, 26 | function(response, code) { 27 | mainVm.showAjaxLoader = false; 28 | throw response; 29 | } 30 | ); 31 | }, 32 | 33 | submitAnswer: function(requestData) { 34 | mainVm.showAjaxLoader = true; 35 | return $http({ 36 | method: 'POST', 37 | url: mainVm.candidate_url + '/quiz/answer', 38 | data: $.param(requestData), 39 | headers: { 40 | 'Content-Type': 'application/x-www-form-urlencoded' 41 | } 42 | }) 43 | .then(function(data) { 44 | mainVm.showAjaxLoader = false; 45 | return data; 46 | }, 47 | function(response, code) { 48 | mainVm.showAjaxLoader = false; 49 | throw response; 50 | } 51 | ); 52 | }, 53 | 54 | getTime: function() { 55 | return MainService.post('/quiz/ping', "", true); 56 | }, 57 | } 58 | } 59 | ]); 60 | -------------------------------------------------------------------------------- /admin/webUI/app/components/candidate/index.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 |
8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /admin/webUI/app/components/candidate/quizLandingService.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function quizLandingService($q, $http, MainService) { 3 | var services = {}; //Object to return 4 | 5 | services.addName = function(data) { 6 | var deferred = $q.defer(); 7 | 8 | var candidateToken = JSON.parse(localStorage.getItem("candidate_info")); 9 | $http.defaults.headers.common["Authorization"] = 10 | "Bearer " + candidateToken.token; 11 | mainVm.showAjaxLoader = true; 12 | $http({ 13 | method: "POST", 14 | url: mainVm.candidate_url + "/quiz/name", 15 | data: $.param(data), 16 | headers: { 17 | "Content-Type": "application/x-www-form-urlencoded" 18 | } 19 | }).then( 20 | function(data) { 21 | mainVm.showAjaxLoader = false; 22 | deferred.resolve(data); 23 | }, 24 | function(response, code) { 25 | mainVm.showAjaxLoader = false; 26 | deferred.reject(response); 27 | } 28 | ); 29 | return deferred.promise; 30 | }; 31 | 32 | return services; 33 | } 34 | 35 | var quizLandingServicesArray = [ 36 | "$q", 37 | "$http", 38 | "MainService", 39 | quizLandingService 40 | ]; 41 | 42 | angular 43 | .module("GruiApp") 44 | .service("quizLandingService", quizLandingServicesArray); 45 | })(); 46 | -------------------------------------------------------------------------------- /admin/webUI/app/components/home/home.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 |

8 |
9 |
10 | QUESTION PANEL 11 |
12 |
13 | 16 | 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |

27 |
28 |
29 | QUIZ PANEL 30 |
31 |
32 | 35 | 38 |
39 |
40 |
41 |
42 |
43 | -------------------------------------------------------------------------------- /admin/webUI/app/components/home/homeController.js: -------------------------------------------------------------------------------- 1 | angular.module('GruiApp').controller('homeController', [ 2 | function homeController() { 3 | homeVm = this; 4 | mainVm.pageName = "home" 5 | } 6 | ]); 7 | -------------------------------------------------------------------------------- /admin/webUI/app/components/invite/index.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /admin/webUI/app/components/invite/inviteService.js: -------------------------------------------------------------------------------- 1 | angular.module("GruiApp").service("inviteService", [ 2 | "MainService", 3 | function inviteService(MainService) { 4 | return { 5 | inviteCandidate: function(data) { 6 | return MainService.post("/candidate", data); 7 | }, 8 | 9 | getInvitedCandidates: function(data) { 10 | return MainService.get("/candidates?quiz_id=" + data); 11 | }, 12 | 13 | getCandidate: function(data) { 14 | return MainService.get("/candidate/" + data); 15 | }, 16 | 17 | editInvite: function(data) { 18 | return MainService.put("/candidate/" + data.id, { 19 | ...data, 20 | validity: data.validity.toISOString(), 21 | }); 22 | }, 23 | 24 | getReport: function(candidateID) { 25 | return MainService.get("/candidate/report/" + candidateID) 26 | .then(function(reportData) { 27 | reportData.questions.map(mainVm.fixQuestionUnescape); 28 | return reportData; 29 | }); 30 | }, 31 | 32 | getFatReport: function(candidateID) { 33 | return MainService.get("/quiz/fatreport/" + candidateID); 34 | }, 35 | 36 | alreadyInvited: function(quizId, emails) { 37 | // TODO - User filter on email after incorporating Dgraph schema. 38 | var query = 39 | "{\ 40 | quiz(func: uid(" + quizId + ")) {\ 41 | uid \ 42 | quiz.candidate {\ 43 | email\ 44 | }\ 45 | }\ 46 | }"; 47 | 48 | return MainService.proxy(query).then(function(data) { 49 | if (!data || !data.data) { 50 | return ""; 51 | } 52 | var candidates = data.data.quiz[0]["quiz.candidate"]; 53 | if (candidates === undefined) { 54 | return ""; 55 | } 56 | for (var j = 0; j < emails.length; j++) { 57 | email = emails[j]; 58 | for (var i = 0; i < candidates.length; i++) { 59 | if (candidates[i].email === email) { 60 | return email; 61 | } 62 | } 63 | } 64 | return ""; 65 | }); 66 | }, 67 | 68 | resendInvite: async function(candidate) { 69 | // We update the validity to be 7 days from now on resending the invite. 70 | var validity = new Date(Date.now() + 3600 * 24 * 7 * 1000).toISOString(); 71 | var mutation = "{\n\ 72 | set {\n\ 73 | <" + candidate.uid + '> "' + validity + '" .\n\ 74 | }}'; 75 | 76 | const res = MainService.mutateProxy(mutation); 77 | if (!res.data || res.data.code != MainService.dgraphSuccess) { 78 | throw { 79 | success: false, 80 | message: "Validity couldn't be extended." 81 | } 82 | } 83 | 84 | const inviteRes = MainService.post( 85 | "/candidate/invite/" + candidate.uid, { 86 | email: candidate.email, 87 | token: candidate.token, 88 | validity: validity, 89 | }); 90 | 91 | return { 92 | sucess: res.Success, 93 | message: res.Message + " " + res.Error, 94 | }; 95 | }, 96 | 97 | cancelInvite: function(candidate, quizId) { 98 | var mutation = 99 | "{\n\ 100 | delete {\n\ 101 | <" + candidate.uid + "> * * .\n\ 102 | <" + quizId + "> <" + candidate.uid + "> .\n\ 103 | }\n\ 104 | }"; 105 | return MainService.mutateProxy(mutation).then(function(data) { 106 | return (data.data && data.data.code == MainService.dgraphSuccess); 107 | }); 108 | }, 109 | 110 | deleteCand: function(candidateId) { 111 | var mutation = 112 | "{\n\ 113 | set {\n\ 114 | <" + candidateId + '> "true" . \n\ 115 | }}'; 116 | return MainService.mutateProxy(mutation).then(function(data) { 117 | return (data.code == MainService.dgraphSuccess); 118 | }); 119 | }, 120 | } 121 | }, 122 | ]); 123 | -------------------------------------------------------------------------------- /admin/webUI/app/components/invite/views/edit-invite.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Update Invite

4 |
5 |
6 | 7 | 8 | Should be valid email 9 |
10 |
11 | 12 | 13 | Should be valid date 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 27 | 30 |
31 |
32 |
33 |
34 | -------------------------------------------------------------------------------- /admin/webUI/app/components/invite/views/invite-user.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Invite candidate for the quiz

4 |
5 |
6 | 7 | 8 | 9 | Should be valid email 10 |
11 |
12 | 13 | 14 | Should be valid date 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 28 |
29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /admin/webUI/app/components/invite/views/quiz-selection.html: -------------------------------------------------------------------------------- 1 |

List of Quizes

2 |
3 | 4 | 5 |
6 |
7 |
8 |
9 | 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /admin/webUI/app/components/login/index.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Login
4 |
5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 | 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /admin/webUI/app/components/login/loginController.js: -------------------------------------------------------------------------------- 1 | angular.module('GruiApp').controller('loginController', [ 2 | "$scope", 3 | "$state", 4 | "MainService", 5 | function loginController($scope, $state, MainService) { 6 | if (mainVm.isLoggedIn()) { 7 | $state.transitionTo("root"); 8 | } 9 | 10 | loginVm = this; 11 | loginVm.authData = {}; 12 | mainVm.pageName = "login-page" 13 | 14 | loginVm.authenticate = function() { 15 | if (!loginVm.authData.user || !loginVm.authData.password) { 16 | SNACKBAR({ 17 | message: "Please fill all the details", 18 | messageType: "error", 19 | }) 20 | return; 21 | } 22 | MainService.post('/login', loginVm.authData) 23 | .then(function(data) { 24 | if (data.token) { 25 | SNACKBAR({ 26 | message: "Logged In Successfuly", 27 | messageType: "success", 28 | }) 29 | localStorage.setItem("token", data.token); 30 | $state.transitionTo('root') 31 | } 32 | }, function(err) { 33 | SNACKBAR({ 34 | message: err.data.Message, 35 | messageType: "error", 36 | }) 37 | }) 38 | } 39 | } 40 | ]); 41 | -------------------------------------------------------------------------------- /admin/webUI/app/components/login/loginService.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | function loginService($q, $http, $rootScope, MainService) { 4 | 5 | } 6 | var loginServiceArray = [ 7 | "$q", 8 | "$http", 9 | "$rootScope", 10 | "MainService", 11 | loginService 12 | ]; 13 | 14 | angular.module('GruiApp').service('loginService', loginServiceArray); 15 | 16 | })(); 17 | -------------------------------------------------------------------------------- /admin/webUI/app/components/profile/index.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /admin/webUI/app/components/profile/profileController.js: -------------------------------------------------------------------------------- 1 | angular.module('GruiApp').controller('profileController', [ 2 | "$scope", 3 | "$rootScope", 4 | "profileService", 5 | function profileController($scope, $rootScope, profileService) { 6 | profileVm = this; 7 | mainVm.pageName = "profile-page" 8 | 9 | profileVm.editorSetting = { 10 | lineWrapping: true 11 | }; 12 | 13 | profileVm.initCodeMirror = function initCodeMirror() { 14 | $scope.cmOption = {} 15 | $scope.cmOption2 = {} 16 | setTimeout(function() { 17 | $scope.cmOption = profileVm.editorSetting; 18 | $scope.cmOption2 = profileVm.editorSetting; 19 | // To refresh the reject mail editor which is hidden initially. 20 | $(".CodeMirror").length > 0 && $(".CodeMirror")[1].CodeMirror.refresh() 21 | }, 500); 22 | }; 23 | } 24 | ]); 25 | 26 | 27 | angular.module('GruiApp').controller('editProfileController', [ 28 | "$scope", 29 | "$rootScope", 30 | "$state", 31 | "profileService", 32 | function editProfileController($scope, $rootScope, $state, profileService) { 33 | editProfileVm = this; 34 | editProfileVm.update = updateProfile; 35 | editProfileVm.info = {}; 36 | 37 | profileService.getProfile() 38 | .then(function(data) { 39 | editProfileVm.info = {} 40 | if (!data.info || !data.info[0]) { 41 | return; 42 | } 43 | var info = data.info[0]; 44 | 45 | editProfileVm.info.uid = info.uid; 46 | editProfileVm.info.name = info["company.name"] 47 | editProfileVm.info.email = info["company.email"] 48 | editProfileVm.info.invite_email = decodeURI(info["company.invite_email"]) 49 | editProfileVm.info.invite_email = editProfileVm.info.invite_email === "undefined" ? "" : editProfileVm.info.invite_email 50 | editProfileVm.info.reject_email = decodeURI(info["company.reject_email"]) 51 | editProfileVm.info.reject_email = editProfileVm.info.reject_email === "undefined" ? "" : editProfileVm.info.reject_email 52 | editProfileVm.info.reject = info["company.reject"] === "true" 53 | editProfileVm.info.backup = parseInt(info.backup) 54 | editProfileVm.info.backup_days = parseInt(info.backup_days) 55 | editProfileVm.info.backup = isNaN(editProfileVm.info.backup) ? 1 : editProfileVm.info.backup 56 | editProfileVm.info.backup_days = isNaN(editProfileVm.info.backup_days) ? 5 : editProfileVm.info.backup_days 57 | }, function(err) { 58 | console.error(err) 59 | }); 60 | 61 | function valid(input) { 62 | if (!input.name) { 63 | return "Name shouldn't be empty."; 64 | } 65 | if (!isValidEmail(input.email)) { 66 | return "Please enter a valid email." 67 | } 68 | if (input.reject && !input.reject_email) { 69 | return "Rejection email can't be empty."; 70 | } 71 | return true 72 | } 73 | 74 | function updateProfile() { 75 | var validateInput = valid(editProfileVm.info); 76 | if (validateInput != true) { 77 | SNACKBAR({ 78 | message: validateInput, 79 | messageType: "error", 80 | }) 81 | return 82 | } 83 | 84 | editProfileVm.info.invite_email = encodeURI(editProfileVm.info.invite_email) 85 | editProfileVm.info.reject_email = encodeURI(editProfileVm.info.reject_email) 86 | var requestData = angular.copy(editProfileVm.info); 87 | 88 | profileService.updateProfile(requestData) 89 | .then(function(data) { 90 | SNACKBAR({ 91 | message: "Profile updated successfully.", 92 | messageType: "success", 93 | }) 94 | $state.transitionTo("root") 95 | }, function(err) { 96 | console.error(err) 97 | SNACKBAR({ 98 | message: "Something went wrong: " + JSON.stringify(err), 99 | messageType: "error", 100 | }) 101 | }) 102 | } 103 | 104 | $rootScope.$on('$viewContentLoaded', function() { 105 | profileVm.initCodeMirror(); 106 | }); 107 | 108 | profileVm.initCodeMirror(); 109 | } 110 | ]); 111 | -------------------------------------------------------------------------------- /admin/webUI/app/components/profile/profileService.js: -------------------------------------------------------------------------------- 1 | angular.module("GruiApp").service("profileService", [ 2 | "$http", 3 | "$rootScope", 4 | "MainService", 5 | function profileService($http, $rootScope, MainService) { 6 | return { 7 | getProfile: function() { 8 | var query = "{\ 9 | info(func: has(is_company_info)) {\ 10 | uid \ 11 | company.name \ 12 | company.email \ 13 | company.invite_email \ 14 | company.reject_email \ 15 | company.reject \ 16 | backup \ 17 | backup_days \ 18 | }\ 19 | }"; 20 | 21 | return MainService.proxy(query).then(function(data) { 22 | return data.data; 23 | }); 24 | }, 25 | 26 | updateProfile: function(data) { 27 | var uid = data.uid || "_:co" 28 | var mutation = '{\n\ 29 | set {\n\ 30 | <' + uid + '> "' + data.name + '" . \n\ 31 | <' + uid + '> "' + data.name + '" . \n\ 32 | <' + uid + '> "' + data.email + '" . \n\ 33 | <' + uid + '> "' + data.backup + '" . \n\ 34 | <' + uid + '> "' + data.backup_days + '" . \n'; 35 | 36 | if (data.invite_email != "") { 37 | mutation += '<' + uid + '> "' + 38 | data.invite_email + 39 | '" . \n'; 40 | } 41 | if (data.reject_email != "") { 42 | mutation += '<' + uid + '> "' + 43 | data.reject_email + 44 | '" . \n'; 45 | } 46 | 47 | mutation += '<' + uid + '> "' + 48 | (data.reject ? "true" : "false") + '" . \n\ 49 | }\n\ 50 | }'; 51 | 52 | return MainService.mutateProxy(mutation).then(function(data) { 53 | return data.code == "Success"; 54 | }); 55 | }, 56 | }; 57 | }, 58 | ]); 59 | -------------------------------------------------------------------------------- /admin/webUI/app/components/question/addQuestionController.js: -------------------------------------------------------------------------------- 1 | angular.module("GruiApp").controller("addQuestionController", [ 2 | "$rootScope", 3 | "$state", 4 | "allQuestions", 5 | "questionService", 6 | function addQuestionController( 7 | $rootScope, 8 | $state, 9 | allQuestions, 10 | questionService 11 | ) { 12 | addQueVm = this; 13 | 14 | addQueVm.loadEmptyQuestion = function() { 15 | addQueVm.newQuestion = { 16 | options: [], 17 | tags: [], 18 | }; 19 | for (var i = 0; i < questionVm.optionsCount; i++) { 20 | addQueVm.newQuestion.options.push({ 21 | is_correct: false, 22 | name: '', 23 | }); 24 | } 25 | } 26 | 27 | addQueVm.loadEmptyQuestion(); 28 | 29 | setTimeout(function() { 30 | addQueVm.editor = questionVm.initOptionEditor(); 31 | }, 500); 32 | 33 | addQueVm.markdownPreview = function() { 34 | return marked(addQueVm.newQuestion.text || ""); 35 | } 36 | 37 | addQueVm.submitForm = function() { 38 | var validataionError = questionVm.validateInput(addQueVm.newQuestion); 39 | if (validataionError) { 40 | SNACKBAR({ 41 | message: validataionError, 42 | messageType: "error" 43 | }); 44 | return; 45 | } 46 | 47 | questionService.saveQuestion(addQueVm.newQuestion).then( 48 | function(data) { 49 | if (data.code != "Error") { 50 | addQueVm.loadEmptyQuestion(); 51 | } 52 | allQuestions.refresh(); 53 | 54 | SNACKBAR({ 55 | message: data.message || data.Message, 56 | messageType: data.code == "Error" ? "error" : "success", 57 | }); 58 | 59 | $state.transitionTo("question.all"); 60 | }); 61 | } 62 | 63 | $rootScope.$on("$viewContentLoaded", function() { 64 | questionVm.initCodeMirror(); 65 | }); 66 | questionVm.initCodeMirror(); 67 | 68 | addQueVm.resetForm = function() { 69 | addQueVm.loadEmptyQuestion(); 70 | } 71 | } 72 | ]); 73 | -------------------------------------------------------------------------------- /admin/webUI/app/components/question/editQuestionController.js: -------------------------------------------------------------------------------- 1 | angular.module("GruiApp").controller("editQuestionController", [ 2 | "$state", 3 | "$stateParams", 4 | "questionService", 5 | "allQuestions", 6 | function editQuestionController( 7 | $state, 8 | $stateParams, 9 | questionService, 10 | allQuestions 11 | ) { 12 | editQuesVm = this; 13 | editQuesVm.newQuestion = {}; 14 | 15 | questionVm.initCodeMirror(); 16 | questionVm.getAllTags(); 17 | 18 | setTimeout(function() { 19 | editQuesVm.editor = questionVm.initOptionEditor(); 20 | }, 500); 21 | 22 | editQuesVm.markdownPreview = function() { 23 | return marked(editQuesVm.newQuestion.text || ""); 24 | } 25 | 26 | questionService.getQuestion($stateParams.quesID).then( 27 | function(question) { 28 | var correctUids = question.correct.reduce(function(acc, val) { 29 | return Object.assign(acc, {[val.uid]: true}); 30 | }, {}) 31 | question.options.forEach(function(opt) { 32 | opt.is_correct = !!correctUids[opt.uid]; 33 | }) 34 | question.positive = parseFloat(question.positive); 35 | question.negative = parseFloat(question.negative); 36 | 37 | question.tags = question.tags || []; 38 | question.savedTags = question.tags.slice(); 39 | 40 | editQuesVm.newQuestion = question; 41 | } 42 | ); 43 | 44 | editQuesVm.cancelEdit = function() { 45 | if ($stateParams.returnQuizId) { 46 | $state.transitionTo("quiz.edit", { 47 | quizId: $stateParams.returnQuizId, 48 | }); 49 | } else { 50 | $state.transitionTo("question.all", { 51 | quesID: editQuesVm.newQuestion.uid 52 | }); 53 | } 54 | } 55 | 56 | editQuesVm.submitForm = function() { 57 | var question = editQuesVm.newQuestion; 58 | 59 | var validataionError = questionVm.validateInput(question); 60 | if (validataionError) { 61 | SNACKBAR({ 62 | message: validataionError, 63 | messageType: "error" 64 | }); 65 | return; 66 | } 67 | 68 | question.savedTags.forEach(function(oldTag) { 69 | if (!question.tags.find(function(t) { return t.uid == oldTag.uid })) { 70 | question.tags.push({ 71 | uid: oldTag.uid, 72 | name: 'tag_to_delete uid_' + oldTag.uid, 73 | is_delete: true, 74 | }) 75 | } 76 | }); 77 | 78 | questionService.editQuestion(question).then( 79 | function(data) { 80 | allQuestions.refresh(); 81 | SNACKBAR({ 82 | message: data.Message 83 | }); 84 | if ($stateParams.returnQuizId) { 85 | $state.transitionTo("quiz.edit", { 86 | quizId: $stateParams.returnQuizId, 87 | }); 88 | } else { 89 | $state.transitionTo("question.all", { 90 | quesID: $stateParams.quesID, 91 | }); 92 | } 93 | }, 94 | function(err) { 95 | console.error(err); 96 | // Should not happen, but if it does remove tag deletions from the UI. 97 | question.tags = question.tags.filter(function(tag) { 98 | return !tag.is_delete; 99 | }) 100 | } 101 | ); 102 | } 103 | } 104 | ]); 105 | -------------------------------------------------------------------------------- /admin/webUI/app/components/question/index.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /admin/webUI/app/components/quiz/all-quiz.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | List of all quizzes 7 |
8 |
9 |
10 | 11 |
12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 37 | 38 | 39 |
Quiz NameDuration (min)Actions
{{quiz.name}}{{quiz.duration}} 31 | EDIT 32 |  |  33 | CANDIDATES 34 |  |  35 | INVITE 36 |
40 |
41 |
42 |
43 |

44 | Could not find any quizzes. 45 |

Go to Add Quiz page to some. 46 |
47 |
48 | -------------------------------------------------------------------------------- /admin/webUI/app/components/quiz/index.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /admin/webUI/app/components/quiz/question-selection.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | 6 |
7 |
8 |
12 |
27 |
28 |
29 | 30 |
36 | 40 | add 41 | done 42 | 43 |
44 |
45 | {{question.name}} 46 |
50 | {{tag.name}} 51 |
52 | 53 | 56 | Edit 57 | 58 | 59 |
63 | Score: +{{question.positive}} -{{question.negative}} 64 |    65 | done 66 | done_all 67 | 68 |
69 | 70 | 73 | Easy {{ question.difficulty * 100 | number:0 }}% 74 | 75 | 78 | Hard {{ question.difficulty * 100 | number:0 }}% 79 | 80 | 84 | Average {{ question.difficulty * 100 | number:0 }}% 85 |    86 | 87 | {{question.answerCount}} Answers ({{question.skipCount}} skips) 88 |
89 | 90 |
95 |
96 |
97 |
98 |
99 | -------------------------------------------------------------------------------- /admin/webUI/app/components/quiz/quizServices.js: -------------------------------------------------------------------------------- 1 | angular.module('GruiApp').service('quizService', [ 2 | "$q", 3 | "$http", 4 | "$rootScope", 5 | "MainService", 6 | function quizService($q, $http, $rootScope, MainService) { 7 | return { 8 | getAllQuizzes: function() { 9 | return MainService.get('/get-all-quizzes').then(function(data) { 10 | return data.data.quizzes || []; 11 | }) 12 | }, 13 | 14 | saveQuiz: function(data) { 15 | return MainService.post('/add-quiz', data); 16 | }, 17 | 18 | editQuiz: function(data) { 19 | return MainService.put('/quiz/' + data.uid, data); 20 | }, 21 | 22 | getQuiz: function(data) { 23 | return MainService.get('/quiz/' + data).then(function(data) { 24 | return data.data.quiz[0]; 25 | }); 26 | }, 27 | }; 28 | }, 29 | ]); 30 | -------------------------------------------------------------------------------- /admin/webUI/app/components/quiz/views/edit-invite.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Update Invite

4 |
5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 | Should be valid email 13 |
14 |
15 | 16 | 17 | Should be valid date 18 |
19 |
20 | 21 |
22 | 23 | {{editInviteVm.edit.quiz.name}} 24 | 27 | 28 |
29 |
30 |
31 | 34 | 35 | Select the quiz to proceed 36 | 37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /admin/webUI/app/components/quiz/views/invite-dashboard.html: -------------------------------------------------------------------------------- 1 |
Candidate List for Quiz 2 | Invite 3 |
4 |
5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 |
NameEmailValidityActions
{{candidate.name}}{{candidate.email}}{{candidate.validity}} 21 | RESEND EMAIL  |  22 | EDIT  |  23 | DELETE 24 |
28 |
29 |
30 | -------------------------------------------------------------------------------- /admin/webUI/app/components/quiz/views/quiz-selection.html: -------------------------------------------------------------------------------- 1 |

List of Quizzes

2 |
3 | 4 | 5 |
6 |
7 |
8 |
9 | 13 |
14 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /admin/webUI/app/shared/_footer.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /admin/webUI/app/shared/_modal_template.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 10 |
11 | -------------------------------------------------------------------------------- /admin/webUI/app/shared/_not_authorized.html: -------------------------------------------------------------------------------- 1 |
2 |

report_problem{{mainVm.errorMessage}}

3 |
4 | -------------------------------------------------------------------------------- /admin/webUI/app/shared/_server_crash.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
Sorry to inform you but there is a problem with the server.
5 |
Please send us a email on - contact@dgraph.io
6 |
7 |
8 |
9 | -------------------------------------------------------------------------------- /admin/webUI/assets/css/_base.css: -------------------------------------------------------------------------------- 1 | body, html, *{ 2 | font-family: 'Montserrat', sans-serif; 3 | } 4 | 5 | body, html { 6 | height: 100%; 7 | } 8 | a { 9 | text-decoration: none; 10 | color: #F44336; 11 | } 12 | 13 | a:hover { 14 | color: #E91E63; 15 | } 16 | 17 | table { 18 | width: 100%; 19 | } 20 | 21 | th { 22 | text-transform: uppercase; 23 | } 24 | 25 | pre p { 26 | white-space: pre-line; 27 | } 28 | pre { 29 | margin: 0; 30 | white-space: inherit; 31 | } 32 | 33 | pre * { 34 | font-family: 'Helvetica Neue', 'Helvetica', 'Montserrat', sans-serif; 35 | } 36 | 37 | pre h1, 38 | pre h2, 39 | pre h3, 40 | pre h4, 41 | pre h5 { 42 | margin: 0; 43 | } 44 | pre h1 { 45 | line-height: 45px; 46 | } 47 | 48 | code { 49 | white-space: pre-wrap; 50 | } 51 | -------------------------------------------------------------------------------- /admin/webUI/assets/css/_colors.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgraph-io/gru/c95cca154cc01ebf99744c6149e24314876dc503/admin/webUI/assets/css/_colors.css -------------------------------------------------------------------------------- /admin/webUI/assets/css/main.css: -------------------------------------------------------------------------------- 1 | @import '_base.css'; 2 | @import '_layout.css'; 3 | @import '_utility.css'; 4 | @import '_colors.css'; 5 | @import 'custom.css'; -------------------------------------------------------------------------------- /admin/webUI/assets/img/home-cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgraph-io/gru/c95cca154cc01ebf99744c6149e24314876dc503/admin/webUI/assets/img/home-cover.jpg -------------------------------------------------------------------------------- /admin/webUI/assets/img/question-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgraph-io/gru/c95cca154cc01ebf99744c6149e24314876dc503/admin/webUI/assets/img/question-cover.png -------------------------------------------------------------------------------- /admin/webUI/assets/img/quiz-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgraph-io/gru/c95cca154cc01ebf99744c6149e24314876dc503/admin/webUI/assets/img/quiz-cover.png -------------------------------------------------------------------------------- /admin/webUI/assets/lib/css/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #998; 18 | font-style: italic; 19 | } 20 | 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-subst { 24 | color: #333; 25 | font-weight: bold; 26 | } 27 | 28 | .hljs-number, 29 | .hljs-literal, 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-tag .hljs-attr { 33 | color: #008080; 34 | } 35 | 36 | .hljs-string, 37 | .hljs-doctag { 38 | color: #d14; 39 | } 40 | 41 | .hljs-title, 42 | .hljs-section, 43 | .hljs-selector-id { 44 | color: #900; 45 | font-weight: bold; 46 | } 47 | 48 | .hljs-subst { 49 | font-weight: normal; 50 | } 51 | 52 | .hljs-type, 53 | .hljs-class .hljs-title { 54 | color: #458; 55 | font-weight: bold; 56 | } 57 | 58 | .hljs-tag, 59 | .hljs-name, 60 | .hljs-attribute { 61 | color: #000080; 62 | font-weight: normal; 63 | } 64 | 65 | .hljs-regexp, 66 | .hljs-link { 67 | color: #009926; 68 | } 69 | 70 | .hljs-symbol, 71 | .hljs-bullet { 72 | color: #990073; 73 | } 74 | 75 | .hljs-built_in, 76 | .hljs-builtin-name { 77 | color: #0086b3; 78 | } 79 | 80 | .hljs-meta { 81 | color: #999; 82 | font-weight: bold; 83 | } 84 | 85 | .hljs-deletion { 86 | background: #fdd; 87 | } 88 | 89 | .hljs-addition { 90 | background: #dfd; 91 | } 92 | 93 | .hljs-emphasis { 94 | font-style: italic; 95 | } 96 | 97 | .hljs-strong { 98 | font-weight: bold; 99 | } 100 | -------------------------------------------------------------------------------- /admin/webUI/assets/lib/css/select2x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dgraph-io/gru/c95cca154cc01ebf99744c6149e24314876dc503/admin/webUI/assets/lib/css/select2x2.png -------------------------------------------------------------------------------- /admin/webUI/assets/lib/js/angular-route.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.13 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(q,d,C){'use strict';function v(r,k,h){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,f,b,c,y){function z(){l&&(h.cancel(l),l=null);m&&(m.$destroy(),m=null);n&&(l=h.leave(n),l.then(function(){l=null}),n=null)}function x(){var b=r.current&&r.current.locals;if(d.isDefined(b&&b.$template)){var b=a.$new(),c=r.current;n=y(b,function(b){h.enter(b,null,n||f).then(function(){!d.isDefined(t)||t&&!a.$eval(t)||k()});z()});m=c.scope=b;m.$emit("$viewContentLoaded"); 7 | m.$eval(w)}else z()}var m,n,l,t=b.autoscroll,w=b.onload||"";a.$on("$routeChangeSuccess",x);x()}}}function A(d,k,h){return{restrict:"ECA",priority:-400,link:function(a,f){var b=h.current,c=b.locals;f.html(c.$template);var y=d(f.contents());b.controller&&(c.$scope=a,c=k(b.controller,c),b.controllerAs&&(a[b.controllerAs]=c),f.data("$ngControllerController",c),f.children().data("$ngControllerController",c));y(a)}}}q=d.module("ngRoute",["ng"]).provider("$route",function(){function r(a,f){return d.extend(Object.create(a), 8 | f)}function k(a,d){var b=d.caseInsensitiveMatch,c={originalPath:a,regexp:a},h=c.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,d,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});d=d||"";return""+(a?"":d)+"(?:"+(a?d:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");c.regexp=new RegExp("^"+a+"$",b?"i":"");return c}var h={};this.when=function(a,f){var b=d.copy(f);d.isUndefined(b.reloadOnSearch)&&(b.reloadOnSearch=!0); 9 | d.isUndefined(b.caseInsensitiveMatch)&&(b.caseInsensitiveMatch=this.caseInsensitiveMatch);h[a]=d.extend(b,a&&k(a,b));if(a){var c="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";h[c]=d.extend({redirectTo:a},k(c,b))}return this};this.caseInsensitiveMatch=!1;this.otherwise=function(a){"string"===typeof a&&(a={redirectTo:a});this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(a,f,b,c,k,q,x){function m(b){var e=s.current; 10 | (v=(p=l())&&e&&p.$$route===e.$$route&&d.equals(p.pathParams,e.pathParams)&&!p.reloadOnSearch&&!w)||!e&&!p||a.$broadcast("$routeChangeStart",p,e).defaultPrevented&&b&&b.preventDefault()}function n(){var u=s.current,e=p;if(v)u.params=e.params,d.copy(u.params,b),a.$broadcast("$routeUpdate",u);else if(e||u)w=!1,(s.current=e)&&e.redirectTo&&(d.isString(e.redirectTo)?f.path(t(e.redirectTo,e.params)).search(e.params).replace():f.url(e.redirectTo(e.pathParams,f.path(),f.search())).replace()),c.when(e).then(function(){if(e){var a= 11 | d.extend({},e.resolve),b,g;d.forEach(a,function(b,e){a[e]=d.isString(b)?k.get(b):k.invoke(b,null,null,e)});d.isDefined(b=e.template)?d.isFunction(b)&&(b=b(e.params)):d.isDefined(g=e.templateUrl)&&(d.isFunction(g)&&(g=g(e.params)),g=x.getTrustedResourceUrl(g),d.isDefined(g)&&(e.loadedTemplateUrl=g,b=q(g)));d.isDefined(b)&&(a.$template=b);return c.all(a)}}).then(function(c){e==s.current&&(e&&(e.locals=c,d.copy(e.params,b)),a.$broadcast("$routeChangeSuccess",e,u))},function(b){e==s.current&&a.$broadcast("$routeChangeError", 12 | e,u,b)})}function l(){var a,b;d.forEach(h,function(c,h){var g;if(g=!b){var k=f.path();g=c.keys;var m={};if(c.regexp)if(k=c.regexp.exec(k)){for(var l=1,n=k.length;l: default . 2 | : default . 3 | : default . 4 | : default . 5 | : uid . 6 | : uid . 7 | : float . 8 | : default . 9 | : default . 10 | : default . 11 | : default . 12 | : default . 13 | : bool @index(bool) . 14 | : default . 15 | : default . 16 | : default . 17 | : string . 18 | : password . 19 | : uid @reverse . 20 | : string @index(exact) . 21 | : int . 22 | : default . 23 | : default . 24 | : default . 25 | : default . 26 | : string . 27 | : string . 28 | : string . 29 | : bool . 30 | : string @index(exact, term) . 31 | : float . 32 | : default . 33 | : float . 34 | : datetime . 35 | : datetime . 36 | : uid . 37 | : uid . 38 | : uid . 39 | : float . 40 | : uid . 41 | : uid . 42 | : uid . 43 | : uid . 44 | : datetime @index(hour) . 45 | : float @index(float) . 46 | : uid . 47 | : default . 48 | : default . 49 | : default . 50 | : default . 51 | -------------------------------------------------------------------------------- /migrations/cscore/16112016_candidate_score.go: -------------------------------------------------------------------------------- 1 | // This is used to populate the total score fields for each candidate. 2 | // From now on it would automatically be stored when the quiz for a candidate ends. 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "log" 8 | "strconv" 9 | 10 | "github.com/dgraph-io/gru/dgraph" 11 | "github.com/dgraph-io/gru/x" 12 | ) 13 | 14 | var quiz = flag.String("quiz", "", "Quiz id") 15 | 16 | type Quiz struct { 17 | Quizzes []struct { 18 | Cand []struct { 19 | Id string `json:"_uid_"` 20 | Complete bool `json:",string"` 21 | Question []struct { 22 | Score float64 `json:"candidate.score,string"` 23 | } `json:"candidate.question"` 24 | Score float64 25 | } `json:"quiz.candidate"` 26 | } `json:"quiz"` 27 | } 28 | 29 | func candidates() string { 30 | return `{ 31 | quiz(id: ` + *quiz + `) { 32 | quiz.candidate { 33 | _uid_ 34 | complete 35 | candidate.question { 36 | candidate.score 37 | } 38 | } 39 | } 40 | }` 41 | } 42 | 43 | func main() { 44 | flag.Parse() 45 | if *quiz == "" { 46 | log.Fatal("Quiz can't be empty") 47 | } 48 | q := candidates() 49 | var qu Quiz 50 | dgraph.QueryAndUnmarshal(q, &qu) 51 | if len(qu.Quizzes) == 0 || len(qu.Quizzes[0].Cand) == 0 { 52 | log.Fatal("Couldn't find candidate data for the quiz.") 53 | } 54 | cand := qu.Quizzes[0].Cand 55 | for _, c := range cand { 56 | if !c.Complete { 57 | continue 58 | } 59 | score := 0.0 60 | for _, qn := range c.Question { 61 | score += qn.Score 62 | } 63 | c.Score = x.ToFixed(score, 2) 64 | m := new(dgraph.Mutation) 65 | m.Set(`<` + c.Id + `> "` + strconv.FormatFloat(c.Score, 'g', -1, 64) + `" .`) 66 | if _, err := dgraph.SendMutation(m.String()); err != nil { 67 | log.Fatalf("Error: %v for candidate with uid: %v", err, c.Id) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /migrations/quizdur/16112016_quiz_duration.go: -------------------------------------------------------------------------------- 1 | // This migration changes the quiz duration stored to number of minutes instead of 2 | // a go time.Duration string. 3 | // Earlier duration was stored as "1h40m0s", we want to change it to just "100". 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/dgraph-io/gru/dgraph" 13 | ) 14 | 15 | type res struct { 16 | Root []struct { 17 | Quizzes []struct { 18 | Id string `json:"_uid_"` 19 | Duration string `json:"duration"` 20 | } `json:"quiz"` 21 | } ` json:"root"` 22 | } 23 | 24 | func quizzes() string { 25 | return `{ 26 | root(id: root) { 27 | quiz { 28 | _uid_ 29 | duration 30 | } 31 | } 32 | }` 33 | } 34 | 35 | func main() { 36 | q := quizzes() 37 | var res res 38 | dgraph.QueryAndUnmarshal(q, &res) 39 | if len(res.Root) == 0 || len(res.Root[0].Quizzes) == 0 { 40 | log.Fatal("No quizzes found.") 41 | } 42 | for _, quiz := range res.Root[0].Quizzes { 43 | t, err := time.ParseDuration(quiz.Duration) 44 | if err != nil { 45 | fmt.Printf("Couldn't convert duration: %v. Got err: %v", 46 | quiz.Duration, err) 47 | } 48 | m := new(dgraph.Mutation) 49 | m.Set(`<` + quiz.Id + `> "` + strconv.FormatFloat(t.Minutes(), 'g', -1, 64) + `" .`) 50 | if _, err := dgraph.SendMutation(m.String()); err != nil { 51 | log.Fatalf("Error: %v while performing mutation for quiz with uid: %v", err, quiz.Id) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /quiz/answer_test.go: -------------------------------------------------------------------------------- 1 | package quiz 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func prepareCand() Candidate { 10 | c := Candidate{ 11 | level: EASY, 12 | qns: make(map[difficulty][]Question), 13 | } 14 | c.qns[EASY] = []Question{ 15 | {Id: "Q1"}, {Id: "Q2"}, {Id: "Q3"}, {Id: "Q4"}, 16 | } 17 | c.qns[MEDIUM] = []Question{ 18 | {Id: "Q5"}, {Id: "Q6"}, {Id: "Q7"}, {Id: "Q8"}, {Id: "Q16"}, {Id: "Q17"}, 19 | } 20 | c.qns[HARD] = []Question{ 21 | {Id: "Q9"}, {Id: "Q10"}, {Id: "Q11"}, {Id: "Q12"}, {Id: "Q13"}, {Id: "Q14"}, {Id: "Q15"}, 22 | } 23 | return c 24 | } 25 | 26 | func TestGoodCand(t *testing.T) { 27 | c := prepareCand() 28 | calibrateLevel(&c, true) 29 | require.Equal(t, 1, c.streak, "They should be equal.") 30 | 31 | calibrateLevel(&c, true) 32 | require.Equal(t, 2, c.streak, "They should be equal.") 33 | require.Equal(t, EASY, c.level, "They should be equal.") 34 | 35 | calibrateLevel(&c, true) 36 | require.Equal(t, 0, c.streak, "They should be equal.") 37 | require.Equal(t, MEDIUM, c.level, "They should be equal.") 38 | 39 | calibrateLevel(&c, true) 40 | calibrateLevel(&c, true) 41 | calibrateLevel(&c, true) 42 | require.Equal(t, 0, c.streak, "They should be equal.") 43 | require.Equal(t, HARD, c.level, "They should be equal.") 44 | 45 | calibrateLevel(&c, true) 46 | calibrateLevel(&c, true) 47 | calibrateLevel(&c, true) 48 | require.Equal(t, 3, c.streak, "They should be equal.") 49 | require.Equal(t, HARD, c.level, "They should be equal.") 50 | 51 | calibrateLevel(&c, true) 52 | calibrateLevel(&c, true) 53 | calibrateLevel(&c, true) 54 | require.Equal(t, 6, c.streak, "They should be equal.") 55 | require.Equal(t, HARD, c.level, "They should be equal.") 56 | 57 | calibrateLevel(&c, true) 58 | require.Equal(t, 0, c.streak, "They should be equal.") 59 | require.Equal(t, EASY, c.level, "They should be equal.") 60 | 61 | calibrateLevel(&c, true) 62 | require.Equal(t, 0, c.streak, "They should be equal.") 63 | require.Equal(t, MEDIUM, c.level, "They should be equal.") 64 | } 65 | 66 | func TestBumpyRide(t *testing.T) { 67 | c := prepareCand() 68 | calibrateLevel(&c, true) 69 | calibrateLevel(&c, true) 70 | calibrateLevel(&c, true) 71 | 72 | // moves to medium 73 | calibrateLevel(&c, true) 74 | calibrateLevel(&c, true) 75 | calibrateLevel(&c, true) 76 | 77 | // moves to hard 78 | require.Equal(t, 0, c.streak, "They should be equal.") 79 | require.Equal(t, HARD, c.level, "They should be equal.") // 80 | calibrateLevel(&c, false) 81 | calibrateLevel(&c, false) 82 | calibrateLevel(&c, true) 83 | calibrateLevel(&c, true) 84 | calibrateLevel(&c, false) 85 | calibrateLevel(&c, false) 86 | require.Equal(t, -2, c.streak, "They should be equal.") 87 | require.Equal(t, HARD, c.level, "They should be equal.") 88 | calibrateLevel(&c, false) 89 | 90 | // moves back to medium. 91 | require.Equal(t, 0, c.streak, "They should be equal.") 92 | require.Equal(t, MEDIUM, c.level, "They should be equal.") 93 | calibrateLevel(&c, false) 94 | calibrateLevel(&c, false) 95 | calibrateLevel(&c, false) 96 | 97 | // moves to easy because negative streak == level streak. 98 | require.Equal(t, 0, c.streak, "They should be equal.") 99 | require.Equal(t, EASY, c.level, "They should be equal.") 100 | 101 | } 102 | 103 | func TestBumpyRide2(t *testing.T) { 104 | c := prepareCand() 105 | // first question 106 | calibrateLevel(&c, true) 107 | calibrateLevel(&c, true) 108 | calibrateLevel(&c, true) 109 | 110 | // moves to medium 111 | calibrateLevel(&c, false) 112 | require.Equal(t, -1, c.streak, "They should be equal.") 113 | require.Equal(t, MEDIUM, c.level, "They should be equal.") 114 | calibrateLevel(&c, true) 115 | calibrateLevel(&c, false) 116 | calibrateLevel(&c, true) 117 | calibrateLevel(&c, false) 118 | calibrateLevel(&c, true) 119 | 120 | // Medium questions finish, we move to hard ones now. 121 | require.Equal(t, 0, c.streak, "They should be equal.") 122 | require.Equal(t, HARD, c.level, "They should be equal.") 123 | calibrateLevel(&c, true) 124 | calibrateLevel(&c, false) 125 | calibrateLevel(&c, true) 126 | calibrateLevel(&c, false) 127 | calibrateLevel(&c, true) 128 | calibrateLevel(&c, false) 129 | calibrateLevel(&c, true) 130 | 131 | // Hard finish, back to EASY. 132 | require.Equal(t, 0, c.streak, "They should be equal.") 133 | require.Equal(t, EASY, c.level, "They should be equal.") 134 | } 135 | -------------------------------------------------------------------------------- /quiz/feedback.go: -------------------------------------------------------------------------------- 1 | package quiz 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/dgraph-io/gru/admin/server" 7 | "github.com/dgraph-io/gru/dgraph" 8 | ) 9 | 10 | // TODO - Later remove this when we have a proxy endpoint for quiz candidates too. 11 | func Feedback(w http.ResponseWriter, r *http.Request) { 12 | sr := server.Response{} 13 | userId, err := validateToken(r) 14 | if err != nil { 15 | sr.Write(w, err.Error(), "", http.StatusUnauthorized) 16 | return 17 | } 18 | 19 | f := r.PostFormValue("feedback") 20 | if f == "" { 21 | sr.Write(w, "", "Feedback can't be empty.", http.StatusBadRequest) 22 | return 23 | } 24 | 25 | m := new(dgraph.Mutation) 26 | m.Set(`<` + userId + `> "` + f + `" .`) 27 | 28 | if _, err = dgraph.SendMutation(m); err != nil { 29 | sr.Write(w, "", err.Error(), http.StatusInternalServerError) 30 | return 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /quiz/name.go: -------------------------------------------------------------------------------- 1 | package quiz 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/dgraph-io/gru/admin/server" 7 | "github.com/dgraph-io/gru/dgraph" 8 | ) 9 | 10 | func CandidateName(w http.ResponseWriter, r *http.Request) { 11 | sr := server.Response{} 12 | userId, err := validateToken(r) 13 | if err != nil { 14 | sr.Write(w, err.Error(), "Unauthorized", http.StatusUnauthorized) 15 | return 16 | } 17 | 18 | n := r.PostFormValue("name") 19 | country := r.PostFormValue("country") 20 | if n == "" || country == "" { 21 | sr.Write(w, "", "Name/Country can't be empty.", http.StatusBadRequest) 22 | return 23 | } 24 | 25 | if status, err := checkAndUpdate(userId); err != nil { 26 | sr.Write(w, "", err.Error(), status) 27 | return 28 | } 29 | 30 | c, err := readMap(userId) 31 | if err != nil { 32 | sr.Write(w, "", "Candidate not found.", http.StatusBadRequest) 33 | return 34 | } 35 | 36 | c.name = n 37 | updateMap(userId, c) 38 | m := new(dgraph.Mutation) 39 | m.Set(`<` + userId + `> "` + n + `" .`) 40 | m.Set(`<` + userId + `> "` + country + `" .`) 41 | _, err = dgraph.SendMutation(m) 42 | if err != nil { 43 | sr.Write(w, "", err.Error(), http.StatusInternalServerError) 44 | return 45 | } 46 | w.WriteHeader(http.StatusOK) 47 | } 48 | -------------------------------------------------------------------------------- /quiz/ping.go: -------------------------------------------------------------------------------- 1 | package quiz 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/dgraph-io/gru/admin/server" 10 | "github.com/dgraph-io/gru/dgraph" 11 | ) 12 | 13 | type pingRes struct { 14 | TimeLeft string `json:"time_left"` 15 | } 16 | 17 | func PingHandler(w http.ResponseWriter, r *http.Request) { 18 | var userId string 19 | var err error 20 | sr := server.Response{} 21 | if userId, err = validateToken(r); err != nil { 22 | sr.Write(w, err.Error(), "", http.StatusUnauthorized) 23 | return 24 | } 25 | 26 | c, err := readMap(userId) 27 | if err != nil { 28 | sr.Write(w, "", "Candidate not found.", http.StatusBadRequest) 29 | return 30 | } 31 | 32 | c.lastExchange = time.Now() 33 | updateMap(userId, c) 34 | pingResult := &pingRes{TimeLeft: "-1"} 35 | // If quiz hasn't started yet, we return time_left as -1. 36 | if c.quizStart.IsZero() { 37 | json.NewEncoder(w).Encode(pingResult) 38 | return 39 | } 40 | 41 | end := c.quizStart.Add(c.quizDuration).Truncate(time.Second) 42 | timeLeft := end.Sub(time.Now().UTC().Truncate(time.Second)) 43 | pingResult.TimeLeft = timeLeft.String() 44 | if timeLeft > 0 { 45 | json.NewEncoder(w).Encode(pingResult) 46 | return 47 | } 48 | 49 | // Time left is <=0, that means quiz should end now. Lets store this information. 50 | m := new(dgraph.Mutation) 51 | m.SetString(userId, "complete", "true") 52 | m.SetString(userId, "completed_at", time.Now().Format(time.RFC3339Nano)) 53 | m.SetString(userId, "score", strconv.FormatFloat(c.score, 'f', 2, 64)) 54 | _, err = dgraph.SendMutation(m) 55 | if err != nil { 56 | sr.Write(w, "", err.Error(), http.StatusInternalServerError) 57 | return 58 | } 59 | // Client may call ping twice after the timeLeft <= 0, but we want to send mail 60 | // only once. So we check if we already sent the mail. 61 | if err = sendMail(c, userId); err != nil { 62 | sr.Write(w, err.Error(), "", http.StatusInternalServerError) 63 | return 64 | } 65 | json.NewEncoder(w).Encode(pingResult) 66 | } 67 | -------------------------------------------------------------------------------- /quiz/report.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
NameEmailTime TakenScore
{{.Name}}{{.Email}}{{.TimeTaken}}{{.TotalScore}}
31 |
32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {{range .Questions}} 41 | 42 | 43 | 44 | 45 | 46 | {{end}} 47 |
QuestionTime TakenScore
{{.Name}}{{.TimeTaken}}{{.Score}}
48 |
49 |
50 | You can view the full report at {{.Ip}}/#/admin/invite/candidate-report/{{.Id}} 51 | 52 | -------------------------------------------------------------------------------- /quiz/util.go: -------------------------------------------------------------------------------- 1 | package quiz 2 | 3 | import "math/rand" 4 | 5 | func shuffleQuestions(qns []Question) { 6 | for i := range qns { 7 | j := rand.Intn(i + 1) 8 | qns[i], qns[j] = qns[j], qns[i] 9 | } 10 | } 11 | 12 | func shuffleOptions(opts []Answer) { 13 | for i := range opts { 14 | j := rand.Intn(i + 1) 15 | opts[i], opts[j] = opts[j], opts[i] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /quiz/util_test.go: -------------------------------------------------------------------------------- 1 | package quiz 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestIsCorrectAnswer(t *testing.T) { 10 | score := getScore([]string{"ringplanets-saturn", "ringplanets-neptune"}, []string{"ringplanets-saturn"}, 2.5, 3) 11 | require.Equal(t, 2.5, score) 12 | 13 | score = getScore([]string{"ringplanets-saturn", "ringplanets-neptune"}, []string{"ringplanets-saturn", "ringplanets-neptune"}, 2.5, 3) 14 | require.Equal(t, 5.0, score) 15 | 16 | score = getScore([]string{"ringplanets-venus", "ringplanets-mars"}, []string{"ringplanets-saturn", "ringplanets-neptune"}, 2.5, 3) 17 | require.Equal(t, -6.0, score) 18 | 19 | score = getScore([]string{"skip"}, []string{"ringplanets-saturn", "ringplanets-neptune"}, 2.5, 3) 20 | require.Equal(t, 0.0, score) 21 | 22 | // Single choice questions 23 | score = getScore([]string{"sunmass-99"}, []string{"sunmass-99"}, 5.0, 2.5) 24 | require.Equal(t, 5.0, score) 25 | 26 | score = getScore([]string{"sunmass-1"}, []string{"sunmass-99"}, 5.0, 2.5) 27 | require.Equal(t, -2.5, score) 28 | } 29 | -------------------------------------------------------------------------------- /quiz/validate_test.go: -------------------------------------------------------------------------------- 1 | package quiz 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestFilter(t *testing.T) { 10 | qns := []Question{ 11 | Question{ 12 | Tags: []Tag{ 13 | Tag{Name: "easy"}, 14 | Tag{Name: "medium"}, 15 | }, 16 | }, 17 | Question{ 18 | Tags: []Tag{ 19 | Tag{Name: "hard"}, 20 | Tag{Name: "algorithms"}, 21 | }, 22 | }, 23 | Question{ 24 | Tags: []Tag{ 25 | Tag{Name: "systems"}, 26 | Tag{Name: "easy"}, 27 | }, 28 | }, 29 | Question{ 30 | Tags: []Tag{ 31 | Tag{Name: "medium"}, 32 | Tag{Name: "timecomplexity"}, 33 | }, 34 | }, 35 | } 36 | 37 | qnMap := filter(qns) 38 | 39 | require.Equal(t, 2, len(qnMap[EASY]), "We should have 2 easy questions.") 40 | require.Equal(t, 1, len(qnMap[MEDIUM]), "We should have 1 medium question.") 41 | require.Equal(t, 1, len(qnMap[HARD]), "We should have 1 hard question.") 42 | } 43 | -------------------------------------------------------------------------------- /vendor/github.com/auth0/go-jwt-middleware/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Auth0, Inc. (http://auth0.com) 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 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013 Dave Collins 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/bypasssafe.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Dave Collins 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // NOTE: Due to the following build constraints, this file will only be compiled 16 | // when either the code is running on Google App Engine or "-tags disableunsafe" 17 | // is added to the go build command line. 18 | // +build appengine disableunsafe 19 | 20 | package spew 21 | 22 | import "reflect" 23 | 24 | const ( 25 | // UnsafeDisabled is a build-time constant which specifies whether or 26 | // not access to the unsafe package is available. 27 | UnsafeDisabled = true 28 | ) 29 | 30 | // unsafeReflectValue typically converts the passed reflect.Value into a one 31 | // that bypasses the typical safety restrictions preventing access to 32 | // unaddressable and unexported data. However, doing this relies on access to 33 | // the unsafe package. This is a stub version which simply returns the passed 34 | // reflect.Value when the unsafe package is not available. 35 | func unsafeReflectValue(v reflect.Value) reflect.Value { 36 | return v 37 | } 38 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Dave Grijalva 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/claims.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "crypto/subtle" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | // For a type to be a Claims object, it must just have a Valid method that determines 10 | // if the token is invalid for any supported reason 11 | type Claims interface { 12 | Valid() error 13 | } 14 | 15 | // Structured version of Claims Section, as referenced at 16 | // https://tools.ietf.org/html/rfc7519#section-4.1 17 | // See examples for how to use this with your own claim types 18 | type StandardClaims struct { 19 | Audience string `json:"aud,omitempty"` 20 | ExpiresAt int64 `json:"exp,omitempty"` 21 | Id string `json:"jti,omitempty"` 22 | IssuedAt int64 `json:"iat,omitempty"` 23 | Issuer string `json:"iss,omitempty"` 24 | NotBefore int64 `json:"nbf,omitempty"` 25 | Subject string `json:"sub,omitempty"` 26 | } 27 | 28 | // Validates time based claims "exp, iat, nbf". 29 | // There is no accounting for clock skew. 30 | // As well, if any of the above claims are not in the token, it will still 31 | // be considered a valid claim. 32 | func (c StandardClaims) Valid() error { 33 | vErr := new(ValidationError) 34 | now := TimeFunc().Unix() 35 | 36 | // The claims below are optional, by default, so if they are set to the 37 | // default value in Go, let's not fail the verification for them. 38 | if c.VerifyExpiresAt(now, false) == false { 39 | delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0)) 40 | vErr.Inner = fmt.Errorf("token is expired by %v", delta) 41 | vErr.Errors |= ValidationErrorExpired 42 | } 43 | 44 | if c.VerifyIssuedAt(now, false) == false { 45 | vErr.Inner = fmt.Errorf("Token used before issued") 46 | vErr.Errors |= ValidationErrorIssuedAt 47 | } 48 | 49 | if c.VerifyNotBefore(now, false) == false { 50 | vErr.Inner = fmt.Errorf("token is not valid yet") 51 | vErr.Errors |= ValidationErrorNotValidYet 52 | } 53 | 54 | if vErr.valid() { 55 | return nil 56 | } 57 | 58 | return vErr 59 | } 60 | 61 | // Compares the aud claim against cmp. 62 | // If required is false, this method will return true if the value matches or is unset 63 | func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool { 64 | return verifyAud(c.Audience, cmp, req) 65 | } 66 | 67 | // Compares the exp claim against cmp. 68 | // If required is false, this method will return true if the value matches or is unset 69 | func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool { 70 | return verifyExp(c.ExpiresAt, cmp, req) 71 | } 72 | 73 | // Compares the iat claim against cmp. 74 | // If required is false, this method will return true if the value matches or is unset 75 | func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool { 76 | return verifyIat(c.IssuedAt, cmp, req) 77 | } 78 | 79 | // Compares the iss claim against cmp. 80 | // If required is false, this method will return true if the value matches or is unset 81 | func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool { 82 | return verifyIss(c.Issuer, cmp, req) 83 | } 84 | 85 | // Compares the nbf claim against cmp. 86 | // If required is false, this method will return true if the value matches or is unset 87 | func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool { 88 | return verifyNbf(c.NotBefore, cmp, req) 89 | } 90 | 91 | // ----- helpers 92 | 93 | func verifyAud(aud string, cmp string, required bool) bool { 94 | if aud == "" { 95 | return !required 96 | } 97 | if subtle.ConstantTimeCompare([]byte(aud), []byte(cmp)) != 0 { 98 | return true 99 | } else { 100 | return false 101 | } 102 | } 103 | 104 | func verifyExp(exp int64, now int64, required bool) bool { 105 | if exp == 0 { 106 | return !required 107 | } 108 | return now <= exp 109 | } 110 | 111 | func verifyIat(iat int64, now int64, required bool) bool { 112 | if iat == 0 { 113 | return !required 114 | } 115 | return now >= iat 116 | } 117 | 118 | func verifyIss(iss string, cmp string, required bool) bool { 119 | if iss == "" { 120 | return !required 121 | } 122 | if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 { 123 | return true 124 | } else { 125 | return false 126 | } 127 | } 128 | 129 | func verifyNbf(nbf int64, now int64, required bool) bool { 130 | if nbf == 0 { 131 | return !required 132 | } 133 | return now >= nbf 134 | } 135 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/doc.go: -------------------------------------------------------------------------------- 1 | // Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html 2 | // 3 | // See README.md for more info. 4 | package jwt 5 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/ecdsa.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "crypto" 5 | "crypto/ecdsa" 6 | "crypto/rand" 7 | "errors" 8 | "math/big" 9 | ) 10 | 11 | var ( 12 | // Sadly this is missing from crypto/ecdsa compared to crypto/rsa 13 | ErrECDSAVerification = errors.New("crypto/ecdsa: verification error") 14 | ) 15 | 16 | // Implements the ECDSA family of signing methods signing methods 17 | type SigningMethodECDSA struct { 18 | Name string 19 | Hash crypto.Hash 20 | KeySize int 21 | CurveBits int 22 | } 23 | 24 | // Specific instances for EC256 and company 25 | var ( 26 | SigningMethodES256 *SigningMethodECDSA 27 | SigningMethodES384 *SigningMethodECDSA 28 | SigningMethodES512 *SigningMethodECDSA 29 | ) 30 | 31 | func init() { 32 | // ES256 33 | SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256, 32, 256} 34 | RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod { 35 | return SigningMethodES256 36 | }) 37 | 38 | // ES384 39 | SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384, 48, 384} 40 | RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod { 41 | return SigningMethodES384 42 | }) 43 | 44 | // ES512 45 | SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512, 66, 521} 46 | RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod { 47 | return SigningMethodES512 48 | }) 49 | } 50 | 51 | func (m *SigningMethodECDSA) Alg() string { 52 | return m.Name 53 | } 54 | 55 | // Implements the Verify method from SigningMethod 56 | // For this verify method, key must be an ecdsa.PublicKey struct 57 | func (m *SigningMethodECDSA) Verify(signingString, signature string, key interface{}) error { 58 | var err error 59 | 60 | // Decode the signature 61 | var sig []byte 62 | if sig, err = DecodeSegment(signature); err != nil { 63 | return err 64 | } 65 | 66 | // Get the key 67 | var ecdsaKey *ecdsa.PublicKey 68 | switch k := key.(type) { 69 | case *ecdsa.PublicKey: 70 | ecdsaKey = k 71 | default: 72 | return ErrInvalidKeyType 73 | } 74 | 75 | if len(sig) != 2*m.KeySize { 76 | return ErrECDSAVerification 77 | } 78 | 79 | r := big.NewInt(0).SetBytes(sig[:m.KeySize]) 80 | s := big.NewInt(0).SetBytes(sig[m.KeySize:]) 81 | 82 | // Create hasher 83 | if !m.Hash.Available() { 84 | return ErrHashUnavailable 85 | } 86 | hasher := m.Hash.New() 87 | hasher.Write([]byte(signingString)) 88 | 89 | // Verify the signature 90 | if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true { 91 | return nil 92 | } else { 93 | return ErrECDSAVerification 94 | } 95 | } 96 | 97 | // Implements the Sign method from SigningMethod 98 | // For this signing method, key must be an ecdsa.PrivateKey struct 99 | func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) { 100 | // Get the key 101 | var ecdsaKey *ecdsa.PrivateKey 102 | switch k := key.(type) { 103 | case *ecdsa.PrivateKey: 104 | ecdsaKey = k 105 | default: 106 | return "", ErrInvalidKeyType 107 | } 108 | 109 | // Create the hasher 110 | if !m.Hash.Available() { 111 | return "", ErrHashUnavailable 112 | } 113 | 114 | hasher := m.Hash.New() 115 | hasher.Write([]byte(signingString)) 116 | 117 | // Sign the string and return r, s 118 | if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil { 119 | curveBits := ecdsaKey.Curve.Params().BitSize 120 | 121 | if m.CurveBits != curveBits { 122 | return "", ErrInvalidKey 123 | } 124 | 125 | keyBytes := curveBits / 8 126 | if curveBits%8 > 0 { 127 | keyBytes += 1 128 | } 129 | 130 | // We serialize the outpus (r and s) into big-endian byte arrays and pad 131 | // them with zeros on the left to make sure the sizes work out. Both arrays 132 | // must be keyBytes long, and the output must be 2*keyBytes long. 133 | rBytes := r.Bytes() 134 | rBytesPadded := make([]byte, keyBytes) 135 | copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) 136 | 137 | sBytes := s.Bytes() 138 | sBytesPadded := make([]byte, keyBytes) 139 | copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) 140 | 141 | out := append(rBytesPadded, sBytesPadded...) 142 | 143 | return EncodeSegment(out), nil 144 | } else { 145 | return "", err 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/x509" 6 | "encoding/pem" 7 | "errors" 8 | ) 9 | 10 | var ( 11 | ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key") 12 | ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key") 13 | ) 14 | 15 | // Parse PEM encoded Elliptic Curve Private Key Structure 16 | func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) { 17 | var err error 18 | 19 | // Parse PEM block 20 | var block *pem.Block 21 | if block, _ = pem.Decode(key); block == nil { 22 | return nil, ErrKeyMustBePEMEncoded 23 | } 24 | 25 | // Parse the key 26 | var parsedKey interface{} 27 | if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil { 28 | return nil, err 29 | } 30 | 31 | var pkey *ecdsa.PrivateKey 32 | var ok bool 33 | if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok { 34 | return nil, ErrNotECPrivateKey 35 | } 36 | 37 | return pkey, nil 38 | } 39 | 40 | // Parse PEM encoded PKCS1 or PKCS8 public key 41 | func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) { 42 | var err error 43 | 44 | // Parse PEM block 45 | var block *pem.Block 46 | if block, _ = pem.Decode(key); block == nil { 47 | return nil, ErrKeyMustBePEMEncoded 48 | } 49 | 50 | // Parse the key 51 | var parsedKey interface{} 52 | if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { 53 | if cert, err := x509.ParseCertificate(block.Bytes); err == nil { 54 | parsedKey = cert.PublicKey 55 | } else { 56 | return nil, err 57 | } 58 | } 59 | 60 | var pkey *ecdsa.PublicKey 61 | var ok bool 62 | if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok { 63 | return nil, ErrNotECPublicKey 64 | } 65 | 66 | return pkey, nil 67 | } 68 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/errors.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // Error constants 8 | var ( 9 | ErrInvalidKey = errors.New("key is invalid") 10 | ErrInvalidKeyType = errors.New("key is of invalid type") 11 | ErrHashUnavailable = errors.New("the requested hash function is unavailable") 12 | ) 13 | 14 | // The errors that might occur when parsing and validating a token 15 | const ( 16 | ValidationErrorMalformed uint32 = 1 << iota // Token is malformed 17 | ValidationErrorUnverifiable // Token could not be verified because of signing problems 18 | ValidationErrorSignatureInvalid // Signature validation failed 19 | 20 | // Standard Claim validation errors 21 | ValidationErrorAudience // AUD validation failed 22 | ValidationErrorExpired // EXP validation failed 23 | ValidationErrorIssuedAt // IAT validation failed 24 | ValidationErrorIssuer // ISS validation failed 25 | ValidationErrorNotValidYet // NBF validation failed 26 | ValidationErrorId // JTI validation failed 27 | ValidationErrorClaimsInvalid // Generic claims validation error 28 | ) 29 | 30 | // Helper for constructing a ValidationError with a string error message 31 | func NewValidationError(errorText string, errorFlags uint32) *ValidationError { 32 | return &ValidationError{ 33 | text: errorText, 34 | Errors: errorFlags, 35 | } 36 | } 37 | 38 | // The error from Parse if token is not valid 39 | type ValidationError struct { 40 | Inner error // stores the error returned by external dependencies, i.e.: KeyFunc 41 | Errors uint32 // bitfield. see ValidationError... constants 42 | text string // errors that do not have a valid error just have text 43 | } 44 | 45 | // Validation error is an error type 46 | func (e ValidationError) Error() string { 47 | if e.Inner != nil { 48 | return e.Inner.Error() 49 | } else if e.text != "" { 50 | return e.text 51 | } else { 52 | return "token is invalid" 53 | } 54 | return e.Inner.Error() 55 | } 56 | 57 | // No errors 58 | func (e *ValidationError) valid() bool { 59 | if e.Errors > 0 { 60 | return false 61 | } 62 | return true 63 | } 64 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/hmac.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "crypto" 5 | "crypto/hmac" 6 | "errors" 7 | ) 8 | 9 | // Implements the HMAC-SHA family of signing methods signing methods 10 | type SigningMethodHMAC struct { 11 | Name string 12 | Hash crypto.Hash 13 | } 14 | 15 | // Specific instances for HS256 and company 16 | var ( 17 | SigningMethodHS256 *SigningMethodHMAC 18 | SigningMethodHS384 *SigningMethodHMAC 19 | SigningMethodHS512 *SigningMethodHMAC 20 | ErrSignatureInvalid = errors.New("signature is invalid") 21 | ) 22 | 23 | func init() { 24 | // HS256 25 | SigningMethodHS256 = &SigningMethodHMAC{"HS256", crypto.SHA256} 26 | RegisterSigningMethod(SigningMethodHS256.Alg(), func() SigningMethod { 27 | return SigningMethodHS256 28 | }) 29 | 30 | // HS384 31 | SigningMethodHS384 = &SigningMethodHMAC{"HS384", crypto.SHA384} 32 | RegisterSigningMethod(SigningMethodHS384.Alg(), func() SigningMethod { 33 | return SigningMethodHS384 34 | }) 35 | 36 | // HS512 37 | SigningMethodHS512 = &SigningMethodHMAC{"HS512", crypto.SHA512} 38 | RegisterSigningMethod(SigningMethodHS512.Alg(), func() SigningMethod { 39 | return SigningMethodHS512 40 | }) 41 | } 42 | 43 | func (m *SigningMethodHMAC) Alg() string { 44 | return m.Name 45 | } 46 | 47 | // Verify the signature of HSXXX tokens. Returns nil if the signature is valid. 48 | func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error { 49 | // Verify the key is the right type 50 | keyBytes, ok := key.([]byte) 51 | if !ok { 52 | return ErrInvalidKeyType 53 | } 54 | 55 | // Decode signature, for comparison 56 | sig, err := DecodeSegment(signature) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | // Can we use the specified hashing method? 62 | if !m.Hash.Available() { 63 | return ErrHashUnavailable 64 | } 65 | 66 | // This signing method is symmetric, so we validate the signature 67 | // by reproducing the signature from the signing string and key, then 68 | // comparing that against the provided signature. 69 | hasher := hmac.New(m.Hash.New, keyBytes) 70 | hasher.Write([]byte(signingString)) 71 | if !hmac.Equal(sig, hasher.Sum(nil)) { 72 | return ErrSignatureInvalid 73 | } 74 | 75 | // No validation errors. Signature is good. 76 | return nil 77 | } 78 | 79 | // Implements the Sign method from SigningMethod for this signing method. 80 | // Key must be []byte 81 | func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) { 82 | if keyBytes, ok := key.([]byte); ok { 83 | if !m.Hash.Available() { 84 | return "", ErrHashUnavailable 85 | } 86 | 87 | hasher := hmac.New(m.Hash.New, keyBytes) 88 | hasher.Write([]byte(signingString)) 89 | 90 | return EncodeSegment(hasher.Sum(nil)), nil 91 | } 92 | 93 | return "", ErrInvalidKey 94 | } 95 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/map_claims.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | // "fmt" 7 | ) 8 | 9 | // Claims type that uses the map[string]interface{} for JSON decoding 10 | // This is the default claims type if you don't supply one 11 | type MapClaims map[string]interface{} 12 | 13 | // Compares the aud claim against cmp. 14 | // If required is false, this method will return true if the value matches or is unset 15 | func (m MapClaims) VerifyAudience(cmp string, req bool) bool { 16 | aud, _ := m["aud"].(string) 17 | return verifyAud(aud, cmp, req) 18 | } 19 | 20 | // Compares the exp claim against cmp. 21 | // If required is false, this method will return true if the value matches or is unset 22 | func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool { 23 | switch exp := m["exp"].(type) { 24 | case float64: 25 | return verifyExp(int64(exp), cmp, req) 26 | case json.Number: 27 | v, _ := exp.Int64() 28 | return verifyExp(v, cmp, req) 29 | } 30 | return req == false 31 | } 32 | 33 | // Compares the iat claim against cmp. 34 | // If required is false, this method will return true if the value matches or is unset 35 | func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool { 36 | switch iat := m["iat"].(type) { 37 | case float64: 38 | return verifyIat(int64(iat), cmp, req) 39 | case json.Number: 40 | v, _ := iat.Int64() 41 | return verifyIat(v, cmp, req) 42 | } 43 | return req == false 44 | } 45 | 46 | // Compares the iss claim against cmp. 47 | // If required is false, this method will return true if the value matches or is unset 48 | func (m MapClaims) VerifyIssuer(cmp string, req bool) bool { 49 | iss, _ := m["iss"].(string) 50 | return verifyIss(iss, cmp, req) 51 | } 52 | 53 | // Compares the nbf claim against cmp. 54 | // If required is false, this method will return true if the value matches or is unset 55 | func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool { 56 | switch nbf := m["nbf"].(type) { 57 | case float64: 58 | return verifyNbf(int64(nbf), cmp, req) 59 | case json.Number: 60 | v, _ := nbf.Int64() 61 | return verifyNbf(v, cmp, req) 62 | } 63 | return req == false 64 | } 65 | 66 | // Validates time based claims "exp, iat, nbf". 67 | // There is no accounting for clock skew. 68 | // As well, if any of the above claims are not in the token, it will still 69 | // be considered a valid claim. 70 | func (m MapClaims) Valid() error { 71 | vErr := new(ValidationError) 72 | now := TimeFunc().Unix() 73 | 74 | if m.VerifyExpiresAt(now, false) == false { 75 | vErr.Inner = errors.New("Token is expired") 76 | vErr.Errors |= ValidationErrorExpired 77 | } 78 | 79 | if m.VerifyIssuedAt(now, false) == false { 80 | vErr.Inner = errors.New("Token used before issued") 81 | vErr.Errors |= ValidationErrorIssuedAt 82 | } 83 | 84 | if m.VerifyNotBefore(now, false) == false { 85 | vErr.Inner = errors.New("Token is not valid yet") 86 | vErr.Errors |= ValidationErrorNotValidYet 87 | } 88 | 89 | if vErr.valid() { 90 | return nil 91 | } 92 | 93 | return vErr 94 | } 95 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/none.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | // Implements the none signing method. This is required by the spec 4 | // but you probably should never use it. 5 | var SigningMethodNone *signingMethodNone 6 | 7 | const UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed" 8 | 9 | var NoneSignatureTypeDisallowedError error 10 | 11 | type signingMethodNone struct{} 12 | type unsafeNoneMagicConstant string 13 | 14 | func init() { 15 | SigningMethodNone = &signingMethodNone{} 16 | NoneSignatureTypeDisallowedError = NewValidationError("'none' signature type is not allowed", ValidationErrorSignatureInvalid) 17 | 18 | RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod { 19 | return SigningMethodNone 20 | }) 21 | } 22 | 23 | func (m *signingMethodNone) Alg() string { 24 | return "none" 25 | } 26 | 27 | // Only allow 'none' alg type if UnsafeAllowNoneSignatureType is specified as the key 28 | func (m *signingMethodNone) Verify(signingString, signature string, key interface{}) (err error) { 29 | // Key must be UnsafeAllowNoneSignatureType to prevent accidentally 30 | // accepting 'none' signing method 31 | if _, ok := key.(unsafeNoneMagicConstant); !ok { 32 | return NoneSignatureTypeDisallowedError 33 | } 34 | // If signing method is none, signature must be an empty string 35 | if signature != "" { 36 | return NewValidationError( 37 | "'none' signing method with non-empty signature", 38 | ValidationErrorSignatureInvalid, 39 | ) 40 | } 41 | 42 | // Accept 'none' signing method. 43 | return nil 44 | } 45 | 46 | // Only allow 'none' signing if UnsafeAllowNoneSignatureType is specified as the key 47 | func (m *signingMethodNone) Sign(signingString string, key interface{}) (string, error) { 48 | if _, ok := key.(unsafeNoneMagicConstant); ok { 49 | return "", nil 50 | } 51 | return "", NoneSignatureTypeDisallowedError 52 | } 53 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/parser.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | type Parser struct { 11 | ValidMethods []string // If populated, only these methods will be considered valid 12 | UseJSONNumber bool // Use JSON Number format in JSON decoder 13 | SkipClaimsValidation bool // Skip claims validation during token parsing 14 | } 15 | 16 | // Parse, validate, and return a token. 17 | // keyFunc will receive the parsed token and should return the key for validating. 18 | // If everything is kosher, err will be nil 19 | func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { 20 | return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc) 21 | } 22 | 23 | func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) { 24 | parts := strings.Split(tokenString, ".") 25 | if len(parts) != 3 { 26 | return nil, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed) 27 | } 28 | 29 | var err error 30 | token := &Token{Raw: tokenString} 31 | 32 | // parse Header 33 | var headerBytes []byte 34 | if headerBytes, err = DecodeSegment(parts[0]); err != nil { 35 | if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") { 36 | return token, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed) 37 | } 38 | return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} 39 | } 40 | if err = json.Unmarshal(headerBytes, &token.Header); err != nil { 41 | return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} 42 | } 43 | 44 | // parse Claims 45 | var claimBytes []byte 46 | token.Claims = claims 47 | 48 | if claimBytes, err = DecodeSegment(parts[1]); err != nil { 49 | return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} 50 | } 51 | dec := json.NewDecoder(bytes.NewBuffer(claimBytes)) 52 | if p.UseJSONNumber { 53 | dec.UseNumber() 54 | } 55 | // JSON Decode. Special case for map type to avoid weird pointer behavior 56 | if c, ok := token.Claims.(MapClaims); ok { 57 | err = dec.Decode(&c) 58 | } else { 59 | err = dec.Decode(&claims) 60 | } 61 | // Handle decode error 62 | if err != nil { 63 | return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} 64 | } 65 | 66 | // Lookup signature method 67 | if method, ok := token.Header["alg"].(string); ok { 68 | if token.Method = GetSigningMethod(method); token.Method == nil { 69 | return token, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable) 70 | } 71 | } else { 72 | return token, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable) 73 | } 74 | 75 | // Verify signing method is in the required set 76 | if p.ValidMethods != nil { 77 | var signingMethodValid = false 78 | var alg = token.Method.Alg() 79 | for _, m := range p.ValidMethods { 80 | if m == alg { 81 | signingMethodValid = true 82 | break 83 | } 84 | } 85 | if !signingMethodValid { 86 | // signing method is not in the listed set 87 | return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid) 88 | } 89 | } 90 | 91 | // Lookup key 92 | var key interface{} 93 | if keyFunc == nil { 94 | // keyFunc was not provided. short circuiting validation 95 | return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable) 96 | } 97 | if key, err = keyFunc(token); err != nil { 98 | // keyFunc returned an error 99 | return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable} 100 | } 101 | 102 | vErr := &ValidationError{} 103 | 104 | // Validate Claims 105 | if !p.SkipClaimsValidation { 106 | if err := token.Claims.Valid(); err != nil { 107 | 108 | // If the Claims Valid returned an error, check if it is a validation error, 109 | // If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set 110 | if e, ok := err.(*ValidationError); !ok { 111 | vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid} 112 | } else { 113 | vErr = e 114 | } 115 | } 116 | } 117 | 118 | // Perform validation 119 | token.Signature = parts[2] 120 | if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil { 121 | vErr.Inner = err 122 | vErr.Errors |= ValidationErrorSignatureInvalid 123 | } 124 | 125 | if vErr.valid() { 126 | token.Valid = true 127 | return token, nil 128 | } 129 | 130 | return token, vErr 131 | } 132 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/rsa.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | ) 8 | 9 | // Implements the RSA family of signing methods signing methods 10 | type SigningMethodRSA struct { 11 | Name string 12 | Hash crypto.Hash 13 | } 14 | 15 | // Specific instances for RS256 and company 16 | var ( 17 | SigningMethodRS256 *SigningMethodRSA 18 | SigningMethodRS384 *SigningMethodRSA 19 | SigningMethodRS512 *SigningMethodRSA 20 | ) 21 | 22 | func init() { 23 | // RS256 24 | SigningMethodRS256 = &SigningMethodRSA{"RS256", crypto.SHA256} 25 | RegisterSigningMethod(SigningMethodRS256.Alg(), func() SigningMethod { 26 | return SigningMethodRS256 27 | }) 28 | 29 | // RS384 30 | SigningMethodRS384 = &SigningMethodRSA{"RS384", crypto.SHA384} 31 | RegisterSigningMethod(SigningMethodRS384.Alg(), func() SigningMethod { 32 | return SigningMethodRS384 33 | }) 34 | 35 | // RS512 36 | SigningMethodRS512 = &SigningMethodRSA{"RS512", crypto.SHA512} 37 | RegisterSigningMethod(SigningMethodRS512.Alg(), func() SigningMethod { 38 | return SigningMethodRS512 39 | }) 40 | } 41 | 42 | func (m *SigningMethodRSA) Alg() string { 43 | return m.Name 44 | } 45 | 46 | // Implements the Verify method from SigningMethod 47 | // For this signing method, must be an rsa.PublicKey structure. 48 | func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error { 49 | var err error 50 | 51 | // Decode the signature 52 | var sig []byte 53 | if sig, err = DecodeSegment(signature); err != nil { 54 | return err 55 | } 56 | 57 | var rsaKey *rsa.PublicKey 58 | var ok bool 59 | 60 | if rsaKey, ok = key.(*rsa.PublicKey); !ok { 61 | return ErrInvalidKeyType 62 | } 63 | 64 | // Create hasher 65 | if !m.Hash.Available() { 66 | return ErrHashUnavailable 67 | } 68 | hasher := m.Hash.New() 69 | hasher.Write([]byte(signingString)) 70 | 71 | // Verify the signature 72 | return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig) 73 | } 74 | 75 | // Implements the Sign method from SigningMethod 76 | // For this signing method, must be an rsa.PrivateKey structure. 77 | func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) { 78 | var rsaKey *rsa.PrivateKey 79 | var ok bool 80 | 81 | // Validate type of key 82 | if rsaKey, ok = key.(*rsa.PrivateKey); !ok { 83 | return "", ErrInvalidKey 84 | } 85 | 86 | // Create the hasher 87 | if !m.Hash.Available() { 88 | return "", ErrHashUnavailable 89 | } 90 | 91 | hasher := m.Hash.New() 92 | hasher.Write([]byte(signingString)) 93 | 94 | // Sign the string and return the encoded bytes 95 | if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil { 96 | return EncodeSegment(sigBytes), nil 97 | } else { 98 | return "", err 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/rsa_pss.go: -------------------------------------------------------------------------------- 1 | // +build go1.4 2 | 3 | package jwt 4 | 5 | import ( 6 | "crypto" 7 | "crypto/rand" 8 | "crypto/rsa" 9 | ) 10 | 11 | // Implements the RSAPSS family of signing methods signing methods 12 | type SigningMethodRSAPSS struct { 13 | *SigningMethodRSA 14 | Options *rsa.PSSOptions 15 | } 16 | 17 | // Specific instances for RS/PS and company 18 | var ( 19 | SigningMethodPS256 *SigningMethodRSAPSS 20 | SigningMethodPS384 *SigningMethodRSAPSS 21 | SigningMethodPS512 *SigningMethodRSAPSS 22 | ) 23 | 24 | func init() { 25 | // PS256 26 | SigningMethodPS256 = &SigningMethodRSAPSS{ 27 | &SigningMethodRSA{ 28 | Name: "PS256", 29 | Hash: crypto.SHA256, 30 | }, 31 | &rsa.PSSOptions{ 32 | SaltLength: rsa.PSSSaltLengthAuto, 33 | Hash: crypto.SHA256, 34 | }, 35 | } 36 | RegisterSigningMethod(SigningMethodPS256.Alg(), func() SigningMethod { 37 | return SigningMethodPS256 38 | }) 39 | 40 | // PS384 41 | SigningMethodPS384 = &SigningMethodRSAPSS{ 42 | &SigningMethodRSA{ 43 | Name: "PS384", 44 | Hash: crypto.SHA384, 45 | }, 46 | &rsa.PSSOptions{ 47 | SaltLength: rsa.PSSSaltLengthAuto, 48 | Hash: crypto.SHA384, 49 | }, 50 | } 51 | RegisterSigningMethod(SigningMethodPS384.Alg(), func() SigningMethod { 52 | return SigningMethodPS384 53 | }) 54 | 55 | // PS512 56 | SigningMethodPS512 = &SigningMethodRSAPSS{ 57 | &SigningMethodRSA{ 58 | Name: "PS512", 59 | Hash: crypto.SHA512, 60 | }, 61 | &rsa.PSSOptions{ 62 | SaltLength: rsa.PSSSaltLengthAuto, 63 | Hash: crypto.SHA512, 64 | }, 65 | } 66 | RegisterSigningMethod(SigningMethodPS512.Alg(), func() SigningMethod { 67 | return SigningMethodPS512 68 | }) 69 | } 70 | 71 | // Implements the Verify method from SigningMethod 72 | // For this verify method, key must be an rsa.PublicKey struct 73 | func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interface{}) error { 74 | var err error 75 | 76 | // Decode the signature 77 | var sig []byte 78 | if sig, err = DecodeSegment(signature); err != nil { 79 | return err 80 | } 81 | 82 | var rsaKey *rsa.PublicKey 83 | switch k := key.(type) { 84 | case *rsa.PublicKey: 85 | rsaKey = k 86 | default: 87 | return ErrInvalidKey 88 | } 89 | 90 | // Create hasher 91 | if !m.Hash.Available() { 92 | return ErrHashUnavailable 93 | } 94 | hasher := m.Hash.New() 95 | hasher.Write([]byte(signingString)) 96 | 97 | return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, m.Options) 98 | } 99 | 100 | // Implements the Sign method from SigningMethod 101 | // For this signing method, key must be an rsa.PrivateKey struct 102 | func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (string, error) { 103 | var rsaKey *rsa.PrivateKey 104 | 105 | switch k := key.(type) { 106 | case *rsa.PrivateKey: 107 | rsaKey = k 108 | default: 109 | return "", ErrInvalidKeyType 110 | } 111 | 112 | // Create the hasher 113 | if !m.Hash.Available() { 114 | return "", ErrHashUnavailable 115 | } 116 | 117 | hasher := m.Hash.New() 118 | hasher.Write([]byte(signingString)) 119 | 120 | // Sign the string and return the encoded bytes 121 | if sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil), m.Options); err == nil { 122 | return EncodeSegment(sigBytes), nil 123 | } else { 124 | return "", err 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/rsa_utils.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/x509" 6 | "encoding/pem" 7 | "errors" 8 | ) 9 | 10 | var ( 11 | ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key") 12 | ErrNotRSAPrivateKey = errors.New("Key is not a valid RSA private key") 13 | ErrNotRSAPublicKey = errors.New("Key is not a valid RSA public key") 14 | ) 15 | 16 | // Parse PEM encoded PKCS1 or PKCS8 private key 17 | func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) { 18 | var err error 19 | 20 | // Parse PEM block 21 | var block *pem.Block 22 | if block, _ = pem.Decode(key); block == nil { 23 | return nil, ErrKeyMustBePEMEncoded 24 | } 25 | 26 | var parsedKey interface{} 27 | if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil { 28 | if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { 29 | return nil, err 30 | } 31 | } 32 | 33 | var pkey *rsa.PrivateKey 34 | var ok bool 35 | if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok { 36 | return nil, ErrNotRSAPrivateKey 37 | } 38 | 39 | return pkey, nil 40 | } 41 | 42 | // Parse PEM encoded PKCS1 or PKCS8 public key 43 | func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) { 44 | var err error 45 | 46 | // Parse PEM block 47 | var block *pem.Block 48 | if block, _ = pem.Decode(key); block == nil { 49 | return nil, ErrKeyMustBePEMEncoded 50 | } 51 | 52 | // Parse the key 53 | var parsedKey interface{} 54 | if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { 55 | if cert, err := x509.ParseCertificate(block.Bytes); err == nil { 56 | parsedKey = cert.PublicKey 57 | } else { 58 | return nil, err 59 | } 60 | } 61 | 62 | var pkey *rsa.PublicKey 63 | var ok bool 64 | if pkey, ok = parsedKey.(*rsa.PublicKey); !ok { 65 | return nil, ErrNotRSAPublicKey 66 | } 67 | 68 | return pkey, nil 69 | } 70 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/signing_method.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var signingMethods = map[string]func() SigningMethod{} 8 | var signingMethodLock = new(sync.RWMutex) 9 | 10 | // Implement SigningMethod to add new methods for signing or verifying tokens. 11 | type SigningMethod interface { 12 | Verify(signingString, signature string, key interface{}) error // Returns nil if signature is valid 13 | Sign(signingString string, key interface{}) (string, error) // Returns encoded signature or error 14 | Alg() string // returns the alg identifier for this method (example: 'HS256') 15 | } 16 | 17 | // Register the "alg" name and a factory function for signing method. 18 | // This is typically done during init() in the method's implementation 19 | func RegisterSigningMethod(alg string, f func() SigningMethod) { 20 | signingMethodLock.Lock() 21 | defer signingMethodLock.Unlock() 22 | 23 | signingMethods[alg] = f 24 | } 25 | 26 | // Get a signing method from an "alg" string 27 | func GetSigningMethod(alg string) (method SigningMethod) { 28 | signingMethodLock.RLock() 29 | defer signingMethodLock.RUnlock() 30 | 31 | if methodF, ok := signingMethods[alg]; ok { 32 | method = methodF() 33 | } 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /vendor/github.com/dgrijalva/jwt-go/token.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | // TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time). 11 | // You can override it to use another time value. This is useful for testing or if your 12 | // server uses a different time zone than your tokens. 13 | var TimeFunc = time.Now 14 | 15 | // Parse methods use this callback function to supply 16 | // the key for verification. The function receives the parsed, 17 | // but unverified Token. This allows you to use properties in the 18 | // Header of the token (such as `kid`) to identify which key to use. 19 | type Keyfunc func(*Token) (interface{}, error) 20 | 21 | // A JWT Token. Different fields will be used depending on whether you're 22 | // creating or parsing/verifying a token. 23 | type Token struct { 24 | Raw string // The raw token. Populated when you Parse a token 25 | Method SigningMethod // The signing method used or to be used 26 | Header map[string]interface{} // The first segment of the token 27 | Claims Claims // The second segment of the token 28 | Signature string // The third segment of the token. Populated when you Parse a token 29 | Valid bool // Is the token valid? Populated when you Parse/Verify a token 30 | } 31 | 32 | // Create a new Token. Takes a signing method 33 | func New(method SigningMethod) *Token { 34 | return NewWithClaims(method, MapClaims{}) 35 | } 36 | 37 | func NewWithClaims(method SigningMethod, claims Claims) *Token { 38 | return &Token{ 39 | Header: map[string]interface{}{ 40 | "typ": "JWT", 41 | "alg": method.Alg(), 42 | }, 43 | Claims: claims, 44 | Method: method, 45 | } 46 | } 47 | 48 | // Get the complete, signed token 49 | func (t *Token) SignedString(key interface{}) (string, error) { 50 | var sig, sstr string 51 | var err error 52 | if sstr, err = t.SigningString(); err != nil { 53 | return "", err 54 | } 55 | if sig, err = t.Method.Sign(sstr, key); err != nil { 56 | return "", err 57 | } 58 | return strings.Join([]string{sstr, sig}, "."), nil 59 | } 60 | 61 | // Generate the signing string. This is the 62 | // most expensive part of the whole deal. Unless you 63 | // need this for something special, just go straight for 64 | // the SignedString. 65 | func (t *Token) SigningString() (string, error) { 66 | var err error 67 | parts := make([]string, 2) 68 | for i, _ := range parts { 69 | var jsonValue []byte 70 | if i == 0 { 71 | if jsonValue, err = json.Marshal(t.Header); err != nil { 72 | return "", err 73 | } 74 | } else { 75 | if jsonValue, err = json.Marshal(t.Claims); err != nil { 76 | return "", err 77 | } 78 | } 79 | 80 | parts[i] = EncodeSegment(jsonValue) 81 | } 82 | return strings.Join(parts, "."), nil 83 | } 84 | 85 | // Parse, validate, and return a token. 86 | // keyFunc will receive the parsed token and should return the key for validating. 87 | // If everything is kosher, err will be nil 88 | func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { 89 | return new(Parser).Parse(tokenString, keyFunc) 90 | } 91 | 92 | func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) { 93 | return new(Parser).ParseWithClaims(tokenString, claims, keyFunc) 94 | } 95 | 96 | // Encode JWT specific base64url encoding with padding stripped 97 | func EncodeSegment(seg []byte) string { 98 | return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=") 99 | } 100 | 101 | // Decode JWT specific base64url encoding with padding stripped 102 | func DecodeSegment(seg string) ([]byte, error) { 103 | if l := len(seg) % 4; l > 0 { 104 | seg += strings.Repeat("=", 4-l) 105 | } 106 | 107 | return base64.URLEncoding.DecodeString(seg) 108 | } 109 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/README.md: -------------------------------------------------------------------------------- 1 | context 2 | ======= 3 | [![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context) 4 | 5 | gorilla/context is a general purpose registry for global request variables. 6 | 7 | > Note: gorilla/context, having been born well before `context.Context` existed, does not play well 8 | > with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`. 9 | 10 | Read the full documentation here: http://www.gorillatoolkit.org/pkg/context 11 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package context 6 | 7 | import ( 8 | "net/http" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | var ( 14 | mutex sync.RWMutex 15 | data = make(map[*http.Request]map[interface{}]interface{}) 16 | datat = make(map[*http.Request]int64) 17 | ) 18 | 19 | // Set stores a value for a given key in a given request. 20 | func Set(r *http.Request, key, val interface{}) { 21 | mutex.Lock() 22 | if data[r] == nil { 23 | data[r] = make(map[interface{}]interface{}) 24 | datat[r] = time.Now().Unix() 25 | } 26 | data[r][key] = val 27 | mutex.Unlock() 28 | } 29 | 30 | // Get returns a value stored for a given key in a given request. 31 | func Get(r *http.Request, key interface{}) interface{} { 32 | mutex.RLock() 33 | if ctx := data[r]; ctx != nil { 34 | value := ctx[key] 35 | mutex.RUnlock() 36 | return value 37 | } 38 | mutex.RUnlock() 39 | return nil 40 | } 41 | 42 | // GetOk returns stored value and presence state like multi-value return of map access. 43 | func GetOk(r *http.Request, key interface{}) (interface{}, bool) { 44 | mutex.RLock() 45 | if _, ok := data[r]; ok { 46 | value, ok := data[r][key] 47 | mutex.RUnlock() 48 | return value, ok 49 | } 50 | mutex.RUnlock() 51 | return nil, false 52 | } 53 | 54 | // GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. 55 | func GetAll(r *http.Request) map[interface{}]interface{} { 56 | mutex.RLock() 57 | if context, ok := data[r]; ok { 58 | result := make(map[interface{}]interface{}, len(context)) 59 | for k, v := range context { 60 | result[k] = v 61 | } 62 | mutex.RUnlock() 63 | return result 64 | } 65 | mutex.RUnlock() 66 | return nil 67 | } 68 | 69 | // GetAllOk returns all stored values for the request as a map and a boolean value that indicates if 70 | // the request was registered. 71 | func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { 72 | mutex.RLock() 73 | context, ok := data[r] 74 | result := make(map[interface{}]interface{}, len(context)) 75 | for k, v := range context { 76 | result[k] = v 77 | } 78 | mutex.RUnlock() 79 | return result, ok 80 | } 81 | 82 | // Delete removes a value stored for a given key in a given request. 83 | func Delete(r *http.Request, key interface{}) { 84 | mutex.Lock() 85 | if data[r] != nil { 86 | delete(data[r], key) 87 | } 88 | mutex.Unlock() 89 | } 90 | 91 | // Clear removes all values stored for a given request. 92 | // 93 | // This is usually called by a handler wrapper to clean up request 94 | // variables at the end of a request lifetime. See ClearHandler(). 95 | func Clear(r *http.Request) { 96 | mutex.Lock() 97 | clear(r) 98 | mutex.Unlock() 99 | } 100 | 101 | // clear is Clear without the lock. 102 | func clear(r *http.Request) { 103 | delete(data, r) 104 | delete(datat, r) 105 | } 106 | 107 | // Purge removes request data stored for longer than maxAge, in seconds. 108 | // It returns the amount of requests removed. 109 | // 110 | // If maxAge <= 0, all request data is removed. 111 | // 112 | // This is only used for sanity check: in case context cleaning was not 113 | // properly set some request data can be kept forever, consuming an increasing 114 | // amount of memory. In case this is detected, Purge() must be called 115 | // periodically until the problem is fixed. 116 | func Purge(maxAge int) int { 117 | mutex.Lock() 118 | count := 0 119 | if maxAge <= 0 { 120 | count = len(data) 121 | data = make(map[*http.Request]map[interface{}]interface{}) 122 | datat = make(map[*http.Request]int64) 123 | } else { 124 | min := time.Now().Unix() - int64(maxAge) 125 | for r := range data { 126 | if datat[r] < min { 127 | clear(r) 128 | count++ 129 | } 130 | } 131 | } 132 | mutex.Unlock() 133 | return count 134 | } 135 | 136 | // ClearHandler wraps an http.Handler and clears request values at the end 137 | // of a request lifetime. 138 | func ClearHandler(h http.Handler) http.Handler { 139 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 140 | defer Clear(r) 141 | h.ServeHTTP(w, r) 142 | }) 143 | } 144 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/context/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package context stores values shared during a request lifetime. 7 | 8 | Note: gorilla/context, having been born well before `context.Context` existed, 9 | does not play well > with the shallow copying of the request that 10 | [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) 11 | (added to net/http Go 1.7 onwards) performs. You should either use *just* 12 | gorilla/context, or moving forward, the new `http.Request.Context()`. 13 | 14 | For example, a router can set variables extracted from the URL and later 15 | application handlers can access those values, or it can be used to store 16 | sessions values to be saved at the end of a request. There are several 17 | others common uses. 18 | 19 | The idea was posted by Brad Fitzpatrick to the go-nuts mailing list: 20 | 21 | http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53 22 | 23 | Here's the basic usage: first define the keys that you will need. The key 24 | type is interface{} so a key can be of any type that supports equality. 25 | Here we define a key using a custom int type to avoid name collisions: 26 | 27 | package foo 28 | 29 | import ( 30 | "github.com/gorilla/context" 31 | ) 32 | 33 | type key int 34 | 35 | const MyKey key = 0 36 | 37 | Then set a variable. Variables are bound to an http.Request object, so you 38 | need a request instance to set a value: 39 | 40 | context.Set(r, MyKey, "bar") 41 | 42 | The application can later access the variable using the same key you provided: 43 | 44 | func MyHandler(w http.ResponseWriter, r *http.Request) { 45 | // val is "bar". 46 | val := context.Get(r, foo.MyKey) 47 | 48 | // returns ("bar", true) 49 | val, ok := context.GetOk(r, foo.MyKey) 50 | // ... 51 | } 52 | 53 | And that's all about the basic usage. We discuss some other ideas below. 54 | 55 | Any type can be stored in the context. To enforce a given type, make the key 56 | private and wrap Get() and Set() to accept and return values of a specific 57 | type: 58 | 59 | type key int 60 | 61 | const mykey key = 0 62 | 63 | // GetMyKey returns a value for this package from the request values. 64 | func GetMyKey(r *http.Request) SomeType { 65 | if rv := context.Get(r, mykey); rv != nil { 66 | return rv.(SomeType) 67 | } 68 | return nil 69 | } 70 | 71 | // SetMyKey sets a value for this package in the request values. 72 | func SetMyKey(r *http.Request, val SomeType) { 73 | context.Set(r, mykey, val) 74 | } 75 | 76 | Variables must be cleared at the end of a request, to remove all values 77 | that were stored. This can be done in an http.Handler, after a request was 78 | served. Just call Clear() passing the request: 79 | 80 | context.Clear(r) 81 | 82 | ...or use ClearHandler(), which conveniently wraps an http.Handler to clear 83 | variables at the end of a request lifetime. 84 | 85 | The Routers from the packages gorilla/mux and gorilla/pat call Clear() 86 | so if you are using either of them you don't need to clear the context manually. 87 | */ 88 | package context 89 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/context_gorilla.go: -------------------------------------------------------------------------------- 1 | // +build !go1.7 2 | 3 | package mux 4 | 5 | import ( 6 | "net/http" 7 | 8 | "github.com/gorilla/context" 9 | ) 10 | 11 | func contextGet(r *http.Request, key interface{}) interface{} { 12 | return context.Get(r, key) 13 | } 14 | 15 | func contextSet(r *http.Request, key, val interface{}) *http.Request { 16 | if val == nil { 17 | return r 18 | } 19 | 20 | context.Set(r, key, val) 21 | return r 22 | } 23 | 24 | func contextClear(r *http.Request) { 25 | context.Clear(r) 26 | } 27 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/mux/context_native.go: -------------------------------------------------------------------------------- 1 | // +build go1.7 2 | 3 | package mux 4 | 5 | import ( 6 | "context" 7 | "net/http" 8 | ) 9 | 10 | func contextGet(r *http.Request, key interface{}) interface{} { 11 | return r.Context().Value(key) 12 | } 13 | 14 | func contextSet(r *http.Request, key, val interface{}) *http.Request { 15 | if val == nil { 16 | return r 17 | } 18 | 19 | return r.WithContext(context.WithValue(r.Context(), key, val)) 20 | } 21 | 22 | func contextClear(r *http.Request) { 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Dave Cheney 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/README.md: -------------------------------------------------------------------------------- 1 | # errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) 2 | 3 | Package errors provides simple error handling primitives. 4 | 5 | `go get github.com/pkg/errors` 6 | 7 | The traditional error handling idiom in Go is roughly akin to 8 | ```go 9 | if err != nil { 10 | return err 11 | } 12 | ``` 13 | which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. 14 | 15 | ## Adding context to an error 16 | 17 | The errors.Wrap function returns a new error that adds context to the original error. For example 18 | ```go 19 | _, err := ioutil.ReadAll(r) 20 | if err != nil { 21 | return errors.Wrap(err, "read failed") 22 | } 23 | ``` 24 | ## Retrieving the cause of an error 25 | 26 | Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. 27 | ```go 28 | type causer interface { 29 | Cause() error 30 | } 31 | ``` 32 | `errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: 33 | ```go 34 | switch err := errors.Cause(err).(type) { 35 | case *MyError: 36 | // handle specifically 37 | default: 38 | // unknown error 39 | } 40 | ``` 41 | 42 | [Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). 43 | 44 | ## Contributing 45 | 46 | We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. 47 | 48 | Before proposing a change, please discuss your change by raising an issue. 49 | 50 | ## Licence 51 | 52 | BSD-2-Clause 53 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/appveyor.yml: -------------------------------------------------------------------------------- 1 | version: build-{build}.{branch} 2 | 3 | clone_folder: C:\gopath\src\github.com\pkg\errors 4 | shallow_clone: true # for startup speed 5 | 6 | environment: 7 | GOPATH: C:\gopath 8 | 9 | platform: 10 | - x64 11 | 12 | # http://www.appveyor.com/docs/installed-software 13 | install: 14 | # some helpful output for debugging builds 15 | - go version 16 | - go env 17 | # pre-installed MinGW at C:\MinGW is 32bit only 18 | # but MSYS2 at C:\msys64 has mingw64 19 | - set PATH=C:\msys64\mingw64\bin;%PATH% 20 | - gcc --version 21 | - g++ --version 22 | 23 | build_script: 24 | - go install -v ./... 25 | 26 | test_script: 27 | - set PATH=C:\gopath\bin;%PATH% 28 | - go test -v ./... 29 | 30 | #artifacts: 31 | # - path: '%GOPATH%\bin\*.exe' 32 | deploy: off 33 | -------------------------------------------------------------------------------- /vendor/github.com/pmezard/go-difflib/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Patrick Mezard 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | The names of its contributors may not be used to endorse or promote 14 | products derived from this software without specific prior written 15 | permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 18 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 23 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/russross/blackfriday/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Blackfriday is distributed under the Simplified BSD License: 2 | 3 | > Copyright © 2011 Russ Ross 4 | > All rights reserved. 5 | > 6 | > Redistribution and use in source and binary forms, with or without 7 | > modification, are permitted provided that the following conditions 8 | > are met: 9 | > 10 | > 1. Redistributions of source code must retain the above copyright 11 | > notice, this list of conditions and the following disclaimer. 12 | > 13 | > 2. Redistributions in binary form must reproduce the above 14 | > copyright notice, this list of conditions and the following 15 | > disclaimer in the documentation and/or other materials provided with 16 | > the distribution. 17 | > 18 | > THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | > "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | > LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | > FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | > COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | > INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | > BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | > LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | > CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | > LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | > ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | > POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /vendor/github.com/sendgrid/rest/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## [2.2.0] - 2016-07-28 7 | ### Added 8 | - Pull [#9](https://github.com/sendgrid/rest/pull/9): Allow for setting a custom HTTP client 9 | - [Here](https://github.com/sendgrid/rest/blob/master/rest_test.go#L127) is an example of usage 10 | - This enables usage of the [sendgrid-go library](https://github.com/sendgrid/sendgrid-go) on [Google App Engine (GAE)](https://cloud.google.com/appengine/) 11 | - Special thanks to [Chris Broadfoot](https://github.com/broady) and [Sridhar Venkatakrishnan](https://github.com/sridharv) for providing code and feedback! 12 | 13 | ## [2.1.0] - 2016-06-10 14 | ### Added 15 | - Automatically add Content-Type: application/json when there is a request body 16 | 17 | ## [2.0.0] - 2016-06-03 18 | ### Changed 19 | - Made the Request and Response variables non-redundant. e.g. request.RequestBody becomes request.Body 20 | 21 | ## [1.0.2] - 2016-04-07 22 | ### Added 23 | - these changes are thanks to [deckarep](https://github.com/deckarep). Thanks! 24 | - more updates to error naming convention 25 | - more error handing on HTTP request 26 | 27 | ## [1.0.1] - 2016-04-07 28 | ### Added 29 | - these changes are thanks to [deckarep](https://github.com/deckarep). Thanks! 30 | - update error naming convention 31 | - explicitely define supported HTTP verbs 32 | - better error handing on HTTP request 33 | 34 | ## [1.0.0] - 2016-04-05 35 | ### Added 36 | - We are live! 37 | -------------------------------------------------------------------------------- /vendor/github.com/sendgrid/rest/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 SendGrid, Inc. 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 | -------------------------------------------------------------------------------- /vendor/github.com/sendgrid/rest/README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/sendgrid/rest.svg?branch=master)](https://travis-ci.org/sendgrid/rest) [![GoDoc](https://godoc.org/github.com/sendgrid/rest?status.png)](http://godoc.org/github.com/sendgrid/rest) 2 | 3 | **Quickly and easily access any RESTful or RESTful-like API.** 4 | 5 | If you are looking for the SendGrid API client library, please see [this repo](https://github.com/sendgrid/sendgrid-go). 6 | 7 | # Announcements 8 | 9 | All updates to this library is documented in our [CHANGELOG](https://github.com/sendgrid/rest/blob/master/CHANGELOG.md). 10 | 11 | # Installation 12 | 13 | ## Prerequisites 14 | 15 | - Go version 1.6 16 | 17 | ## Install Package 18 | 19 | ```bash 20 | go get github.com/sendgrid/rest 21 | ``` 22 | 23 | # Quick Start 24 | 25 | `GET /your/api/{param}/call` 26 | 27 | ```go 28 | package main 29 | 30 | import "github.com/sendgrid/rest" 31 | import "fmt" 32 | 33 | func main() { 34 | const host = "https://api.example.com" 35 | param := "myparam" 36 | endpoint := "/your/api/" + param + "/call" 37 | baseURL := host + endpoint 38 | method := rest.Get 39 | request := rest.Request{ 40 | Method: method, 41 | BaseURL: baseURL, 42 | } 43 | response, err := rest.API(request) 44 | if err != nil { 45 | fmt.Println(err) 46 | } else { 47 | fmt.Println(response.StatusCode) 48 | fmt.Println(response.Body) 49 | fmt.Println(response.Headers) 50 | } 51 | } 52 | ``` 53 | 54 | `POST /your/api/{param}/call` with headers, query parameters and a request body. 55 | 56 | ```go 57 | package main 58 | 59 | import "github.com/sendgrid/rest" 60 | import "fmt" 61 | 62 | func main() { 63 | const host = "https://api.example.com" 64 | param := "myparam" 65 | endpoint := "/your/api/" + param + "/call" 66 | baseURL := host + endpoint 67 | Headers := make(map[string]string) 68 | key := os.Getenv("API_KEY") 69 | Headers["Authorization"] = "Bearer " + key 70 | Headers["X-Test"] = "Test" 71 | var Body = []byte(`{"some": 0, "awesome": 1, "data": 3}`) 72 | queryParams := make(map[string]string) 73 | queryParams["hello"] = "0" 74 | queryParams["world"] = "1" 75 | method := rest.Post 76 | request = rest.Request{ 77 | Method: method, 78 | BaseURL: baseURL, 79 | Headers: Headers, 80 | QueryParams: queryParams, 81 | Body: Body, 82 | } 83 | response, err := rest.API(request) 84 | if err != nil { 85 | fmt.Println(err) 86 | } else { 87 | fmt.Println(response.StatusCode) 88 | fmt.Println(response.Body) 89 | fmt.Println(response.Headers) 90 | } 91 | } 92 | ``` 93 | 94 | # Usage 95 | 96 | - [Example Code](https://github.com/sendgrid/rest/tree/master/examples) 97 | 98 | ## Roadmap 99 | 100 | If you are interested in the future direction of this project, please take a look at our [milestones](https://github.com/sendgrid/rest/milestones). We would love to hear your feedback. 101 | 102 | ## How to Contribute 103 | 104 | We encourage contribution to our projects, please see our [CONTRIBUTING](https://github.com/sendgrid/rest/blob/master/CONTRIBUTING.md) guide for details. 105 | 106 | Quick links: 107 | 108 | - [Feature Request](https://github.com/sendgrid/rest/blob/master/CONTRIBUTING.md#feature_request) 109 | - [Bug Reports](https://github.com/sendgrid/rest/blob/master/CONTRIBUTING.md#submit_a_bug_report) 110 | - [Sign the CLA to Create a Pull Request](https://github.com/sendgrid/rest/blob/master/CONTRIBUTING.md#cla) 111 | - [Improvements to the Codebase](https://github.com/sendgrid/rest/blob/master/CONTRIBUTING.md#improvements_to_the_codebase) 112 | 113 | # About 114 | 115 | rest is guided and supported by the SendGrid [Developer Experience Team](mailto:dx@sendgrid.com). 116 | 117 | rest is maintained and funded by SendGrid, Inc. The names and logos for rest are trademarks of SendGrid, Inc. 118 | 119 | ![SendGrid Logo] 120 | (https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.png) 121 | -------------------------------------------------------------------------------- /vendor/github.com/sendgrid/rest/rest.go: -------------------------------------------------------------------------------- 1 | // Package rest allows for quick and easy access any REST or REST-like API. 2 | package rest 3 | 4 | import ( 5 | "bytes" 6 | "io/ioutil" 7 | "net/http" 8 | "net/url" 9 | ) 10 | 11 | // Method contains the supported HTTP verbs. 12 | type Method string 13 | 14 | // Supported HTTP verbs. 15 | const ( 16 | Get Method = "GET" 17 | Post Method = "POST" 18 | Put Method = "PUT" 19 | Patch Method = "PATCH" 20 | Delete Method = "DELETE" 21 | ) 22 | 23 | // Request holds the request to an API Call. 24 | type Request struct { 25 | Method Method 26 | BaseURL string // e.g. https://api.sendgrid.com 27 | Headers map[string]string 28 | QueryParams map[string]string 29 | Body []byte 30 | } 31 | 32 | // DefaultClient is used if no custom HTTP client is defined 33 | var DefaultClient = &Client{HTTPClient: http.DefaultClient} 34 | 35 | // Client allows modification of client headers, redirect policy 36 | // and other settings 37 | // See https://golang.org/pkg/net/http 38 | type Client struct { 39 | HTTPClient *http.Client 40 | } 41 | 42 | // Response holds the response from an API call. 43 | type Response struct { 44 | StatusCode int // e.g. 200 45 | Body string // e.g. {"result: success"} 46 | Headers map[string][]string // e.g. map[X-Ratelimit-Limit:[600]] 47 | } 48 | 49 | // AddQueryParameters adds query paramaters to the URL. 50 | func AddQueryParameters(baseURL string, queryParams map[string]string) string { 51 | baseURL += "?" 52 | params := url.Values{} 53 | for key, value := range queryParams { 54 | params.Add(key, value) 55 | } 56 | return baseURL + params.Encode() 57 | } 58 | 59 | // BuildRequestObject creates the HTTP request object. 60 | func BuildRequestObject(request Request) (*http.Request, error) { 61 | req, err := http.NewRequest(string(request.Method), request.BaseURL, bytes.NewBuffer(request.Body)) 62 | for key, value := range request.Headers { 63 | req.Header.Set(key, value) 64 | } 65 | if len(request.Body) > 0 { 66 | req.Header.Set("Content-Type", "application/json") 67 | } 68 | return req, err 69 | } 70 | 71 | // MakeRequest makes the API call. 72 | func MakeRequest(req *http.Request) (*http.Response, error) { 73 | return DefaultClient.HTTPClient.Do(req) 74 | } 75 | 76 | // BuildResponse builds the response struct. 77 | func BuildResponse(res *http.Response) (*Response, error) { 78 | body, err := ioutil.ReadAll(res.Body) 79 | if err != nil { 80 | return nil, err 81 | } 82 | defer res.Body.Close() 83 | response := Response{ 84 | StatusCode: res.StatusCode, 85 | Body: string(body), 86 | Headers: res.Header, 87 | } 88 | return &response, nil 89 | } 90 | 91 | // API is the main interface to the API. 92 | func API(request Request) (*Response, error) { 93 | return DefaultClient.API(request) 94 | } 95 | 96 | // The following functions enable the ability to define a 97 | // custom HTTP Client 98 | 99 | // MakeRequest makes the API call. 100 | func (c *Client) MakeRequest(req *http.Request) (*http.Response, error) { 101 | return c.HTTPClient.Do(req) 102 | } 103 | 104 | // API is the main interface to the API. 105 | func (c *Client) API(request Request) (*Response, error) { 106 | // Add any query parameters to the URL. 107 | if len(request.QueryParams) != 0 { 108 | request.BaseURL = AddQueryParameters(request.BaseURL, request.QueryParams) 109 | } 110 | 111 | // Build the HTTP request object. 112 | req, err := BuildRequestObject(request) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | // Build the HTTP client and make the request. 118 | res, err := c.MakeRequest(req) 119 | if err != nil { 120 | return nil, err 121 | } 122 | 123 | // Build Response object. 124 | response, err := BuildResponse(res) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | return response, nil 130 | } 131 | -------------------------------------------------------------------------------- /vendor/github.com/sendgrid/sendgrid-go/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [3.2.2] - 2016-09-08 5 | ### Added 6 | - Merged pull request: [update prismPath and update prism binary](https://github.com/sendgrid/sendgrid-go/pull/80) 7 | - Special thanks to [Tom Pytleski](https://github.com/pytlesk4) for the pull request! 8 | 9 | ## [3.2.1] - 2016-08-24 10 | ### Added 11 | - Table of Contents in the README 12 | - Added a [USE_CASES.md](https://github.com/sendgrid/sendgrid-go/blob/master/USE_CASES.md) section, with the first use case example for transactional templates 13 | 14 | ## [3.2.0] - 2016-08-17 15 | ### Added 16 | - Merged pull request: [make contents var args in NewV3MailInit](https://github.com/sendgrid/sendgrid-go/pull/75) 17 | - The `NewV3MailInit` [Mail Helper](https://github.com/sendgrid/sendgrid-go/tree/master/helpers/mail) constructor can now take in multiple content objects. 18 | - Thanks to [Adrien Delorme](https://github.com/azr) for the pull request! 19 | 20 | ## [3.1.0] - 2016-07-28 21 | - Dependency update to v2.2.0 of [sendGrid-rest](https://github.com/sendgrid/rest/releases/tag/v2.2.0) 22 | - Pull [#9](https://github.com/sendgrid/rest/pull/9): Allow for setting a custom HTTP client 23 | - [Here](https://github.com/sendgrid/rest/blob/master/rest_test.go#L127) is an example of usage 24 | - This enables usage of the [sendgrid-go library](https://github.com/sendgrid/sendgrid-go) on [Google App Engine (GAE)](https://cloud.google.com/appengine/) 25 | - Special thanks to [Chris Broadfoot](https://github.com/broady) and [Sridhar Venkatakrishnan](https://github.com/sridharv) for providing code and feedback! 26 | 27 | ## [3.0.6] - 2016-07-26 ## 28 | ### Added 29 | - [Troubleshooting](https://github.com/sendgrid/sendgrid-go/blob/master/TROUBLESHOOTING.md) section 30 | 31 | ## [3.0.5] - 2016-07-20 32 | ### Added 33 | - README updates 34 | - Update introduction blurb to include information regarding our forward path 35 | - Update the v3 /mail/send example to include non-helper usage 36 | - Update the generic v3 example to include non-fluent interface usage 37 | 38 | ## [3.0.4] - 2016-07-12 39 | ### Added 40 | - Update docs, unit tests and examples to include Sender ID 41 | ### Fixed 42 | - Missing example query params for the examples 43 | 44 | ## [3.0.3] - 2016-07-08 45 | ### Fixed 46 | - [Can't disable subscription tracking #68](https://github.com/sendgrid/sendgrid-go/issues/68) 47 | 48 | ## [3.0.2] - 2016-07-07 49 | ### Added 50 | - Tests now mocked automatically against [prism](https://stoplight.io/prism/) 51 | 52 | ## [3.0.1] - 2016-07-05 53 | ### Added 54 | - Accept: application/json header per https://sendgrid.com/docs/API_Reference/Web_API_v3/How_To_Use_The_Web_API_v3/requests.html 55 | 56 | ### Updated 57 | - Content based on our updated [Swagger/OAI doc](https://github.com/sendgrid/sendgrid-oai) 58 | 59 | ## [3.0.0] - 2016-06-14 60 | ### Added 61 | - Breaking change to support the v3 Web API 62 | - New HTTP client 63 | - v3 Mail Send helper 64 | 65 | ## [2.0.0] - 2015-05-02 66 | ### Changed 67 | - Fixed a nasty bug with orphaned connections but drops support for Go versions < 1.3. Thanks [trinchan](https://github.com/sendgrid/sendgrid-go/pull/24) 68 | 69 | ## [1.2.0] - 2015-04-27 70 | ### Added 71 | - Support for API keys 72 | 73 | -------------------------------------------------------------------------------- /vendor/github.com/sendgrid/sendgrid-go/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2016 SendGrid, Inc. 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 | -------------------------------------------------------------------------------- /vendor/github.com/sendgrid/sendgrid-go/TROUBLESHOOTING.md: -------------------------------------------------------------------------------- 1 | If you have a non-library SendGrid issue, please contact our [support team](https://support.sendgrid.com). 2 | 3 | If you can't find a solution below, please open an [issue](https://github.com/sendgrid/sendgrid-go/issues). 4 | 5 | 6 | ## Table of Contents 7 | 8 | * [Migrating from v2 to v3](#migrating) 9 | * [Continue Using v2](#v2) 10 | * [Testing v3 /mail/send Calls Directly](#testing) 11 | * [Error Messages](#error) 12 | * [Versions](#versions) 13 | * [Environment Variables and Your SendGrid API Key](#environment) 14 | 15 | 16 | ## Migrating from v2 to v3 17 | 18 | Please review [our guide](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/how_to_migrate_from_v2_to_v3_mail_send.html) on how to migrate from v2 to v3. 19 | 20 | 21 | ## Continue Using v2 22 | 23 | [Here](https://github.com/sendgrid/sendgrid-go/tree/0bf6332788d0230b7da84a1ae68d7531073200e1) is the last working version with v2 support. 24 | 25 | Download: 26 | 27 | Click the "Clone or download" green button in [GitHub](https://github.com/sendgrid/sendgrid-go/tree/0bf6332788d0230b7da84a1ae68d7531073200e1) and choose download. 28 | 29 | 30 | ## Testing v3 /mail/send Calls Directly 31 | 32 | [Here](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/curl_examples.html) are some cURL examples for common use cases. 33 | 34 | 35 | ## Error Messages 36 | 37 | To read the error message returned by SendGrid's API: 38 | 39 | ```go 40 | func main() { 41 | from := mail.NewEmail("Example User", "test@example.com") 42 | subject := "Hello World from the SendGrid Go Library" 43 | to := mail.NewEmail("Example User", "test@example.com") 44 | content := mail.NewContent("text/plain", "some text here") 45 | m := mail.NewV3MailInit(from, subject, to, content) 46 | 47 | request := sendgrid.GetRequest(os.Getenv("SENDGRID_API_KE"), "/v3/mail/send", "https://api.sendgrid.com") 48 | request.Method = "POST" 49 | request.Body = mail.GetRequestBody(m) 50 | response, err := sendgrid.API(request) 51 | if err != nil { 52 | fmt.Println(err) 53 | } else { 54 | fmt.Println(response.StatusCode) 55 | fmt.Println(response.Body) 56 | fmt.Println(response.Headers) 57 | } 58 | } 59 | ``` 60 | 61 | 62 | ## Versions 63 | 64 | We follow the MAJOR.MINOR.PATCH versioning scheme as described by [SemVer.org](http://semver.org). Therefore, we recommend that you always pin (or vendor) the particular version you are working with to your code and never auto-update to the latest version. Especially when there is a MAJOR point release, since that is guarenteed to be a breaking change. Changes are documented in the [CHANGELOG](https://github.com/sendgrid/sendgrid-go/blob/master/CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-go/releases) section. 65 | 66 | 67 | ## Environment Variables and Your SendGrid API Key 68 | 69 | All of our examples assume you are using [environment variables](https://github.com/sendgrid/sendgrid-go#setup-environment-variables) to hold your SendGrid API key. 70 | 71 | If you choose to add your SendGrid API key directly (not recommended): 72 | 73 | `os.Getenv("SENDGRID_API_KEY")` 74 | 75 | becomes 76 | 77 | `"SENDGRID_API_KEY"` 78 | 79 | In the first case SENDGRID_API_KEY is in reference to the name of the environment variable, while the second case references the actual SendGrid API Key. -------------------------------------------------------------------------------- /vendor/github.com/sendgrid/sendgrid-go/USE_CASES.md: -------------------------------------------------------------------------------- 1 | This documentation provides examples for specific use cases. Please [open an issue](https://github.com/sendgrid/sendgrid-go/issues) or make a pull request for any use cases you would like us to document here. Thank you! 2 | 3 | # Table of Contents 4 | 5 | * [Transactional Templates](#transactional_templates) 6 | 7 | 8 | # Transactional Templates 9 | 10 | For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). Following is the template content we used for testing. 11 | 12 | Template ID (replace with your own): 13 | 14 | ```text 15 | 13b8f94f-bcae-4ec6-b752-70d6cb59f932 16 | ``` 17 | 18 | Email Subject: 19 | 20 | ```text 21 | <%subject%> 22 | ``` 23 | 24 | Template Body: 25 | 26 | ```html 27 | 28 | 29 | 30 | 31 | 32 | Hello -name-, 33 |

34 | I'm glad you are trying out the template feature! 35 |

36 | <%body%> 37 |

38 | I hope you are having a great day in -city- :) 39 |

40 | 41 | 42 | ``` 43 | 44 | ## With Mail Helper Class 45 | 46 | ```go 47 | package main 48 | 49 | import ( 50 | "fmt" 51 | "os" 52 | 53 | "github.com/sendgrid/sendgrid-go" 54 | "github.com/sendgrid/sendgrid-go/helpers/mail" 55 | ) 56 | 57 | func main() { 58 | from := mail.NewEmail("Example User", "test@example.com") 59 | subject := "I'm replacing the subject tag" 60 | to := mail.NewEmail("Example User", "test@example.com") 61 | content := mail.NewContent("text/html", "I'm replacing the body tag") 62 | m := mail.NewV3MailInit(from, subject, to, content) 63 | m.Personalizations[0].SetSubstitution("-name-", "Example User") 64 | m.Personalizations[0].SetSubstitution("-city-", "Denver") 65 | m.SetTemplateID("13b8f94f-bcae-4ec6-b752-70d6cb59f932") 66 | 67 | request := sendgrid.GetRequest(os.Getenv("SENDGRID_API_KEY"), "/v3/mail/send", "https://api.sendgrid.com") 68 | request.Method = "POST" 69 | request.Body = mail.GetRequestBody(m) 70 | response, err := sendgrid.API(request) 71 | if err != nil { 72 | fmt.Println(err) 73 | } else { 74 | fmt.Println(response.StatusCode) 75 | fmt.Println(response.Body) 76 | fmt.Println(response.Headers) 77 | } 78 | } 79 | ``` 80 | 81 | ## Without Mail Helper Class 82 | 83 | ```go 84 | package main 85 | 86 | import ( 87 | "fmt" 88 | "os" 89 | 90 | "github.com/sendgrid/sendgrid-go" 91 | ) 92 | 93 | func main() { 94 | request := sendgrid.GetRequest(os.Getenv("SENDGRID_API_KEY"), "/v3/mail/send", "https://api.sendgrid.com") 95 | request.Method = "POST" 96 | request.Body = []byte(` { 97 | "personalizations": [ 98 | { 99 | "to": [ 100 | { 101 | "email": "test@example.com" 102 | } 103 | ], 104 | "subject": "I'm replacing the subject tag", 105 | "substitutions": { 106 | "-name-": "Example User", 107 | "-city-": "Denver" 108 | }, 109 | } 110 | ], 111 | "from": { 112 | "email": "test@example.com" 113 | }, 114 | "content": [ 115 | { 116 | "type": "text/html", 117 | "value": "I'm replacing the body tag" 118 | } 119 | ], 120 | "template_id": "13b8f94f-bcae-4ec6-b752-70d6cb59f932" 121 | }`) 122 | response, err := sendgrid.API(request) 123 | if err != nil { 124 | fmt.Println(err) 125 | } else { 126 | fmt.Println(response.StatusCode) 127 | fmt.Println(response.Body) 128 | fmt.Println(response.Headers) 129 | } 130 | } 131 | ``` -------------------------------------------------------------------------------- /vendor/github.com/sendgrid/sendgrid-go/helpers/mail/README.md: -------------------------------------------------------------------------------- 1 | **This helper allows you to quickly and easily build a Mail object for sending email through SendGrid.** 2 | 3 | ## Dependencies 4 | 5 | - [rest](https://github.com/sendgrid/rest) 6 | 7 | # Quick Start 8 | 9 | Run the [example](https://github.com/sendgrid/sendgrid-go/tree/master/examples/mail) (make sure you have set your environment variable to include your SENDGRID_API_KEY). 10 | 11 | ```bash 12 | go run examples/mail/example.go 13 | ``` 14 | 15 | ## Usage 16 | 17 | - See the [example](https://github.com/sendgrid/sendgrid-go/tree/master/examples/mail) for a complete working example. 18 | - [Documentation](https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/overview.html) 19 | 20 | ## Test 21 | 22 | ```bash 23 | go test ./... -v 24 | ``` 25 | 26 | or 27 | 28 | ```bash 29 | cd helpers/mail 30 | go test -v 31 | ``` 32 | -------------------------------------------------------------------------------- /vendor/github.com/sendgrid/sendgrid-go/prism.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | install () { 4 | 5 | set -eu 6 | 7 | UNAME=$(uname) 8 | ARCH=$(uname -m) 9 | if [ "$UNAME" != "Linux" ] && [ "$UNAME" != "Darwin" ] && [ "$ARCH" != "x86_64" ] && [ "$ARCH" != "i686" ]; then 10 | echo "Sorry, OS/Architecture not supported: ${UNAME}/${ARCH}. Download binary from https://github.com/stoplightio/prism/releases" 11 | exit 1 12 | fi 13 | 14 | if [ "$UNAME" = "Darwin" ] ; then 15 | OSX_ARCH=$(uname -m) 16 | if [ "${OSX_ARCH}" = "x86_64" ] ; then 17 | PLATFORM="darwin_amd64" 18 | fi 19 | elif [ "$UNAME" = "Linux" ] ; then 20 | LINUX_ARCH=$(uname -m) 21 | if [ "${LINUX_ARCH}" = "i686" ] ; then 22 | PLATFORM="linux_386" 23 | elif [ "${LINUX_ARCH}" = "x86_64" ] ; then 24 | PLATFORM="linux_amd64" 25 | fi 26 | fi 27 | 28 | #LATEST=$(curl -s https://api.github.com/repos/stoplightio/prism/tags | grep -Eo '"name":.*?[^\\]",' | head -n 1 | sed 's/[," ]//g' | cut -d ':' -f 2) 29 | LATEST="v0.1.5" 30 | URL="https://github.com/stoplightio/prism/releases/download/$LATEST/prism_$PLATFORM" 31 | DEST=/home/travis/gopath/bin/prism 32 | 33 | if [ -z $LATEST ] ; then 34 | echo "Error requesting. Download binary from ${URL}" 35 | exit 1 36 | else 37 | curl -L $URL -o $DEST 38 | chmod +x $DEST 39 | fi 40 | } 41 | 42 | install -------------------------------------------------------------------------------- /vendor/github.com/sendgrid/sendgrid-go/sendgrid.go: -------------------------------------------------------------------------------- 1 | // Package sendgrid provides a simple interface to interact with the SendGrid API 2 | package sendgrid 3 | 4 | import "github.com/sendgrid/rest" // depends on version 2.2.0 5 | 6 | // Version is this client library's current version 7 | const Version = "3.1.0" 8 | 9 | // GetRequest returns a default request object. 10 | func GetRequest(key string, endpoint string, host string) rest.Request { 11 | if host == "" { 12 | host = "https://api.sendgrid.com" 13 | } 14 | baseURL := host + endpoint 15 | requestHeaders := make(map[string]string) 16 | requestHeaders["Authorization"] = "Bearer " + key 17 | requestHeaders["User-Agent"] = "sendgrid/" + Version + ";go" 18 | requestHeaders["Accept"] = "application/json" 19 | request := rest.Request{ 20 | BaseURL: baseURL, 21 | Headers: requestHeaders, 22 | } 23 | return request 24 | } 25 | 26 | // DefaultClient is used if no custom HTTP client is defined 27 | var DefaultClient = rest.DefaultClient 28 | 29 | // API sets up the request to the SendGrid API, this is main interface. 30 | func API(request rest.Request) (*rest.Response, error) { 31 | return DefaultClient.API(request) 32 | } 33 | -------------------------------------------------------------------------------- /vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Dmitri Shuralyov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/github.com/shurcooL/sanitized_anchor_name/README.md: -------------------------------------------------------------------------------- 1 | sanitized_anchor_name 2 | ===================== 3 | 4 | [![Build Status](https://travis-ci.org/shurcooL/sanitized_anchor_name.svg?branch=master)](https://travis-ci.org/shurcooL/sanitized_anchor_name) [![GoDoc](https://godoc.org/github.com/shurcooL/sanitized_anchor_name?status.svg)](https://godoc.org/github.com/shurcooL/sanitized_anchor_name) 5 | 6 | Package sanitized_anchor_name provides a func to create sanitized anchor names. 7 | 8 | Its logic can be reused by multiple packages to create interoperable anchor names and links to those anchors. 9 | 10 | At this time, it does not try to ensure that generated anchor names are unique, that responsibility falls on the caller. 11 | 12 | Installation 13 | ------------ 14 | 15 | ```bash 16 | go get -u github.com/shurcooL/sanitized_anchor_name 17 | ``` 18 | 19 | Example 20 | ------- 21 | 22 | ```Go 23 | anchorName := sanitized_anchor_name.Create("This is a header") 24 | 25 | fmt.Println(anchorName) 26 | 27 | // Output: 28 | // this-is-a-header 29 | ``` 30 | 31 | License 32 | ------- 33 | 34 | - [MIT License](LICENSE) 35 | -------------------------------------------------------------------------------- /vendor/github.com/shurcooL/sanitized_anchor_name/main.go: -------------------------------------------------------------------------------- 1 | // Package sanitized_anchor_name provides a func to create sanitized anchor names. 2 | // 3 | // Its logic can be reused by multiple packages to create interoperable anchor names 4 | // and links to those anchors. 5 | // 6 | // At this time, it does not try to ensure that generated anchor names 7 | // are unique, that responsibility falls on the caller. 8 | package sanitized_anchor_name // import "github.com/shurcooL/sanitized_anchor_name" 9 | 10 | import "unicode" 11 | 12 | // Create returns a sanitized anchor name for the given text. 13 | func Create(text string) string { 14 | var anchorName []rune 15 | var futureDash = false 16 | for _, r := range []rune(text) { 17 | switch { 18 | case unicode.IsLetter(r) || unicode.IsNumber(r): 19 | if futureDash && len(anchorName) > 0 { 20 | anchorName = append(anchorName, '-') 21 | } 22 | futureDash = false 23 | anchorName = append(anchorName, unicode.ToLower(r)) 24 | default: 25 | futureDash = true 26 | } 27 | } 28 | return string(anchorName) 29 | } 30 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell 2 | 3 | Please consider promoting this project if you find it useful. 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of the Software, 10 | and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 20 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 21 | OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 22 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl: -------------------------------------------------------------------------------- 1 | {{.CommentWithoutT "a"}} 2 | func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool { 3 | return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) 4 | } 5 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/doc.go: -------------------------------------------------------------------------------- 1 | // Package assert provides a set of comprehensive testing tools for use with the normal Go testing system. 2 | // 3 | // Example Usage 4 | // 5 | // The following is a complete example using assert in a standard test function: 6 | // import ( 7 | // "testing" 8 | // "github.com/stretchr/testify/assert" 9 | // ) 10 | // 11 | // func TestSomething(t *testing.T) { 12 | // 13 | // var a string = "Hello" 14 | // var b string = "Hello" 15 | // 16 | // assert.Equal(t, a, b, "The two words should be the same.") 17 | // 18 | // } 19 | // 20 | // if you assert many times, use the format below: 21 | // 22 | // import ( 23 | // "testing" 24 | // "github.com/stretchr/testify/assert" 25 | // ) 26 | // 27 | // func TestSomething(t *testing.T) { 28 | // assert := assert.New(t) 29 | // 30 | // var a string = "Hello" 31 | // var b string = "Hello" 32 | // 33 | // assert.Equal(a, b, "The two words should be the same.") 34 | // } 35 | // 36 | // Assertions 37 | // 38 | // Assertions allow you to easily write test code, and are global funcs in the `assert` package. 39 | // All assertion functions take, as the first argument, the `*testing.T` object provided by the 40 | // testing framework. This allows the assertion funcs to write the failings and other details to 41 | // the correct place. 42 | // 43 | // Every assertion function also takes an optional string message as the final argument, 44 | // allowing custom error messages to be appended to the message the assertion method outputs. 45 | package assert 46 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/errors.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // AnError is an error instance useful for testing. If the code does not care 8 | // about error specifics, and only needs to return the error for example, this 9 | // error should be used to make the test code more readable. 10 | var AnError = errors.New("assert.AnError general error for testing") 11 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/forward_assertions.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | // Assertions provides assertion methods around the 4 | // TestingT interface. 5 | type Assertions struct { 6 | t TestingT 7 | } 8 | 9 | // New makes a new Assertions object for the specified TestingT. 10 | func New(t TestingT) *Assertions { 11 | return &Assertions{ 12 | t: t, 13 | } 14 | } 15 | 16 | //go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl 17 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/http_assertions.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "net/url" 8 | "strings" 9 | ) 10 | 11 | // httpCode is a helper that returns HTTP code of the response. It returns -1 12 | // if building a new request fails. 13 | func httpCode(handler http.HandlerFunc, method, url string, values url.Values) int { 14 | w := httptest.NewRecorder() 15 | req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) 16 | if err != nil { 17 | return -1 18 | } 19 | handler(w, req) 20 | return w.Code 21 | } 22 | 23 | // HTTPSuccess asserts that a specified handler returns a success status code. 24 | // 25 | // assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) 26 | // 27 | // Returns whether the assertion was successful (true) or not (false). 28 | func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { 29 | code := httpCode(handler, method, url, values) 30 | if code == -1 { 31 | return false 32 | } 33 | return code >= http.StatusOK && code <= http.StatusPartialContent 34 | } 35 | 36 | // HTTPRedirect asserts that a specified handler returns a redirect status code. 37 | // 38 | // assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} 39 | // 40 | // Returns whether the assertion was successful (true) or not (false). 41 | func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { 42 | code := httpCode(handler, method, url, values) 43 | if code == -1 { 44 | return false 45 | } 46 | return code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect 47 | } 48 | 49 | // HTTPError asserts that a specified handler returns an error status code. 50 | // 51 | // assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} 52 | // 53 | // Returns whether the assertion was successful (true) or not (false). 54 | func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { 55 | code := httpCode(handler, method, url, values) 56 | if code == -1 { 57 | return false 58 | } 59 | return code >= http.StatusBadRequest 60 | } 61 | 62 | // HTTPBody is a helper that returns HTTP body of the response. It returns 63 | // empty string if building a new request fails. 64 | func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string { 65 | w := httptest.NewRecorder() 66 | req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) 67 | if err != nil { 68 | return "" 69 | } 70 | handler(w, req) 71 | return w.Body.String() 72 | } 73 | 74 | // HTTPBodyContains asserts that a specified handler returns a 75 | // body that contains a string. 76 | // 77 | // assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") 78 | // 79 | // Returns whether the assertion was successful (true) or not (false). 80 | func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { 81 | body := HTTPBody(handler, method, url, values) 82 | 83 | contains := strings.Contains(body, fmt.Sprint(str)) 84 | if !contains { 85 | Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) 86 | } 87 | 88 | return contains 89 | } 90 | 91 | // HTTPBodyNotContains asserts that a specified handler returns a 92 | // body that does not contain a string. 93 | // 94 | // assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") 95 | // 96 | // Returns whether the assertion was successful (true) or not (false). 97 | func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { 98 | body := HTTPBody(handler, method, url, values) 99 | 100 | contains := strings.Contains(body, fmt.Sprint(str)) 101 | if contains { 102 | Fail(t, "Expected response body for %s to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body) 103 | } 104 | 105 | return !contains 106 | } 107 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/require/doc.go: -------------------------------------------------------------------------------- 1 | // Package require implements the same assertions as the `assert` package but 2 | // stops test execution when a test fails. 3 | // 4 | // Example Usage 5 | // 6 | // The following is a complete example using require in a standard test function: 7 | // import ( 8 | // "testing" 9 | // "github.com/stretchr/testify/require" 10 | // ) 11 | // 12 | // func TestSomething(t *testing.T) { 13 | // 14 | // var a string = "Hello" 15 | // var b string = "Hello" 16 | // 17 | // require.Equal(t, a, b, "The two words should be the same.") 18 | // 19 | // } 20 | // 21 | // Assertions 22 | // 23 | // The `require` package have same global functions as in the `assert` package, 24 | // but instead of returning a boolean result they call `t.FailNow()`. 25 | // 26 | // Every assertion function also takes an optional string message as the final argument, 27 | // allowing custom error messages to be appended to the message the assertion method outputs. 28 | package require 29 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/require/forward_requirements.go: -------------------------------------------------------------------------------- 1 | package require 2 | 3 | // Assertions provides assertion methods around the 4 | // TestingT interface. 5 | type Assertions struct { 6 | t TestingT 7 | } 8 | 9 | // New makes a new Assertions object for the specified TestingT. 10 | func New(t TestingT) *Assertions { 11 | return &Assertions{ 12 | t: t, 13 | } 14 | } 15 | 16 | //go:generate go run ../_codegen/main.go -output-package=require -template=require_forward.go.tmpl 17 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/require/require.go.tmpl: -------------------------------------------------------------------------------- 1 | {{.Comment}} 2 | func {{.DocInfo.Name}}(t TestingT, {{.Params}}) { 3 | if !assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { 4 | t.FailNow() 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/require/require_forward.go.tmpl: -------------------------------------------------------------------------------- 1 | {{.CommentWithoutT "a"}} 2 | func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) { 3 | {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) 4 | } 5 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/require/requirements.go: -------------------------------------------------------------------------------- 1 | package require 2 | 3 | // TestingT is an interface wrapper around *testing.T 4 | type TestingT interface { 5 | Errorf(format string, args ...interface{}) 6 | FailNow() 7 | } 8 | 9 | //go:generate go run ../_codegen/main.go -output-package=require -template=require.go.tmpl 10 | -------------------------------------------------------------------------------- /vendor/github.com/urfave/negroni/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | **ATTN**: This project uses [semantic versioning](http://semver.org/). 4 | 5 | ## [Unreleased] 6 | ### Added 7 | - `Recovery.ErrorHandlerFunc` for custom error handling during recovery 8 | - `With()` helper for building a new `Negroni` struct chaining handlers from 9 | existing `Negroni` structs 10 | 11 | ### Fixed 12 | - `Written()` correct returns `false` if no response header has been written 13 | 14 | ### Changed 15 | - Set default status to `0` in the case that no handler writes status -- was 16 | previously `200` (in 0.2.0, before that it was `0` so this reestablishes that 17 | behavior) 18 | - Catch `panic`s thrown by callbacks provided to the `Recovery` handler 19 | 20 | ## [0.2.0] - 2016-05-10 21 | ### Added 22 | - Support for variadic handlers in `New()` 23 | - Added `Negroni.Handlers()` to fetch all of the handlers for a given chain 24 | - Allowed size in `Recovery` handler was bumped to 8k 25 | - `Negroni.UseFunc` to push another handler onto the chain 26 | 27 | ### Changed 28 | - Set the status before calling `beforeFuncs` so the information is available to them 29 | - Set default status to `200` in the case that no handler writes status -- was previously `0` 30 | - Panic if `nil` handler is given to `negroni.Use` 31 | 32 | ## 0.1.0 - 2013-07-22 33 | ### Added 34 | - Initial implementation. 35 | 36 | [Unreleased]: https://github.com/urfave/negroni/compare/v0.2.0...HEAD 37 | [0.2.0]: https://github.com/urfave/negroni/compare/v0.1.0...v0.2.0 38 | -------------------------------------------------------------------------------- /vendor/github.com/urfave/negroni/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jeremy Saenz 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 | -------------------------------------------------------------------------------- /vendor/github.com/urfave/negroni/doc.go: -------------------------------------------------------------------------------- 1 | // Package negroni is an idiomatic approach to web middleware in Go. It is tiny, non-intrusive, and encourages use of net/http Handlers. 2 | // 3 | // If you like the idea of Martini, but you think it contains too much magic, then Negroni is a great fit. 4 | // 5 | // For a full guide visit http://github.com/urfave/negroni 6 | // 7 | // package main 8 | // 9 | // import ( 10 | // "github.com/urfave/negroni" 11 | // "net/http" 12 | // "fmt" 13 | // ) 14 | // 15 | // func main() { 16 | // mux := http.NewServeMux() 17 | // mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 18 | // fmt.Fprintf(w, "Welcome to the home page!") 19 | // }) 20 | // 21 | // n := negroni.Classic() 22 | // n.UseHandler(mux) 23 | // n.Run(":3000") 24 | // } 25 | package negroni 26 | -------------------------------------------------------------------------------- /vendor/github.com/urfave/negroni/logger.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "bytes" 5 | 6 | "log" 7 | "net/http" 8 | "os" 9 | "text/template" 10 | "time" 11 | ) 12 | 13 | // LoggerEntry is the structure 14 | // passed to the template. 15 | type LoggerEntry struct { 16 | StartTime string 17 | Status int 18 | Duration time.Duration 19 | Hostname string 20 | Method string 21 | Path string 22 | } 23 | 24 | // LoggerDefaultFormat is the format 25 | // logged used by the default Logger instance. 26 | var LoggerDefaultFormat = "{{.StartTime}} | {{.Status}} | \t {{.Duration}} | {{.Hostname}} | {{.Method}} {{.Path}} \n" 27 | 28 | // LoggerDefaultDateFormat is the 29 | // format used for date by the 30 | // default Logger instance. 31 | var LoggerDefaultDateFormat = time.RFC3339 32 | 33 | // ALogger interface 34 | type ALogger interface { 35 | Println(v ...interface{}) 36 | Printf(format string, v ...interface{}) 37 | } 38 | 39 | // Logger is a middleware handler that logs the request as it goes in and the response as it goes out. 40 | type Logger struct { 41 | // ALogger implements just enough log.Logger interface to be compatible with other implementations 42 | ALogger 43 | dateFormat string 44 | template *template.Template 45 | } 46 | 47 | // NewLogger returns a new Logger instance 48 | func NewLogger() *Logger { 49 | logger := &Logger{ALogger: log.New(os.Stdout, "[negroni] ", 0), dateFormat: LoggerDefaultDateFormat} 50 | logger.SetFormat(LoggerDefaultFormat) 51 | return logger 52 | } 53 | 54 | func (l *Logger) SetFormat(format string) { 55 | l.template = template.Must(template.New("negroni_parser").Parse(format)) 56 | } 57 | 58 | func (l *Logger) SetDateFormat(format string) { 59 | l.dateFormat = format 60 | } 61 | 62 | func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 63 | start := time.Now() 64 | 65 | next(rw, r) 66 | 67 | res := rw.(ResponseWriter) 68 | log := LoggerEntry{ 69 | StartTime: start.Format(l.dateFormat), 70 | Status: res.Status(), 71 | Duration: time.Since(start), 72 | Hostname: r.Host, 73 | Method: r.Method, 74 | Path: r.URL.Path, 75 | } 76 | 77 | buff := &bytes.Buffer{} 78 | l.template.Execute(buff, log) 79 | l.Printf(buff.String()) 80 | } 81 | -------------------------------------------------------------------------------- /vendor/github.com/urfave/negroni/recovery.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | "runtime" 9 | "runtime/debug" 10 | ) 11 | 12 | // Recovery is a Negroni middleware that recovers from any panics and writes a 500 if there was one. 13 | type Recovery struct { 14 | Logger ALogger 15 | PrintStack bool 16 | ErrorHandlerFunc func(interface{}) 17 | StackAll bool 18 | StackSize int 19 | } 20 | 21 | // NewRecovery returns a new instance of Recovery 22 | func NewRecovery() *Recovery { 23 | return &Recovery{ 24 | Logger: log.New(os.Stdout, "[negroni] ", 0), 25 | PrintStack: true, 26 | StackAll: false, 27 | StackSize: 1024 * 8, 28 | } 29 | } 30 | 31 | func (rec *Recovery) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 32 | defer func() { 33 | if err := recover(); err != nil { 34 | if rw.Header().Get("Content-Type") == "" { 35 | rw.Header().Set("Content-Type", "text/plain; charset=utf-8") 36 | } 37 | 38 | rw.WriteHeader(http.StatusInternalServerError) 39 | 40 | stack := make([]byte, rec.StackSize) 41 | stack = stack[:runtime.Stack(stack, rec.StackAll)] 42 | 43 | f := "PANIC: %s\n%s" 44 | rec.Logger.Printf(f, err, stack) 45 | 46 | if rec.PrintStack { 47 | fmt.Fprintf(rw, f, err, stack) 48 | } 49 | 50 | if rec.ErrorHandlerFunc != nil { 51 | func() { 52 | defer func() { 53 | if err := recover(); err != nil { 54 | rec.Logger.Printf("provided ErrorHandlerFunc panic'd: %s, trace:\n%s", err, debug.Stack()) 55 | rec.Logger.Printf("%s\n", debug.Stack()) 56 | } 57 | }() 58 | rec.ErrorHandlerFunc(err) 59 | }() 60 | } 61 | } 62 | }() 63 | 64 | next(rw, r) 65 | } 66 | -------------------------------------------------------------------------------- /vendor/github.com/urfave/negroni/response_writer.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | ) 9 | 10 | // ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about 11 | // the response. It is recommended that middleware handlers use this construct to wrap a responsewriter 12 | // if the functionality calls for it. 13 | type ResponseWriter interface { 14 | http.ResponseWriter 15 | http.Flusher 16 | // Status returns the status code of the response or 200 if the response has 17 | // not been written (as this is the default response code in net/http) 18 | Status() int 19 | // Written returns whether or not the ResponseWriter has been written. 20 | Written() bool 21 | // Size returns the size of the response body. 22 | Size() int 23 | // Before allows for a function to be called before the ResponseWriter has been written to. This is 24 | // useful for setting headers or any other operations that must happen before a response has been written. 25 | Before(func(ResponseWriter)) 26 | } 27 | 28 | type beforeFunc func(ResponseWriter) 29 | 30 | // NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter 31 | func NewResponseWriter(rw http.ResponseWriter) ResponseWriter { 32 | nrw := &responseWriter{ 33 | ResponseWriter: rw, 34 | } 35 | 36 | if _, ok := rw.(http.CloseNotifier); ok { 37 | return &responseWriterCloseNotifer{nrw} 38 | } 39 | 40 | return nrw 41 | } 42 | 43 | type responseWriter struct { 44 | http.ResponseWriter 45 | status int 46 | size int 47 | beforeFuncs []beforeFunc 48 | } 49 | 50 | func (rw *responseWriter) WriteHeader(s int) { 51 | rw.status = s 52 | rw.callBefore() 53 | rw.ResponseWriter.WriteHeader(s) 54 | } 55 | 56 | func (rw *responseWriter) Write(b []byte) (int, error) { 57 | if !rw.Written() { 58 | // The status will be StatusOK if WriteHeader has not been called yet 59 | rw.WriteHeader(http.StatusOK) 60 | } 61 | size, err := rw.ResponseWriter.Write(b) 62 | rw.size += size 63 | return size, err 64 | } 65 | 66 | func (rw *responseWriter) Status() int { 67 | return rw.status 68 | } 69 | 70 | func (rw *responseWriter) Size() int { 71 | return rw.size 72 | } 73 | 74 | func (rw *responseWriter) Written() bool { 75 | return rw.status != 0 76 | } 77 | 78 | func (rw *responseWriter) Before(before func(ResponseWriter)) { 79 | rw.beforeFuncs = append(rw.beforeFuncs, before) 80 | } 81 | 82 | func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { 83 | hijacker, ok := rw.ResponseWriter.(http.Hijacker) 84 | if !ok { 85 | return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface") 86 | } 87 | return hijacker.Hijack() 88 | } 89 | 90 | func (rw *responseWriter) callBefore() { 91 | for i := len(rw.beforeFuncs) - 1; i >= 0; i-- { 92 | rw.beforeFuncs[i](rw) 93 | } 94 | } 95 | 96 | func (rw *responseWriter) Flush() { 97 | flusher, ok := rw.ResponseWriter.(http.Flusher) 98 | if ok { 99 | if !rw.Written() { 100 | // The status will be StatusOK if WriteHeader has not been called yet 101 | rw.WriteHeader(http.StatusOK) 102 | } 103 | flusher.Flush() 104 | } 105 | } 106 | 107 | type responseWriterCloseNotifer struct { 108 | *responseWriter 109 | } 110 | 111 | func (rw *responseWriterCloseNotifer) CloseNotify() <-chan bool { 112 | return rw.ResponseWriter.(http.CloseNotifier).CloseNotify() 113 | } 114 | -------------------------------------------------------------------------------- /vendor/github.com/urfave/negroni/static.go: -------------------------------------------------------------------------------- 1 | package negroni 2 | 3 | import ( 4 | "net/http" 5 | "path" 6 | "strings" 7 | ) 8 | 9 | // Static is a middleware handler that serves static files in the given 10 | // directory/filesystem. If the file does not exist on the filesystem, it 11 | // passes along to the next middleware in the chain. If you desire "fileserver" 12 | // type behavior where it returns a 404 for unfound files, you should consider 13 | // using http.FileServer from the Go stdlib. 14 | type Static struct { 15 | // Dir is the directory to serve static files from 16 | Dir http.FileSystem 17 | // Prefix is the optional prefix used to serve the static directory content 18 | Prefix string 19 | // IndexFile defines which file to serve as index if it exists. 20 | IndexFile string 21 | } 22 | 23 | // NewStatic returns a new instance of Static 24 | func NewStatic(directory http.FileSystem) *Static { 25 | return &Static{ 26 | Dir: directory, 27 | Prefix: "", 28 | IndexFile: "index.html", 29 | } 30 | } 31 | 32 | func (s *Static) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 33 | if r.Method != "GET" && r.Method != "HEAD" { 34 | next(rw, r) 35 | return 36 | } 37 | file := r.URL.Path 38 | // if we have a prefix, filter requests by stripping the prefix 39 | if s.Prefix != "" { 40 | if !strings.HasPrefix(file, s.Prefix) { 41 | next(rw, r) 42 | return 43 | } 44 | file = file[len(s.Prefix):] 45 | if file != "" && file[0] != '/' { 46 | next(rw, r) 47 | return 48 | } 49 | } 50 | f, err := s.Dir.Open(file) 51 | if err != nil { 52 | // discard the error? 53 | next(rw, r) 54 | return 55 | } 56 | defer f.Close() 57 | 58 | fi, err := f.Stat() 59 | if err != nil { 60 | next(rw, r) 61 | return 62 | } 63 | 64 | // try to serve index file 65 | if fi.IsDir() { 66 | // redirect if missing trailing slash 67 | if !strings.HasSuffix(r.URL.Path, "/") { 68 | http.Redirect(rw, r, r.URL.Path+"/", http.StatusFound) 69 | return 70 | } 71 | 72 | file = path.Join(file, s.IndexFile) 73 | f, err = s.Dir.Open(file) 74 | if err != nil { 75 | next(rw, r) 76 | return 77 | } 78 | defer f.Close() 79 | 80 | fi, err = f.Stat() 81 | if err != nil || fi.IsDir() { 82 | next(rw, r) 83 | return 84 | } 85 | } 86 | 87 | http.ServeContent(rw, r, file, fi.ModTime(), f) 88 | } 89 | -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "test", 4 | "package": [ 5 | { 6 | "checksumSHA1": "7G7ADsNRwBcyfkJyTrX1oW4I0Rs=", 7 | "path": "github.com/auth0/go-jwt-middleware", 8 | "revision": "f3f7de3b9e394e3af3b88e1b9457f6f71d1ae0ac", 9 | "revisionTime": "2016-04-21T21:57:38Z" 10 | }, 11 | { 12 | "checksumSHA1": "5rPfda8jFccr3A6heL+JAmi9K9g=", 13 | "origin": "github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/spew", 14 | "path": "github.com/davecgh/go-spew/spew", 15 | "revision": "6fe211e493929a8aac0469b93f28b1d0688a9a3a", 16 | "revisionTime": "2016-03-05T16:54:46Z" 17 | }, 18 | { 19 | "checksumSHA1": "EsJksAKyY2+r3mY+rdNocSCe1wg=", 20 | "path": "github.com/dgrijalva/jwt-go", 21 | "revision": "24c63f56522a87ec5339cc3567883f1039378fdb", 22 | "revisionTime": "2016-08-31T18:35:34Z" 23 | }, 24 | { 25 | "checksumSHA1": "g/V4qrXjUGG9B+e3hB+4NAYJ5Gs=", 26 | "path": "github.com/gorilla/context", 27 | "revision": "08b5f424b9271eedf6f9f0ce86cb9396ed337a42", 28 | "revisionTime": "2016-08-17T18:46:32Z" 29 | }, 30 | { 31 | "checksumSHA1": "urMd7A9QPAJYY0GZJL9qBhlUmD8=", 32 | "path": "github.com/gorilla/mux", 33 | "revision": "757bef944d0f21880861c2dd9c871ca543023cba", 34 | "revisionTime": "2016-09-20T23:08:13Z" 35 | }, 36 | { 37 | "checksumSHA1": "Hky3u+8Rqum+wB5BHMj0A8ZmT4g=", 38 | "path": "github.com/pkg/errors", 39 | "revision": "17b591df37844cde689f4d5813e5cea0927d8dd2", 40 | "revisionTime": "2016-08-22T09:00:10Z" 41 | }, 42 | { 43 | "checksumSHA1": "zKKp5SZ3d3ycKe4EKMNT0BqAWBw=", 44 | "origin": "github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/difflib", 45 | "path": "github.com/pmezard/go-difflib/difflib", 46 | "revision": "6fe211e493929a8aac0469b93f28b1d0688a9a3a", 47 | "revisionTime": "2016-03-05T16:54:46Z" 48 | }, 49 | { 50 | "checksumSHA1": "c7jHQZk5ZEsFR9EXsWJXkszPBZA=", 51 | "path": "github.com/russross/blackfriday", 52 | "revision": "5f33e7b7878355cd2b7e6b8eefc48a5472c69f70", 53 | "revisionTime": "2016-10-03T16:27:22Z" 54 | }, 55 | { 56 | "checksumSHA1": "eRKkbaq9IvZqS2lQW8RLqZ9IP7s=", 57 | "path": "github.com/sendgrid/rest", 58 | "revision": "d37acbf70185e33da36a233f4df95848b052427c", 59 | "revisionTime": "2016-07-28T16:05:22Z" 60 | }, 61 | { 62 | "checksumSHA1": "FCnYbQ+GmjnpVxq2+swzeZ+xBiE=", 63 | "path": "github.com/sendgrid/sendgrid-go", 64 | "revision": "e0b3a5eae9f862e5f6d9961bf2d3e494d982c39e", 65 | "revisionTime": "2016-09-23T06:24:49Z" 66 | }, 67 | { 68 | "checksumSHA1": "VGZ56xIw8bTXnSJLrr/qzLzdVho=", 69 | "path": "github.com/sendgrid/sendgrid-go/helpers/mail", 70 | "revision": "e0b3a5eae9f862e5f6d9961bf2d3e494d982c39e", 71 | "revisionTime": "2016-09-23T06:24:49Z" 72 | }, 73 | { 74 | "checksumSHA1": "kbgJvKG3NRoqU91rYnXGnyR+8HQ=", 75 | "path": "github.com/shurcooL/sanitized_anchor_name", 76 | "revision": "1dba4b3954bc059efc3991ec364f9f9a35f597d2", 77 | "revisionTime": "2016-09-18T04:11:01Z" 78 | }, 79 | { 80 | "checksumSHA1": "Bn333k9lTndxU3D6n/G5c+GMcYY=", 81 | "path": "github.com/stretchr/testify/assert", 82 | "revision": "6fe211e493929a8aac0469b93f28b1d0688a9a3a", 83 | "revisionTime": "2016-03-05T16:54:46Z" 84 | }, 85 | { 86 | "checksumSHA1": "P9FJpir2c4G5PA46qEkaWy3l60U=", 87 | "path": "github.com/stretchr/testify/require", 88 | "revision": "6fe211e493929a8aac0469b93f28b1d0688a9a3a", 89 | "revisionTime": "2016-03-05T16:54:46Z" 90 | }, 91 | { 92 | "checksumSHA1": "ZlmrkfCidRa8G0o5xIW/Kzo+tOc=", 93 | "path": "github.com/urfave/negroni", 94 | "revision": "0c3570ad2938a95d5d584eb76304b7f70642e4aa", 95 | "revisionTime": "2017-05-02T03:14:59Z" 96 | } 97 | ], 98 | "rootPath": "github.com/dgraph-io/gru" 99 | } 100 | -------------------------------------------------------------------------------- /x/x.go: -------------------------------------------------------------------------------- 1 | package x 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "math" 8 | "net/http" 9 | "os" 10 | "path/filepath" 11 | "time" 12 | 13 | "github.com/dgraph-io/gru/admin/company" 14 | "github.com/dgraph-io/gru/dgraph" 15 | jwt "github.com/dgrijalva/jwt-go" 16 | ) 17 | 18 | var ( 19 | debug = flag.Bool("debug", false, "Whether to print debug info") 20 | backup = flag.String("backup", "", "Dgraph backup directory path") 21 | ) 22 | 23 | func Debug(log interface{}) { 24 | if *debug { 25 | fmt.Println(log) 26 | } 27 | } 28 | 29 | func StringInSlice(a string, list []string) int { 30 | for idx, b := range list { 31 | if b == a { 32 | return idx 33 | } 34 | } 35 | return -1 36 | } 37 | 38 | func round(num float64) int { 39 | return int(num + math.Copysign(0.5, num)) 40 | } 41 | 42 | type Claims struct { 43 | UserId string `json:"user_id"` 44 | jwt.StandardClaims 45 | } 46 | 47 | func backupDuration() (int, error) { 48 | c, err := company.Info() 49 | if err != nil { 50 | return 0, err 51 | } 52 | if c.Backup == 0 { 53 | return 60, nil 54 | } 55 | return c.Backup, nil 56 | } 57 | 58 | func Backup() { 59 | d, err := backupDuration() 60 | if err != nil { 61 | fmt.Println(err) 62 | return 63 | } 64 | 65 | ticker := time.NewTicker(time.Hour * time.Duration(d)) 66 | for range ticker.C { 67 | dur, err := backupDuration() 68 | if err != nil { 69 | fmt.Println(err) 70 | continue 71 | } 72 | 73 | if d != dur { 74 | ticker = time.NewTicker(time.Hour * time.Duration(d)) 75 | } 76 | 77 | res, err := http.Get(fmt.Sprintf("%v/admin/backup", *dgraph.Server)) 78 | if err != nil || res.StatusCode != http.StatusOK { 79 | fmt.Println(err) 80 | } 81 | } 82 | } 83 | 84 | var filenameTimeLayout = "2006-01-02-15-04" 85 | 86 | func check(file os.FileInfo) { 87 | fname := file.Name() 88 | // Filename is like dgraph-0-2016-12-15-20-12.rdf.gz or dgraph-schema-1-2016-12-15-20-12.rdf.gz 89 | // Length of file name should be atleast 16(for datetime) + 7(.rdf.gz) 90 | if len(fname) < 23 { 91 | fmt.Printf("Can't parse file name format: %+v\n", fname) 92 | return 93 | } 94 | // remove .rdf.gz 95 | fname = fname[:len(fname)-7] 96 | dateTime := fname[len(fname)-16:] 97 | 98 | t, err := time.Parse(filenameTimeLayout, dateTime) 99 | if err != nil { 100 | fmt.Println("While parsing backup filename: ", file.Name()) 101 | return 102 | } 103 | 104 | c, err := company.Info() 105 | if err != nil { 106 | fmt.Println(err) 107 | return 108 | } 109 | 110 | if time.Now().After(t.Add(time.Duration(c.BackupDays) * 24 * time.Hour)) { 111 | if err := os.Remove(filepath.Join(*backup, file.Name())); err != nil { 112 | fmt.Println("While removing file with name: ", err) 113 | } 114 | fmt.Println("Deleted old backup file: ", fname) 115 | } 116 | } 117 | 118 | func deleteOldBackups() { 119 | files, err := ioutil.ReadDir(*backup) 120 | if err != nil { 121 | fmt.Println("While reading backup directory: ", err) 122 | return 123 | } 124 | for _, file := range files { 125 | check(file) 126 | } 127 | } 128 | 129 | func DeleteOldBackups() { 130 | ticker := time.NewTicker(24 * time.Hour) 131 | deleteOldBackups() 132 | for range ticker.C { 133 | deleteOldBackups() 134 | } 135 | } 136 | --------------------------------------------------------------------------------