diff options
author | Max Magorsch <arzano@gentoo.org> | 2020-12-08 01:21:04 +0000 |
---|---|---|
committer | Max Magorsch <arzano@gentoo.org> | 2020-12-08 01:21:04 +0000 |
commit | ba76c05ceca6a7879678873f360cdaf575f0f493 (patch) | |
tree | 597f2ee142ca4e57f1daffd29286e533c10a397a | |
download | go-gentoo-ba76c05ceca6a7879678873f360cdaf575f0f493.tar.gz go-gentoo-ba76c05ceca6a7879678873f360cdaf575f0f493.tar.bz2 go-gentoo-ba76c05ceca6a7879678873f360cdaf575f0f493.zip |
Initial version
Signed-off-by: Max Magorsch <arzano@gentoo.org>
40 files changed, 2241 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd43d2a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +bin +.idea +node_modules +assets diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..d815c1d --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,31 @@ +stages: + - build + +build: + stage: build + except: + - tags + variables: + IMAGE_TAG: $CI_REGISTRY_IMAGE/$CI_COMMIT_BRANCH:$CI_COMMIT_SHA + LATEST_IMAGE_TAG: $CI_REGISTRY_IMAGE/$CI_COMMIT_BRANCH:latest + script: + - echo $IMAGE_TAG + - echo $LATEST_IMAGE_TAG + - docker info + - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" "$CI_REGISTRY" --password-stdin + - docker build --no-cache -t $IMAGE_TAG -t $LATEST_IMAGE_TAG . + - docker push $LATEST_IMAGE_TAG + - docker push $IMAGE_TAG + +build-tag: + stage: build + only: + - tags + variables: + IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG + script: + - echo $IMAGE_TAG + - docker info + - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" "$CI_REGISTRY" --password-stdin + - docker build -t $IMAGE_TAG . + - docker push $IMAGE_TAG diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..92f4fed --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.14.0 AS builder +WORKDIR /go/src/go-gentoo +COPY . /go/src/go-gentoo +RUN go get github.com/go-pg/pg/v9 +RUN go get github.com/mcuadros/go-version +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o bin . + +FROM scratch +WORKDIR /go/src/go-gentoo +COPY --from=builder /go/src/go-gentoo/assets /go/src/go-gentoo/assets +COPY --from=builder /go/src/go-gentoo/bin /go/src/go-gentoo/bin +COPY --from=builder /go/src/go-gentoo/pkg /go/src/go-gentoo/pkg +COPY --from=builder /go/src/go-gentoo/web /go/src/go-gentoo/web +ENTRYPOINT ["/go/src/go-gentoo/bin/go-gentoo", "--serve"] diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..2e346d6 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,6 @@ +FROM golang:1.14.0 +RUN apt update && apt install -y ca-certificates ntp ntpdate +WORKDIR /go/src/go-gentoo +COPY . /go/src/go-gentoo + +CMD tail -f /dev/null diff --git a/assets/.keep b/assets/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/assets/.keep diff --git a/assets/application.css b/assets/application.css new file mode 100644 index 0000000..204e3c8 --- /dev/null +++ b/assets/application.css @@ -0,0 +1,40 @@ +.typeahead-field input, +.typeahead-select { + display:block; + width:100%; + font-size:13px; + color:#555; + background:0 0; + border-radius:2px 0 0 2px; + -webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075); + box-shadow:inset 0 1px 1px rgba(0,0,0,0.075); + -webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s; + -o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s; + transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s +} +.typeahead-field input { + -webkit-appearance:none; + background:0 0 +} +.typeahead-field input:last-child, +.typeahead-hint { + background:#fff +} + +.separator { + display: flex; + align-items: center; + text-align: center; + color: grey; +} +.separator::before, .separator::after { + content: ''; + flex: 1; + border-bottom: 1px solid lightgrey; +} +.separator::before { + margin-right: .25em; +} +.separator::after { + margin-left: .25em; +} diff --git a/assets/application.js b/assets/application.js new file mode 100644 index 0000000..7dd3951 --- /dev/null +++ b/assets/application.js @@ -0,0 +1,13 @@ +function togglePrefix(){ + if(document.getElementById('prefix').value == ''){ + document.getElementById('token').value = ""; + document.getElementById('token').disabled = true; + document.getElementById('token').placeholder = "Only available for prefix != '/'"; + document.getElementById('index').value = "no"; + document.getElementById('index').disabled = true; + }else{ + document.getElementById('token').disabled = false; + document.getElementById('token').placeholder = "Automatically generated if empty"; + document.getElementById('index').disabled = false; + } +} diff --git a/assets/clipboard.min.js b/assets/clipboard.min.js new file mode 100644 index 0000000..b9ed143 --- /dev/null +++ b/assets/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.6 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return o={},r.m=n=[function(t,e){t.exports=function(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o<r;o++)n[o].fn.apply(n[o].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),o=n[t],r=[];if(o&&e)for(var i=0,a=o.length;i<a;i++)o[i].fn!==e&&o[i].fn._!==e&&r.push(o[i]);return r.length?n[t]=r:delete n[t],this}},t.exports=n,t.exports.TinyEmitter=n},function(t,e,n){var d=n(3),h=n(4);t.exports=function(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!d.string(e))throw new TypeError("Second argument must be a String");if(!d.fn(n))throw new TypeError("Third argument must be a Function");if(d.node(t))return s=e,f=n,(u=t).addEventListener(s,f),{destroy:function(){u.removeEventListener(s,f)}};if(d.nodeList(t))return a=t,c=e,l=n,Array.prototype.forEach.call(a,function(t){t.addEventListener(c,l)}),{destroy:function(){Array.prototype.forEach.call(a,function(t){t.removeEventListener(c,l)})}};if(d.string(t))return o=t,r=e,i=n,h(document.body,o,r,i);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList");var o,r,i,a,c,l,u,s,f}},function(t,n){n.node=function(t){return void 0!==t&&t instanceof HTMLElement&&1===t.nodeType},n.nodeList=function(t){var e=Object.prototype.toString.call(t);return void 0!==t&&("[object NodeList]"===e||"[object HTMLCollection]"===e)&&"length"in t&&(0===t.length||n.node(t[0]))},n.string=function(t){return"string"==typeof t||t instanceof String},n.fn=function(t){return"[object Function]"===Object.prototype.toString.call(t)}},function(t,e,n){var a=n(5);function i(t,e,n,o,r){var i=function(e,n,t,o){return function(t){t.delegateTarget=a(t.target,n),t.delegateTarget&&o.call(e,t)}}.apply(this,arguments);return t.addEventListener(n,i,r),{destroy:function(){t.removeEventListener(n,i,r)}}}t.exports=function(t,e,n,o,r){return"function"==typeof t.addEventListener?i.apply(null,arguments):"function"==typeof n?i.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return i(t,e,n,o,r)}))}},function(t,e){if("undefined"!=typeof Element&&!Element.prototype.matches){var n=Element.prototype;n.matches=n.matchesSelector||n.mozMatchesSelector||n.msMatchesSelector||n.oMatchesSelector||n.webkitMatchesSelector}t.exports=function(t,e){for(;t&&9!==t.nodeType;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}},function(t,e,n){"use strict";n.r(e);var o=n(0),r=n.n(o),i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};function a(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}function c(t){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,c),this.resolveOptions(t),this.initSelection()}var l=(function(t,e,n){return e&&a(t.prototype,e),n&&a(t,n),t}(c,[{key:"resolveOptions",value:function(t){var e=0<arguments.length&&void 0!==t?t:{};this.action=e.action,this.container=e.container,this.emitter=e.emitter,this.target=e.target,this.text=e.text,this.trigger=e.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=r()(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=r()(this.target),this.copyText()}},{key:"copyText",value:function(){var e=void 0;try{e=document.execCommand(this.action)}catch(t){e=!1}this.handleResult(e)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),document.activeElement.blur(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(t){var e=0<arguments.length&&void 0!==t?t:"copy";if(this._action=e,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":i(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),c),u=n(1),s=n.n(u),f=n(2),d=n.n(f),h="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},p=function(t,e,n){return e&&y(t.prototype,e),n&&y(t,n),t};function y(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}var m=(function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}(v,s.a),p(v,[{key:"resolveOptions",value:function(t){var e=0<arguments.length&&void 0!==t?t:{};this.action="function"==typeof e.action?e.action:this.defaultAction,this.target="function"==typeof e.target?e.target:this.defaultTarget,this.text="function"==typeof e.text?e.text:this.defaultText,this.container="object"===h(e.container)?e.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=d()(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new l({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return b("action",t)}},{key:"defaultTarget",value:function(t){var e=b("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return b("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(t){var e=0<arguments.length&&void 0!==t?t:["copy","cut"],n="string"==typeof e?[e]:e,o=!!document.queryCommandSupported;return n.forEach(function(t){o=o&&!!document.queryCommandSupported(t)}),o}}]),v);function v(t,e){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,v);var n=function(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}(this,(v.__proto__||Object.getPrototypeOf(v)).call(this));return n.resolveOptions(e),n.listenClick(t),n}function b(t,e){var n="data-clipboard-"+t;if(e.hasAttribute(n))return e.getAttribute(n)}e.default=m}],r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=6).default;function r(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports}var n,o}); diff --git a/bin/.keep b/bin/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/bin/.keep diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..e2779d4 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,47 @@ +version: '3.2' + +services: + http-serving: + build: + context: . + dockerfile: Dockerfile.dev + volumes: + - type: "bind" + source: "/var/log/go-gentoo" + target: "/var/log/go-gentoo" + - type: "bind" + source: "." + target: "/go/src/go-gentoo" + environment: + GO_GENTOO_LOG_FILE: '/var/log/go-gentoo/web.log' + GO_GENTOO_DEVMODE: 'true' +# GO_GENTOO_DEBUG: 'true' + ports: + - 127.0.0.1:5000:5000 + depends_on: + - db + db: + image: postgres:12 + restart: always + environment: + POSTGRES_USER: ${GO_GENTOO_POSTGRES_USER:-root} + POSTGRES_PASSWORD: ${GO_GENTOO_POSTGRES_PASSWORD:-root} + POSTGRES_DB: ${GO_GENTOO_POSTGRES_DB:-gogentoo} + volumes: + - pgdata:/var/lib/postgresql/data + pgadmin: + image: dpage/pgadmin4 + logging: + driver: none + environment: + PGADMIN_DEFAULT_EMAIL: admin@admin.org + PGADMIN_DEFAULT_PASSWORD: admin + volumes: + - pgadmin:/root/.pgadmin + ports: + - "5050:80" + restart: unless-stopped + +volumes: + pgdata: + pgadmin: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..022f6a6 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,35 @@ +version: '3.2' + +services: + http-serving: + image: ${SOKO_IMAGE:-gentoo/go-gentoo:latest} + volumes: + - type: "bind" + source: "/var/log/go-gentoo" + target: "/var/log/go-gentoo" + ports: + - 127.0.0.1:5000:5000 + labels: + com.centurylinklabs.watchtower.enable: "true" + restart: always + environment: + SOKO_LOG_FILE: '/var/log/go-gentoo/web.log' + depends_on: + - db + db: + image: postgres:12 + restart: always + environment: + POSTGRES_USER: ${SOKO_POSTGRES_USER:-root} + POSTGRES_PASSWORD: ${SOKO_POSTGRES_PASSWORD:-root} + POSTGRES_DB: ${SOKO_POSTGRES_DB:-gogentoo} + shm_size: 512mb + volumes: + - ${POSTGRES_DATA_PATH:-/var/lib/gentoo-go/data}:/var/lib/postgresql/data + watchtower: + image: containrrr/watchtower:0.3.10 + restart: always + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /root/.docker/config.json:/config.json + command: --label-enable @@ -0,0 +1,16 @@ +module go-gentoo + +go 1.15 + +require ( + github.com/catinello/base62 v0.0.0-20160325105823-e0daaeb631c9 + github.com/coreos/go-oidc v2.2.1+incompatible + github.com/go-pg/pg/v10 v10.7.3 // indirect + github.com/go-pg/pg/v9 v9.2.0 + github.com/google/go-github v17.0.0+incompatible + github.com/google/go-querystring v1.0.0 // indirect + github.com/gorilla/sessions v1.2.1 + github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect + golang.org/x/oauth2 v0.0.0-20201207163604-931764155e3f + gopkg.in/square/go-jose.v2 v2.5.1 // indirect +) @@ -0,0 +1,455 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/catinello/base62 v0.0.0-20160325105823-e0daaeb631c9 h1:Wwb8B2cNpapZu1JAunAxwn59RaxUOsIoGY9vNCraF14= +github.com/catinello/base62 v0.0.0-20160325105823-e0daaeb631c9/go.mod h1:5LcXhHD4xsYgujv9TLgV4rIhOxGqBJgpdUG42MHSKaQ= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/codemodus/kace v0.5.1 h1:4OCsBlE2c/rSJo375ggfnucv9eRzge/U5LrrOZd47HA= +github.com/codemodus/kace v0.5.1/go.mod h1:coddaHoX1ku1YFSe4Ip0mL9kQjJvKkzb9CfIdG1YR04= +github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= +github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-pg/pg v8.0.7+incompatible h1:ty/sXL1OZLo+47KK9N8llRcmbA9tZasqbQ/OO4ld53g= +github.com/go-pg/pg/v10 v10.7.3 h1:oL/Hz5MJie/9epmwxlZjfReO+2wlLPOK6BeGb9I+XHk= +github.com/go-pg/pg/v10 v10.7.3/go.mod h1:UsDYtA+ihbBNX1OeIvDejxkL4RXzL3wsZYoEv5NUEqM= +github.com/go-pg/pg/v9 v9.2.0 h1:AC+lI8RFFJwf8Pesb7AnUPWv94zhxHZo3MlAKuLE3TE= +github.com/go-pg/pg/v9 v9.2.0/go.mod h1:fG8qbL+ei4e/fCZLHK+Z+/7b9B+pliZtbpaucG4/YNQ= +github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= +github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac h1:jWKYCNlX4J5s8M0nHYkh7Y7c9gRVDEb3mq51j5J0F5M= +github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/segmentio/encoding v0.1.15 h1:btgfyAuFo3uLw7eOrRDPo8H4Bc881+bSPHzAEe0ukho= +github.com/segmentio/encoding v0.1.15/go.mod h1:RWhr02uzMB9gQC1x+MfYxedtmBibb9cZ6Vv9VxRSSbw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94= +github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ= +github.com/vmihailenco/msgpack/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/msgpack/v5 v5.0.0 h1:nCaMMPEyfgwkGc/Y0GreJPhuvzqCqW+Ufq5lY7zLO2c= +github.com/vmihailenco/msgpack/v5 v5.0.0/go.mod h1:HVxBVPUK/+fZMonk4bi1islLa8V3cfnBug0+4dykPzo= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= +github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/otel v0.14.0 h1:YFBEfjCk9MTjaytCNSUkp9Q8lF7QJezA06T71FbQxLQ= +go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o= +golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20201203001011-0b49973bad19 h1:ZD+2Sd/BnevwJp8PSli8WgGAGzb9IZtxBsv1iZMYeEA= +golang.org/x/oauth2 v0.0.0-20201203001011-0b49973bad19/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201207163604-931764155e3f h1:bGuVhRryQ3m1t3U3cQOa4TdSuMIXKrTrvmdJjQLbMKc= +golang.org/x/oauth2 v0.0.0-20201207163604-931764155e3f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +mellium.im/sasl v0.2.1 h1:nspKSRg7/SyO0cRGY71OkfHab8tf9kCts6a6oTDut0w= +mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/gogentoo.go b/gogentoo.go new file mode 100644 index 0000000..bfbcd0b --- /dev/null +++ b/gogentoo.go @@ -0,0 +1,66 @@ +package main + +import ( + "flag" + "go-gentoo/pkg/app" + "go-gentoo/pkg/config" + "go-gentoo/pkg/logger" + "io" + "io/ioutil" + "os" + "time" +) + +func main() { + + //waitForPostgres() + + errorLogFile := logger.CreateLogFile(config.LogFile()) + defer errorLogFile.Close() + initLoggers(os.Stdout, errorLogFile) + + serve := flag.Bool("serve", false, "Start serving the application") + help := flag.Bool("help", false, "Print the usage of this application") + flag.Parse() + + if *serve { + app.Serve() + } + + if *help { + flag.PrintDefaults() + } +} + +// initialize the loggers depending on whether +// config.debug is set to true +func initLoggers(infoHandler io.Writer, errorHandler io.Writer) { + if config.Debug() == "true" { + logger.Init(os.Stdout, infoHandler, errorHandler) + } else { + logger.Init(ioutil.Discard, infoHandler, errorHandler) + } +} + +// TODO this has to be solved differently +// wait for postgres to come up +func waitForPostgres() { + time.Sleep(5 * time.Second) +} + +// Login + +// Logout + +// Create New +// - [optional] select project +// - [optional] select expire +// - [optional] select path + +// View existing +// - personal +// - per Project +// => options per entry +// - change expire +// - delete +// (- add new) diff --git a/pkg/app/handler/admin/admin.go b/pkg/app/handler/admin/admin.go new file mode 100644 index 0000000..299f0b7 --- /dev/null +++ b/pkg/app/handler/admin/admin.go @@ -0,0 +1,21 @@ +// Used to show the landing page of the application + +package admin + +import ( + "html/template" + "net/http" +) + +// Show renders a template to show the landing page of the application +func Show(w http.ResponseWriter, r *http.Request) { + + templates := template.Must( + template.Must( + template.New("Show"). + Funcs(getFuncMap()). + ParseGlob("web/templates/layout/*.tmpl")). + ParseGlob("web/templates/admin/*.tmpl")) + + templates.ExecuteTemplate(w, "show.tmpl", getPageData(w, r)) +} diff --git a/pkg/app/handler/admin/utils.go b/pkg/app/handler/admin/utils.go new file mode 100644 index 0000000..67bb0d6 --- /dev/null +++ b/pkg/app/handler/admin/utils.go @@ -0,0 +1,60 @@ +// miscellaneous utility functions used for the landing page of the application + +package admin + +import ( + "crypto/md5" + "fmt" + "go-gentoo/pkg/app/handler/auth" + "go-gentoo/pkg/database" + "go-gentoo/pkg/models" + "html/template" + "net/http" + "strings" +) + +func getPageData(w http.ResponseWriter, r *http.Request) interface{} { + user := auth.GetUser(w, r) + return struct { + Tab string + User *models.User + UserLinks []models.Link + }{ + Tab: "admin", + User: user, + UserLinks: getAllLinks(), + } +} + +func getFuncMap() template.FuncMap { + return template.FuncMap{ + "gravatar": emailToGravater, + "replaceAll": strings.ReplaceAll, + "getPrefixList": getPrefixList, + } +} + +func emailToGravater(email string) string { + return "https://www.gravatar.com/avatar/" + fmt.Sprintf("%x", md5.Sum([]byte(email))) +} + +func getAllLinks() []models.Link { + var links []models.Link + database.DBCon.Model(&links). + Select() + return links +} + +func getPrefixList(links []models.Link) []string { + prefixMap := make(map[string]bool) + var prefixList []string + for _, link := range links { + if link.Prefix != "" { + prefixMap[link.Prefix] = true + } + } + for key, _ := range prefixMap { + prefixList = append(prefixList, key) + } + return prefixList +} diff --git a/pkg/app/handler/auth/handlers.go b/pkg/app/handler/auth/handlers.go new file mode 100644 index 0000000..8e79959 --- /dev/null +++ b/pkg/app/handler/auth/handlers.go @@ -0,0 +1,101 @@ +package auth + +import ( + "crypto/rand" + "encoding/base64" + "encoding/json" + "go-gentoo/pkg/config" + "go-gentoo/pkg/models" + "golang.org/x/oauth2" + "net/http" + "net/url" +) + +func Login(w http.ResponseWriter, r *http.Request) { + b := make([]byte, 16) + rand.Read(b) + + state := base64.URLEncoding.EncodeToString(b) + + session, _ := CookieStore.Get(r, config.SessionStoreKey()) + session.Values["state"] = state + session.Save(r, w) + + url := Oauth2Config.AuthCodeURL(state) + http.Redirect(w, r, url, http.StatusFound) +} + +func Callback(w http.ResponseWriter, r *http.Request) { + session, err := CookieStore.Get(r, config.SessionStoreKey()) + + if err != nil { + http.Error(w, "state did not match", http.StatusBadRequest) + return + } + + if r.URL.Query().Get("state") != session.Values["state"] { + http.Error(w, "state did not match", http.StatusBadRequest) + return + } + + oauth2Token, err := Oauth2Config.Exchange(Ctx, r.URL.Query().Get("code")) + if err != nil { + http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError) + return + } + rawIDToken, ok := oauth2Token.Extra("id_token").(string) + if !ok { + http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError) + return + } + idToken, err := Verifier.Verify(Ctx, rawIDToken) + if err != nil { + http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError) + return + } + + resp := struct { + OAuth2Token *oauth2.Token + IDTokenClaims *json.RawMessage // ID Token payload is just JSON. + }{oauth2Token, new(json.RawMessage)} + + if err := idToken.Claims(&resp.IDTokenClaims); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + type Response struct { + GivenName string `json:"given_name"` + Username string `json:"preferred_username"` + Email string `json:"email"` + } + + var keycloakResponse Response + err = json.Unmarshal(*resp.IDTokenClaims, &keycloakResponse) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + session.Values["idToken"] = rawIDToken + session.Values["user"] = models.User{ + Email: keycloakResponse.Email, + RealName: keycloakResponse.GivenName, + UserName: keycloakResponse.Username, + Projects: nil, + } + err = session.Save(r, w) + + http.Redirect(w, r, "/", http.StatusFound) +} + +// http://www.gorillatoolkit.org/pkg/sessions#CookieStore.MaxAge +func Logout(w http.ResponseWriter, r *http.Request) { + session, err := CookieStore.Get(r, config.SessionStoreKey()) + if err != nil { + return + } + session.Options.MaxAge = -1 + session.Save(r, w) + http.Redirect(w, r, config.OIDConfigURL()+"/protocol/openid-connect/logout?redirect_uri="+url.QueryEscape(config.ApplicationURL()), 302) +} diff --git a/pkg/app/handler/auth/init.go b/pkg/app/handler/auth/init.go new file mode 100644 index 0000000..e97e997 --- /dev/null +++ b/pkg/app/handler/auth/init.go @@ -0,0 +1,47 @@ +package auth + +import ( + "context" + "encoding/gob" + "github.com/coreos/go-oidc" + "github.com/gorilla/sessions" + "go-gentoo/pkg/config" + "go-gentoo/pkg/models" + "golang.org/x/oauth2" +) + +var ( + Oauth2Config oauth2.Config + Verifier *oidc.IDTokenVerifier + Ctx context.Context + CookieStore *sessions.CookieStore +) + +func Init() { + gob.Register(&models.User{}) + + Ctx = context.Background() + provider, err := oidc.NewProvider(Ctx, config.OIDConfigURL()) + if err != nil { + panic(err) + } + + CookieStore = sessions.NewCookieStore([]byte(config.SessionSecret())) + + // Configure an OpenID Connect aware OAuth2 client. + Oauth2Config = oauth2.Config{ + ClientID: config.OIDClientID(), + ClientSecret: config.OIDClientSecret(), + RedirectURL: config.ApplicationURL() + "/auth/callback", + // Discovery returns the OAuth2 endpoints. + Endpoint: provider.Endpoint(), + // "openid" is a required scope for OpenID Connect flows. + Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, + } + + oidcConfig := &oidc.Config{ + ClientID: config.OIDClientID(), + } + Verifier = provider.Verifier(oidcConfig) + +} diff --git a/pkg/app/handler/auth/user.go b/pkg/app/handler/auth/user.go new file mode 100644 index 0000000..9761d80 --- /dev/null +++ b/pkg/app/handler/auth/user.go @@ -0,0 +1,35 @@ +package auth + +import ( + "go-gentoo/pkg/config" + "go-gentoo/pkg/models" + "net/http" +) + +func IsValidUser(w http.ResponseWriter, r *http.Request) bool { + session, err := CookieStore.Get(r, config.SessionStoreKey()) + + if err != nil { + return false + } + + if token, ok := session.Values["idToken"].(string); ok { + _, err = Verifier.Verify(Ctx, token) + return err == nil + } + + return false +} + +func GetUser(w http.ResponseWriter, r *http.Request) *models.User { + session, err := CookieStore.Get(r, config.SessionStoreKey()) + if err != nil { + return nil + } + user := session.Values["user"].(*models.User) + err = user.ComputeProjects() + if err != nil { + return nil + } + return user +} diff --git a/pkg/app/handler/index/index.go b/pkg/app/handler/index/index.go new file mode 100644 index 0000000..e31d32c --- /dev/null +++ b/pkg/app/handler/index/index.go @@ -0,0 +1,38 @@ +// Used to show the landing page of the application + +package index + +import ( + "go-gentoo/pkg/database" + "go-gentoo/pkg/models" + "net/http" +) + +// Show renders a template to show the landing page of the application +func Handle(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + redirect(w, r) + return + } + http.Redirect(w, r, "/links/create", http.StatusFound) +} + +func redirect(w http.ResponseWriter, r *http.Request) { + links := getLink(r.URL.Path) + if len(links) != 1 { + http.Error(w, "Not found", http.StatusNotFound) + } else { + link := links[0] + link.Hits++ + database.DBCon.Model(&link).WherePK().Update() + http.Redirect(w, r, links[0].TargetLink, http.StatusFound) + } +} + +func getLink(shortlink string) []models.Link { + var links []models.Link + database.DBCon.Model(&links). + Where("short_link = ?", shortlink). + Select() + return links +} diff --git a/pkg/app/handler/links/create.go b/pkg/app/handler/links/create.go new file mode 100644 index 0000000..3d88f59 --- /dev/null +++ b/pkg/app/handler/links/create.go @@ -0,0 +1,66 @@ +package links + +import ( + "go-gentoo/pkg/app/handler/auth" + "html/template" + "net/http" +) + +// Show renders a template to show the landing page of the application +func Create(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + showForm(w, r) + case http.MethodPost: + createLink(w, r) + } +} + +func showForm(w http.ResponseWriter, r *http.Request) { + + templates := template.Must( + template.Must( + template.New("Show"). + Funcs(getFuncMap()). + ParseGlob("web/templates/layout/*.tmpl")). + ParseGlob("web/templates/links/create.tmpl")) + + templates.ExecuteTemplate(w, "create.tmpl", getPageData(w, r, "create")) +} + +func createLink(w http.ResponseWriter, r *http.Request) { + user := auth.GetUser(w, r) + + r.ParseForm() + prefix := r.Form.Get("prefix") + index := r.Form.Get("index") + token := r.Form.Get("token") + target := r.Form.Get("target") + + if !user.IsAdmin() && prefix == "" { + token = "" + } + + if isValidPrefix(user, prefix) && isValidToken(prefix) && isValidToken(token) && isValidTarget(target) { + shortlink, ok := createShortURL(prefix, token, target, user.Email, index == "yes", 0) + if ok { + renderCreatedTemplate(w, r, shortlink, target) + return + } else { + http.Error(w, "Could not create shortened URL", http.StatusInternalServerError) + } + } else { + http.Error(w, "Params are not valid", http.StatusBadRequest) + } +} + +func renderCreatedTemplate(w http.ResponseWriter, r *http.Request, shortlink, target string) { + templates := template.Must( + template.Must( + template.New("Show"). + Funcs(getFuncMap()). + ParseGlob("web/templates/layout/*.tmpl")). + ParseGlob("web/templates/links/created.tmpl")) + + templates.ExecuteTemplate(w, "created.tmpl", getCreatePageData(w, r, shortlink, target)) +} diff --git a/pkg/app/handler/links/delete.go b/pkg/app/handler/links/delete.go new file mode 100644 index 0000000..ee4bce4 --- /dev/null +++ b/pkg/app/handler/links/delete.go @@ -0,0 +1,41 @@ +package links + +import ( + "go-gentoo/pkg/app/handler/auth" + "go-gentoo/pkg/database" + "go-gentoo/pkg/models" + "net/http" +) + +func Delete(w http.ResponseWriter, r *http.Request) { + user := auth.GetUser(w, r) + + r.ParseForm() + prefix := r.Form.Get("prefix") + token := r.Form.Get("token") + from := r.Form.Get("from") + var shortlink string + if prefix != "" { + shortlink = "/" + prefix + "/" + token + } else { + shortlink = "/" + token + } + + links := getLink(shortlink) + + if len(links) != 1 { + http.Error(w, "Could not delete shortened URL", http.StatusInternalServerError) + return + } + + if user.IsAdmin() || links[0].UserEmail == user.Email || contains(user.Projects, links[0].Prefix) || links[0].Prefix == user.UserName { + link := new(models.Link) + _, err := database.DBCon.Model(link).Where("short_link = ?", shortlink).Delete() + if err != nil { + http.Error(w, "Could not delete shortened URL", http.StatusInternalServerError) + } else { + http.Redirect(w, r, from, http.StatusFound) + } + } + +} diff --git a/pkg/app/handler/links/show.go b/pkg/app/handler/links/show.go new file mode 100644 index 0000000..4e5c5c6 --- /dev/null +++ b/pkg/app/handler/links/show.go @@ -0,0 +1,19 @@ +package links + +import ( + "html/template" + "net/http" +) + +// Show renders a template to show the landing page of the application +func Show(w http.ResponseWriter, r *http.Request) { + + templates := template.Must( + template.Must( + template.New("Show"). + Funcs(getFuncMap()). + ParseGlob("web/templates/layout/*.tmpl")). + ParseGlob("web/templates/links/show.tmpl")) + + templates.ExecuteTemplate(w, "show.tmpl", getPageData(w, r, "show")) +} diff --git a/pkg/app/handler/links/utils.go b/pkg/app/handler/links/utils.go new file mode 100644 index 0000000..26afbbf --- /dev/null +++ b/pkg/app/handler/links/utils.go @@ -0,0 +1,157 @@ +package links + +import ( + "crypto/md5" + "fmt" + "github.com/catinello/base62" + "go-gentoo/pkg/app/handler/auth" + "go-gentoo/pkg/config" + "go-gentoo/pkg/database" + "go-gentoo/pkg/models" + "html/template" + "net/http" + "net/url" + "strings" +) + +const ( + MAX_TRY = 10 + MAX_TOKEN_LENGTH = 75 +) + +func getPageData(w http.ResponseWriter, r *http.Request, tab string) interface{} { + user := auth.GetUser(w, r) + return struct { + Tab string + User *models.User + UserLinks []models.Link + }{ + Tab: tab, + User: user, + UserLinks: getLinks(user), + } +} + +func getCreatePageData(w http.ResponseWriter, r *http.Request, shortlink, target string) interface{} { + return struct { + Tab string + User *models.User + ShortLink string + Target string + }{ + Tab: "new", + User: auth.GetUser(w, r), + ShortLink: config.ApplicationURL() + shortlink, + Target: target, + } +} + +func getFuncMap() template.FuncMap { + return template.FuncMap{ + "gravatar": emailToGravater, + "replaceAll": strings.ReplaceAll, + } +} + +func emailToGravater(email string) string { + return "https://www.gravatar.com/avatar/" + fmt.Sprintf("%x", md5.Sum([]byte(email))) +} + +func getLinks(user *models.User) []models.Link { + var links []models.Link + database.DBCon.Model(&links). + Where("user_email = '" + user.Email + "'"). + WhereOr(createQuery(user)). + Select() + return links +} + +func createQuery(user *models.User) string { + var queryParts []string + for _, project := range user.Projects { + queryParts = append(queryParts, "prefix = '"+project+"'") + } + return strings.Join(queryParts, " OR ") +} + +func isValidPrefix(user *models.User, prefix string) bool { + return prefixIsNotReserved(prefix) && (contains(user.Projects, prefix) || prefix == "" || prefix == user.UserName) +} + +func prefixIsNotReserved(prefix string) bool { + return prefix != "auth" && prefix != "assets" && prefix != "links" && prefix != "admin" +} + +func isValidToken(token string) bool { + for _, char := range token { + if !strings.Contains(config.ValidURLTokenChars(), strings.ToLower(string(char))) { + return false + } + } + return len(token) < MAX_TOKEN_LENGTH +} + +func isValidTarget(target string) bool { + _, err := url.ParseRequestURI(target) + return err == nil +} + +func getLatestId() int { + + var links []models.Link + database.DBCon.Model(&links). + Order("id DESC"). + Limit(1). + Select() + + if len(links) != 1 { + return 4000 + } + + return links[0].Id +} + +func createShortURL(prefix, token, target, username string, setIndex bool, try int) (string, bool) { + var shortlink string + id := getLatestId() + 1 + if token == "" && (!setIndex || prefix == "") { + token = base62.Encode(id) + } + if prefix != "" { + shortlink = "/" + prefix + "/" + token + } else { + shortlink = "/" + token + } + + if len(getLink(shortlink)) > 0 && try <= MAX_TRY { + return createShortURL(prefix, token, target, username, setIndex, try+1) + } + + err := database.DBCon.Insert(&models.Link{ + Id: id, + Prefix: prefix, + URLToken: token, + ShortLink: shortlink, + TargetLink: target, + UserEmail: username, + Hits: 0, + }) + return shortlink, err == nil +} + +func getLink(shortlink string) []models.Link { + var links []models.Link + database.DBCon.Model(&links). + Where("short_link = ?", shortlink). + Select() + return links +} + +func contains(list []string, value string) bool { + for _, v := range list { + if v == value { + return true + } + } + return false +} diff --git a/pkg/app/serve.go b/pkg/app/serve.go new file mode 100644 index 0000000..fc2cdc3 --- /dev/null +++ b/pkg/app/serve.go @@ -0,0 +1,74 @@ +package app + +import ( + "go-gentoo/pkg/app/handler/admin" + "go-gentoo/pkg/app/handler/auth" + "go-gentoo/pkg/app/handler/index" + "go-gentoo/pkg/app/handler/links" + "go-gentoo/pkg/config" + "go-gentoo/pkg/database" + "go-gentoo/pkg/logger" + "log" + "net/http" +) + +// Serve is used to serve the web application +func Serve() { + + database.Connect() + defer database.DBCon.Close() + + auth.Init() + setRoute("/auth/login", auth.Login) + setRoute("/auth/logout", auth.Logout) + setRoute("/auth/callback", auth.Callback) + + setProtectedRoute("/links/show", links.Show) + setProtectedRoute("/links/create", links.Create) + setProtectedRoute("/links/delete", links.Delete) + + setProtectedRoute("/admin/", admin.Show) + + setProtectedRoute("/", index.Handle) + + fs := http.StripPrefix("/assets/", http.FileServer(http.Dir("/go/src/go-gentoo/assets"))) + http.Handle("/assets/", fs) + + logger.Info.Println("Serving on port: " + config.Port()) + log.Fatal(http.ListenAndServe(":"+config.Port(), nil)) +} + +// define a route using the default middleware and the given handler +func setProtectedRoute(path string, handler http.HandlerFunc) { + http.HandleFunc(path, protectedMW(handler)) +} + +// mw is used as default middleware to set the default headers +func protectedMW(handler http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if auth.IsValidUser(w, r) { + setDefaultHeaders(w) + handler(w, r) + } else { + http.Redirect(w, r, "/auth/login", 301) + } + } +} + +// define a route using the default middleware and the given handler +func setRoute(path string, handler http.HandlerFunc) { + http.HandleFunc(path, mw(handler)) +} + +// mw is used as default middleware to set the default headers +func mw(handler http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + setDefaultHeaders(w) + handler(w, r) + } +} + +// setDefaultHeaders sets the default headers that apply for all pages +func setDefaultHeaders(w http.ResponseWriter) { + w.Header().Set("Cache-Control", "no-cache") +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..261ff3a --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,75 @@ +package config + +import "os" + +func PostgresUser() string { + return getEnv("GO_GENTOO_POSTGRES_USER", "root") +} + +func PostgresPass() string { + return getEnv("GO_GENTOO_POSTGRES_PASS", "root") +} + +func PostgresDb() string { + return getEnv("GO_GENTOO_POSTGRES_DB", "gogentoo") +} + +func PostgresHost() string { + return getEnv("GO_GENTOO_POSTGRES_HOST", "db") +} + +func PostgresPort() string { + return getEnv("GO_GENTOO_POSTGRES_PORT", "5432") +} + +func Port() string { + return getEnv("GO_GENTOO_PORT", "5000") +} + +func CacheControl() string { + return getEnv("GO_GENTOO_CACHE_CONTROL", "max-age=300") +} + +func Debug() string { + return getEnv("GO_GENTOO_DEBUG", "false") +} + +func LogFile() string { + return getEnv("GO_GENTOO_LOG_FILE", "/var/log/go-gentoo/errors.log") +} + +func OIDConfigURL() string { + return getEnv("GO_GENTOO_OID_CONFIG_URL", "https://sso.gentoo.org/auth/realms/gentoo") +} + +func OIDClientID() string { + return getEnv("GO_GENTOO_OID_CLIENT_ID", "demo-client") +} + +func OIDClientSecret() string { + return getEnv("GO_GENTOO_OID_CLIENT_SECRET", "00000000-0000-0000-0000-000000000000") +} + +func ApplicationURL() string { + return getEnv("GO_GENTOO_APPLICATION_URL", "https://go.gentoo.org") +} + +func SessionStoreKey() string { + return getEnv("GO_GENTOO_SESSION_STORE_KEY", "gentoo_sess") +} + +func SessionSecret() string { + return getEnv("GO_GENTOO_SESSION_SECRET", "123456789") +} + +func ValidURLTokenChars() string { + return getEnv("GO_GENTOO_VALID_URL_TOKEN_CHARS", "abcdefghijklmnopqrstuvwxyz1234567890-") +} + +func getEnv(key string, fallback string) string { + if os.Getenv(key) != "" { + return os.Getenv(key) + } else { + return fallback + } +} diff --git a/pkg/database/connection.go b/pkg/database/connection.go new file mode 100644 index 0000000..c5e7c7b --- /dev/null +++ b/pkg/database/connection.go @@ -0,0 +1,68 @@ +// Contains utility functions around the database + +package database + +import ( + "context" + "github.com/go-pg/pg/v9" + "github.com/go-pg/pg/v9/orm" + "go-gentoo/pkg/config" + "go-gentoo/pkg/logger" + "go-gentoo/pkg/models" + "log" +) + +// DBCon is the connection handle +// for the database +var ( + DBCon *pg.DB +) + +// CreateSchema creates the tables in the database +// in case they don't alreay exist +func CreateSchema() error { + for _, model := range []interface{}{(*models.Link)(nil)} { + + err := DBCon.CreateTable(model, &orm.CreateTableOptions{ + IfNotExists: true, + }) + if err != nil { + return err + } + + } + return nil +} + +type dbLogger struct{} + +func (d dbLogger) BeforeQuery(c context.Context, q *pg.QueryEvent) (context.Context, error) { + return c, nil +} + +// AfterQuery is used to log SQL queries +func (d dbLogger) AfterQuery(c context.Context, q *pg.QueryEvent) error { + logger.Debug.Println(q.FormattedQuery()) + return nil +} + +// Connect is used to connect to the database +// and turn on logging if desired +func Connect() { + DBCon = pg.Connect(&pg.Options{ + User: config.PostgresUser(), + Password: config.PostgresPass(), + Database: config.PostgresDb(), + Addr: config.PostgresHost() + ":" + config.PostgresPort(), + }) + + DBCon.AddQueryHook(dbLogger{}) + + err := CreateSchema() + if err != nil { + logger.Error.Println("ERROR: Could not create database schema") + logger.Error.Println(err) + log.Fatalln(err) + } + +} diff --git a/pkg/logger/loggers.go b/pkg/logger/loggers.go new file mode 100644 index 0000000..0ef51e9 --- /dev/null +++ b/pkg/logger/loggers.go @@ -0,0 +1,39 @@ +package logger + +import ( + "io" + "log" + "os" +) + +var ( + Debug *log.Logger + Info *log.Logger + Error *log.Logger +) + +func Init( + debugHandle io.Writer, + infoHandle io.Writer, + errorHandle io.Writer) { + + Debug = log.New(debugHandle, + "DEBUG: ", + log.Ldate|log.Ltime|log.Lshortfile) + + Info = log.New(infoHandle, + "INFO: ", + log.Ldate|log.Ltime|log.Lshortfile) + + Error = log.New(errorHandle, + "ERROR: ", + log.Ldate|log.Ltime|log.Lshortfile) +} + +func CreateLogFile(path string) *os.File { + f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + log.Println(err) + } + return f +} diff --git a/pkg/models/link.go b/pkg/models/link.go new file mode 100644 index 0000000..34d96fa --- /dev/null +++ b/pkg/models/link.go @@ -0,0 +1,13 @@ +// Contains the model of the application data + +package models + +type Link struct { + Id int `pg:",pk"` + Prefix string + URLToken string + ShortLink string `pg:",unique"` + TargetLink string + UserEmail string + Hits int +} diff --git a/pkg/models/projects.go b/pkg/models/projects.go new file mode 100644 index 0000000..63b1c35 --- /dev/null +++ b/pkg/models/projects.go @@ -0,0 +1,25 @@ +package models + +import "encoding/xml" + +type ProjectList struct { + XMLName xml.Name `xml:"projects"` + Projects []Project `xml:"project"` +} + +type Project struct { + XMLName xml.Name `xml:"project" pg:"-"` + Email string `xml:"email" pg:",pk"` + Name string `xml:"name"` + Url string `xml:"url"` + Description string `xml:"description"` + Members []Member `xml:"member"` +} + +type Member struct { + XMLName xml.Name `xml:"member" json:"-" pg:"-"` + IsLead bool `xml:"is-lead,attr"` + Email string `xml:"email"` + Name string `xml:"name"` + Role string `xml:"role"` +} diff --git a/pkg/models/user.go b/pkg/models/user.go new file mode 100644 index 0000000..09dc247 --- /dev/null +++ b/pkg/models/user.go @@ -0,0 +1,61 @@ +// Contains the model of the application data + +package models + +import ( + "encoding/xml" + "io/ioutil" + "net/http" + "strings" +) + +type User struct { + Email string `pg:",pk"` + RealName string + UserName string + Projects []string +} + +func (u *User) IsAdmin() bool { + for _, project := range u.Projects { + if project == "infra" { + return true + } + } + return false +} + +func (u *User) ComputeProjects() error { + projects, err := parseProjectList() + + if err != nil { + return err + } + + for _, project := range projects.Projects { + for _, member := range project.Members { + if member.Email == u.Email { + abbreviation := strings.ReplaceAll(project.Email, "@gentoo.org", "") + u.Projects = append(u.Projects, abbreviation) + } + } + } + + return nil +} + +// parseQAReport gets the xml from qa-reports.gentoo.org and parses it +func parseProjectList() (ProjectList, error) { + resp, err := http.Get("https://api.gentoo.org/metastructure/projects.xml") + if err != nil { + return ProjectList{}, err + } + defer resp.Body.Close() + xmlData, err := ioutil.ReadAll(resp.Body) + if err != nil { + return ProjectList{}, err + } + var projectList ProjectList + xml.Unmarshal(xmlData, &projectList) + return projectList, err +} diff --git a/web/templates/admin/show.tmpl b/web/templates/admin/show.tmpl new file mode 100644 index 0000000..2dbbb78 --- /dev/null +++ b/web/templates/admin/show.tmpl @@ -0,0 +1,121 @@ +<!DOCTYPE html> +<html lang="en"> +{{template "head" .}} +<body> +{{template "header" .}} + +<div class="container mb-5"> + <div class="row"> + <div class="col-12"> + + <h2 class="mt-4"><span style="font-family: monospace;">/</span></h2> + + {{$empty := true}} + + <table class="table"> + <colgroup> + <col span="1" style="width: 20%;"> + <col span="1" style="width: 60%;"> + <col span="1" style="width: 10%;"> + <col span="1" style="width: 5%;"> + <col span="1" style="width: 5%;"> + </colgroup> + <thead> + <tr> + <th scope="col">Short URL</th> + <th scope="col">Target</th> + <th scope="col">Creator</th> + <th scope="col">Hits</th> + <th scope="col">Action</th> + </tr> + </thead> + <tbody> + {{$empty = true}} + {{range .UserLinks}} + {{if eq .Prefix ""}} + {{$empty = false}} + <tr> + <th scope="row"><a class="text-dark" href="{{.ShortLink}}">go.gentoo.org{{.ShortLink}}</a></th> + <td><a class="text-dark" href="{{.TargetLink}}">{{.TargetLink}}</a></td> + <td>{{replaceAll .UserEmail "@gentoo.org" ""}}</td> + <td>{{.Hits}}</td> + <td> + <form action="/links/delete" method="post"> + <input hidden name="prefix" value="{{.Prefix}}"> + <input hidden name="token" value="{{.URLToken}}"> + <input hidden name="from" value="/admin/"> + <button type="submit" class="btn btn-link py-0 text-dark"> + <i class="fa fa-trash-o" aria-hidden="true"></i> + </button> + </form> + </td> + </tr> + {{end}} + {{end}} + {{if $empty}} + <tr> + <td colspan="4" class="text-center"><i>No shortened URLs yet</i></td> + </tr> + {{end}} + </tbody> + </table> + + + {{range $index, $project := getPrefixList .UserLinks}} + <h2 class="mt-5"><span style="font-family: monospace;">/{{$project}}/</span></h2> + + <table class="table"> + <colgroup> + <col span="1" style="width: 20%;"> + <col span="1" style="width: 65%;"> + <col span="1" style="width: 10%;"> + <col span="1" style="width: 5%;"> + </colgroup> + <thead> + <tr> + <th scope="col">Short URL</th> + <th scope="col">Target</th> + <th scope="col">Creator</th> + <th scope="col">Action</th> + </tr> + </thead> + <tbody> + {{$empty = true}} + {{range $.UserLinks}} + {{if eq .Prefix $project}} + {{$empty = false}} + <tr> + <th scope="row"><a class="text-dark" href="{{.ShortLink}}">go.gentoo.org{{.ShortLink}}</a></th> + <td><a class="text-dark" href="{{.TargetLink}}">{{.TargetLink}}</a></td> + <td>{{replaceAll .UserEmail "@gentoo.org" ""}}</td> + <td> + <form action="/links/delete" method="post"> + <input hidden name="prefix" value="{{.Prefix}}"> + <input hidden name="token" value="{{.URLToken}}"> + <input hidden name="from" value="/admin/"> + <button type="submit" class="btn btn-link py-0 text-dark"> + <i class="fa fa-trash-o" aria-hidden="true"></i> + </button> + </form> + </td> + </tr> + {{end}} + {{end}} + {{if $empty}} + <tr> + <td colspan="4" class="text-center"><i>No shortened URLs yet</i></td> + </tr> + {{end}} + </tbody> + </table> + {{end}} + </div> + </div> +</div> + + +{{template "footer" .}} + + +</body> +</html> diff --git a/web/templates/layout/footer.tmpl b/web/templates/layout/footer.tmpl new file mode 100644 index 0000000..3b5efcf --- /dev/null +++ b/web/templates/layout/footer.tmpl @@ -0,0 +1,51 @@ +{{define "footer"}} + <footer style="background-color: #fafafa; box-shadow:none!important;"> + <div class="container pt-4" style="border-top: 1px solid #dddddd;"> + <div class="row d-none"> + <div class="col-12 offset-md-2 col-md-7"> + <h3 class="footerhead">Gentoo Packages Database</h3> + <div class="row"> + <div class="col-xs-12 col-md-4"> + </div> + <div class="col-xs-12 col-md-4"> + </div> + <div class="col-xs-12 col-md-4"> + </div> + </div> + </div> + <div class="col-12 col-md-3"> + <h3 class="footerhead">Questions or comments?</h3> + Please feel free to <a href="https://www.gentoo.org/inside-gentoo/contact/">contact us</a>. + </div> + </div> + <div class="row"> + <div class="col-2 col-sm-2 col-md-2"> + <ul class="footerlinks three-icons"> + <li><a href="https://twitter.com/gentoo" title="@Gentoo on Twitter"><span class="fa fa-twitter fa-fw"></span></a></li> + <li><a href="https://www.facebook.com/gentoo.org" title="Gentoo on Facebook"><span class="fa fa-facebook fa-fw"></span></a></li> + <li><a href="https://www.reddit.com/r/Gentoo/" title="Gentoo on Reddit"><span class="fa fa-reddit-alien fa-fw"></span></a></li> + </ul> + </div> + <div class="col-8 col-sm-8 col-md-8"> + <strong>© 2001–2020 Gentoo Foundation, Inc.</strong><br> + <small> + Gentoo is a trademark of the Gentoo Foundation, Inc. + The contents of this document, unless otherwise expressly stated, are licensed under the + <a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="license">CC-BY-SA-4.0</a> license. + The <a href="https://www.gentoo.org/inside-gentoo/foundation/name-logo-guidelines.html">Gentoo Name and Logo Usage Guidelines</a> apply. + </small> + </div> + <div class="col-2 col-sm-2 col-md-2 text-right"> + <strong><a class="text-dark" href="https://www.gentoo.org/inside-gentoo/contact/">Contact</a></strong><br> + </div> + </div> + </div> + </footer> + + <script + src="https://code.jquery.com/jquery-3.5.1.slim.min.js" + integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs=" + crossorigin="anonymous"></script> + <script src="https://assets.gentoo.org/tyrian/v2/popper.min.js"></script> + <script src="https://assets.gentoo.org/tyrian/v2/bootstrap.min.js"></script> +{{end}} diff --git a/web/templates/layout/head.tmpl b/web/templates/layout/head.tmpl new file mode 100644 index 0000000..4ab796a --- /dev/null +++ b/web/templates/layout/head.tmpl @@ -0,0 +1,12 @@ +{{define "head"}} + <head> + <title>Gentoo URL Shortener</title> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="theme-color" content="#54487a"> + <meta name="description" content="Gentoo URL Shortener"> + <link href="https://assets.gentoo.org/tyrian/v2/tyrian.min.css" rel="stylesheet" media="screen"> + <link href="/assets/application.css" rel="stylesheet" media="screen"> + <link rel="icon" href="https://packages.gentoo.org/favicon.ico" type="image/x-icon"> + </head> +{{end}} diff --git a/web/templates/layout/header.tmpl b/web/templates/layout/header.tmpl new file mode 100644 index 0000000..571a58b --- /dev/null +++ b/web/templates/layout/header.tmpl @@ -0,0 +1,6 @@ +{{define "header"}} + <header> + {{template "sitetitle"}} + {{template "tyrian-navbar" .}} + </header> +{{end}} diff --git a/web/templates/layout/sitetitle.tmpl b/web/templates/layout/sitetitle.tmpl new file mode 100644 index 0000000..bd07b34 --- /dev/null +++ b/web/templates/layout/sitetitle.tmpl @@ -0,0 +1,37 @@ +{{define "sitetitle"}} + <div class="site-title"> + <div class="container"> + <div class="row justify-content-between"> + <div class="logo"> + <a href="/" title="Back to the homepage" class="site-logo"> + <img src="https://assets.gentoo.org/tyrian/site-logo.png" alt="Gentoo" srcset="https://assets.gentoo.org/tyrian/site-logo.svg"> + </a> + <span class="site-label">URL Shortener</span> + </div> + <div class="site-title-buttons"> + <div class="btn-group btn-group-sm"> + <a href="https://get.gentoo.org/" role="button" class="btn get-gentoo"><span class="fa fa-fw fa-download"></span> <strong>Get Gentoo!</strong></a> + <div class="btn-group btn-group-sm"> + <a class="btn gentoo-org-sites dropdown-toggle" data-toggle="dropdown" data-target="#" href="#"> + <span class="fa fa-fw fa-map-o"></span> <span class="d-none d-sm-inline">gentoo.org sites</span> <span class="caret"></span> + </a> + <div class="dropdown-menu dropdown-menu-right"> + <a class="dropdown-item" href="https://www.gentoo.org/" title="Main Gentoo website"><span class="fa fa-home fa-fw"></span> gentoo.org</a> + <a class="dropdown-item" href="https://wiki.gentoo.org/" title="Find and contribute documentation"><span class="fa fa-file-text-o fa-fw"></span> Wiki</a> + <a class="dropdown-item" href="https://bugs.gentoo.org/" title="Report issues and find common issues"><span class="fa fa-bug fa-fw"></span> Bugs</a> + <a class="dropdown-item" href="https://forums.gentoo.org/" title="Discuss with the community"><span class="fa fa-comments-o fa-fw"></span> Forums</a> + <a class="dropdown-item" href="https://packages.gentoo.org/" title="Find software for your Gentoo"><span class="fa fa-hdd-o fa-fw"></span> Packages</a> + <div class="dropdown-divider"></div> + <a class="dropdown-item" href="https://planet.gentoo.org/" title="Find out what's going on in the developer community"><span class="fa fa-rss fa-fw"></span> Planet</a> + <a class="dropdown-item" href="https://archives.gentoo.org/" title="Read up on past discussions"><span class="fa fa-archive fa-fw"></span> Archives</a> + <a class="dropdown-item" href="https://sources.gentoo.org/" title="Browse our source code"><span class="fa fa-code fa-fw"></span> Sources</a> + <div class="dropdown-divider"></div> + <a class="dropdown-item" href="https://infra-status.gentoo.org/" title="Get updates on the services provided by Gentoo"><span class="fa fa-server fa-fw"></span> Infra Status</a> + </div> + </div> + </div> + </div> + </div> + </div> + </div> +{{end}} diff --git a/web/templates/layout/tyriannav.tmpl b/web/templates/layout/tyriannav.tmpl new file mode 100644 index 0000000..6e1de17 --- /dev/null +++ b/web/templates/layout/tyriannav.tmpl @@ -0,0 +1,34 @@ +{{define "tyrian-navbar"}} + <nav class="tyrian-navbar navbar navbar-dark navbar-expand-lg bg-primary" role="navigation"> + <div class="container"> + <div class="navbar-header"> + <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-main-collapse" aria-controls="navbar-main-collapse" aria-expanded="false" aria-label="Toggle navigation"> + <span class="navbar-toggler-icon"></span> + </button> + </div> + <div class="collapse navbar-collapse navbar-main-collapse" id="navbar-main-collapse"> + <ul class="navbar-nav mr-auto"> + <li class="nav-item {{if eq .Tab "create"}}active{{end}}"><a class="nav-link" href="/links/create">Shorten</a></li> + <li class="nav-item {{if eq .Tab "show"}}active{{end}}"><a class="nav-link" href="/links/show">Your Links</a></li> + </ul> + + <ul class="navbar-nav"> + {{if .User.IsAdmin }} + <li class="nav-item {{if eq .Tab "admin"}}active{{end}}"><a class="nav-link" href="/admin/">Admin</a></li> + {{end}} + <li class="nav-item dropdown"> + <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + <img style="border-radius: 50%;height:20px;border: 1px solid white;margin-right:5px;" src="{{gravatar .User.Email}}" /> + </a> + <div class="dropdown-menu" aria-labelledby="navbarDropdown"> + <a class="dropdown-item disabled">Signed in as <b>{{.User.UserName}}</b></a> + <div class="dropdown-divider"></div> + <a class="dropdown-item" href="/auth/logout">Logout</a> + </div> + </li> + </ul> + + </div> + </div> + </nav> +{{end}} diff --git a/web/templates/links/create.tmpl b/web/templates/links/create.tmpl new file mode 100644 index 0000000..3644e08 --- /dev/null +++ b/web/templates/links/create.tmpl @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<html lang="en"> +{{template "head" .}} +<body> +{{template "header" .}} + +<div class="container mb-5 h-100"> + <div class="row align-items-center h-100"> + <div class="col-12 pb-5 mb-5"> + + <div class="jumbotron my-auto" style="background: none!important;"> + <h2 style="font-size: 3.25em;text-align: center;margin-bottom: 0.75em;margin-top: 0;font-weight: 500;" class="site-welcome stick-top">Welcome <span style="color: #54487A !important;">{{.User.UserName}}</span>!<br> + Shorten your next link</h2> + + <form action="/links/create" method="post"> + <div class="typeahead-container px-5" style="font-family: 'Open Sans',Arial,Helvetica,Sans-Serif;position: relative;"> + <div class="row"> + <div class="col-12"> + <div class="typeahead-field" style="display: table;border-collapse: separate;box-sizing: border-box;position: relative;width: 100%;"> + <span class="typeahead-query" style="font-size: 1.1em; height: 2.3em;display: table-cell;vertical-align: top;width: 100%;"> + <input class="rounded" style="border: 1px solid #ccc;padding-left:10px;font-size: 1.3em; height: 2.5em;" id="target" name="target" type="search" autocomplete="off" placeholder="Paste your link" aria-label="Paste your link" autofocus=""> + </span> + </div> + </div> + </div> + + <div class="collapse" id="collapseExample"> + <div class="row mt-4"> + <div class="col-5"> + <div class="form-group"> + <label for="prefix">Prefix</label> + <select class="form-control" name="prefix" id="prefix" onchange="togglePrefix();"> + <option value="">/</option> + <option value="{{$.User.UserName}}">/{{$.User.UserName}}/</option> + {{range $index, $project := .User.Projects}} + <option value="{{$project}}">/{{$project}}/</option> + {{end}} + </select> + </div> + </div> + <div class="col-2"> + <div class="form-group"> + <label for="index">Index</label> + <select class="form-control" name="index" id="index" disabled> + <option value="no" selected>no</option> + <option value="yes">yes</option> + </select> + </div> + </div> + <div class="col-5"> + <div class="form-group"> + <label for="token">Custom Link</label> + <input class="form-control" name="token" id="token" placeholder="Only available for prefix != '/'" disabled> + </div> + </div> + </div> + </div> + + <div class="row mt-4"> + <div class="col-12"> + <button class="float-right btn btn-primary">Shorten</button> + <button class="float-right btn btn-link text-dark" type="button" data-toggle="collapse" data-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample"> + Customize + </button> + </div> + </div> + </div> + </form> + </div> + + </div> + </div> +</div> + + +{{template "footer" .}} + +<script src="/assets/application.js"></script> + +</body> +</html> diff --git a/web/templates/links/created.tmpl b/web/templates/links/created.tmpl new file mode 100644 index 0000000..e3f93a5 --- /dev/null +++ b/web/templates/links/created.tmpl @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html lang="en"> +{{template "head" .}} +<body> +{{template "header" .}} + +<div class="container mb-5 h-100"> + <div class="row align-items-center h-100"> + <div class="col-12 pb-5 mb-5"> + + <div class="jumbotron my-auto" style="background: none!important;"> + <h2 style="font-size: 3.25em;text-align: center;margin-bottom: 0.75em;margin-top: 0;font-weight: 500;" class="site-welcome stick-top">Congratulations <span style="color: #54487A !important;">{{.User.UserName}}</span>!<br> + Your link has been shortened</h2> + + <div class="typeahead-container px-5" style="font-family: 'Open Sans',Arial,Helvetica,Sans-Serif;position: relative;"> + <div class="row"> + <div class="col-12"> + <div class="typeahead-field" style="display: table;border-collapse: separate;box-sizing: border-box;position: relative;width: 100%;"> + <span class="typeahead-query" style="font-size: 1.1em; height: 2.3em;display: table-cell;vertical-align: top;width: 100%;"> + <input class="rounded" style="border: 1px solid #ccc;padding-left:10px;font-size: 1.3em; height: 2.5em;" id="shortlink" name="shortlink" type="search" autocomplete="off" aria-label="Shortened Link" autofocus="" value="{{.ShortLink}}" readonly> + </span> + </div> + </div> + </div> + + <div class="row"> + <div id="copied" class="col-12 text-right text-primary" style="display: none;"> + Successfully copied to the Clipboard. + </div> + </div> + + <div class="row mt-4"> + <div class="col-12"> + <button data-clipboard-target="#shortlink" onclick="document.getElementById('copied').style.display = 'block';" class="float-right btn btn-primary">Copy to Clipbaord</button> + </div> + </div> + </div> + </div> + + </div> + </div> +</div> + +{{template "footer" .}} + +<script src="/assets/clipboard.min.js"></script> +<script> + new ClipboardJS('.btn'); +</script> + +</body> +</html> diff --git a/web/templates/links/show.tmpl b/web/templates/links/show.tmpl new file mode 100644 index 0000000..de961f5 --- /dev/null +++ b/web/templates/links/show.tmpl @@ -0,0 +1,173 @@ +<!DOCTYPE html> +<html lang="en"> +{{template "head" .}} +<body> +{{template "header" .}} + +<div class="container mb-5"> + <div class="row"> + <div class="col-12"> + + <h2 class="mt-4"><span style="font-family: monospace;">/</span></h2> + + {{$empty := true}} + + <table class="table"> + <colgroup> + <col span="1" style="width: 20%;"> + <col span="1" style="width: 60%;"> + <col span="1" style="width: 10%;"> + <col span="1" style="width: 5%;"> + <col span="1" style="width: 5%;"> + </colgroup> + <thead> + <tr> + <th scope="col">Short URL</th> + <th scope="col">Target</th> + <th scope="col">Creator</th> + <th scope="col">Hits</th> + <th scope="col">Action</th> + </tr> + </thead> + <tbody> + {{$empty = true}} + {{range .UserLinks}} + {{if eq .Prefix ""}} + {{$empty = false}} + <tr> + <th scope="row"><a class="text-dark" href="{{.ShortLink}}">go.gentoo.org{{.ShortLink}}</a></th> + <td><a class="text-dark" href="{{.TargetLink}}">{{.TargetLink}}</a></td> + <td>{{replaceAll .UserEmail "@gentoo.org" ""}}</td> + <td>{{.Hits}}</td> + <td> + <form action="/links/delete" method="post"> + <input hidden name="prefix" value="{{.Prefix}}"> + <input hidden name="token" value="{{.URLToken}}"> + <input hidden name="from" value="/links/show"> + <button type="submit" class="btn btn-link py-0 text-dark"> + <i class="fa fa-trash-o" aria-hidden="true"></i> + </button> + </form> + </td> + </tr> + {{end}} + {{end}} + {{if $empty}} + <tr> + <td colspan="5" class="text-center"><i>No shortened URLs yet</i></td> + </tr> + {{end}} + </tbody> + </table> + + <h2 class="mt-5"><span style="font-family: monospace;">/{{.User.UserName}}/</span></h2> + + <table class="table"> + <colgroup> + <col span="1" style="width: 20%;"> + <col span="1" style="width: 60%;"> + <col span="1" style="width: 10%;"> + <col span="1" style="width: 5%;"> + <col span="1" style="width: 5%;"> + </colgroup> + <thead> + <tr> + <th scope="col">Short URL</th> + <th scope="col">Target</th> + <th scope="col">Creator</th> + <th scope="col">Hits</th> + <th scope="col">Action</th> + </tr> + </thead> + <tbody> + {{$empty = true}} + {{range .UserLinks}} + {{if eq .Prefix $.User.UserName}} + {{$empty = false}} + <tr> + <th scope="row"><a class="text-dark" href="{{.ShortLink}}">go.gentoo.org{{.ShortLink}}</a></th> + <td><a class="text-dark" href="{{.TargetLink}}">{{.TargetLink}}</a></td> + <td>{{replaceAll .UserEmail "@gentoo.org" ""}}</td> + <td>{{.Hits}}</td> + <td> + <form action="/links/delete" method="post"> + <input hidden name="prefix" value="{{.Prefix}}"> + <input hidden name="token" value="{{.URLToken}}"> + <input hidden name="from" value="/links/show"> + <button type="submit" class="btn btn-link py-0 text-dark"> + <i class="fa fa-trash-o" aria-hidden="true"></i> + </button> + </form> + </td> + </tr> + {{end}} + {{end}} + {{if $empty}} + <tr> + <td colspan="4" class="text-center"><i>No shortened URLs yet</i></td> + </tr> + {{end}} + </tbody> + </table> + + <div class="separator mt-5">Projects</div> + + {{range $index, $project := .User.Projects}} + <h2 class="mt-5"><span style="font-family: monospace;">/{{$project}}/</span></h2> + + <table class="table"> + <colgroup> + <col span="1" style="width: 20%;"> + <col span="1" style="width: 60%;"> + <col span="1" style="width: 10%;"> + <col span="1" style="width: 5%;"> + <col span="1" style="width: 5%;"> + </colgroup> + <thead> + <tr> + <th scope="col">Short URL</th> + <th scope="col">Target</th> + <th scope="col">Creator</th> + <th scope="col">Hits</th> + <th scope="col">Action</th> + </tr> + </thead> + <tbody> + {{$empty = true}} + {{range $.UserLinks}} + {{if eq .Prefix $project}} + {{$empty = false}} + <tr> + <th scope="row"><a class="text-dark" href="{{.ShortLink}}">go.gentoo.org{{.ShortLink}}</a></th> + <td><a class="text-dark" href="{{.TargetLink}}">{{.TargetLink}}</a></td> + <td>{{replaceAll .UserEmail "@gentoo.org" ""}}</td> + <td>{{.Hits}}</td> + <td> + <form action="/links/delete" method="post"> + <input hidden name="prefix" value="{{.Prefix}}"> + <input hidden name="token" value="{{.URLToken}}"> + <input hidden name="from" value="/links/show"> + <button type="submit" class="btn btn-link py-0 text-dark"> + <i class="fa fa-trash-o" aria-hidden="true"></i> + </button> + </form> + </td> + </tr> + {{end}} + {{end}} + {{if $empty}} + <tr> + <td colspan="4" class="text-center"><i>No shortened URLs yet</i></td> + </tr> + {{end}} + </tbody> + </table> + {{end}} + </div> + </div> +</div> + +{{template "footer" .}} + +</body> +</html> |