mirror of
https://github.com/Kugelschieber/marvinblum.git
synced 2026-01-18 14:50:27 +00:00
@@ -19,8 +19,9 @@ WORKDIR /app
|
|||||||
# default config
|
# default config
|
||||||
ENV MB_LOGLEVEL=info
|
ENV MB_LOGLEVEL=info
|
||||||
ENV MB_ALLOWED_ORIGINS=*
|
ENV MB_ALLOWED_ORIGINS=*
|
||||||
ENV MB_HOST=0.0.0.0:80
|
ENV MB_HOST=0.0.0.0:8888
|
||||||
|
ENV MB_DB_PORT=5432
|
||||||
|
ENV MB_DB_SSLMODE=disable
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 8888
|
||||||
EXPOSE 443
|
|
||||||
CMD ["/app/main"]
|
CMD ["/app/main"]
|
||||||
|
|||||||
11
blog/blog.go
11
blog/blog.go
@@ -2,6 +2,7 @@ package blog
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/Kugelschieber/marvinblum.de/tpl"
|
||||||
emvi "github.com/emvi/api-go"
|
emvi "github.com/emvi/api-go"
|
||||||
"github.com/emvi/logbuch"
|
"github.com/emvi/logbuch"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,11 +31,13 @@ type Blog struct {
|
|||||||
articles map[string]emvi.Article // id -> article
|
articles map[string]emvi.Article // id -> article
|
||||||
articlesYear map[int][]emvi.Article // year -> articles
|
articlesYear map[int][]emvi.Article // year -> articles
|
||||||
nextUpdate time.Time
|
nextUpdate time.Time
|
||||||
|
cache *tpl.Cache
|
||||||
|
m sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBlog() *Blog {
|
func NewBlog(cache *tpl.Cache) *Blog {
|
||||||
logbuch.Info("Initializing blog")
|
logbuch.Info("Initializing blog")
|
||||||
b := new(Blog)
|
b := &Blog{cache: cache}
|
||||||
b.client = emvi.NewClient(os.Getenv("MB_EMVI_CLIENT_ID"),
|
b.client = emvi.NewClient(os.Getenv("MB_EMVI_CLIENT_ID"),
|
||||||
os.Getenv("MB_EMVI_CLIENT_SECRET"),
|
os.Getenv("MB_EMVI_CLIENT_SECRET"),
|
||||||
os.Getenv("MB_EMVI_ORGA"),
|
os.Getenv("MB_EMVI_ORGA"),
|
||||||
@@ -76,6 +80,8 @@ func (blog *Blog) GetLatestArticles() []emvi.Article {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (blog *Blog) loadArticles() {
|
func (blog *Blog) loadArticles() {
|
||||||
|
blog.m.Lock()
|
||||||
|
defer blog.m.Unlock()
|
||||||
logbuch.Info("Refreshing blog articles...")
|
logbuch.Info("Refreshing blog articles...")
|
||||||
articles, offset, count := make(map[string]emvi.Article), 0, 1
|
articles, offset, count := make(map[string]emvi.Article), 0, 1
|
||||||
var err error
|
var err error
|
||||||
@@ -188,6 +194,7 @@ func (blog *Blog) setArticles(articles map[string]emvi.Article) {
|
|||||||
|
|
||||||
func (blog *Blog) refreshIfRequired() {
|
func (blog *Blog) refreshIfRequired() {
|
||||||
if blog.nextUpdate.Before(time.Now()) {
|
if blog.nextUpdate.Before(time.Now()) {
|
||||||
|
blog.cache.Clear()
|
||||||
blog.loadArticles()
|
blog.loadArticles()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
dev.sh
9
dev.sh
@@ -10,5 +10,14 @@ export MB_HOT_RELOAD=true
|
|||||||
export MB_EMVI_CLIENT_ID=3fBBn144yvSF9R3dPC8l
|
export MB_EMVI_CLIENT_ID=3fBBn144yvSF9R3dPC8l
|
||||||
export MB_EMVI_CLIENT_SECRET=dw3FeshelTgdf1Gj13J7uF5FfdPDi40sQvvwqeFVKTTyIDuCdlAHhRY72csFL6yg
|
export MB_EMVI_CLIENT_SECRET=dw3FeshelTgdf1Gj13J7uF5FfdPDi40sQvvwqeFVKTTyIDuCdlAHhRY72csFL6yg
|
||||||
export MB_EMVI_ORGA=marvin
|
export MB_EMVI_ORGA=marvin
|
||||||
|
export MB_DB_HOST=localhost
|
||||||
|
export MB_DB_PORT=5432
|
||||||
|
export MB_DB_USER=postgres
|
||||||
|
export MB_DB_PASSWORD=postgres
|
||||||
|
export MB_DB_SCHEMA=marvinblum
|
||||||
|
export MB_DB_SSLMODE=disable
|
||||||
|
export MB_DB_SSLCERT=
|
||||||
|
export MB_DB_SSLKEY=
|
||||||
|
export MB_DB_SSLROOTCERT=
|
||||||
|
|
||||||
go run main.go
|
go run main.go
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ services:
|
|||||||
image: "traefik:v2.2"
|
image: "traefik:v2.2"
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
restart: always
|
restart: always
|
||||||
|
networks:
|
||||||
|
- traefik-internal
|
||||||
command:
|
command:
|
||||||
# - "--log.level=DEBUG"
|
# - "--log.level=DEBUG"
|
||||||
# - "--api.insecure=true"
|
# - "--api.insecure=true"
|
||||||
- "--providers.docker=true"
|
- "--providers.docker=true"
|
||||||
- "--providers.docker.exposedbydefault=false"
|
- "--providers.docker.exposedbydefault=false"
|
||||||
|
- "--providers.docker.network=marvinblum_traefik-internal"
|
||||||
- "--entrypoints.web.address=:80"
|
- "--entrypoints.web.address=:80"
|
||||||
- "--entrypoints.websecure.address=:443"
|
- "--entrypoints.websecure.address=:443"
|
||||||
- "--certificatesresolvers.tls-resolver.acme.httpchallenge=true"
|
- "--certificatesresolvers.tls-resolver.acme.httpchallenge=true"
|
||||||
@@ -24,18 +27,31 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
- /root/marvinblum/letsencrypt:/letsencrypt
|
- /root/marvinblum/letsencrypt:/letsencrypt
|
||||||
|
# labels:
|
||||||
|
# - "traefik.enable=true"
|
||||||
|
# - "traefik.port=8080"
|
||||||
|
# - "traefik.http.routers.traefik.entrypoints=web"
|
||||||
|
# - "traefik.http.routers.traefik.service=api@internal"
|
||||||
marvinblum:
|
marvinblum:
|
||||||
image: kugel/marvinblum
|
image: kugel/marvinblum
|
||||||
container_name: marvinblum
|
container_name: marvinblum
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
- traefik
|
- traefik
|
||||||
|
networks:
|
||||||
|
- postgres_db-internal
|
||||||
|
- traefik-internal
|
||||||
environment:
|
environment:
|
||||||
- MB_EMVI_CLIENT_ID="3fBBn144yvSF9R3dPC8l"
|
MB_EMVI_CLIENT_ID: 3fBBn144yvSF9R3dPC8l
|
||||||
- MB_EMVI_CLIENT_SECRET="dw3FeshelTgdf1Gj13J7uF5FfdPDi40sQvvwqeFVKTTyIDuCdlAHhRY72csFL6yg"
|
MB_EMVI_CLIENT_SECRET: dw3FeshelTgdf1Gj13J7uF5FfdPDi40sQvvwqeFVKTTyIDuCdlAHhRY72csFL6yg
|
||||||
- MB_EMVI_ORGA=marvin
|
MB_EMVI_ORGA: marvin
|
||||||
|
MB_DB_HOST: postgres
|
||||||
|
MB_DB_USER:
|
||||||
|
MB_DB_PASSWORD:
|
||||||
|
MB_DB_SCHEMA:
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
|
- "traefik.port=8888"
|
||||||
- "traefik.http.routers.marvinblum.entrypoints=web"
|
- "traefik.http.routers.marvinblum.entrypoints=web"
|
||||||
- "traefik.http.routers.marvinblum.rule=Host(`marvinblum.de`)"
|
- "traefik.http.routers.marvinblum.rule=Host(`marvinblum.de`)"
|
||||||
- "traefik.http.routers.marvinblum.middlewares=http-redirect"
|
- "traefik.http.routers.marvinblum.middlewares=http-redirect"
|
||||||
@@ -44,3 +60,9 @@ services:
|
|||||||
- "traefik.http.routers.marvinblum-secure.tls.certresolver=tls-resolver"
|
- "traefik.http.routers.marvinblum-secure.tls.certresolver=tls-resolver"
|
||||||
- "traefik.http.middlewares.http-redirect.redirectscheme.scheme=https"
|
- "traefik.http.middlewares.http-redirect.redirectscheme.scheme=https"
|
||||||
- "traefik.http.middlewares.http-redirect.redirectscheme.permanent=true"
|
- "traefik.http.middlewares.http-redirect.redirectscheme.permanent=true"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
traefik-internal:
|
||||||
|
driver: bridge
|
||||||
|
postgres_db-internal:
|
||||||
|
external: true
|
||||||
|
|||||||
15
go.mod
15
go.mod
@@ -4,10 +4,21 @@ go 1.14
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/NYTimes/gziphandler v1.1.1
|
github.com/NYTimes/gziphandler v1.1.1
|
||||||
github.com/caddyserver/certmagic v0.10.13
|
github.com/caddyserver/certmagic v0.11.2
|
||||||
|
github.com/cenkalti/backoff/v4 v4.0.2 // indirect
|
||||||
github.com/emvi/api-go v0.0.0-20191210194347-0a945446f6a8
|
github.com/emvi/api-go v0.0.0-20191210194347-0a945446f6a8
|
||||||
github.com/emvi/logbuch v0.0.0-20200214115750-61de9b6d5934
|
github.com/emvi/logbuch v1.1.1
|
||||||
|
github.com/emvi/pirsch v0.0.0-20200624123353-86381b017755
|
||||||
github.com/gorilla/mux v1.7.4
|
github.com/gorilla/mux v1.7.4
|
||||||
github.com/gosimple/slug v1.9.0
|
github.com/gosimple/slug v1.9.0
|
||||||
|
github.com/jmoiron/sqlx v1.2.0
|
||||||
|
github.com/klauspost/cpuid v1.3.0 // indirect
|
||||||
|
github.com/lib/pq v1.7.0
|
||||||
|
github.com/miekg/dns v1.1.29 // indirect
|
||||||
github.com/rs/cors v1.7.0
|
github.com/rs/cors v1.7.0
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 // indirect
|
||||||
|
golang.org/x/text v0.3.3 // indirect
|
||||||
|
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
43
go.sum
43
go.sum
@@ -51,10 +51,12 @@ github.com/aws/aws-sdk-go v1.30.20/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZve
|
|||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/caddyserver/certmagic v0.10.13 h1:wfyYpXVXSSYMS1ZFpSr7HptwsC+j7elda5PUERrHtRc=
|
github.com/caddyserver/certmagic v0.11.2 h1:nPBqyuFNHJEf2FwC1ixJjArtTKWyPqpaH6k4jl7gxYI=
|
||||||
github.com/caddyserver/certmagic v0.10.13/go.mod h1:Yz6cSRUdddGy6Ut5JfrvcqBwrm1BqXxJRqJq2TwjPnA=
|
github.com/caddyserver/certmagic v0.11.2/go.mod h1:fqY1IZk5iqhsj5FU3Vw20Sjq66tEKaanTFYNZ74soMY=
|
||||||
github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU=
|
github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU=
|
||||||
github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.0.2 h1:JIufpQLbh4DkbQoii76ItQIUFzevQSqOLZca4eamEDs=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
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/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
@@ -75,8 +77,18 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
|
|||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
github.com/emvi/api-go v0.0.0-20191210194347-0a945446f6a8 h1:DZJfRdvwlybLvHkyeN8HRU4QnBFqVZg21ki4to1AVPo=
|
github.com/emvi/api-go v0.0.0-20191210194347-0a945446f6a8 h1:DZJfRdvwlybLvHkyeN8HRU4QnBFqVZg21ki4to1AVPo=
|
||||||
github.com/emvi/api-go v0.0.0-20191210194347-0a945446f6a8/go.mod h1:g9RdDC3s5ebCknAHQQ5PjoM2vRFSyyGoOUX3QkDKU+o=
|
github.com/emvi/api-go v0.0.0-20191210194347-0a945446f6a8/go.mod h1:g9RdDC3s5ebCknAHQQ5PjoM2vRFSyyGoOUX3QkDKU+o=
|
||||||
github.com/emvi/logbuch v0.0.0-20200214115750-61de9b6d5934 h1:+G10WRp72llJuaW89QezNK8QU9SsOmd5Ja7ocrrUaI0=
|
github.com/emvi/logbuch v1.1.1 h1:poBGNbHy/nB95oNoqLKAaJoBrcKxTO0W9DhMijKEkkU=
|
||||||
github.com/emvi/logbuch v0.0.0-20200214115750-61de9b6d5934/go.mod h1:J2Wgbr3BuSc1JO+D2MBVh6q3WPVSK5GzktwWz8pvkKw=
|
github.com/emvi/logbuch v1.1.1/go.mod h1:J2Wgbr3BuSc1JO+D2MBVh6q3WPVSK5GzktwWz8pvkKw=
|
||||||
|
github.com/emvi/pirsch v0.0.0-20200623183712-76dda933f960 h1:7q8uRCOILenzPP7au4BsylEvkJeIkqJcKN5z/49Wpm8=
|
||||||
|
github.com/emvi/pirsch v0.0.0-20200623183712-76dda933f960/go.mod h1:+YmBbltJ3feZz9L/QQyqwywltYvQKBfzrGD51TPKl5g=
|
||||||
|
github.com/emvi/pirsch v0.0.0-20200623190422-b0f4c9980175 h1:rj+o6eyEkYy9bYd/pupAiUnLZxo9U1qRfdHJ9nzQSLY=
|
||||||
|
github.com/emvi/pirsch v0.0.0-20200623190422-b0f4c9980175/go.mod h1:+YmBbltJ3feZz9L/QQyqwywltYvQKBfzrGD51TPKl5g=
|
||||||
|
github.com/emvi/pirsch v0.0.0-20200623192632-5c5fcd1d78cc h1:G05tiq3JKZwjnPSObRBwypzWplKTUWG2XFWhgFnqD/g=
|
||||||
|
github.com/emvi/pirsch v0.0.0-20200623192632-5c5fcd1d78cc/go.mod h1:+YmBbltJ3feZz9L/QQyqwywltYvQKBfzrGD51TPKl5g=
|
||||||
|
github.com/emvi/pirsch v0.0.0-20200623193552-b3a3d4a6434d h1:+HRIYgA9AaXlGFQfhXpUShXxJ1knG2stwNIai1M6mzs=
|
||||||
|
github.com/emvi/pirsch v0.0.0-20200623193552-b3a3d4a6434d/go.mod h1:+YmBbltJ3feZz9L/QQyqwywltYvQKBfzrGD51TPKl5g=
|
||||||
|
github.com/emvi/pirsch v0.0.0-20200624123353-86381b017755 h1:TdiDC7+IfV6giMtFNvbYKFlSPJJZewr9jfh3KGcr8QQ=
|
||||||
|
github.com/emvi/pirsch v0.0.0-20200624123353-86381b017755/go.mod h1:+YmBbltJ3feZz9L/QQyqwywltYvQKBfzrGD51TPKl5g=
|
||||||
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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
|
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
|
||||||
@@ -93,6 +105,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
|
|||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
@@ -150,6 +163,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
|||||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
|
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||||
|
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||||
|
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
@@ -160,6 +175,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
|||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
|
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
|
||||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/cpuid v1.3.0 h1:2JqaNE1hGdABW2YbA3TenkO7RiPFRvSWnEnGqWh9sHE=
|
||||||
|
github.com/klauspost/cpuid v1.3.0/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||||
github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
|
github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
@@ -169,15 +186,22 @@ 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/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
|
github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
|
||||||
github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
|
github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
|
||||||
|
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
|
||||||
|
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA=
|
github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA=
|
||||||
github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
|
github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
|
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
|
||||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
|
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
||||||
|
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
|
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
@@ -268,6 +292,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
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-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-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@@ -323,6 +349,8 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/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 h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/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-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
|
||||||
|
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
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-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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -364,11 +392,16 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/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 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/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-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8=
|
||||||
|
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||||
|
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-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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@@ -465,6 +498,8 @@ gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
|
|||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
|
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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|||||||
111
main.go
111
main.go
@@ -1,17 +1,22 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"github.com/Kugelschieber/marvinblum.de/blog"
|
"github.com/Kugelschieber/marvinblum.de/blog"
|
||||||
"github.com/Kugelschieber/marvinblum.de/tpl"
|
"github.com/Kugelschieber/marvinblum.de/tpl"
|
||||||
|
"github.com/Kugelschieber/marvinblum.de/tracking"
|
||||||
"github.com/NYTimes/gziphandler"
|
"github.com/NYTimes/gziphandler"
|
||||||
"github.com/caddyserver/certmagic"
|
|
||||||
emvi "github.com/emvi/api-go"
|
emvi "github.com/emvi/api-go"
|
||||||
"github.com/emvi/logbuch"
|
"github.com/emvi/logbuch"
|
||||||
|
"github.com/emvi/pirsch"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -21,9 +26,12 @@ const (
|
|||||||
staticDirPrefix = "/static/"
|
staticDirPrefix = "/static/"
|
||||||
logTimeFormat = "2006-01-02_15:04:05"
|
logTimeFormat = "2006-01-02_15:04:05"
|
||||||
envPrefix = "MB_"
|
envPrefix = "MB_"
|
||||||
|
shutdownTimeout = time.Second * 30
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
tracker *pirsch.Tracker
|
||||||
|
tplCache *tpl.Cache
|
||||||
blogInstance *blog.Blog
|
blogInstance *blog.Blog
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -52,36 +60,36 @@ func logEnvConfig() {
|
|||||||
|
|
||||||
func serveAbout() http.HandlerFunc {
|
func serveAbout() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
data := struct {
|
tracker.Hit(r)
|
||||||
|
tplCache.Render(w, "about.html", struct {
|
||||||
Articles []emvi.Article
|
Articles []emvi.Article
|
||||||
}{
|
}{
|
||||||
blogInstance.GetLatestArticles(),
|
blogInstance.GetLatestArticles(),
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := tpl.Get().ExecuteTemplate(w, "about.html", data); err != nil {
|
func serveLegal() http.HandlerFunc {
|
||||||
logbuch.Error("Error executing blog template", logbuch.Fields{"err": err})
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
tracker.Hit(r)
|
||||||
}
|
tplCache.Render(w, "legal.html", nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveBlogPage() http.HandlerFunc {
|
func serveBlogPage() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
data := struct {
|
tracker.Hit(r)
|
||||||
|
tplCache.Render(w, "blog.html", struct {
|
||||||
Articles map[int][]emvi.Article
|
Articles map[int][]emvi.Article
|
||||||
}{
|
}{
|
||||||
blogInstance.GetArticles(),
|
blogInstance.GetArticles(),
|
||||||
}
|
})
|
||||||
|
|
||||||
if err := tpl.Get().ExecuteTemplate(w, "blog.html", data); err != nil {
|
|
||||||
logbuch.Error("Error executing blog template", logbuch.Fields{"err": err})
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveBlogArticle() http.HandlerFunc {
|
func serveBlogArticle() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tracker.Hit(r)
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
slug := strings.Split(vars["slug"], "-")
|
slug := strings.Split(vars["slug"], "-")
|
||||||
|
|
||||||
@@ -97,7 +105,7 @@ func serveBlogArticle() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := struct {
|
tplCache.Render(w, "article.html", struct {
|
||||||
Title string
|
Title string
|
||||||
Content template.HTML
|
Content template.HTML
|
||||||
Published time.Time
|
Published time.Time
|
||||||
@@ -105,12 +113,38 @@ func serveBlogArticle() http.HandlerFunc {
|
|||||||
article.LatestArticleContent.Title,
|
article.LatestArticleContent.Title,
|
||||||
template.HTML(article.LatestArticleContent.Content),
|
template.HTML(article.LatestArticleContent.Content),
|
||||||
article.Published,
|
article.Published,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveTracking() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
start, _ := strconv.Atoi(r.URL.Query().Get("start"))
|
||||||
|
|
||||||
|
if start > 365 {
|
||||||
|
start = 365
|
||||||
|
} else if start < 7 {
|
||||||
|
start = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tpl.Get().ExecuteTemplate(w, "article.html", data); err != nil {
|
totalVisitorsLabels, totalVisitorsDps := tracking.GetTotalVisitors(start)
|
||||||
logbuch.Error("Error executing blog article template", logbuch.Fields{"err": err})
|
tplCache.RenderWithoutCache(w, "tracking.html", struct {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
Start int
|
||||||
}
|
TotalVisitorsLabels template.JS
|
||||||
|
TotalVisitorsDps template.JS
|
||||||
|
PageVisits []tracking.PageVisits
|
||||||
|
}{
|
||||||
|
start,
|
||||||
|
totalVisitorsLabels,
|
||||||
|
totalVisitorsDps,
|
||||||
|
tracking.GetPageVisits(start),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveNotFound() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tplCache.Render(w, "notfound.html", nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,9 +153,10 @@ func setupRouter() *mux.Router {
|
|||||||
router.PathPrefix(staticDirPrefix).Handler(http.StripPrefix(staticDirPrefix, gziphandler.GzipHandler(http.FileServer(http.Dir(staticDir)))))
|
router.PathPrefix(staticDirPrefix).Handler(http.StripPrefix(staticDirPrefix, gziphandler.GzipHandler(http.FileServer(http.Dir(staticDir)))))
|
||||||
router.Handle("/blog/{slug}", serveBlogArticle())
|
router.Handle("/blog/{slug}", serveBlogArticle())
|
||||||
router.Handle("/blog", serveBlogPage())
|
router.Handle("/blog", serveBlogPage())
|
||||||
router.Handle("/legal", tpl.ServeTemplate("legal.html"))
|
router.Handle("/legal", serveLegal())
|
||||||
|
router.Handle("/tracking", serveTracking())
|
||||||
router.Handle("/", serveAbout())
|
router.Handle("/", serveAbout())
|
||||||
router.NotFoundHandler = tpl.ServeTemplate("notfound.html")
|
router.NotFoundHandler = serveNotFound()
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,28 +176,36 @@ func configureCors(router *mux.Router) http.Handler {
|
|||||||
|
|
||||||
func start(handler http.Handler) {
|
func start(handler http.Handler) {
|
||||||
logbuch.Info("Starting server...")
|
logbuch.Info("Starting server...")
|
||||||
|
var server http.Server
|
||||||
|
server.Handler = handler
|
||||||
|
server.Addr = os.Getenv("MB_HOST")
|
||||||
|
|
||||||
if strings.ToLower(os.Getenv("MB_TLS")) == "true" {
|
go func() {
|
||||||
logbuch.Info("TLS enabled")
|
sigint := make(chan os.Signal)
|
||||||
certmagic.DefaultACME.Agreed = true
|
signal.Notify(sigint, os.Interrupt)
|
||||||
certmagic.DefaultACME.Email = os.Getenv("MB_TLS_EMAIL")
|
<-sigint
|
||||||
certmagic.DefaultACME.CA = certmagic.LetsEncryptProductionCA
|
logbuch.Info("Shutting down server...")
|
||||||
|
tracker.Stop()
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), shutdownTimeout)
|
||||||
|
|
||||||
if err := certmagic.HTTPS(strings.Split(os.Getenv("MB_DOMAIN"), ","), handler); err != nil {
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
logbuch.Fatal("Error starting server", logbuch.Fields{"err": err})
|
logbuch.Fatal("Error shutting down server gracefully", logbuch.Fields{"err": err})
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := http.ListenAndServe(os.Getenv("MB_HOST"), handler); err != nil {
|
|
||||||
logbuch.Fatal("Error starting server", logbuch.Fields{"err": err})
|
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := server.ListenAndServe(); err != http.ErrServerClosed {
|
||||||
|
logbuch.Fatal("Error starting server", logbuch.Fields{"err": err})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logbuch.Info("Server shut down")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
configureLog()
|
configureLog()
|
||||||
logEnvConfig()
|
logEnvConfig()
|
||||||
tpl.LoadTemplate()
|
tracker = tracking.NewTracker()
|
||||||
blogInstance = blog.NewBlog()
|
tplCache = tpl.NewCache()
|
||||||
|
blogInstance = blog.NewBlog(tplCache)
|
||||||
router := setupRouter()
|
router := setupRouter()
|
||||||
corsConfig := configureCors(router)
|
corsConfig := configureCors(router)
|
||||||
start(corsConfig)
|
start(corsConfig)
|
||||||
|
|||||||
4
postgres/clear_logs.sh
Normal file
4
postgres/clear_logs.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Clearing docker logs..."
|
||||||
|
echo "" > $(docker inspect --format='{{.LogPath}}' postgres)
|
||||||
22
postgres/docker-compose.yml
Normal file
22
postgres/docker-compose.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:12-alpine
|
||||||
|
container_name: postgres
|
||||||
|
restart: always
|
||||||
|
command: -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key
|
||||||
|
networks:
|
||||||
|
- db-internal
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD:
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- /root/postgres/data:/var/lib/postgresql/data
|
||||||
|
- /root/postgres/cert/server.crt:/var/lib/postgresql/server.crt
|
||||||
|
- /root/postgres/cert/server.key:/var/lib/postgresql/server.key
|
||||||
|
|
||||||
|
networks:
|
||||||
|
db-internal:
|
||||||
|
driver: bridge
|
||||||
10
postgres/gen_cert.sh
Executable file
10
postgres/gen_cert.sh
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
mkdir cert
|
||||||
|
cd cert
|
||||||
|
openssl req -new -text -passout pass:$1 -subj /CN=localhost -out server.req -keyout privkey.pem
|
||||||
|
openssl rsa -in privkey.pem -passin pass:$1 -out server.key
|
||||||
|
openssl req -x509 -in server.req -text -key server.key -out server.crt
|
||||||
|
chown 0:70 server.key
|
||||||
|
chmod 640 server.key
|
||||||
|
echo "done"
|
||||||
7
static/js/Chart-v2.9.3.bundle.min.js
vendored
Normal file
7
static/js/Chart-v2.9.3.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -47,6 +47,11 @@ body {
|
|||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tracking {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
margin: 80px 0;
|
margin: 80px 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,14 @@
|
|||||||
{{template "menu.html"}}
|
{{template "menu.html"}}
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>who am I?</h2>
|
<h2>Who Am I?</h2>
|
||||||
<p>
|
<p>
|
||||||
I'm a full stack software engineer from Germany, open source and Linux enthusiast and co-founder of <a href="https://emvi.com/" target="_blank">Emvi</a>.
|
I'm a full stack software engineer from Germany, open source and Linux enthusiast and co-founder of <a href="https://emvi.com/" target="_blank">Emvi</a>.
|
||||||
In love with Go, but fluent in a lot of programming languages.
|
In love with Go, but fluent in a lot of programming languages.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>latest blog posts</h2>
|
<h2>Latest Blog Posts</h2>
|
||||||
{{range $article := .Articles}}
|
{{range $article := .Articles}}
|
||||||
<p>
|
<p>
|
||||||
<a href="/blog/{{slug $article.LatestArticleContent.Title}}-{{$article.Id}}">{{$article.LatestArticleContent.Title}}</a>
|
<a href="/blog/{{slug $article.LatestArticleContent.Title}}-{{$article.Id}}">{{$article.LatestArticleContent.Title}}</a>
|
||||||
@@ -32,11 +32,14 @@
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>projects</h2>
|
<h2>Projects</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://emvi.com/" target="_blank">Emvi</a> a note taking, collaboration and knowledge management tool for personal use and teams of any size
|
<a href="https://emvi.com/" target="_blank">Emvi</a> a note taking, collaboration and knowledge management tool for personal use and teams of any size
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/emvi/pirsch" target="_blank">pirsch</a> a server side, no-cookie and privacy focused tracking library for Golang
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://github.com/emvi/logbuch" target="_blank">logbuch</a> a simple Golang logging library
|
<a href="https://github.com/emvi/logbuch" target="_blank">logbuch</a> a simple Golang logging library
|
||||||
</li>
|
</li>
|
||||||
@@ -52,7 +55,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>skills</h2>
|
<h2>Skills</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Go (Golang)</li>
|
<li>Go (Golang)</li>
|
||||||
<li>JavaScript (Vue, Node)</li>
|
<li>JavaScript (Vue, Node)</li>
|
||||||
@@ -66,7 +69,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>work</h2>
|
<h2>Work</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://emvi.com/" target="_blank">Emvi</a> co-founder of a note taking, collaboration and knowledge management SaaS startup
|
<a href="https://emvi.com/" target="_blank">Emvi</a> co-founder of a note taking, collaboration and knowledge management SaaS startup
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h1>Blog</h1>
|
<h1>Blog</h1>
|
||||||
{{range $year, $articles := .Articles}}
|
</section>
|
||||||
|
{{range $year, $articles := .Articles}}
|
||||||
|
<section>
|
||||||
<h2>{{$year}}</h2>
|
<h2>{{$year}}</h2>
|
||||||
|
|
||||||
{{range $article := $articles}}
|
{{range $article := $articles}}
|
||||||
@@ -12,9 +14,11 @@
|
|||||||
<small>{{format $article.Published "2. January 2006"}}</small>
|
<small>{{format $article.Published "2. January 2006"}}</small>
|
||||||
</p>
|
</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
</section>
|
||||||
|
{{else}}
|
||||||
|
<section>
|
||||||
<p>There are no blog posts yet...</p>
|
<p>There are no blog posts yet...</p>
|
||||||
{{end}}
|
</section>
|
||||||
</section>
|
{{end}}
|
||||||
|
|
||||||
{{template "end.html"}}
|
{{template "end.html"}}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<hr />
|
<hr />
|
||||||
<section>
|
<section>
|
||||||
<p>
|
<p>
|
||||||
Like to see more? Read my blog articles on <a href="https://emvi.com/blog" target="_blank">Emvi</a>, my project page on <a href="https://github.com/Kugelschieber" target="_blank">GitHub</a> or send me a <a href="mailto:marvin@marvinblum.de">mail</a>.
|
Would you like to see more? Read my blog articles on <a href="https://emvi.com/blog" target="_blank">Emvi</a>, my project page on <a href="https://github.com/Kugelschieber" target="_blank">GitHub</a> or send me a <a href="mailto:marvin@marvinblum.de">mail</a>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
This page uses <a href="https://concrete.style/" target="_blank">concrete</a> for styling. Check it out!
|
This page uses <a href="https://concrete.style/" target="_blank">concrete</a> for styling. Check it out!
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="/legal">Legal</a>
|
This page does not use cookies. <a href="/legal">Legal</a>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
{{template "head.html"}}
|
{{template "head.html"}}
|
||||||
{{template "menu.html"}}
|
{{template "menu.html"}}
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h1>Legal</h1>
|
||||||
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>According to §5 TMG</h2>
|
<h2>According to §5 TMG</h2>
|
||||||
<p>
|
<p>
|
||||||
@@ -10,5 +13,11 @@
|
|||||||
<a href="mailto:marvin@marvinblum.de">marvin@marvinblum.de</a>
|
<a href="mailto:marvin@marvinblum.de">marvin@marvinblum.de</a>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>Cookie Policy</h2>
|
||||||
|
<p>
|
||||||
|
This page does not use cookies.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
{{template "end.html"}}
|
{{template "end.html"}}
|
||||||
|
|||||||
74
template/tracking.html
Normal file
74
template/tracking.html
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{{template "head.html"}}
|
||||||
|
{{template "menu.html"}}
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h1>Tracking</h1>
|
||||||
|
<p>
|
||||||
|
This page shows tracking statistics for my website using <a href="https://github.com/emvi/pirsch" target="_blank">Pirsch</a> and <a href="https://www.chartjs.org/" target="_blank">Chart.Js</a>. The data shows unique visitors.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="/tracking?start=7" class="button {{if eq .Start 7}}filled{{end}}">Week</a>
|
||||||
|
<a href="/tracking?start=30" class="button {{if eq .Start 30}}filled{{end}}">Month</a>
|
||||||
|
<a href="/tracking?start=90" class="button {{if eq .Start 90}}filled{{end}}">Quarter</a>
|
||||||
|
<a href="/tracking?start=182" class="button {{if eq .Start 182}}filled{{end}}">Half Year</a>
|
||||||
|
<a href="/tracking?start=365" class="button {{if eq .Start 365}}filled{{end}}">Year</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- TODO real time -->
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Total Visitors</h2>
|
||||||
|
<canvas id="totalVisitors" class="tracking"></canvas>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>Pages Visits</h2>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{range $i, $data := .PageVisits}}
|
||||||
|
<section>
|
||||||
|
<h3>{{$data.Path}}</h3>
|
||||||
|
<canvas id="pageVisits{{$i}}" class="tracking"></canvas>
|
||||||
|
</section>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<!-- TODO -->
|
||||||
|
<!--<section>
|
||||||
|
<h2>Languages</h2>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>Visitors Per Hour</h2>
|
||||||
|
</section>-->
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/static/js/Chart-v2.9.3.bundle.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
new Chart(document.getElementById('totalVisitors').getContext('2d'), {
|
||||||
|
type: "line",
|
||||||
|
data: {
|
||||||
|
labels: [{{.TotalVisitorsLabels}}],
|
||||||
|
datasets: [{
|
||||||
|
backgroundColor: "rgba(0, 0, 0, 0.05)",
|
||||||
|
borderColor: "#000",
|
||||||
|
label: "Total Visitors",
|
||||||
|
data: [{{.TotalVisitorsDps}}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
{{range $i, $data := .PageVisits}}
|
||||||
|
new Chart(document.getElementById('pageVisits{{$i}}').getContext('2d'), {
|
||||||
|
type: "line",
|
||||||
|
data: {
|
||||||
|
labels: [{{$data.Labels}}],
|
||||||
|
datasets: [{
|
||||||
|
backgroundColor: "rgba(0, 0, 0, 0.05)",
|
||||||
|
borderColor: "#000",
|
||||||
|
label: "Page Visits",
|
||||||
|
data: [{{$data.Data}}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
{{end}}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{template "end.html"}}
|
||||||
101
tpl/template.go
101
tpl/template.go
@@ -7,6 +7,7 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,59 +15,87 @@ const (
|
|||||||
templateDir = "template/*"
|
templateDir = "template/*"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type Cache struct {
|
||||||
tpl *template.Template
|
tpl *template.Template
|
||||||
tplCache = make(map[string][]byte)
|
cache map[string][]byte
|
||||||
hotReload bool
|
hotReload bool
|
||||||
)
|
m sync.RWMutex
|
||||||
|
|
||||||
var funcMap = template.FuncMap{
|
|
||||||
"slug": slug.Make,
|
|
||||||
"format": func(t time.Time, layout string) string {
|
|
||||||
return t.Format(layout)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadTemplate() {
|
func NewCache() *Cache {
|
||||||
|
cache := &Cache{
|
||||||
|
cache: make(map[string][]byte),
|
||||||
|
hotReload: os.Getenv("MB_HOT_RELOAD") == "true",
|
||||||
|
}
|
||||||
|
logbuch.Debug("Template cache hot reload", logbuch.Fields{"hot_reload": cache.hotReload})
|
||||||
|
cache.load()
|
||||||
|
return cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *Cache) load() {
|
||||||
logbuch.Debug("Loading templates")
|
logbuch.Debug("Loading templates")
|
||||||
|
funcMap := template.FuncMap{
|
||||||
|
"slug": slug.Make,
|
||||||
|
"format": func(t time.Time, layout string) string {
|
||||||
|
return t.Format(layout)
|
||||||
|
},
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
tpl, err = template.New("").Funcs(funcMap).ParseGlob(templateDir)
|
cache.tpl, err = template.New("").Funcs(funcMap).ParseGlob(templateDir)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logbuch.Fatal("Error loading template", logbuch.Fields{"err": err})
|
logbuch.Fatal("Error loading template", logbuch.Fields{"err": err})
|
||||||
}
|
}
|
||||||
|
|
||||||
hotReload = os.Getenv("MB_HOT_RELOAD") == "true"
|
logbuch.Debug("Templates loaded", logbuch.Fields{"hot_reload": cache.hotReload})
|
||||||
logbuch.Debug("Templates loaded", logbuch.Fields{"hot_reload": hotReload})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTemplate(name string) {
|
func (cache *Cache) Render(w http.ResponseWriter, name string, data interface{}) {
|
||||||
logbuch.Debug("Rendering template", logbuch.Fields{"name": name})
|
cache.m.RLock()
|
||||||
var buffer bytes.Buffer
|
|
||||||
|
|
||||||
if err := tpl.ExecuteTemplate(&buffer, name, nil); err != nil {
|
if cache.cache[name] == nil || cache.hotReload {
|
||||||
logbuch.Fatal("Error executing template", logbuch.Fields{"err": err, "name": name})
|
cache.m.RUnlock()
|
||||||
}
|
cache.m.Lock()
|
||||||
|
defer cache.m.Unlock()
|
||||||
|
logbuch.Debug("Rendering template", logbuch.Fields{"name": name})
|
||||||
|
|
||||||
tplCache[name] = buffer.Bytes()
|
if cache.hotReload {
|
||||||
}
|
logbuch.Debug("Reloading templates")
|
||||||
|
cache.load()
|
||||||
func Get() *template.Template {
|
|
||||||
return tpl
|
|
||||||
}
|
|
||||||
|
|
||||||
func ServeTemplate(name string) http.HandlerFunc {
|
|
||||||
// render once so we have it in cache
|
|
||||||
renderTemplate(name)
|
|
||||||
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if hotReload {
|
|
||||||
LoadTemplate()
|
|
||||||
renderTemplate(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := w.Write(tplCache[name]); err != nil {
|
var buffer bytes.Buffer
|
||||||
logbuch.Error("Error returning page to client", logbuch.Fields{"err": err, "name": name})
|
|
||||||
|
if err := cache.tpl.ExecuteTemplate(&buffer, name, data); err != nil {
|
||||||
|
logbuch.Error("Error executing template", logbuch.Fields{"err": err, "name": name})
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache.cache[name] = buffer.Bytes()
|
||||||
|
} else {
|
||||||
|
cache.m.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := w.Write(cache.cache[name]); err != nil {
|
||||||
|
logbuch.Error("Error sending response to client", logbuch.Fields{"err": err, "template": name})
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cache *Cache) RenderWithoutCache(w http.ResponseWriter, name string, data interface{}) {
|
||||||
|
if cache.hotReload {
|
||||||
|
logbuch.Debug("Reloading templates")
|
||||||
|
cache.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cache.tpl.ExecuteTemplate(w, name, data); err != nil {
|
||||||
|
logbuch.Error("Error executing template", logbuch.Fields{"err": err, "name": name})
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *Cache) Clear() {
|
||||||
|
cache.m.Lock()
|
||||||
|
defer cache.m.Unlock()
|
||||||
|
cache.cache = make(map[string][]byte)
|
||||||
|
}
|
||||||
|
|||||||
102
tracking/statistics.go
Normal file
102
tracking/statistics.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package tracking
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/emvi/logbuch"
|
||||||
|
"github.com/emvi/pirsch"
|
||||||
|
"html/template"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
statisticsDateFormat = "2006-01-02"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PageVisits struct {
|
||||||
|
Path string
|
||||||
|
Labels template.JS
|
||||||
|
Data template.JS
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTotalVisitors(start int) (template.JS, template.JS) {
|
||||||
|
query := `SELECT "date" "day",
|
||||||
|
CASE WHEN "visitors_per_day".visitors IS NULL THEN 0 ELSE "visitors_per_day".visitors END
|
||||||
|
FROM (SELECT * FROM generate_series(date($1), date(now()), interval '1 day') "date") AS date_series
|
||||||
|
LEFT JOIN "visitors_per_day" ON date("visitors_per_day"."day") = date("date")
|
||||||
|
ORDER BY "date" ASC`
|
||||||
|
startTime := today()
|
||||||
|
startTime = startTime.Add(-time.Hour * 24 * time.Duration(start-1))
|
||||||
|
logbuch.Debug("Reading total visitors since", logbuch.Fields{"since": startTime})
|
||||||
|
var visitors []pirsch.VisitorsPerDay
|
||||||
|
|
||||||
|
if err := db.Select(&visitors, query, startTime); err != nil {
|
||||||
|
logbuch.Error("Error reading total visitors", logbuch.Fields{"err": err, "since": startTime})
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
today := today()
|
||||||
|
visitorsToday, err := store.VisitorsPerDay(today)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logbuch.Error("Error reading total visitors for today", logbuch.Fields{"err": err, "since": startTime})
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
visitors[len(visitors)-1].Visitors = visitorsToday
|
||||||
|
return getLabelsAndData(visitors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPageVisits(start int) []PageVisits {
|
||||||
|
pathsQuery := `SELECT * FROM (SELECT DISTINCT "path" FROM "visitors_per_page" WHERE day >= $1) AS paths ORDER BY length("path") ASC`
|
||||||
|
query := `SELECT "date" "day",
|
||||||
|
CASE WHEN "visitors_per_page".visitors IS NULL THEN 0 ELSE "visitors_per_page".visitors END
|
||||||
|
FROM (SELECT * FROM generate_series(date($1), date(now() - INTERVAL '1 day'), interval '1 day') "date") AS date_series
|
||||||
|
LEFT JOIN "visitors_per_page" ON date("visitors_per_page"."day") = date("date") AND "visitors_per_page"."path" = $2
|
||||||
|
ORDER BY "date" ASC, length("visitors_per_page"."path") ASC`
|
||||||
|
startTime := today()
|
||||||
|
startTime = startTime.Add(-time.Hour * 24 * time.Duration(start-1))
|
||||||
|
logbuch.Debug("Reading page visits", logbuch.Fields{"since": startTime})
|
||||||
|
var paths []string
|
||||||
|
|
||||||
|
if err := db.Select(&paths, pathsQuery, startTime); err != nil {
|
||||||
|
logbuch.Error("Error reading distinct paths", logbuch.Fields{"err": err})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add visitors for today
|
||||||
|
pageVisits := make([]PageVisits, len(paths))
|
||||||
|
|
||||||
|
for i, path := range paths {
|
||||||
|
var visitors []pirsch.VisitorsPerDay
|
||||||
|
|
||||||
|
if err := db.Select(&visitors, query, startTime, path); err != nil {
|
||||||
|
logbuch.Error("Error reading visitor for path", logbuch.Fields{"err": err, "since": startTime})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
labels, data := getLabelsAndData(visitors)
|
||||||
|
pageVisits[i] = PageVisits{path, labels, data}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageVisits
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLabelsAndData(visitors []pirsch.VisitorsPerDay) (template.JS, template.JS) {
|
||||||
|
var labels strings.Builder
|
||||||
|
var dp strings.Builder
|
||||||
|
|
||||||
|
for _, point := range visitors {
|
||||||
|
labels.WriteString(fmt.Sprintf("'%s',", point.Day.Format(statisticsDateFormat)))
|
||||||
|
dp.WriteString(fmt.Sprintf("%d,", point.Visitors))
|
||||||
|
}
|
||||||
|
|
||||||
|
labelsStr := labels.String()
|
||||||
|
dataStr := dp.String()
|
||||||
|
return template.JS(labelsStr[:len(labelsStr)-1]), template.JS(dataStr[:len(dataStr)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func today() time.Time {
|
||||||
|
now := time.Now()
|
||||||
|
return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||||
|
}
|
||||||
70
tracking/tracking.go
Normal file
70
tracking/tracking.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package tracking
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"github.com/emvi/logbuch"
|
||||||
|
"github.com/emvi/pirsch"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
connectionString = `host=%s port=%s user=%s password=%s dbname=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s connectTimeout=%s timezone=%s`
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *sqlx.DB
|
||||||
|
var store pirsch.Store
|
||||||
|
|
||||||
|
func NewTracker() *pirsch.Tracker {
|
||||||
|
logbuch.Info("Connecting to database...")
|
||||||
|
host := os.Getenv("MB_DB_HOST")
|
||||||
|
port := os.Getenv("MB_DB_PORT")
|
||||||
|
user := os.Getenv("MB_DB_USER")
|
||||||
|
password := os.Getenv("MB_DB_PASSWORD")
|
||||||
|
schema := os.Getenv("MB_DB_SCHEMA")
|
||||||
|
sslMode := os.Getenv("MB_DB_SSLMODE")
|
||||||
|
sslCert := os.Getenv("MB_DB_SSLCERT")
|
||||||
|
sslKey := os.Getenv("MB_DB_SSLKEY")
|
||||||
|
sslRootCert := os.Getenv("MB_DB_SSLROOTCERT")
|
||||||
|
zone, offset := time.Now().Zone()
|
||||||
|
timezone := zone + strconv.Itoa(-offset/3600)
|
||||||
|
logbuch.Info("Setting time zone", logbuch.Fields{"timezone": timezone})
|
||||||
|
connectionStr := fmt.Sprintf(connectionString, host, port, user, password, schema, sslMode, sslCert, sslKey, sslRootCert, "30", timezone)
|
||||||
|
conn, err := sql.Open("postgres", connectionStr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logbuch.Fatal("Error connecting to database", logbuch.Fields{"err": err})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := conn.Ping(); err != nil {
|
||||||
|
logbuch.Fatal("Error pinging database", logbuch.Fields{"err": err})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
db = sqlx.NewDb(conn, "postgres")
|
||||||
|
store = pirsch.NewPostgresStore(conn)
|
||||||
|
tracker := pirsch.NewTracker(store, nil)
|
||||||
|
processor := pirsch.NewProcessor(store)
|
||||||
|
processTrackingData(processor)
|
||||||
|
pirsch.RunAtMidnight(func() {
|
||||||
|
processTrackingData(processor)
|
||||||
|
})
|
||||||
|
return tracker
|
||||||
|
}
|
||||||
|
|
||||||
|
func processTrackingData(processor *pirsch.Processor) {
|
||||||
|
logbuch.Info("Processing tracking data...")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
logbuch.Error("Error processing tracking data", logbuch.Fields{"err": err})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
processor.Process()
|
||||||
|
logbuch.Info("Done processing tracking data")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user