mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
Compare commits
56 Commits
Author | SHA1 | Date | |
---|---|---|---|
fbd90d2cc1 | |||
129e208169 | |||
9fd9ffbb3d | |||
0884f620f1 | |||
7ab9c45f4f | |||
915436822b | |||
0f1d1bce4d | |||
6256c8e10a | |||
2a9fbfdfd7 | |||
56247b4e1e | |||
9d7afde6a9 | |||
0df0168584 | |||
a6fe15d69b | |||
ae363c1c82 | |||
127a614190 | |||
b8cefeb595 | |||
ae97095688 | |||
4706809170 | |||
ddc29f0414 | |||
f4af787ecf | |||
da6a00fec5 | |||
6ad33e3c3b | |||
e6e134678a | |||
1783858854 | |||
e1d040bd55 | |||
7f3a654b26 | |||
2b57da224c | |||
01d51b78b1 | |||
6b83600acc | |||
65bbd744b5 | |||
81ca703501 | |||
2d1010e9d9 | |||
5ca9a6a8be | |||
caf87de887 | |||
9fc3c65efe | |||
f73285160d | |||
1f557d562f | |||
3685f3a156 | |||
b3afe9bfa2 | |||
9de2c20885 | |||
2846748b26 | |||
f2f6fe1483 | |||
17ddd7ca76 | |||
292ae41c58 | |||
4f86f67716 | |||
017530ac4a | |||
81d3251856 | |||
16af17fc37 | |||
701ed0a3e1 | |||
218c93e975 | |||
44de057022 | |||
e55adf6287 | |||
56800be8e8 | |||
c149766ecc | |||
759e8e4dfd | |||
708863fd33 |
4
.github/workflows/linux-build-on-release.yml
vendored
4
.github/workflows/linux-build-on-release.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.13
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
@ -25,9 +25,7 @@ jobs:
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get github.com/markbates/pkger/cmd/pkger
|
||||
go get
|
||||
go generate
|
||||
|
||||
- name: Build
|
||||
run: GO111MODULE=on go build -v .
|
||||
|
4
.github/workflows/win-build-on-release.yml
vendored
4
.github/workflows/win-build-on-release.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.13
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
@ -25,9 +25,7 @@ jobs:
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get github.com/markbates/pkger/cmd/pkger
|
||||
go get
|
||||
go generate
|
||||
|
||||
- name: Enable Go 1.11 modules
|
||||
run: cmd /c "set GO111MODULE=on"
|
||||
|
@ -1,16 +1,16 @@
|
||||
# Build Stage
|
||||
|
||||
FROM golang:1.15 AS build-env
|
||||
FROM golang:1.16 AS build-env
|
||||
WORKDIR /src
|
||||
|
||||
ADD ./go.mod .
|
||||
RUN go mod download && go get github.com/markbates/pkger/cmd/pkger
|
||||
RUN go mod download
|
||||
|
||||
RUN curl "https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh" -o wait-for-it.sh && \
|
||||
chmod +x wait-for-it.sh
|
||||
|
||||
ADD . .
|
||||
RUN go generate && go build -o wakapi
|
||||
RUN go build -o wakapi
|
||||
|
||||
WORKDIR /app
|
||||
RUN cp /src/wakapi . && \
|
||||
@ -31,6 +31,7 @@ WORKDIR /app
|
||||
RUN apt update && \
|
||||
apt install -y ca-certificates
|
||||
|
||||
# See README.md and config.default.yml for all config options
|
||||
ENV ENVIRONMENT prod
|
||||
ENV WAKAPI_DB_TYPE sqlite3
|
||||
ENV WAKAPI_DB_USER ''
|
||||
|
31
README.md
31
README.md
@ -49,6 +49,8 @@
|
||||
* [Support](#-support)
|
||||
* [FAQs](#-faqs)
|
||||
|
||||
Further instructions can be found in the [Wiki](https://github.com/muety/wakapi/wiki).
|
||||
|
||||
## 📬 **User Survey**
|
||||
I'd love to get some community feedback from active Wakapi users. If you want, please participate in the recent [user survey](https://github.com/muety/wakapi/issues/82). Thanks a lot!
|
||||
|
||||
@ -90,6 +92,8 @@ $ docker run -d \
|
||||
|
||||
**Note:** By default, SQLite is used as a database. To run Wakapi in Docker with MySQL or Postgres, see [Dockerfile](https://github.com/muety/wakapi/blob/master/Dockerfile) and [config.default.yml](https://github.com/muety/wakapi/blob/master/config.default.yml) for further options.
|
||||
|
||||
If you want to run Wakapi on **Kubernetes**, there is [wakapi-helm-chart](https://github.com/andreymaznyak/wakapi-helm-chart) for quick and easy deployment.
|
||||
|
||||
### 📦 Option 3: Run a release
|
||||
```bash
|
||||
# Download the release and unpack it
|
||||
@ -105,7 +109,7 @@ $ ./wakapi
|
||||
|
||||
### 🧑💻 Option 4: Run from source
|
||||
#### Prerequisites
|
||||
* Go >= 1.13 (with `$GOPATH` properly set)
|
||||
* Go >= 1.16 (with `$GOPATH` properly set)
|
||||
* gcc (to compile [go-sqlite3](https://github.com/mattn/go-sqlite3))
|
||||
* Fedora / RHEL: `dnf install @development-tools`
|
||||
* Ubuntu / Debian: `apt install build-essential`
|
||||
@ -117,12 +121,7 @@ $ ./wakapi
|
||||
$ cp config.default.yml config.yml
|
||||
$ vi config.yml
|
||||
|
||||
# Install packaging tool
|
||||
$ export GO111MODULE=on
|
||||
$ go get github.com/markbates/pkger/cmd/pkger
|
||||
|
||||
# Build the executable
|
||||
$ go generate
|
||||
$ go build -o wakapi
|
||||
|
||||
# Run it
|
||||
@ -147,7 +146,7 @@ api_url = http://localhost:3000/api/heartbeat
|
||||
api_key = 406fe41f-6d69-4183-a4cc-121e0c524c2b
|
||||
```
|
||||
|
||||
Optionally, you can set up a [client-side proxy](docs/advanced_setup.md) in addition.
|
||||
Optionally, you can set up a [client-side proxy](https://github.com/muety/wakapi/wiki/Advanced-Setup:-Client-side-proxy) in addition.
|
||||
|
||||
## 🔧 Configuration Options
|
||||
You can specify configuration options either via a config file (default: `config.yml`, customziable through the `-c` argument) or via environment variables. Here is an overview of all options.
|
||||
@ -172,9 +171,18 @@ You can specify configuration options either via a config file (default: `config
|
||||
| `db.user` | `WAKAPI_DB_USER` | - | Database user |
|
||||
| `db.password` | `WAKAPI_DB_PASSWORD` | - | Database password |
|
||||
| `db.name` | `WAKAPI_DB_NAME` | `wakapi_db.db` | Database name |
|
||||
| `db.dialect` | `WAKAPI_DB_TYPE` | `sqlite3` | Database type (one of `sqlite3`, `mysql`, `postgres`, `cockroach`) |
|
||||
| `db.dialect` | `WAKAPI_DB_TYPE` | `sqlite3` | Database type (one of `sqlite3`, `mysql`, `postgres`, `cockroach`) |
|
||||
| `db.charset` | `WAKAPI_DB_CHARSET` | `utf8mb4` | Database connection charset (for MySQL only) |
|
||||
| `db.max_conn` | `WAKAPI_DB_MAX_CONNECTIONS` | `2` | Maximum number of database connections |
|
||||
| `db.ssl` | `WAKAPI_DB_SSL` | `false` | Whether to use TLS encryption for database connection (Postgres and CockroachDB only) |
|
||||
| `mail.enabled` | `WAKAPI_MAIL_ENABLED` | `true` | Whether to allow Wakapi to send e-mail (e.g. for password resets) |
|
||||
| `mail.provider` | `WAKAPI_MAIL_PROVIDER` | `smtp` | Implementation to use for sending mails (one of [`smtp`, `mailwhale`]) |
|
||||
| `mail.smtp.*` | `WAKAPI_MAIL_SMTP_*` | `-` | Various options to configure SMTP. See [default config](config.default.yaml) for details |
|
||||
| `mail.mailwhale.*` | `WAKAPI_MAIL_MAILWHALE_*` | `-` | Various options to configure [MailWhale](https://mailwhale.dev) sending service. See [default config](config.default.yaml) for details |
|
||||
| `sentry.dsn` | `WAKAPI_SENTRY_DSN` | – | DSN for to integrate [Sentry](https://sentry.io) for error logging and tracing (leave empty to disable) |
|
||||
| `sentry.enable_tracing` | `WAKAPI_SENTRY_TRACING` | `false` | Whether to enable Sentry request tracing |
|
||||
| `sentry.sample_rate` | `WAKAPI_SENTRY_SAMPLE_RATE` | `0.75` | Probability of tracing a request in Sentry |
|
||||
| `sentry.sample_rate_heartbats` | `WAKAPI_SENTRY_SAMPLE_RATE_HEARTBEATS` | `0.1` | Probability of tracing a heartbeats request in Sentry |
|
||||
|
||||
### Supported databases
|
||||
Wakapi uses [GORM](https://gorm.io) as an ORM. As a consequence, a set of different relational databases is supported.
|
||||
@ -248,6 +256,13 @@ However, if you want to expose your wakapi instance to the public anyway, you ne
|
||||
CGO_FLAGS="-g -O2 -Wno-return-local-addr" go test -json -coverprofile=coverage/coverage.out ./... -run ./...
|
||||
```
|
||||
|
||||
### Building Tailwind
|
||||
To keep things minimal, Wakapi does not contain a `package.json`, `node_modules` or any sort of frontend build step. Instead, all JS and CSS assets are included as static files and checked in to Git. This way we can avoid requiring NodeJS to build Wakapi. However, for [TailwindCSS](https://tailwindcss.com/docs/installation#building-for-production) it makes sense to run it through a "build" step to benefit from purging and significantly reduce it in size. To only require this at the time of development, the compiled asset is checked in to Git as well.
|
||||
|
||||
```bash
|
||||
$ tailwindcss-cli build static/assets/vendor/tailwind.css -o static/assets/vendor/tailwind.dist.css
|
||||
```
|
||||
|
||||
## 🙏 Support
|
||||
If you like this project, please consider supporting it 🙂. You can donate either through [buying me a coffee](https://buymeacoff.ee/n1try) or becoming a GitHub sponsor. Every little donation is highly appreciated and boosts the developers' motivation to keep improving Wakapi!
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
env: development
|
||||
|
||||
server:
|
||||
listen_ipv4: 127.0.0.1 # leave blank to disable ipv4
|
||||
listen_ipv6: ::1 # leave blank to disable ipv6
|
||||
tls_cert_path: # leave blank to not use https
|
||||
tls_key_path: # leave blank to not use https
|
||||
listen_ipv4: 127.0.0.1 # leave blank to disable ipv4
|
||||
listen_ipv6: ::1 # leave blank to disable ipv6
|
||||
tls_cert_path: # leave blank to not use https
|
||||
tls_key_path: # leave blank to not use https
|
||||
port: 3000
|
||||
base_path: /
|
||||
public_url: http://localhost:3000 # required for links (e.g. password reset) in e-mail
|
||||
|
||||
app:
|
||||
aggregation_time: '02:15' # time at which to run daily aggregation batch jobs
|
||||
@ -14,6 +15,7 @@ app:
|
||||
custom_languages:
|
||||
vue: Vue
|
||||
jsx: JSX
|
||||
svelte: Svelte
|
||||
|
||||
db:
|
||||
host: # leave blank when using sqlite3
|
||||
@ -22,6 +24,7 @@ db:
|
||||
password: # leave blank when using sqlite3
|
||||
name: wakapi_db.db # database name for mysql / postgres or file path for sqlite (e.g. /tmp/wakapi.db)
|
||||
dialect: sqlite3 # mysql, postgres, sqlite3
|
||||
charset: utf8mb4 # only used for mysql connections
|
||||
max_conn: 2 # maximum number of concurrent connections to maintain
|
||||
ssl: false # whether to use tls for db connection (must be true for cockroachdb) (ignored for mysql and sqlite)
|
||||
|
||||
@ -30,4 +33,25 @@ security:
|
||||
insecure_cookies: false # You need to set this to 'true' when on localhost
|
||||
cookie_max_age: 172800
|
||||
allow_signup: true
|
||||
expose_metrics: false
|
||||
expose_metrics: false
|
||||
|
||||
sentry:
|
||||
dsn: # leave blank to disable sentry integration
|
||||
enable_tracing: true # whether to use performance monitoring
|
||||
sample_rate: 0.75 # probability of tracing a request
|
||||
sample_rate_heartbeats: 0.1 # probability of tracing a heartbeat request
|
||||
|
||||
mail:
|
||||
enabled: true # whether to enable mails (used for password resets, reports, etc.)
|
||||
provider: smtp # method for sending mails, currently one of ['smtp', 'mailwhale']
|
||||
smtp: # smtp settings when sending mails via smtp
|
||||
host:
|
||||
port:
|
||||
username:
|
||||
password:
|
||||
tls:
|
||||
sender: Wakapi <noreply@wakapi.dev>
|
||||
mailwhale: # mailwhale.dev settings when using mailwhale as sending service
|
||||
url:
|
||||
client_id:
|
||||
client_secret:
|
||||
|
131
config/config.go
131
config/config.go
@ -4,20 +4,19 @@ import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/jinzhu/configor"
|
||||
"github.com/markbates/pkger"
|
||||
"github.com/muety/wakapi/data"
|
||||
"github.com/muety/wakapi/models"
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -48,6 +47,16 @@ const (
|
||||
WakatimeApiMachineNamesUrl = "/users/current/machine_names"
|
||||
)
|
||||
|
||||
const (
|
||||
MailProviderSmtp = "smtp"
|
||||
MailProviderMailWhale = "mailwhale"
|
||||
)
|
||||
|
||||
var emailProviders = []string{
|
||||
MailProviderSmtp,
|
||||
MailProviderMailWhale,
|
||||
}
|
||||
|
||||
var cfg *Config
|
||||
var cFlag = flag.String("config", defaultConfigPath, "config file location")
|
||||
|
||||
@ -77,6 +86,7 @@ type dbConfig struct {
|
||||
Password string `env:"WAKAPI_DB_PASSWORD"`
|
||||
Name string `default:"wakapi_db.db" env:"WAKAPI_DB_NAME"`
|
||||
Dialect string `yaml:"-"`
|
||||
Charset string `default:"utf8mb4" env:"WAKAPI_DB_CHARSET"`
|
||||
Type string `yaml:"dialect" default:"sqlite3" env:"WAKAPI_DB_TYPE"`
|
||||
MaxConn uint `yaml:"max_conn" default:"2" env:"WAKAPI_DB_MAX_CONNECTIONS"`
|
||||
Ssl bool `default:"false" env:"WAKAPI_DB_SSL"`
|
||||
@ -87,10 +97,40 @@ type serverConfig struct {
|
||||
ListenIpV4 string `yaml:"listen_ipv4" default:"127.0.0.1" env:"WAKAPI_LISTEN_IPV4"`
|
||||
ListenIpV6 string `yaml:"listen_ipv6" default:"::1" env:"WAKAPI_LISTEN_IPV6"`
|
||||
BasePath string `yaml:"base_path" default:"/" env:"WAKAPI_BASE_PATH"`
|
||||
PublicUrl string `yaml:"public_url" default:"http://localhost:3000" env:"WAKAPI_PUBLIC_URL"`
|
||||
TlsCertPath string `yaml:"tls_cert_path" default:"" env:"WAKAPI_TLS_CERT_PATH"`
|
||||
TlsKeyPath string `yaml:"tls_key_path" default:"" env:"WAKAPI_TLS_KEY_PATH"`
|
||||
}
|
||||
|
||||
type sentryConfig struct {
|
||||
Dsn string `env:"WAKAPI_SENTRY_DSN"`
|
||||
EnableTracing bool `yaml:"enable_tracing" env:"WAKAPI_SENTRY_TRACING"`
|
||||
SampleRate float32 `yaml:"sample_rate" default:"0.75" env:"WAKAPI_SENTRY_SAMPLE_RATE"`
|
||||
SampleRateHeartbeats float32 `yaml:"sample_rate_heartbeats" default:"0.1" env:"WAKAPI_SENTRY_SAMPLE_RATE_HEARTBEATS"`
|
||||
}
|
||||
|
||||
type mailConfig struct {
|
||||
Enabled bool `env:"WAKAPI_MAIL_ENABLED" default:"true"`
|
||||
Provider string `env:"WAKAPI_MAIL_PROVIDER" default:"smtp"`
|
||||
MailWhale MailwhaleMailConfig `yaml:"mailwhale"`
|
||||
Smtp SMTPMailConfig `yaml:"smtp"`
|
||||
}
|
||||
|
||||
type MailwhaleMailConfig struct {
|
||||
Url string `env:"WAKAPI_MAIL_MAILWHALE_URL"`
|
||||
ClientId string `yaml:"client_id" env:"WAKAPI_MAIL_MAILWHALE_CLIENT_ID"`
|
||||
ClientSecret string `yaml:"client_secret" env:"WAKAPI_MAIL_MAILWHALE_CLIENT_SECRET"`
|
||||
}
|
||||
|
||||
type SMTPMailConfig struct {
|
||||
Host string `env:"WAKAPI_MAIL_SMTP_HOST"`
|
||||
Port uint `env:"WAKAPI_MAIL_SMTP_PORT"`
|
||||
Username string `env:"WAKAPI_MAIL_SMTP_USER"`
|
||||
Password string `env:"WAKAPI_MAIL_SMTP_PASS"`
|
||||
TLS bool `env:"WAKAPI_MAIL_SMTP_TLS"`
|
||||
Sender string `env:"WAKAPI_MAIL_SMTP_SENDER"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Env string `default:"dev" env:"ENVIRONMENT"`
|
||||
Version string `yaml:"-"`
|
||||
@ -98,6 +138,8 @@ type Config struct {
|
||||
Security securityConfig
|
||||
Db dbConfig
|
||||
Server serverConfig
|
||||
Sentry sentryConfig
|
||||
Mail mailConfig
|
||||
}
|
||||
|
||||
func (c *Config) CreateCookie(name, value, path string) *http.Cookie {
|
||||
@ -144,24 +186,6 @@ func (c *Config) GetMigrationFunc(dbDialect string) models.MigrationFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) GetFixturesFunc(dbDialect string) models.MigrationFunc {
|
||||
return func(db *gorm.DB) error {
|
||||
migrations := &migrate.HttpFileSystemMigrationSource{
|
||||
FileSystem: pkger.Dir("/migrations"),
|
||||
}
|
||||
|
||||
migrate.SetIgnoreUnknown(true)
|
||||
sqlDb, _ := db.DB()
|
||||
n, err := migrate.Exec(sqlDb, dbDialect, migrations, migrate.Up)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logbuch.Info("applied %d fixtures", n)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dbConfig) GetDialector() gorm.Dialector {
|
||||
switch c.Dialect {
|
||||
case SQLDialectMysql:
|
||||
@ -181,12 +205,13 @@ func (c *dbConfig) GetDialector() gorm.Dialector {
|
||||
|
||||
func mysqlConnectionString(config *dbConfig) string {
|
||||
//location, _ := time.LoadLocation("Local")
|
||||
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=true&loc=%s&sql_mode=ANSI_QUOTES",
|
||||
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true&loc=%s&sql_mode=ANSI_QUOTES",
|
||||
config.User,
|
||||
config.Password,
|
||||
config.Host,
|
||||
config.Port,
|
||||
config.Name,
|
||||
config.Charset,
|
||||
"Local",
|
||||
)
|
||||
}
|
||||
@ -227,23 +252,16 @@ func (c *appConfig) GetOSColors() map[string]string {
|
||||
return cloneStringMap(c.Colors["operating_systems"], true)
|
||||
}
|
||||
|
||||
func IsDev(env string) bool {
|
||||
return env == "dev" || env == "development"
|
||||
func (c *serverConfig) GetPublicUrl() string {
|
||||
return strings.TrimSuffix(c.PublicUrl, "/")
|
||||
}
|
||||
|
||||
func readVersion() string {
|
||||
file, err := pkger.Open("/version.txt")
|
||||
if err != nil {
|
||||
logbuch.Fatal(err.Error())
|
||||
}
|
||||
defer file.Close()
|
||||
func (c *SMTPMailConfig) ConnStr() string {
|
||||
return fmt.Sprintf("%s:%d", c.Host, c.Port)
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
logbuch.Fatal(err.Error())
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(bytes))
|
||||
func IsDev(env string) bool {
|
||||
return env == "dev" || env == "development"
|
||||
}
|
||||
|
||||
func readColors() map[string]map[string]string {
|
||||
@ -256,18 +274,7 @@ func readColors() map[string]map[string]string {
|
||||
// – $x('//span[@class="editor-icon tip"]/@data-original-title').map(e => e.nodeValue)
|
||||
// – $x('//span[@class="editor-icon tip"]/div[1]/text()').map(e => e.nodeValue)
|
||||
var colors = make(map[string]map[string]string)
|
||||
|
||||
file, err := pkger.Open("/data/colors.json")
|
||||
if err != nil {
|
||||
logbuch.Fatal(err.Error())
|
||||
}
|
||||
defer file.Close()
|
||||
bytes, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
logbuch.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(bytes, &colors); err != nil {
|
||||
if err := json.Unmarshal(data.ColorsFile, &colors); err != nil {
|
||||
logbuch.Fatal(err.Error())
|
||||
}
|
||||
|
||||
@ -288,6 +295,15 @@ func resolveDbDialect(dbType string) string {
|
||||
return dbType
|
||||
}
|
||||
|
||||
func findString(needle string, haystack []string, defaultVal string) string {
|
||||
for _, s := range haystack {
|
||||
if s == needle {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func Set(config *Config) {
|
||||
cfg = config
|
||||
}
|
||||
@ -296,7 +312,7 @@ func Get() *Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func Load() *Config {
|
||||
func Load(version string) *Config {
|
||||
config := &Config{}
|
||||
|
||||
flag.Parse()
|
||||
@ -305,7 +321,7 @@ func Load() *Config {
|
||||
logbuch.Fatal("failed to read config: %v", err)
|
||||
}
|
||||
|
||||
config.Version = readVersion()
|
||||
config.Version = strings.TrimSpace(version)
|
||||
config.App.Colors = readColors()
|
||||
config.Db.Dialect = resolveDbDialect(config.Db.Type)
|
||||
config.Security.SecureCookie = securecookie.New(
|
||||
@ -331,6 +347,15 @@ func Load() *Config {
|
||||
logbuch.Fatal("you must allow at least one database connection")
|
||||
}
|
||||
|
||||
if config.Sentry.Dsn != "" {
|
||||
logbuch.Info("enabling sentry integration")
|
||||
initSentry(config.Sentry, config.IsDev())
|
||||
}
|
||||
|
||||
if config.Mail.Provider != "" && findString(config.Mail.Provider, emailProviders, "") == "" {
|
||||
logbuch.Fatal("unknown mail provider '%s'", config.Mail.Provider)
|
||||
}
|
||||
|
||||
Set(config)
|
||||
return Get()
|
||||
}
|
||||
|
@ -22,11 +22,12 @@ func Test_mysqlConnectionString(t *testing.T) {
|
||||
Password: "test_password",
|
||||
Name: "test_name",
|
||||
Dialect: "mysql",
|
||||
Charset: "utf8mb4",
|
||||
MaxConn: 10,
|
||||
}
|
||||
|
||||
assert.Equal(t, fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=true&loc=%s&sql_mode=ANSI_QUOTES",
|
||||
"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true&loc=%s&sql_mode=ANSI_QUOTES",
|
||||
c.User,
|
||||
c.Password,
|
||||
c.Host,
|
||||
|
61
config/sentry.go
Normal file
61
config/sentry.go
Normal file
@ -0,0 +1,61 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/muety/wakapi/models"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SentryErrorWriter struct{}
|
||||
|
||||
// TODO: extend sentry error logging to include context and stacktrace
|
||||
// see https://github.com/muety/wakapi/issues/169
|
||||
func (s *SentryErrorWriter) Write(p []byte) (n int, err error) {
|
||||
sentry.CaptureMessage(string(p))
|
||||
return os.Stderr.Write(p)
|
||||
}
|
||||
|
||||
func init() {
|
||||
logbuch.SetOutput(os.Stdout, &SentryErrorWriter{})
|
||||
}
|
||||
|
||||
func initSentry(config sentryConfig, debug bool) {
|
||||
if err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: config.Dsn,
|
||||
Debug: debug,
|
||||
TracesSampler: sentry.TracesSamplerFunc(func(ctx sentry.SamplingContext) sentry.Sampled {
|
||||
if !config.EnableTracing {
|
||||
return sentry.SampledFalse
|
||||
}
|
||||
|
||||
hub := sentry.GetHubFromContext(ctx.Span.Context())
|
||||
txName := hub.Scope().Transaction()
|
||||
|
||||
if strings.HasPrefix(txName, "GET /assets") || strings.HasPrefix(txName, "GET /api/health") {
|
||||
return sentry.SampledFalse
|
||||
}
|
||||
if txName == "POST /api/heartbeat" {
|
||||
return sentry.UniformTracesSampler(config.SampleRateHeartbeats).Sample(ctx)
|
||||
}
|
||||
return sentry.UniformTracesSampler(config.SampleRate).Sample(ctx)
|
||||
}),
|
||||
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
|
||||
type principalGetter interface {
|
||||
GetPrincipal() *models.User
|
||||
}
|
||||
if hint.Context != nil {
|
||||
if req, ok := hint.Context.Value(sentry.RequestContextKey).(*http.Request); ok {
|
||||
if p := req.Context().Value("principal"); p != nil {
|
||||
event.User.ID = p.(principalGetter).GetPrincipal().ID
|
||||
}
|
||||
}
|
||||
}
|
||||
return event
|
||||
},
|
||||
}); err != nil {
|
||||
logbuch.Fatal("failed to initialized sentry – %v", err)
|
||||
}
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
package config
|
||||
|
||||
const (
|
||||
IndexTemplate = "index.tpl.html"
|
||||
LoginTemplate = "login.tpl.html"
|
||||
ImprintTemplate = "imprint.tpl.html"
|
||||
SignupTemplate = "signup.tpl.html"
|
||||
SettingsTemplate = "settings.tpl.html"
|
||||
SummaryTemplate = "summary.tpl.html"
|
||||
IndexTemplate = "index.tpl.html"
|
||||
LoginTemplate = "login.tpl.html"
|
||||
ImprintTemplate = "imprint.tpl.html"
|
||||
SignupTemplate = "signup.tpl.html"
|
||||
SetPasswordTemplate = "set-password.tpl.html"
|
||||
ResetPasswordTemplate = "reset-password.tpl.html"
|
||||
SettingsTemplate = "settings.tpl.html"
|
||||
SummaryTemplate = "summary.tpl.html"
|
||||
)
|
||||
|
@ -1,25 +1,80 @@
|
||||
mode: set
|
||||
github.com/muety/wakapi/models/heartbeat.go:32.34,34.2 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:36.65,37.28 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:40.2,41.45 2 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:44.2,45.44 2 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:48.2,48.42 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:37.28,39.3 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:41.45,43.3 1 0
|
||||
github.com/muety/wakapi/models/heartbeat.go:45.44,47.3 1 0
|
||||
github.com/muety/wakapi/models/heartbeat.go:51.50,52.11 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:65.2,65.15 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:69.2,69.12 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:53.22,54.18 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:55.21,56.17 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:57.23,58.19 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:59.17,60.26 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:61.22,62.18 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:65.15,67.3 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:72.37,88.2 1 0
|
||||
github.com/muety/wakapi/models/heartbeat.go:96.41,98.16 2 0
|
||||
github.com/muety/wakapi/models/heartbeat.go:101.2,102.10 2 0
|
||||
github.com/muety/wakapi/models/heartbeat.go:98.16,100.3 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:15.13,18.2 2 1
|
||||
github.com/muety/wakapi/models/mail_address.go:24.38,26.2 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:28.35,30.21 2 0
|
||||
github.com/muety/wakapi/models/mail_address.go:36.2,36.11 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:30.21,31.21 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:34.3,34.18 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:31.21,33.4 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:39.35,41.2 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:43.43,45.22 2 0
|
||||
github.com/muety/wakapi/models/mail_address.go:48.2,48.12 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:45.22,47.3 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:51.46,53.22 2 0
|
||||
github.com/muety/wakapi/models/mail_address.go:56.2,56.12 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:53.22,55.3 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:59.40,60.22 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:65.2,65.13 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:60.22,61.17 1 0
|
||||
github.com/muety/wakapi/models/mail_address.go:61.17,63.4 1 0
|
||||
github.com/muety/wakapi/models/summary.go:69.29,71.2 1 1
|
||||
github.com/muety/wakapi/models/summary.go:73.37,80.2 6 1
|
||||
github.com/muety/wakapi/models/summary.go:82.35,84.2 1 1
|
||||
github.com/muety/wakapi/models/summary.go:86.57,94.2 1 1
|
||||
github.com/muety/wakapi/models/summary.go:96.64,98.2 1 0
|
||||
github.com/muety/wakapi/models/summary.go:111.33,116.26 4 1
|
||||
github.com/muety/wakapi/models/summary.go:123.2,123.37 1 1
|
||||
github.com/muety/wakapi/models/summary.go:127.2,130.33 2 1
|
||||
github.com/muety/wakapi/models/summary.go:116.26,117.30 1 1
|
||||
github.com/muety/wakapi/models/summary.go:117.30,119.4 1 1
|
||||
github.com/muety/wakapi/models/summary.go:123.37,125.3 1 0
|
||||
github.com/muety/wakapi/models/summary.go:130.33,136.3 1 1
|
||||
github.com/muety/wakapi/models/summary.go:139.45,144.30 3 1
|
||||
github.com/muety/wakapi/models/summary.go:153.2,153.30 1 1
|
||||
github.com/muety/wakapi/models/summary.go:144.30,145.47 1 1
|
||||
github.com/muety/wakapi/models/summary.go:145.47,146.32 1 1
|
||||
github.com/muety/wakapi/models/summary.go:149.4,149.9 1 1
|
||||
github.com/muety/wakapi/models/summary.go:146.32,148.5 1 1
|
||||
github.com/muety/wakapi/models/summary.go:156.73,158.55 2 1
|
||||
github.com/muety/wakapi/models/summary.go:163.2,163.16 1 1
|
||||
github.com/muety/wakapi/models/summary.go:158.55,159.31 1 1
|
||||
github.com/muety/wakapi/models/summary.go:159.31,161.4 1 1
|
||||
github.com/muety/wakapi/models/summary.go:166.88,168.55 2 1
|
||||
github.com/muety/wakapi/models/summary.go:176.2,176.16 1 1
|
||||
github.com/muety/wakapi/models/summary.go:168.55,169.31 1 1
|
||||
github.com/muety/wakapi/models/summary.go:169.31,170.23 1 1
|
||||
github.com/muety/wakapi/models/summary.go:173.4,173.46 1 1
|
||||
github.com/muety/wakapi/models/summary.go:170.23,171.13 1 1
|
||||
github.com/muety/wakapi/models/summary.go:179.70,181.8 2 1
|
||||
github.com/muety/wakapi/models/summary.go:184.2,184.10 1 1
|
||||
github.com/muety/wakapi/models/summary.go:181.8,183.3 1 1
|
||||
github.com/muety/wakapi/models/summary.go:187.71,188.63 1 1
|
||||
github.com/muety/wakapi/models/summary.go:228.2,234.10 6 1
|
||||
github.com/muety/wakapi/models/summary.go:188.63,191.45 2 1
|
||||
github.com/muety/wakapi/models/summary.go:200.3,200.31 1 1
|
||||
github.com/muety/wakapi/models/summary.go:207.3,207.31 1 1
|
||||
github.com/muety/wakapi/models/summary.go:224.3,224.16 1 1
|
||||
github.com/muety/wakapi/models/summary.go:191.45,192.32 1 1
|
||||
github.com/muety/wakapi/models/summary.go:197.4,197.14 1 1
|
||||
github.com/muety/wakapi/models/summary.go:192.32,193.24 1 1
|
||||
github.com/muety/wakapi/models/summary.go:193.24,195.6 1 1
|
||||
github.com/muety/wakapi/models/summary.go:200.31,202.60 1 1
|
||||
github.com/muety/wakapi/models/summary.go:202.60,204.5 1 1
|
||||
github.com/muety/wakapi/models/summary.go:207.31,209.60 1 1
|
||||
github.com/muety/wakapi/models/summary.go:209.60,210.55 1 1
|
||||
github.com/muety/wakapi/models/summary.go:210.55,212.6 1 1
|
||||
github.com/muety/wakapi/models/summary.go:212.11,220.6 1 1
|
||||
github.com/muety/wakapi/models/summary.go:237.33,239.2 1 1
|
||||
github.com/muety/wakapi/models/summary.go:241.43,243.2 1 1
|
||||
github.com/muety/wakapi/models/summary.go:245.38,247.2 1 1
|
||||
github.com/muety/wakapi/models/user.go:5.13,7.2 1 1
|
||||
github.com/muety/wakapi/models/user.go:70.43,73.2 1 0
|
||||
github.com/muety/wakapi/models/user.go:75.45,78.2 1 0
|
||||
github.com/muety/wakapi/models/user.go:80.33,85.2 1 0
|
||||
github.com/muety/wakapi/models/user.go:87.41,89.2 1 0
|
||||
github.com/muety/wakapi/models/user.go:91.45,93.2 1 0
|
||||
github.com/muety/wakapi/models/user.go:95.45,97.2 1 0
|
||||
github.com/muety/wakapi/models/user.go:99.39,101.2 1 0
|
||||
github.com/muety/wakapi/models/heartbeats.go:7.31,9.2 1 0
|
||||
github.com/muety/wakapi/models/heartbeats.go:11.41,13.2 1 0
|
||||
github.com/muety/wakapi/models/heartbeats.go:15.36,17.2 1 0
|
||||
@ -30,68 +85,6 @@ github.com/muety/wakapi/models/heartbeats.go:26.18,28.3 1 0
|
||||
github.com/muety/wakapi/models/heartbeats.go:32.40,34.18 1 0
|
||||
github.com/muety/wakapi/models/heartbeats.go:37.2,37.24 1 0
|
||||
github.com/muety/wakapi/models/heartbeats.go:34.18,36.3 1 0
|
||||
github.com/muety/wakapi/models/language_mapping.go:11.42,13.2 1 0
|
||||
github.com/muety/wakapi/models/language_mapping.go:15.51,17.2 1 0
|
||||
github.com/muety/wakapi/models/language_mapping.go:19.52,21.2 1 0
|
||||
github.com/muety/wakapi/models/summary.go:68.29,70.2 1 1
|
||||
github.com/muety/wakapi/models/summary.go:72.37,79.2 6 1
|
||||
github.com/muety/wakapi/models/summary.go:81.35,83.2 1 1
|
||||
github.com/muety/wakapi/models/summary.go:85.57,93.2 1 1
|
||||
github.com/muety/wakapi/models/summary.go:95.64,97.2 1 0
|
||||
github.com/muety/wakapi/models/summary.go:110.33,115.26 4 1
|
||||
github.com/muety/wakapi/models/summary.go:122.2,122.37 1 1
|
||||
github.com/muety/wakapi/models/summary.go:126.2,129.33 2 1
|
||||
github.com/muety/wakapi/models/summary.go:115.26,116.30 1 1
|
||||
github.com/muety/wakapi/models/summary.go:116.30,118.4 1 1
|
||||
github.com/muety/wakapi/models/summary.go:122.37,124.3 1 0
|
||||
github.com/muety/wakapi/models/summary.go:129.33,135.3 1 1
|
||||
github.com/muety/wakapi/models/summary.go:138.45,143.30 3 1
|
||||
github.com/muety/wakapi/models/summary.go:152.2,152.30 1 1
|
||||
github.com/muety/wakapi/models/summary.go:143.30,144.47 1 1
|
||||
github.com/muety/wakapi/models/summary.go:144.47,145.32 1 1
|
||||
github.com/muety/wakapi/models/summary.go:148.4,148.9 1 1
|
||||
github.com/muety/wakapi/models/summary.go:145.32,147.5 1 1
|
||||
github.com/muety/wakapi/models/summary.go:155.73,157.55 2 1
|
||||
github.com/muety/wakapi/models/summary.go:162.2,162.16 1 1
|
||||
github.com/muety/wakapi/models/summary.go:157.55,158.31 1 1
|
||||
github.com/muety/wakapi/models/summary.go:158.31,160.4 1 1
|
||||
github.com/muety/wakapi/models/summary.go:165.88,167.55 2 1
|
||||
github.com/muety/wakapi/models/summary.go:175.2,175.16 1 1
|
||||
github.com/muety/wakapi/models/summary.go:167.55,168.31 1 1
|
||||
github.com/muety/wakapi/models/summary.go:168.31,169.23 1 1
|
||||
github.com/muety/wakapi/models/summary.go:172.4,172.46 1 1
|
||||
github.com/muety/wakapi/models/summary.go:169.23,170.13 1 1
|
||||
github.com/muety/wakapi/models/summary.go:178.70,180.8 2 1
|
||||
github.com/muety/wakapi/models/summary.go:183.2,183.10 1 1
|
||||
github.com/muety/wakapi/models/summary.go:180.8,182.3 1 1
|
||||
github.com/muety/wakapi/models/summary.go:186.71,187.63 1 1
|
||||
github.com/muety/wakapi/models/summary.go:227.2,233.10 6 1
|
||||
github.com/muety/wakapi/models/summary.go:187.63,190.45 2 1
|
||||
github.com/muety/wakapi/models/summary.go:199.3,199.31 1 1
|
||||
github.com/muety/wakapi/models/summary.go:206.3,206.31 1 1
|
||||
github.com/muety/wakapi/models/summary.go:223.3,223.16 1 1
|
||||
github.com/muety/wakapi/models/summary.go:190.45,191.32 1 1
|
||||
github.com/muety/wakapi/models/summary.go:196.4,196.14 1 1
|
||||
github.com/muety/wakapi/models/summary.go:191.32,192.24 1 1
|
||||
github.com/muety/wakapi/models/summary.go:192.24,194.6 1 1
|
||||
github.com/muety/wakapi/models/summary.go:199.31,201.60 1 1
|
||||
github.com/muety/wakapi/models/summary.go:201.60,203.5 1 1
|
||||
github.com/muety/wakapi/models/summary.go:206.31,208.60 1 1
|
||||
github.com/muety/wakapi/models/summary.go:208.60,209.55 1 1
|
||||
github.com/muety/wakapi/models/summary.go:209.55,211.6 1 1
|
||||
github.com/muety/wakapi/models/summary.go:211.11,219.6 1 1
|
||||
github.com/muety/wakapi/models/summary.go:236.33,238.2 1 1
|
||||
github.com/muety/wakapi/models/summary.go:240.43,242.2 1 1
|
||||
github.com/muety/wakapi/models/summary.go:244.38,246.2 1 1
|
||||
github.com/muety/wakapi/models/user.go:46.43,49.2 1 0
|
||||
github.com/muety/wakapi/models/user.go:51.33,55.2 1 0
|
||||
github.com/muety/wakapi/models/user.go:57.45,59.2 1 0
|
||||
github.com/muety/wakapi/models/user.go:61.45,63.2 1 0
|
||||
github.com/muety/wakapi/models/alias.go:12.32,14.2 1 0
|
||||
github.com/muety/wakapi/models/alias.go:16.37,17.35 1 0
|
||||
github.com/muety/wakapi/models/alias.go:22.2,22.14 1 0
|
||||
github.com/muety/wakapi/models/alias.go:17.35,18.18 1 0
|
||||
github.com/muety/wakapi/models/alias.go:18.18,20.4 1 0
|
||||
github.com/muety/wakapi/models/filters.go:16.56,17.16 1 0
|
||||
github.com/muety/wakapi/models/filters.go:29.2,29.19 1 0
|
||||
github.com/muety/wakapi/models/filters.go:18.22,19.32 1 0
|
||||
@ -110,10 +103,34 @@ github.com/muety/wakapi/models/filters.go:39.8,39.27 1 1
|
||||
github.com/muety/wakapi/models/filters.go:39.27,41.3 1 0
|
||||
github.com/muety/wakapi/models/filters.go:41.8,41.28 1 1
|
||||
github.com/muety/wakapi/models/filters.go:41.28,43.3 1 0
|
||||
github.com/muety/wakapi/models/heartbeat.go:32.34,34.2 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:36.65,37.46 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:37.46,38.46 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:38.46,41.4 2 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:45.50,46.11 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:59.2,59.15 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:63.2,63.12 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:47.22,48.18 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:49.21,50.17 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:51.23,52.19 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:53.17,54.26 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:55.22,56.18 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:59.15,61.3 1 1
|
||||
github.com/muety/wakapi/models/heartbeat.go:66.37,82.2 1 0
|
||||
github.com/muety/wakapi/models/heartbeat.go:90.41,92.16 2 0
|
||||
github.com/muety/wakapi/models/heartbeat.go:95.2,96.10 2 0
|
||||
github.com/muety/wakapi/models/heartbeat.go:92.16,94.3 1 0
|
||||
github.com/muety/wakapi/models/interval.go:39.47,40.23 1 0
|
||||
github.com/muety/wakapi/models/interval.go:45.2,45.14 1 0
|
||||
github.com/muety/wakapi/models/interval.go:40.23,41.13 1 0
|
||||
github.com/muety/wakapi/models/interval.go:41.13,43.4 1 0
|
||||
github.com/muety/wakapi/models/language_mapping.go:11.42,13.2 1 0
|
||||
github.com/muety/wakapi/models/language_mapping.go:15.51,17.2 1 0
|
||||
github.com/muety/wakapi/models/language_mapping.go:19.52,21.2 1 0
|
||||
github.com/muety/wakapi/models/mail.go:16.44,20.2 3 0
|
||||
github.com/muety/wakapi/models/mail.go:22.44,26.2 3 0
|
||||
github.com/muety/wakapi/models/mail.go:28.32,41.2 1 0
|
||||
github.com/muety/wakapi/models/mail.go:43.41,45.2 1 0
|
||||
github.com/muety/wakapi/models/models.go:3.14,5.2 0 1
|
||||
github.com/muety/wakapi/models/shared.go:35.52,37.2 1 0
|
||||
github.com/muety/wakapi/models/shared.go:39.52,42.16 3 0
|
||||
@ -131,17 +148,99 @@ github.com/muety/wakapi/models/shared.go:83.51,86.2 2 0
|
||||
github.com/muety/wakapi/models/shared.go:88.37,91.2 2 0
|
||||
github.com/muety/wakapi/models/shared.go:93.35,95.2 1 0
|
||||
github.com/muety/wakapi/models/shared.go:97.34,99.2 1 0
|
||||
github.com/muety/wakapi/models/alias.go:12.32,14.2 1 0
|
||||
github.com/muety/wakapi/models/alias.go:16.37,17.35 1 0
|
||||
github.com/muety/wakapi/models/alias.go:22.2,22.14 1 0
|
||||
github.com/muety/wakapi/models/alias.go:17.35,18.18 1 0
|
||||
github.com/muety/wakapi/models/alias.go:18.18,20.4 1 0
|
||||
github.com/muety/wakapi/config/config.go:146.70,148.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:150.65,152.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:154.82,164.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:166.31,168.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:170.32,172.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:174.74,175.19 1 0
|
||||
github.com/muety/wakapi/config/config.go:176.10,177.34 1 0
|
||||
github.com/muety/wakapi/config/config.go:177.34,186.4 8 0
|
||||
github.com/muety/wakapi/config/config.go:190.50,191.19 1 0
|
||||
github.com/muety/wakapi/config/config.go:204.2,204.12 1 0
|
||||
github.com/muety/wakapi/config/config.go:192.23,196.5 1 0
|
||||
github.com/muety/wakapi/config/config.go:197.26,200.5 1 0
|
||||
github.com/muety/wakapi/config/config.go:201.24,202.48 1 0
|
||||
github.com/muety/wakapi/config/config.go:207.53,218.2 1 1
|
||||
github.com/muety/wakapi/config/config.go:220.56,222.16 2 1
|
||||
github.com/muety/wakapi/config/config.go:226.2,233.3 1 1
|
||||
github.com/muety/wakapi/config/config.go:222.16,224.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:236.54,238.2 1 1
|
||||
github.com/muety/wakapi/config/config.go:240.60,242.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:244.59,246.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:248.57,250.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:252.53,254.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:256.46,258.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:260.43,262.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:264.29,266.2 1 1
|
||||
github.com/muety/wakapi/config/config.go:268.48,278.65 2 0
|
||||
github.com/muety/wakapi/config/config.go:282.2,282.15 1 0
|
||||
github.com/muety/wakapi/config/config.go:278.65,280.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:285.38,286.43 1 0
|
||||
github.com/muety/wakapi/config/config.go:289.2,289.15 1 0
|
||||
github.com/muety/wakapi/config/config.go:286.43,288.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:292.45,293.27 1 0
|
||||
github.com/muety/wakapi/config/config.go:296.2,296.15 1 0
|
||||
github.com/muety/wakapi/config/config.go:293.27,295.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:299.50,303.91 1 0
|
||||
github.com/muety/wakapi/config/config.go:303.91,304.29 1 0
|
||||
github.com/muety/wakapi/config/config.go:308.4,311.96 3 0
|
||||
github.com/muety/wakapi/config/config.go:314.4,314.39 1 0
|
||||
github.com/muety/wakapi/config/config.go:317.4,317.69 1 0
|
||||
github.com/muety/wakapi/config/config.go:304.29,306.5 1 0
|
||||
github.com/muety/wakapi/config/config.go:311.96,313.5 1 0
|
||||
github.com/muety/wakapi/config/config.go:314.39,316.5 1 0
|
||||
github.com/muety/wakapi/config/config.go:319.79,323.27 2 0
|
||||
github.com/muety/wakapi/config/config.go:330.4,330.16 1 0
|
||||
github.com/muety/wakapi/config/config.go:323.27,324.84 1 0
|
||||
github.com/muety/wakapi/config/config.go:324.84,325.57 1 0
|
||||
github.com/muety/wakapi/config/config.go:325.57,327.7 1 0
|
||||
github.com/muety/wakapi/config/config.go:332.17,334.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:337.77,338.29 1 0
|
||||
github.com/muety/wakapi/config/config.go:343.2,343.19 1 0
|
||||
github.com/muety/wakapi/config/config.go:338.29,339.18 1 0
|
||||
github.com/muety/wakapi/config/config.go:339.18,341.4 1 0
|
||||
github.com/muety/wakapi/config/config.go:346.26,348.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:350.20,352.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:354.35,359.96 3 0
|
||||
github.com/muety/wakapi/config/config.go:363.2,371.52 5 0
|
||||
github.com/muety/wakapi/config/config.go:375.2,375.47 1 0
|
||||
github.com/muety/wakapi/config/config.go:381.2,381.70 1 0
|
||||
github.com/muety/wakapi/config/config.go:385.2,385.28 1 0
|
||||
github.com/muety/wakapi/config/config.go:389.2,389.29 1 0
|
||||
github.com/muety/wakapi/config/config.go:394.2,394.94 1 0
|
||||
github.com/muety/wakapi/config/config.go:398.2,399.14 2 0
|
||||
github.com/muety/wakapi/config/config.go:359.96,361.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:371.52,373.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:375.47,376.14 1 0
|
||||
github.com/muety/wakapi/config/config.go:376.14,378.4 1 0
|
||||
github.com/muety/wakapi/config/config.go:381.70,383.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:385.28,387.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:389.29,392.3 2 0
|
||||
github.com/muety/wakapi/config/config.go:394.94,396.3 1 0
|
||||
github.com/muety/wakapi/config/utils.go:5.78,7.22 2 0
|
||||
github.com/muety/wakapi/config/utils.go:13.2,13.11 1 0
|
||||
github.com/muety/wakapi/config/utils.go:7.22,8.18 1 0
|
||||
github.com/muety/wakapi/config/utils.go:11.3,11.12 1 0
|
||||
github.com/muety/wakapi/config/utils.go:8.18,10.4 1 0
|
||||
github.com/muety/wakapi/utils/color.go:8.90,10.32 2 0
|
||||
github.com/muety/wakapi/utils/color.go:15.2,15.15 1 0
|
||||
github.com/muety/wakapi/utils/color.go:10.32,11.50 1 0
|
||||
github.com/muety/wakapi/utils/color.go:11.50,13.4 1 0
|
||||
github.com/muety/wakapi/utils/common.go:10.48,12.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:14.40,16.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:18.45,20.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:22.24,24.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:26.56,29.45 3 1
|
||||
github.com/muety/wakapi/utils/common.go:32.2,32.40 1 1
|
||||
github.com/muety/wakapi/utils/common.go:29.45,31.3 1 1
|
||||
github.com/muety/wakapi/utils/common.go:14.52,16.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:18.40,20.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:22.44,24.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:26.45,28.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:30.24,32.2 1 0
|
||||
github.com/muety/wakapi/utils/common.go:34.56,37.45 3 1
|
||||
github.com/muety/wakapi/utils/common.go:40.2,40.40 1 1
|
||||
github.com/muety/wakapi/utils/common.go:37.45,39.3 1 1
|
||||
github.com/muety/wakapi/utils/date.go:8.31,10.2 1 0
|
||||
github.com/muety/wakapi/utils/date.go:12.43,14.2 1 0
|
||||
github.com/muety/wakapi/utils/date.go:16.30,20.2 3 0
|
||||
@ -160,14 +259,11 @@ github.com/muety/wakapi/utils/date.go:71.2,71.13 1 0
|
||||
github.com/muety/wakapi/utils/date.go:59.36,62.3 2 0
|
||||
github.com/muety/wakapi/utils/date.go:63.21,66.3 2 0
|
||||
github.com/muety/wakapi/utils/date.go:67.21,70.3 2 0
|
||||
github.com/muety/wakapi/utils/http.go:9.73,12.58 3 0
|
||||
github.com/muety/wakapi/utils/http.go:12.58,14.3 1 0
|
||||
github.com/muety/wakapi/utils/template.go:8.41,10.16 2 0
|
||||
github.com/muety/wakapi/utils/template.go:13.2,13.23 1 0
|
||||
github.com/muety/wakapi/utils/template.go:10.16,12.3 1 0
|
||||
github.com/muety/wakapi/utils/template.go:16.37,17.30 1 0
|
||||
github.com/muety/wakapi/utils/template.go:20.2,20.10 1 0
|
||||
github.com/muety/wakapi/utils/template.go:17.30,19.3 1 0
|
||||
github.com/muety/wakapi/utils/strings.go:8.34,10.2 1 0
|
||||
github.com/muety/wakapi/utils/strings.go:12.77,13.29 1 0
|
||||
github.com/muety/wakapi/utils/strings.go:18.2,18.19 1 0
|
||||
github.com/muety/wakapi/utils/strings.go:13.29,14.18 1 0
|
||||
github.com/muety/wakapi/utils/strings.go:14.18,16.4 1 0
|
||||
github.com/muety/wakapi/utils/auth.go:16.79,18.54 2 0
|
||||
github.com/muety/wakapi/utils/auth.go:22.2,24.16 3 0
|
||||
github.com/muety/wakapi/utils/auth.go:28.2,30.45 3 0
|
||||
@ -195,11 +291,8 @@ github.com/muety/wakapi/utils/filesystem.go:21.15,23.47 2 0
|
||||
github.com/muety/wakapi/utils/filesystem.go:23.47,25.23 2 0
|
||||
github.com/muety/wakapi/utils/filesystem.go:29.4,29.19 1 0
|
||||
github.com/muety/wakapi/utils/filesystem.go:25.23,27.5 1 0
|
||||
github.com/muety/wakapi/utils/strings.go:8.34,10.2 1 0
|
||||
github.com/muety/wakapi/utils/strings.go:12.77,13.29 1 0
|
||||
github.com/muety/wakapi/utils/strings.go:18.2,18.19 1 0
|
||||
github.com/muety/wakapi/utils/strings.go:13.29,14.18 1 0
|
||||
github.com/muety/wakapi/utils/strings.go:14.18,16.4 1 0
|
||||
github.com/muety/wakapi/utils/http.go:9.73,12.58 3 0
|
||||
github.com/muety/wakapi/utils/http.go:12.58,14.3 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:10.66,11.40 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:16.2,16.48 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:11.40,12.27 1 0
|
||||
@ -225,243 +318,186 @@ github.com/muety/wakapi/utils/summary.go:62.35,63.43 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:64.26,65.21 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:66.10,67.39 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:73.73,80.56 5 0
|
||||
github.com/muety/wakapi/utils/summary.go:96.2,103.8 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:102.2,109.8 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:80.56,82.3 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:82.8,82.54 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:82.54,84.3 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:84.8,86.17 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:90.3,91.17 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:86.17,88.4 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:91.17,93.4 1 0
|
||||
github.com/muety/wakapi/config/config.go:103.70,105.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:107.65,109.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:111.82,121.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:123.31,125.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:127.32,129.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:131.74,132.19 1 0
|
||||
github.com/muety/wakapi/config/config.go:133.10,134.34 1 0
|
||||
github.com/muety/wakapi/config/config.go:134.34,143.4 8 0
|
||||
github.com/muety/wakapi/config/config.go:147.73,148.33 1 0
|
||||
github.com/muety/wakapi/config/config.go:148.33,156.17 5 0
|
||||
github.com/muety/wakapi/config/config.go:160.3,161.13 2 0
|
||||
github.com/muety/wakapi/config/config.go:156.17,158.4 1 0
|
||||
github.com/muety/wakapi/config/config.go:165.50,166.19 1 0
|
||||
github.com/muety/wakapi/config/config.go:179.2,179.12 1 0
|
||||
github.com/muety/wakapi/config/config.go:167.23,171.5 1 0
|
||||
github.com/muety/wakapi/config/config.go:172.26,175.5 1 0
|
||||
github.com/muety/wakapi/config/config.go:176.24,177.48 1 0
|
||||
github.com/muety/wakapi/config/config.go:182.53,192.2 1 1
|
||||
github.com/muety/wakapi/config/config.go:194.56,196.16 2 1
|
||||
github.com/muety/wakapi/config/config.go:200.2,207.3 1 1
|
||||
github.com/muety/wakapi/config/config.go:196.16,198.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:210.54,212.2 1 1
|
||||
github.com/muety/wakapi/config/config.go:214.60,216.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:218.59,220.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:222.57,224.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:226.53,228.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:230.29,232.2 1 1
|
||||
github.com/muety/wakapi/config/config.go:234.27,236.16 2 0
|
||||
github.com/muety/wakapi/config/config.go:239.2,242.16 3 0
|
||||
github.com/muety/wakapi/config/config.go:246.2,246.41 1 0
|
||||
github.com/muety/wakapi/config/config.go:236.16,238.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:242.16,244.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:249.48,261.16 3 0
|
||||
github.com/muety/wakapi/config/config.go:264.2,266.16 3 0
|
||||
github.com/muety/wakapi/config/config.go:270.2,270.55 1 0
|
||||
github.com/muety/wakapi/config/config.go:274.2,274.15 1 0
|
||||
github.com/muety/wakapi/config/config.go:261.16,263.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:266.16,268.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:270.55,272.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:277.38,278.43 1 0
|
||||
github.com/muety/wakapi/config/config.go:281.2,281.15 1 0
|
||||
github.com/muety/wakapi/config/config.go:278.43,280.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:284.45,285.27 1 0
|
||||
github.com/muety/wakapi/config/config.go:288.2,288.15 1 0
|
||||
github.com/muety/wakapi/config/config.go:285.27,287.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:291.26,293.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:295.20,297.2 1 0
|
||||
github.com/muety/wakapi/config/config.go:299.21,304.96 3 0
|
||||
github.com/muety/wakapi/config/config.go:308.2,316.52 5 0
|
||||
github.com/muety/wakapi/config/config.go:320.2,320.47 1 0
|
||||
github.com/muety/wakapi/config/config.go:326.2,326.70 1 0
|
||||
github.com/muety/wakapi/config/config.go:330.2,330.28 1 0
|
||||
github.com/muety/wakapi/config/config.go:334.2,335.14 2 0
|
||||
github.com/muety/wakapi/config/config.go:304.96,306.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:316.52,318.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:320.47,321.14 1 0
|
||||
github.com/muety/wakapi/config/config.go:321.14,323.4 1 0
|
||||
github.com/muety/wakapi/config/config.go:326.70,328.3 1 0
|
||||
github.com/muety/wakapi/config/config.go:330.28,332.3 1 0
|
||||
github.com/muety/wakapi/config/utils.go:5.78,7.22 2 0
|
||||
github.com/muety/wakapi/config/utils.go:13.2,13.11 1 0
|
||||
github.com/muety/wakapi/config/utils.go:7.22,8.18 1 0
|
||||
github.com/muety/wakapi/config/utils.go:11.3,11.12 1 0
|
||||
github.com/muety/wakapi/config/utils.go:8.18,10.4 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:20.91,26.2 1 1
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:28.90,31.2 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:33.90,36.2 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:38.71,39.71 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:39.71,41.3 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:44.107,48.16 3 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:52.2,52.31 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:68.2,69.29 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:48.16,50.3 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:52.31,53.31 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:58.3,58.29 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:65.3,65.9 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:53.31,56.4 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:58.29,61.4 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:61.9,64.4 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:72.70,73.39 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:78.2,78.14 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:73.39,74.60 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:74.60,76.4 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:81.92,83.16 2 1
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:87.2,90.16 4 1
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:93.2,93.18 1 1
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:83.16,85.3 1 1
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:90.16,92.3 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:96.92,98.16 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:102.2,103.16 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:110.2,110.18 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:98.16,100.3 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:103.16,105.3 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:93.3,94.17 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:86.17,88.18 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:88.18,90.5 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:94.17,96.18 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:96.18,98.5 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:112.48,116.51 2 0
|
||||
github.com/muety/wakapi/utils/summary.go:119.2,119.12 1 0
|
||||
github.com/muety/wakapi/utils/summary.go:116.51,118.3 1 0
|
||||
github.com/muety/wakapi/utils/template.go:8.41,10.16 2 0
|
||||
github.com/muety/wakapi/utils/template.go:13.2,13.23 1 0
|
||||
github.com/muety/wakapi/utils/template.go:10.16,12.3 1 0
|
||||
github.com/muety/wakapi/utils/template.go:16.37,17.30 1 0
|
||||
github.com/muety/wakapi/utils/template.go:20.2,20.10 1 0
|
||||
github.com/muety/wakapi/utils/template.go:17.30,19.3 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:19.91,25.2 1 1
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:27.90,30.2 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:32.90,35.2 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:37.71,38.71 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:38.71,40.3 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:43.107,47.16 3 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:51.2,51.31 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:67.2,68.12 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:47.16,49.3 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:51.31,52.31 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:57.3,57.29 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:64.3,64.9 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:52.31,55.4 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:57.29,60.4 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:60.9,63.4 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:71.70,72.39 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:77.2,77.14 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:72.39,73.60 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:73.60,75.4 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:80.92,82.16 2 1
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:86.2,89.16 4 1
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:92.2,92.18 1 1
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:82.16,84.3 1 1
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:89.16,91.3 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:95.92,97.16 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:101.2,102.16 2 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:109.2,109.18 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:97.16,99.3 1 0
|
||||
github.com/muety/wakapi/middlewares/authenticate.go:102.16,104.3 1 0
|
||||
github.com/muety/wakapi/middlewares/filetype.go:13.83,14.43 1 0
|
||||
github.com/muety/wakapi/middlewares/filetype.go:14.43,19.3 1 0
|
||||
github.com/muety/wakapi/middlewares/filetype.go:22.84,24.34 2 0
|
||||
github.com/muety/wakapi/middlewares/filetype.go:31.2,31.27 1 0
|
||||
github.com/muety/wakapi/middlewares/filetype.go:24.34,25.50 1 0
|
||||
github.com/muety/wakapi/middlewares/filetype.go:25.50,29.4 3 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:19.105,20.43 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:20.43,26.3 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:29.80,38.44 7 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:44.2,53.3 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:38.44,39.38 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:39.38,41.4 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:56.41,58.14 2 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:61.2,61.14 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:64.2,64.11 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:58.14,60.3 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:61.14,63.3 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:95.52,97.2 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:109.45,110.20 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:110.20,114.3 3 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:116.54,119.18 3 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:126.2,127.15 2 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:119.18,122.17 2 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:122.17,124.4 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:129.42,130.20 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:130.20,132.3 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:134.36,136.2 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:137.42,139.2 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:140.40,142.2 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:143.52,145.2 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:20.102,21.43 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:21.43,27.3 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:30.80,39.44 7 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:45.2,54.3 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:39.44,40.38 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:40.38,42.4 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:57.41,59.14 2 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:62.2,62.14 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:65.2,65.11 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:59.14,61.3 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:62.14,64.3 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:68.41,69.42 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:72.2,72.12 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:69.42,71.3 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:103.52,105.2 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:117.45,118.20 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:118.20,122.3 3 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:124.54,127.18 3 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:134.2,135.15 2 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:127.18,130.17 2 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:130.17,132.4 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:137.42,138.20 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:138.20,140.3 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:142.36,144.2 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:145.42,147.2 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:148.40,150.2 1 0
|
||||
github.com/muety/wakapi/middlewares/logging.go:151.52,153.2 1 0
|
||||
github.com/muety/wakapi/middlewares/principal.go:15.62,17.2 1 0
|
||||
github.com/muety/wakapi/middlewares/principal.go:19.58,21.2 1 0
|
||||
github.com/muety/wakapi/middlewares/principal.go:42.71,43.43 1 0
|
||||
github.com/muety/wakapi/middlewares/principal.go:43.43,45.3 1 0
|
||||
github.com/muety/wakapi/middlewares/principal.go:48.81,51.2 2 0
|
||||
github.com/muety/wakapi/middlewares/principal.go:53.55,54.52 1 0
|
||||
github.com/muety/wakapi/middlewares/principal.go:54.52,56.3 1 0
|
||||
github.com/muety/wakapi/middlewares/principal.go:59.49,60.52 1 0
|
||||
github.com/muety/wakapi/middlewares/principal.go:63.2,63.12 1 0
|
||||
github.com/muety/wakapi/middlewares/principal.go:60.52,62.3 1 0
|
||||
github.com/muety/wakapi/middlewares/sentry.go:14.60,15.43 1 0
|
||||
github.com/muety/wakapi/middlewares/sentry.go:15.43,19.3 1 0
|
||||
github.com/muety/wakapi/middlewares/sentry.go:22.78,25.54 3 0
|
||||
github.com/muety/wakapi/middlewares/sentry.go:25.54,26.43 1 0
|
||||
github.com/muety/wakapi/middlewares/sentry.go:26.43,28.4 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:24.142,31.2 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:40.43,44.2 3 0
|
||||
github.com/muety/wakapi/services/aggregation.go:46.67,50.40 3 0
|
||||
github.com/muety/wakapi/services/aggregation.go:54.2,54.50 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:59.2,59.60 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:65.2,65.35 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:50.40,52.3 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:54.50,56.3 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:59.60,63.3 3 0
|
||||
github.com/muety/wakapi/services/aggregation.go:68.109,69.24 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:69.24,70.111 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:70.111,72.4 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:72.9,75.4 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:79.80,80.33 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:80.33,81.60 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:81.60,83.4 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:87.100,91.59 3 0
|
||||
github.com/muety/wakapi/services/aggregation.go:106.2,107.16 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:113.2,114.16 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:120.2,121.44 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:126.2,126.41 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:140.2,140.12 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:91.59,94.3 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:94.8,94.47 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:94.47,96.30 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:96.30,97.43 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:97.43,99.5 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:101.8,103.3 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:107.16,110.3 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:114.16,117.3 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:121.44,123.3 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:126.41,127.21 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:127.21,131.4 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:131.9,131.62 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:131.62,135.4 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:143.83,158.41 5 0
|
||||
github.com/muety/wakapi/services/aggregation.go:158.41,168.3 3 0
|
||||
github.com/muety/wakapi/services/aggregation.go:171.34,174.2 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:17.141,23.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:25.72,27.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:29.80,31.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:33.53,35.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:37.76,39.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:41.96,43.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:45.111,47.16 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:50.2,50.43 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:47.16,49.3 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:53.116,55.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:57.78,59.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:61.62,63.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:65.116,67.16 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:71.2,71.28 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:75.2,75.24 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:67.16,69.3 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:71.28,73.3 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:14.89,19.2 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:21.83,23.2 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:25.78,27.16 2 0
|
||||
github.com/muety/wakapi/services/key_value.go:33.2,33.11 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:27.16,32.3 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:36.72,38.2 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:40.60,42.2 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:24.142,31.2 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:40.43,42.37 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:46.2,48.19 3 0
|
||||
github.com/muety/wakapi/services/aggregation.go:42.37,44.3 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:51.67,55.40 3 0
|
||||
github.com/muety/wakapi/services/aggregation.go:59.2,59.50 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:64.2,64.60 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:70.2,70.35 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:55.40,57.3 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:59.50,61.3 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:64.60,68.3 3 0
|
||||
github.com/muety/wakapi/services/aggregation.go:73.109,74.24 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:74.24,75.111 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:75.111,77.4 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:77.9,80.4 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:84.80,85.33 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:85.33,86.60 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:86.60,88.4 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:92.100,96.59 3 0
|
||||
github.com/muety/wakapi/services/aggregation.go:111.2,112.16 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:118.2,119.16 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:125.2,126.44 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:131.2,131.41 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:145.2,145.12 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:96.59,99.3 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:99.8,99.47 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:99.47,101.30 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:101.30,102.43 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:102.43,104.5 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:106.8,108.3 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:112.16,115.3 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:119.16,122.3 2 0
|
||||
github.com/muety/wakapi/services/aggregation.go:126.44,128.3 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:131.41,132.21 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:132.21,136.4 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:136.9,136.62 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:136.62,140.4 1 0
|
||||
github.com/muety/wakapi/services/aggregation.go:148.83,163.41 5 0
|
||||
github.com/muety/wakapi/services/aggregation.go:163.41,173.3 3 0
|
||||
github.com/muety/wakapi/services/aggregation.go:176.34,179.2 2 0
|
||||
github.com/muety/wakapi/services/alias.go:17.77,22.2 1 1
|
||||
github.com/muety/wakapi/services/alias.go:26.60,27.43 1 1
|
||||
github.com/muety/wakapi/services/alias.go:30.2,30.14 1 1
|
||||
github.com/muety/wakapi/services/alias.go:27.43,29.3 1 1
|
||||
github.com/muety/wakapi/services/alias.go:33.62,35.16 2 1
|
||||
github.com/muety/wakapi/services/alias.go:38.2,38.12 1 1
|
||||
github.com/muety/wakapi/services/alias.go:35.16,37.3 1 1
|
||||
github.com/muety/wakapi/services/alias.go:41.76,43.16 2 0
|
||||
github.com/muety/wakapi/services/alias.go:46.2,46.21 1 0
|
||||
github.com/muety/wakapi/services/alias.go:43.16,45.3 1 0
|
||||
github.com/muety/wakapi/services/alias.go:49.113,51.16 2 0
|
||||
github.com/muety/wakapi/services/alias.go:54.2,54.21 1 0
|
||||
github.com/muety/wakapi/services/alias.go:51.16,53.3 1 0
|
||||
github.com/muety/wakapi/services/alias.go:57.108,58.32 1 1
|
||||
github.com/muety/wakapi/services/alias.go:64.2,65.46 2 1
|
||||
github.com/muety/wakapi/services/alias.go:70.2,70.19 1 1
|
||||
github.com/muety/wakapi/services/alias.go:58.32,59.52 1 1
|
||||
github.com/muety/wakapi/services/alias.go:59.52,61.4 1 1
|
||||
github.com/muety/wakapi/services/alias.go:65.46,66.48 1 1
|
||||
github.com/muety/wakapi/services/alias.go:66.48,68.4 1 1
|
||||
github.com/muety/wakapi/services/alias.go:73.77,75.16 2 0
|
||||
github.com/muety/wakapi/services/alias.go:78.2,79.20 2 0
|
||||
github.com/muety/wakapi/services/alias.go:75.16,77.3 1 0
|
||||
github.com/muety/wakapi/services/alias.go:82.60,83.24 1 0
|
||||
github.com/muety/wakapi/services/alias.go:86.2,88.12 3 0
|
||||
github.com/muety/wakapi/services/alias.go:83.24,85.3 1 0
|
||||
github.com/muety/wakapi/services/alias.go:91.69,94.28 3 0
|
||||
github.com/muety/wakapi/services/alias.go:102.2,104.31 2 0
|
||||
github.com/muety/wakapi/services/alias.go:108.2,108.12 1 0
|
||||
github.com/muety/wakapi/services/alias.go:94.28,95.21 1 0
|
||||
github.com/muety/wakapi/services/alias.go:98.3,99.16 2 0
|
||||
github.com/muety/wakapi/services/alias.go:95.21,97.4 1 0
|
||||
github.com/muety/wakapi/services/alias.go:104.31,106.3 1 0
|
||||
github.com/muety/wakapi/services/alias.go:111.52,112.51 1 0
|
||||
github.com/muety/wakapi/services/alias.go:112.51,114.3 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:29.80,34.32 3 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:41.2,41.55 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:34.32,35.36 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:35.36,38.4 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:44.53,46.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:48.76,50.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:52.96,54.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:56.111,58.16 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:61.2,61.43 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:58.16,60.3 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:64.116,66.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:68.78,70.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:72.62,74.2 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:76.116,78.16 2 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:82.2,82.28 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:86.2,86.24 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:78.16,80.3 1 0
|
||||
github.com/muety/wakapi/services/heartbeat.go:82.28,84.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:18.118,24.2 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:26.86,28.2 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:30.96,31.53 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:35.2,36.16 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:39.2,40.22 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:31.53,33.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:36.16,38.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:43.92,46.16 3 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:50.2,50.33 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:53.2,53.22 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:46.16,48.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:50.33,52.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:56.109,58.16 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:62.2,63.20 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:58.16,60.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:66.82,67.26 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:70.2,72.12 3 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:67.26,69.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:75.74,78.2 1 0
|
||||
github.com/muety/wakapi/services/summary.go:27.149,35.2 1 1
|
||||
github.com/muety/wakapi/services/summary.go:39.120,42.52 2 1
|
||||
github.com/muety/wakapi/services/summary.go:39.136,42.66 2 1
|
||||
github.com/muety/wakapi/services/summary.go:47.2,47.44 1 1
|
||||
github.com/muety/wakapi/services/summary.go:53.2,53.65 1 1
|
||||
github.com/muety/wakapi/services/summary.go:58.2,59.16 2 1
|
||||
github.com/muety/wakapi/services/summary.go:64.2,66.30 3 1
|
||||
github.com/muety/wakapi/services/summary.go:42.52,44.3 1 0
|
||||
github.com/muety/wakapi/services/summary.go:42.66,44.3 1 0
|
||||
github.com/muety/wakapi/services/summary.go:47.44,50.3 2 1
|
||||
github.com/muety/wakapi/services/summary.go:53.65,55.3 1 0
|
||||
github.com/muety/wakapi/services/summary.go:59.16,61.3 1 0
|
||||
@ -532,18 +568,20 @@ github.com/muety/wakapi/services/summary.go:288.43,290.3 1 1
|
||||
github.com/muety/wakapi/services/summary.go:295.116,296.25 1 1
|
||||
github.com/muety/wakapi/services/summary.go:300.2,303.44 2 1
|
||||
github.com/muety/wakapi/services/summary.go:308.2,308.40 1 1
|
||||
github.com/muety/wakapi/services/summary.go:324.2,324.54 1 1
|
||||
github.com/muety/wakapi/services/summary.go:328.2,328.18 1 1
|
||||
github.com/muety/wakapi/services/summary.go:333.2,333.54 1 1
|
||||
github.com/muety/wakapi/services/summary.go:337.2,337.18 1 1
|
||||
github.com/muety/wakapi/services/summary.go:296.25,298.3 1 0
|
||||
github.com/muety/wakapi/services/summary.go:303.44,305.3 1 1
|
||||
github.com/muety/wakapi/services/summary.go:308.40,310.19 2 1
|
||||
github.com/muety/wakapi/services/summary.go:315.3,318.22 3 0
|
||||
github.com/muety/wakapi/services/summary.go:317.3,322.34 3 1
|
||||
github.com/muety/wakapi/services/summary.go:327.3,327.22 1 1
|
||||
github.com/muety/wakapi/services/summary.go:310.19,311.12 1 1
|
||||
github.com/muety/wakapi/services/summary.go:318.22,320.4 1 0
|
||||
github.com/muety/wakapi/services/summary.go:324.54,326.3 1 1
|
||||
github.com/muety/wakapi/services/summary.go:331.59,333.25 2 1
|
||||
github.com/muety/wakapi/services/summary.go:336.2,336.32 1 1
|
||||
github.com/muety/wakapi/services/summary.go:333.25,335.3 1 1
|
||||
github.com/muety/wakapi/services/summary.go:322.34,324.4 1 1
|
||||
github.com/muety/wakapi/services/summary.go:327.22,329.4 1 1
|
||||
github.com/muety/wakapi/services/summary.go:333.54,335.3 1 1
|
||||
github.com/muety/wakapi/services/summary.go:340.59,342.25 2 1
|
||||
github.com/muety/wakapi/services/summary.go:345.2,345.32 1 1
|
||||
github.com/muety/wakapi/services/summary.go:342.25,344.3 1 1
|
||||
github.com/muety/wakapi/services/user.go:19.73,25.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:27.74,28.40 1 0
|
||||
github.com/muety/wakapi/services/user.go:32.2,33.16 2 0
|
||||
@ -555,41 +593,67 @@ github.com/muety/wakapi/services/user.go:46.2,47.16 2 0
|
||||
github.com/muety/wakapi/services/user.go:51.2,52.15 2 0
|
||||
github.com/muety/wakapi/services/user.go:42.37,44.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:47.16,49.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:55.58,57.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:59.61,62.2 2 0
|
||||
github.com/muety/wakapi/services/user.go:64.48,66.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:68.102,76.93 2 0
|
||||
github.com/muety/wakapi/services/user.go:82.2,82.38 1 0
|
||||
github.com/muety/wakapi/services/user.go:76.93,78.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:78.8,80.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:85.73,88.2 2 0
|
||||
github.com/muety/wakapi/services/user.go:90.78,94.2 3 0
|
||||
github.com/muety/wakapi/services/user.go:96.99,99.2 2 0
|
||||
github.com/muety/wakapi/services/user.go:101.106,104.96 3 0
|
||||
github.com/muety/wakapi/services/user.go:109.2,109.68 1 0
|
||||
github.com/muety/wakapi/services/user.go:104.96,106.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:106.8,108.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:112.57,115.2 2 0
|
||||
github.com/muety/wakapi/services/user.go:117.38,119.2 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:18.118,24.2 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:26.86,28.2 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:30.96,31.53 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:35.2,36.16 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:39.2,40.22 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:31.53,33.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:36.16,38.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:43.92,46.16 3 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:50.2,50.33 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:53.2,53.22 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:46.16,48.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:50.33,52.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:56.109,58.16 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:62.2,63.20 2 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:58.16,60.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:66.82,67.26 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:70.2,72.12 3 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:67.26,69.3 1 0
|
||||
github.com/muety/wakapi/services/language_mapping.go:75.74,78.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:55.76,57.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:59.86,61.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:63.58,65.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:67.61,70.2 2 0
|
||||
github.com/muety/wakapi/services/user.go:72.48,74.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:76.102,85.93 2 0
|
||||
github.com/muety/wakapi/services/user.go:91.2,91.38 1 0
|
||||
github.com/muety/wakapi/services/user.go:85.93,87.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:87.8,89.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:94.73,97.2 2 0
|
||||
github.com/muety/wakapi/services/user.go:99.78,103.2 3 0
|
||||
github.com/muety/wakapi/services/user.go:105.99,108.2 2 0
|
||||
github.com/muety/wakapi/services/user.go:110.106,113.96 3 0
|
||||
github.com/muety/wakapi/services/user.go:118.2,118.68 1 0
|
||||
github.com/muety/wakapi/services/user.go:113.96,115.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:115.8,117.3 1 0
|
||||
github.com/muety/wakapi/services/user.go:121.85,123.2 1 0
|
||||
github.com/muety/wakapi/services/user.go:125.57,128.2 2 0
|
||||
github.com/muety/wakapi/services/user.go:130.38,132.2 1 0
|
||||
github.com/muety/wakapi/services/alias.go:17.77,22.2 1 1
|
||||
github.com/muety/wakapi/services/alias.go:26.60,27.43 1 1
|
||||
github.com/muety/wakapi/services/alias.go:30.2,30.14 1 1
|
||||
github.com/muety/wakapi/services/alias.go:27.43,29.3 1 1
|
||||
github.com/muety/wakapi/services/alias.go:33.62,35.16 2 1
|
||||
github.com/muety/wakapi/services/alias.go:38.2,38.12 1 1
|
||||
github.com/muety/wakapi/services/alias.go:35.16,37.3 1 1
|
||||
github.com/muety/wakapi/services/alias.go:41.76,43.16 2 0
|
||||
github.com/muety/wakapi/services/alias.go:46.2,46.21 1 0
|
||||
github.com/muety/wakapi/services/alias.go:43.16,45.3 1 0
|
||||
github.com/muety/wakapi/services/alias.go:49.113,51.16 2 0
|
||||
github.com/muety/wakapi/services/alias.go:54.2,54.21 1 0
|
||||
github.com/muety/wakapi/services/alias.go:51.16,53.3 1 0
|
||||
github.com/muety/wakapi/services/alias.go:57.108,58.32 1 1
|
||||
github.com/muety/wakapi/services/alias.go:64.2,65.46 2 1
|
||||
github.com/muety/wakapi/services/alias.go:70.2,70.19 1 1
|
||||
github.com/muety/wakapi/services/alias.go:58.32,59.52 1 1
|
||||
github.com/muety/wakapi/services/alias.go:59.52,61.4 1 1
|
||||
github.com/muety/wakapi/services/alias.go:65.46,66.48 1 1
|
||||
github.com/muety/wakapi/services/alias.go:66.48,68.4 1 1
|
||||
github.com/muety/wakapi/services/alias.go:73.77,75.16 2 0
|
||||
github.com/muety/wakapi/services/alias.go:78.2,79.20 2 0
|
||||
github.com/muety/wakapi/services/alias.go:75.16,77.3 1 0
|
||||
github.com/muety/wakapi/services/alias.go:82.60,83.24 1 0
|
||||
github.com/muety/wakapi/services/alias.go:86.2,88.12 3 0
|
||||
github.com/muety/wakapi/services/alias.go:83.24,85.3 1 0
|
||||
github.com/muety/wakapi/services/alias.go:91.69,94.28 3 0
|
||||
github.com/muety/wakapi/services/alias.go:102.2,104.31 2 0
|
||||
github.com/muety/wakapi/services/alias.go:108.2,108.12 1 0
|
||||
github.com/muety/wakapi/services/alias.go:94.28,95.21 1 0
|
||||
github.com/muety/wakapi/services/alias.go:98.3,99.16 2 0
|
||||
github.com/muety/wakapi/services/alias.go:95.21,97.4 1 0
|
||||
github.com/muety/wakapi/services/alias.go:104.31,106.3 1 0
|
||||
github.com/muety/wakapi/services/alias.go:111.52,112.51 1 0
|
||||
github.com/muety/wakapi/services/alias.go:112.51,114.3 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:14.89,19.2 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:21.83,23.2 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:25.78,27.16 2 0
|
||||
github.com/muety/wakapi/services/key_value.go:33.2,33.11 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:27.16,32.3 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:36.72,38.2 1 0
|
||||
github.com/muety/wakapi/services/key_value.go:40.60,42.2 1 0
|
||||
github.com/muety/wakapi/services/misc.go:23.126,30.2 1 0
|
||||
github.com/muety/wakapi/services/misc.go:42.50,44.48 1 0
|
||||
github.com/muety/wakapi/services/misc.go:48.2,50.19 3 0
|
||||
@ -602,9 +666,9 @@ github.com/muety/wakapi/services/misc.go:66.56,67.27 1 0
|
||||
github.com/muety/wakapi/services/misc.go:67.27,72.4 1 0
|
||||
github.com/muety/wakapi/services/misc.go:73.8,75.3 1 0
|
||||
github.com/muety/wakapi/services/misc.go:80.116,81.24 1 0
|
||||
github.com/muety/wakapi/services/misc.go:81.24,82.144 1 0
|
||||
github.com/muety/wakapi/services/misc.go:81.24,82.151 1 0
|
||||
github.com/muety/wakapi/services/misc.go:91.3,91.48 1 0
|
||||
github.com/muety/wakapi/services/misc.go:82.144,84.4 1 0
|
||||
github.com/muety/wakapi/services/misc.go:82.151,84.4 1 0
|
||||
github.com/muety/wakapi/services/misc.go:84.9,90.4 2 0
|
||||
github.com/muety/wakapi/services/misc.go:91.48,94.4 2 0
|
||||
github.com/muety/wakapi/services/misc.go:98.86,101.30 3 0
|
||||
|
@ -210,6 +210,7 @@
|
||||
"Stan": "#b2011d",
|
||||
"Standard ML": "#dc566d",
|
||||
"SuperCollider": "#46390b",
|
||||
"Svelte": "#ff3e00",
|
||||
"Swift": "#ffac45",
|
||||
"SystemVerilog": "#DAE1C2",
|
||||
"Tcl": "#e4cc98",
|
||||
|
6
data/data.go
Normal file
6
data/data.go
Normal file
@ -0,0 +1,6 @@
|
||||
package data
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed colors.json
|
||||
var ColorsFile []byte
|
@ -7,10 +7,11 @@ services:
|
||||
- 3000:3000
|
||||
restart: always
|
||||
environment:
|
||||
# See README.md and config.default.yml for all config options
|
||||
WAKAPI_DB_TYPE: "postgres"
|
||||
WAKAPI_DB_NAME: "wakapi"
|
||||
WAKAPI_DB_USER: "wakapi"
|
||||
WAKAPI_DB_PASSWORD: "CHANGE_ME!!!"
|
||||
WAKAPI_DB_PASSWORD: "choose-a-password"
|
||||
WAKAPI_DB_HOST: "db"
|
||||
WAKAPI_DB_PORT: "5432"
|
||||
ENVIRONMENT: "prod"
|
||||
@ -19,5 +20,5 @@ services:
|
||||
image: postgres:12.3
|
||||
environment:
|
||||
POSTGRES_USER: "wakapi"
|
||||
POSTGRES_PASSWORD: "CHANGE_ME!!!"
|
||||
POSTGRES_PASSWORD: "choose-a-password"
|
||||
POSTGRES_DB: "wakapi"
|
||||
|
@ -1,47 +0,0 @@
|
||||
# Advanced Setup
|
||||
This page contains instructions for additional setup options, none of which are mandatory.
|
||||
|
||||
## Optional: Client-side proxy
|
||||
Most Wakatime plugins work in a way that, for every heartbeat to send, the plugin calls your local [wakatime-cli](https://github.com/wakatime/wakatime) (a small Python program that is automatically installed when installing a Wakatime plugin) with a few command-line arguments, which is then run as a new process. Inside that process, a heartbeat request is forged and sent to the backend API – Wakapi in this case.
|
||||
|
||||
While this is convenient for plugin developers, as they do not have to deal with sending HTTP requests, etc., it comes with a minor drawback. Because the CLI process shuts down after each request, its TCP connection is closed as well. Accordingly, **TCP connections cannot be re-used** and every single heartbeat request is inevitably preceded by the `SYN` + `SYN ACK` + `ACK` sequence for establishing a new TCP connection as well as a handshake for establishing a new TLS session.
|
||||
|
||||
While this certainly does not hurt, it is still a bit of overhead. You can avoid that by setting up a local reverse proxy on your machine, that keeps running as a daemon and can therefore keep a continuous connection.
|
||||
|
||||
### Option 1: [tinyproxy](https://tinyproxy.github.io) forward proxy (`Linux`, `Mac` only)
|
||||
In this example we use _tinyproxy_ as a small, easy-to-install proxy server, written in C, that runs on your local machine.
|
||||
1. Install [tinyproxy](https://tinyproxy.github.io)
|
||||
* Fedora / RHEL: `dnf install tinyproxy`
|
||||
* Debian / Ubuntu: `apt install tinyproxy`
|
||||
* MacOS: Install from [MacPorts](https://ports.macports.org/port/tinyproxy/summary)
|
||||
1. Enable and start it
|
||||
* Linux: `sudo systemctl start tinyproxy && sudo systemctl enable tinyproxy`
|
||||
* Mac: Not sure, sorry ¯\_(ツ)_/¯
|
||||
1. Update `~/.wakatime.cfg`
|
||||
* Set `proxy = http://localhost:8888`
|
||||
1. Done
|
||||
* All Wakapi requests are passed through tinyproxy now, which keeps a TCP connection with the server open for some time
|
||||
|
||||
### Option 2: [Caddy](https://caddyserver.com) reverse proxy (`Win`, `Linux`, `Mac`)
|
||||
In this example, we misuse Caddy, which is a web server and reverse proxy, to fulfil the above scenario.
|
||||
|
||||
1. [Install Caddy](https://caddyserver.com/)
|
||||
* When installing manually, don't forget to set up a systemd service to start Caddy on system startup
|
||||
1. Create a Caddyfile
|
||||
```
|
||||
# /etc/caddy/Caddyfile
|
||||
|
||||
http://localhost:8070 {
|
||||
reverse_proxy * {
|
||||
to https://wakapi.dev # <-- substitute your own Wakapi host here
|
||||
header_up Host {http.reverse_proxy.upstream.host}
|
||||
header_down -Server
|
||||
}
|
||||
}
|
||||
```
|
||||
1. Restart Caddy
|
||||
1. Verify that you can access [`http://localhost:8070/api/health`](http://localhost:8070/api/health)
|
||||
1. Update `~/.wakatime.cfg`
|
||||
* Set `api_url = http://localhost:8070/api/heartbeat`
|
||||
1. Done
|
||||
* All Wakapi requests are passed through Caddy now, which keeps a TCP connection with the server open for some time
|
7
go.mod
7
go.mod
@ -1,10 +1,13 @@
|
||||
module github.com/muety/wakapi
|
||||
|
||||
go 1.13
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
||||
github.com/emersion/go-smtp v0.15.0
|
||||
github.com/emvi/logbuch v1.1.1
|
||||
github.com/getsentry/sentry-go v0.10.0
|
||||
github.com/go-co-op/gocron v0.3.3
|
||||
github.com/go-openapi/spec v0.20.2 // indirect
|
||||
github.com/gorilla/handlers v1.4.2
|
||||
@ -13,11 +16,9 @@ require (
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/jinzhu/configor v1.2.0
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/markbates/pkger v0.17.1
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.1
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/rubenv/sql-migrate v0.0.0-20200402132117-435005d389bc
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/swaggo/swag v1.7.0
|
||||
|
417
go.sum
417
go.sum
@ -1,189 +1,113 @@
|
||||
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=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
||||
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
|
||||
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
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.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.15.0 h1:3+hMGMGrqP/lqd7qoxZc1hTU8LY8gHV9RFGWlqSDmP8=
|
||||
github.com/emersion/go-smtp v0.15.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/emvi/logbuch v1.1.1 h1:poBGNbHy/nB95oNoqLKAaJoBrcKxTO0W9DhMijKEkkU=
|
||||
github.com/emvi/logbuch v1.1.1/go.mod h1:J2Wgbr3BuSc1JO+D2MBVh6q3WPVSK5GzktwWz8pvkKw=
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
|
||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||
github.com/getsentry/sentry-go v0.10.0 h1:6gwY+66NHKqyZrdi6O2jGdo7wGdo9b3B69E01NFgT5g=
|
||||
github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/go-co-op/gocron v0.3.3 h1:QnarcMZWWKrEP25uCbtDiLsnnGw+PhCjL3wNITdWJOs=
|
||||
github.com/go-co-op/gocron v0.3.3/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
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.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg=
|
||||
github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
|
||||
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
|
||||
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
|
||||
github.com/go-openapi/spec v0.19.14 h1:r4fbYFo6N4ZelmSX8G6p+cv/hZRXzcuqQIADGT1iNKM=
|
||||
github.com/go-openapi/spec v0.19.14/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeEip0M63doA=
|
||||
github.com/go-openapi/spec v0.20.2 h1:pFPUZsiIbZ20kLUcuCGeuQWG735fPMxW7wHF9BWlnQU=
|
||||
github.com/go-openapi/spec v0.20.2/go.mod h1:RW6Xcbs6LOyWLU/mXGdzn2Qc+3aj+ASfI7rvSZh1Vls=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.11 h1:RFTu/dlFySpyVvJDfp/7674JY4SDglYWKztbiIGFpmc=
|
||||
github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY=
|
||||
github.com/go-openapi/swag v0.19.13 h1:233UVgMy1DlmCYYfOiFpta6e2urloh+sEs5id6lyzog=
|
||||
github.com/go-openapi/swag v0.19.13/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
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/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8=
|
||||
github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
|
||||
github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI=
|
||||
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
|
||||
github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg=
|
||||
github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
|
||||
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
||||
github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o=
|
||||
github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
|
||||
github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
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/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
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/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
|
||||
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
|
||||
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
||||
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/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
||||
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
|
||||
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
|
||||
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
|
||||
github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
|
||||
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
|
||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
@ -242,189 +166,110 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
|
||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
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.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
|
||||
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
|
||||
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
|
||||
github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
|
||||
github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
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 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
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/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno=
|
||||
github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
|
||||
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-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.1 h1:L60q1+q7cXE4JeEJJKMnh2brFIe3rZxCihYAB61ypAY=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.1/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cSdjkOY=
|
||||
github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/rubenv/sql-migrate v0.0.0-20200402132117-435005d389bc h1:+2DdDcxVYlarHjYcZTt8dZ4Ec8cXZirzL5ko0mkKPjU=
|
||||
github.com/rubenv/sql-migrate v0.0.0-20200402132117-435005d389bc/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY=
|
||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
@ -437,155 +282,108 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E=
|
||||
github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
||||
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
|
||||
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/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-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/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/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-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190125091013-d26f9f9a57f3/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-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/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/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
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/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 h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/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 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/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-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/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-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e h1:t96dS3DO8DGjawSLJL/HIdz8CycAd2v07XxqB3UPTi0=
|
||||
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
@ -593,55 +391,29 @@ golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.5/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-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
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/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw=
|
||||
gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/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 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@ -655,9 +427,4 @@ gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.20.11 h1:jYHQ0LLUViV85V8dM1TP9VBBkfzKTnuTXDjYObkI6yc=
|
||||
gorm.io/gorm v1.20.11/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/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=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
|
69
main.go
69
main.go
@ -1,23 +1,24 @@
|
||||
package main
|
||||
|
||||
//go:generate $GOPATH/bin/pkger
|
||||
|
||||
import (
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/markbates/pkger"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/migrations"
|
||||
"github.com/muety/wakapi/repositories"
|
||||
"github.com/muety/wakapi/routes/api"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"gorm.io/gorm/logger"
|
||||
"embed"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/gorilla/handlers"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/migrations"
|
||||
"github.com/muety/wakapi/repositories"
|
||||
"github.com/muety/wakapi/routes/api"
|
||||
"github.com/muety/wakapi/services/mail"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"gorm.io/gorm/logger"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/muety/wakapi/middlewares"
|
||||
"github.com/muety/wakapi/routes"
|
||||
@ -30,6 +31,14 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Embed version.txt
|
||||
//go:embed version.txt
|
||||
var version string
|
||||
|
||||
// Embed static files
|
||||
//go:embed static
|
||||
var staticFiles embed.FS
|
||||
|
||||
var (
|
||||
db *gorm.DB
|
||||
config *conf.Config
|
||||
@ -51,6 +60,7 @@ var (
|
||||
languageMappingService services.ILanguageMappingService
|
||||
summaryService services.ISummaryService
|
||||
aggregationService services.IAggregationService
|
||||
mailService services.IMailService
|
||||
keyValueService services.IKeyValueService
|
||||
miscService services.IMiscService
|
||||
)
|
||||
@ -78,7 +88,7 @@ var (
|
||||
|
||||
// @BasePath /api
|
||||
func main() {
|
||||
config = conf.Load()
|
||||
config = conf.Load(version)
|
||||
|
||||
// Set log level
|
||||
if config.IsDev() {
|
||||
@ -117,9 +127,7 @@ func main() {
|
||||
defer sqlDb.Close()
|
||||
|
||||
// Migrate database schema
|
||||
migrations.RunPreMigrations(db, config)
|
||||
runDatabaseMigrations()
|
||||
migrations.RunCustomPostMigrations(db, config)
|
||||
migrations.Run(db, config)
|
||||
|
||||
// Repositories
|
||||
aliasRepository = repositories.NewAliasRepository(db)
|
||||
@ -136,6 +144,7 @@ func main() {
|
||||
heartbeatService = services.NewHeartbeatService(heartbeatRepository, languageMappingService)
|
||||
summaryService = services.NewSummaryService(summaryRepository, heartbeatService, aliasService)
|
||||
aggregationService = services.NewAggregationService(userService, summaryService, heartbeatService)
|
||||
mailService = mail.NewMailService()
|
||||
keyValueService = services.NewKeyValueService(keyValueRepository)
|
||||
miscService = services.NewMiscService(userService, summaryService, keyValueService)
|
||||
|
||||
@ -159,25 +168,23 @@ func main() {
|
||||
|
||||
// MVC Handlers
|
||||
summaryHandler := routes.NewSummaryHandler(summaryService, userService)
|
||||
settingsHandler := routes.NewSettingsHandler(userService, heartbeatService, summaryService, aliasService, aggregationService, languageMappingService, keyValueService)
|
||||
settingsHandler := routes.NewSettingsHandler(userService, heartbeatService, summaryService, aliasService, aggregationService, languageMappingService, keyValueService, mailService)
|
||||
homeHandler := routes.NewHomeHandler(keyValueService)
|
||||
loginHandler := routes.NewLoginHandler(userService)
|
||||
loginHandler := routes.NewLoginHandler(userService, mailService)
|
||||
imprintHandler := routes.NewImprintHandler(keyValueService)
|
||||
|
||||
// Setup Routers
|
||||
router := mux.NewRouter()
|
||||
rootRouter := router.PathPrefix("/").Subrouter()
|
||||
apiRouter := router.PathPrefix("/api").Subrouter()
|
||||
apiRouter := router.PathPrefix("/api").Subrouter().StrictSlash(true)
|
||||
|
||||
// Globally used middlewares
|
||||
recoveryMiddleware := handlers.RecoveryHandler()
|
||||
loggingMiddleware := middlewares.NewLoggingMiddleware(
|
||||
log.New(os.Stdout, "", log.LstdFlags),
|
||||
[]string{"/assets"},
|
||||
)
|
||||
|
||||
// Router configs
|
||||
router.Use(loggingMiddleware, recoveryMiddleware)
|
||||
router.Use(middlewares.NewPrincipalMiddleware())
|
||||
router.Use(middlewares.NewLoggingMiddleware(logbuch.Info, []string{"/assets"}))
|
||||
router.Use(handlers.RecoveryHandler())
|
||||
if config.Sentry.Dsn != "" {
|
||||
router.Use(middlewares.NewSentryMiddleware())
|
||||
}
|
||||
|
||||
// Route registrations
|
||||
homeHandler.RegisterRoutes(rootRouter)
|
||||
@ -197,7 +204,9 @@ func main() {
|
||||
shieldV1BadgeHandler.RegisterRoutes(apiRouter)
|
||||
|
||||
// Static Routes
|
||||
fileServer := http.FileServer(utils.NeuteredFileSystem{Fs: pkger.Dir("/static")})
|
||||
// https://github.com/golang/go/issues/43431
|
||||
static, _ := fs.Sub(staticFiles, "static")
|
||||
fileServer := http.FileServer(utils.NeuteredFileSystem{Fs: http.FS(static)})
|
||||
router.PathPrefix("/assets").Handler(fileServer)
|
||||
router.PathPrefix("/swagger-ui").Handler(fileServer)
|
||||
router.PathPrefix("/docs").Handler(
|
||||
@ -271,9 +280,3 @@ func listen(handler http.Handler) {
|
||||
|
||||
<-make(chan interface{}, 1)
|
||||
}
|
||||
|
||||
func runDatabaseMigrations() {
|
||||
if err := config.GetMigrationFunc(config.Db.Dialect)(db); err != nil {
|
||||
logbuch.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"context"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/services"
|
||||
@ -65,8 +64,8 @@ func (m *AuthenticateMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), models.UserKey, user)
|
||||
next(w, r.WithContext(ctx))
|
||||
SetPrincipal(r, user)
|
||||
next(w, r)
|
||||
}
|
||||
|
||||
func (m *AuthenticateMiddleware) isOptional(requestPath string) bool {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/middlewares"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@ -39,7 +39,7 @@ func (m *WakatimeRelayMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
if user == nil || user.WakatimeApiKey == "" {
|
||||
return
|
||||
}
|
||||
|
@ -4,23 +4,24 @@ package middlewares
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type logFunc func(string, ...interface{})
|
||||
|
||||
type LoggingMiddleware struct {
|
||||
handler http.Handler
|
||||
output *log.Logger
|
||||
logFunc logFunc
|
||||
excludePrefixes []string
|
||||
}
|
||||
|
||||
func NewLoggingMiddleware(output *log.Logger, excludePrefixes []string) func(http.Handler) http.Handler {
|
||||
func NewLoggingMiddleware(logFunc logFunc, excludePrefixes []string) func(http.Handler) http.Handler {
|
||||
return func(h http.Handler) http.Handler {
|
||||
return &LoggingMiddleware{
|
||||
handler: h,
|
||||
output: output,
|
||||
logFunc: logFunc,
|
||||
excludePrefixes: excludePrefixes,
|
||||
}
|
||||
}
|
||||
@ -41,15 +42,15 @@ func (lg *LoggingMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
lg.output.Printf(
|
||||
"%v status=%d, method=%s, uri=%s, duration=%v, bytes=%d, addr=%s\n",
|
||||
time.Now().Format(time.RFC3339Nano),
|
||||
lg.logFunc(
|
||||
"[request] status=%d, method=%s, uri=%s, duration=%v, bytes=%d, addr=%s, user=%s",
|
||||
ww.Status(),
|
||||
r.Method,
|
||||
r.URL.String(),
|
||||
duration,
|
||||
ww.BytesWritten(),
|
||||
readUserIP(r),
|
||||
readUserID(r),
|
||||
)
|
||||
}
|
||||
|
||||
@ -64,6 +65,13 @@ func readUserIP(r *http.Request) string {
|
||||
return ip
|
||||
}
|
||||
|
||||
func readUserID(r *http.Request) string {
|
||||
if user := GetPrincipal(r); user != nil {
|
||||
return user.ID
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
|
||||
// The below writer-wrapping code has been lifted from
|
||||
// https://github.com/zenazn/goji/blob/master/web/middleware/logger.go - because
|
||||
// it does exactly what is needed, and it's unlikely to change in any
|
||||
|
64
middlewares/principal.go
Normal file
64
middlewares/principal.go
Normal file
@ -0,0 +1,64 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/muety/wakapi/models"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const keyPrincipal = "principal"
|
||||
|
||||
type PrincipalContainer struct {
|
||||
principal *models.User
|
||||
}
|
||||
|
||||
func (c *PrincipalContainer) SetPrincipal(user *models.User) {
|
||||
c.principal = user
|
||||
}
|
||||
|
||||
func (c *PrincipalContainer) GetPrincipal() *models.User {
|
||||
return c.principal
|
||||
}
|
||||
|
||||
// This middleware is a bit of a dirty workaround to the fact that a http.Request's context
|
||||
// does not allow to pass values from an inner to an outer middleware. Calling WithContext() on a
|
||||
// request shallow-copies the whole request itself and therefore, in a chain of handler1(handler2()),
|
||||
// handler 1 will not have access to values handler 2 writes to its context. In addition, Context.WithValue
|
||||
// returns a new context with the old context as a parent.
|
||||
//
|
||||
// As a concrete example, SentryMiddleware as well as LoggingMiddleware should be quite the outer layers,
|
||||
// while AuthenticationMiddleware is on the very inside of the chain. However, we still want sentry or the
|
||||
// logger to have access to the user object populated by the auth. middleware, if present.
|
||||
//
|
||||
// This middleware shall be included as the outermost layers and it injects a stateful container that does
|
||||
// nothing but conditionally hold a reference to an authenticated user object.
|
||||
//
|
||||
// Other reference: https://stackoverflow.com/questions/55972869/send-errors-to-sentry-with-golang-and-mux
|
||||
|
||||
type PrincipalMiddleware struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func NewPrincipalMiddleware() func(handler http.Handler) http.Handler {
|
||||
return func(h http.Handler) http.Handler {
|
||||
return &PrincipalMiddleware{handler: h}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PrincipalMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.WithValue(r.Context(), keyPrincipal, &PrincipalContainer{})
|
||||
p.handler.ServeHTTP(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
func SetPrincipal(r *http.Request, user *models.User) {
|
||||
if p := r.Context().Value(keyPrincipal); p != nil {
|
||||
p.(*PrincipalContainer).SetPrincipal(user)
|
||||
}
|
||||
}
|
||||
|
||||
func GetPrincipal(r *http.Request) *models.User {
|
||||
if p := r.Context().Value(keyPrincipal); p != nil {
|
||||
return p.(*PrincipalContainer).GetPrincipal()
|
||||
}
|
||||
return nil
|
||||
}
|
30
middlewares/sentry.go
Normal file
30
middlewares/sentry.go
Normal file
@ -0,0 +1,30 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/getsentry/sentry-go"
|
||||
sentryhttp "github.com/getsentry/sentry-go/http"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type SentryMiddleware struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func NewSentryMiddleware() func(http.Handler) http.Handler {
|
||||
return func(h http.Handler) http.Handler {
|
||||
return sentryhttp.New(sentryhttp.Options{
|
||||
Repanic: true,
|
||||
}).Handle(&SentryMiddleware{handler: h})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SentryMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.WithValue(r.Context(), "-", "-")
|
||||
h.handler.ServeHTTP(w, r.WithContext(ctx))
|
||||
if hub := sentry.GetHubFromContext(ctx); hub != nil {
|
||||
if user := GetPrincipal(r); user != nil {
|
||||
hub.Scope().SetUser(sentry.User{ID: user.ID})
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/muety/wakapi/config"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
f := migrationFunc{
|
||||
name: "000-apply_fixtures",
|
||||
f: func(db *gorm.DB, cfg *config.Config) error {
|
||||
return cfg.GetFixturesFunc(cfg.Db.Dialect)(db)
|
||||
},
|
||||
}
|
||||
|
||||
registerPostMigration(f)
|
||||
}
|
@ -14,7 +14,7 @@ func init() {
|
||||
migrator := db.Migrator()
|
||||
|
||||
if !migrator.HasColumn(&models.User{}, "badges_enabled") {
|
||||
// empty database, nothing to migrate
|
||||
// empty database or already migrated, nothing to migrate
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -37,11 +37,15 @@ func init() {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.Db.Dialect == config.SQLDialectSqlite {
|
||||
logbuch.Info("not attempting to drop column 'badges_enabled' on sqlite")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := migrator.DropColumn(&models.User{}, "badges_enabled"); err != nil {
|
||||
return err
|
||||
} else {
|
||||
logbuch.Info("dropped column 'badges_enabled' after substituting it by sharing indicators")
|
||||
}
|
||||
logbuch.Info("dropped column 'badges_enabled' after substituting it by sharing indicators")
|
||||
|
||||
return nil
|
||||
},
|
||||
|
@ -1,17 +1,38 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
const name = "20210213-add_has_data_field"
|
||||
f := migrationFunc{
|
||||
name: "20210213_add_has_data_field",
|
||||
name: name,
|
||||
f: func(db *gorm.DB, cfg *config.Config) error {
|
||||
if err := db.Exec("UPDATE users SET has_data = TRUE WHERE 1").Error; err != nil {
|
||||
condition := "key = ?"
|
||||
if cfg.Db.Dialect == config.SQLDialectMysql {
|
||||
condition = "`key` = ?"
|
||||
}
|
||||
lookupResult := db.Where(condition, name).First(&models.KeyStringValue{})
|
||||
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
|
||||
logbuch.Info("no need to migrate '%s'", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := db.Exec("UPDATE users SET has_data = TRUE WHERE TRUE").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := db.Create(&models.KeyStringValue{
|
||||
Key: name,
|
||||
Value: "done",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
41
migrations/20210221_add_created_date_column.go
Normal file
41
migrations/20210221_add_created_date_column.go
Normal file
@ -0,0 +1,41 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
const name = "20210221-add_created_date_column"
|
||||
f := migrationFunc{
|
||||
name: name,
|
||||
f: func(db *gorm.DB, cfg *config.Config) error {
|
||||
condition := "key = ?"
|
||||
if cfg.Db.Dialect == config.SQLDialectMysql {
|
||||
condition = "`key` = ?"
|
||||
}
|
||||
lookupResult := db.Where(condition, name).First(&models.KeyStringValue{})
|
||||
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
|
||||
logbuch.Info("no need to migrate '%s'", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := db.Exec("UPDATE heartbeats SET created_at = time WHERE TRUE").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := db.Create(&models.KeyStringValue{
|
||||
Key: name,
|
||||
Value: "done",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
registerPostMigration(f)
|
||||
}
|
47
migrations/20210411_add_imprint_content.go
Normal file
47
migrations/20210411_add_imprint_content.go
Normal file
@ -0,0 +1,47 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
func init() {
|
||||
const name = "20210411-add_imprint_content"
|
||||
f := migrationFunc{
|
||||
name: name,
|
||||
f: func(db *gorm.DB, cfg *config.Config) error {
|
||||
condition := "key = ?"
|
||||
if cfg.Db.Dialect == config.SQLDialectMysql {
|
||||
condition = "`key` = ?"
|
||||
}
|
||||
lookupResult := db.Where(condition, name).First(&models.KeyStringValue{})
|
||||
if lookupResult.Error == nil && lookupResult.RowsAffected > 0 {
|
||||
logbuch.Info("no need to migrate '%s'", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
imprintKv := &models.KeyStringValue{Key: "imprint", Value: "no content here"}
|
||||
if err := db.
|
||||
Clauses(clause.OnConflict{UpdateAll: false, DoNothing: true}).
|
||||
Where(condition, imprintKv.Key).
|
||||
Assign(imprintKv).
|
||||
Create(imprintKv).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := db.Create(&models.KeyStringValue{
|
||||
Key: name,
|
||||
Value: "done",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
registerPostMigration(f)
|
||||
}
|
22
migrations/20210411_drop_migrations_table.go
Normal file
22
migrations/20210411_drop_migrations_table.go
Normal file
@ -0,0 +1,22 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/muety/wakapi/config"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
const name = "20210411-drop_migrations_table"
|
||||
f := migrationFunc{
|
||||
name: name,
|
||||
f: func(db *gorm.DB, cfg *config.Config) error {
|
||||
if err := db.Migrator().DropTable("gorp_migrations"); err != nil {
|
||||
logbuch.Info("dropped table 'gorp_migrations'")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
registerPostMigration(f)
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
-- +migrate Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
insert into key_string_values ("key", "value") values ('imprint', 'no content here');
|
||||
|
||||
-- +migrate Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
SET SQL_MODE=ANSI_QUOTES;
|
||||
delete from key_string_values where key = 'imprint';
|
@ -28,9 +28,17 @@ func registerPostMigration(f migrationFunc) {
|
||||
postMigrations = append(postMigrations, f)
|
||||
}
|
||||
|
||||
// NOTE: Currently, migrations themselves keep track
|
||||
// of whether they have run, yet or not, because some
|
||||
// simply run on every start.
|
||||
func Run(db *gorm.DB, cfg *config.Config) {
|
||||
RunPreMigrations(db, cfg)
|
||||
RunSchemaMigrations(db, cfg)
|
||||
RunPostMigrations(db, cfg)
|
||||
}
|
||||
|
||||
func RunSchemaMigrations(db *gorm.DB, cfg *config.Config) {
|
||||
if err := cfg.GetMigrationFunc(cfg.Db.Dialect)(db); err != nil {
|
||||
logbuch.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func RunPreMigrations(db *gorm.DB, cfg *config.Config) {
|
||||
sort.Sort(preMigrations)
|
||||
@ -43,7 +51,7 @@ func RunPreMigrations(db *gorm.DB, cfg *config.Config) {
|
||||
}
|
||||
}
|
||||
|
||||
func RunCustomPostMigrations(db *gorm.DB, cfg *config.Config) {
|
||||
func RunPostMigrations(db *gorm.DB, cfg *config.Config) {
|
||||
sort.Sort(postMigrations)
|
||||
|
||||
for _, m := range postMigrations {
|
||||
|
@ -19,6 +19,16 @@ func (m *UserServiceMock) GetUserByKey(s string) (*models.User, error) {
|
||||
return args.Get(0).(*models.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *UserServiceMock) GetUserByEmail(s string) (*models.User, error) {
|
||||
args := m.Called(s)
|
||||
return args.Get(0).(*models.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *UserServiceMock) GetUserByResetToken(s string) (*models.User, error) {
|
||||
args := m.Called(s)
|
||||
return args.Get(0).(*models.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *UserServiceMock) GetAll() ([]*models.User, error) {
|
||||
args := m.Called()
|
||||
return args.Get(0).([]*models.User), args.Error(1)
|
||||
@ -69,6 +79,11 @@ func (m *UserServiceMock) MigrateMd5Password(user *models.User, login *models.Lo
|
||||
return args.Get(0).(*models.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *UserServiceMock) GenerateResetToken(user *models.User) (*models.User, error) {
|
||||
args := m.Called(user)
|
||||
return args.Get(0).(*models.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *UserServiceMock) FlushCache() {
|
||||
m.Called()
|
||||
}
|
||||
|
@ -4,29 +4,29 @@ import (
|
||||
"fmt"
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Heartbeat struct {
|
||||
ID uint `gorm:"primary_key" hash:"ignore"`
|
||||
User *User `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" hash:"ignore"`
|
||||
UserID string `json:"-" gorm:"not null; index:idx_time_user"`
|
||||
Entity string `json:"entity" gorm:"not null; index:idx_entity"`
|
||||
Type string `json:"type"`
|
||||
Category string `json:"category"`
|
||||
Project string `json:"project"`
|
||||
Branch string `json:"branch"`
|
||||
Language string `json:"language" gorm:"index:idx_language"`
|
||||
IsWrite bool `json:"is_write"`
|
||||
Editor string `json:"editor" hash:"ignore"` // ignored because editor might be parsed differently by wakatime
|
||||
OperatingSystem string `json:"operating_system" hash:"ignore"` // ignored because os might be parsed differently by wakatime
|
||||
Machine string `json:"machine" hash:"ignore"` // ignored because wakatime api doesn't return machines currently
|
||||
Time CustomTime `json:"time" gorm:"type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time,idx_time_user" swaggertype:"primitive,number"`
|
||||
Hash string `json:"-" gorm:"type:varchar(17); uniqueIndex"`
|
||||
Origin string `json:"-" hash:"ignore"`
|
||||
OriginId string `json:"-" hash:"ignore"`
|
||||
languageRegex *regexp.Regexp `hash:"ignore"`
|
||||
ID uint `gorm:"primary_key" hash:"ignore"`
|
||||
User *User `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" hash:"ignore"`
|
||||
UserID string `json:"-" gorm:"not null; index:idx_time_user"`
|
||||
Entity string `json:"entity" gorm:"not null; index:idx_entity"`
|
||||
Type string `json:"type"`
|
||||
Category string `json:"category"`
|
||||
Project string `json:"project"`
|
||||
Branch string `json:"branch"`
|
||||
Language string `json:"language" gorm:"index:idx_language"`
|
||||
IsWrite bool `json:"is_write"`
|
||||
Editor string `json:"editor" hash:"ignore"` // ignored because editor might be parsed differently by wakatime
|
||||
OperatingSystem string `json:"operating_system" hash:"ignore"` // ignored because os might be parsed differently by wakatime
|
||||
Machine string `json:"machine" hash:"ignore"` // ignored because wakatime api doesn't return machines currently
|
||||
Time CustomTime `json:"time" gorm:"type:timestamp; index:idx_time,idx_time_user" swaggertype:"primitive,number"`
|
||||
Hash string `json:"-" gorm:"type:varchar(17); uniqueIndex"`
|
||||
Origin string `json:"-" hash:"ignore"`
|
||||
OriginId string `json:"-" hash:"ignore"`
|
||||
CreatedAt CustomTime `json:"created_at" gorm:"type:timestamp" swaggertype:"primitive,number" hash:"ignore"` // https://gorm.io/docs/conventions.html#CreatedAt
|
||||
}
|
||||
|
||||
func (h *Heartbeat) Valid() bool {
|
||||
@ -34,18 +34,12 @@ func (h *Heartbeat) Valid() bool {
|
||||
}
|
||||
|
||||
func (h *Heartbeat) Augment(languageMappings map[string]string) {
|
||||
if h.languageRegex == nil {
|
||||
h.languageRegex = regexp.MustCompile(`^.+\.(.+)$`)
|
||||
for ending, value := range languageMappings {
|
||||
if strings.HasSuffix(h.Entity, "."+ending) {
|
||||
h.Language = value
|
||||
return
|
||||
}
|
||||
}
|
||||
groups := h.languageRegex.FindAllStringSubmatch(h.Entity, -1)
|
||||
if len(groups) == 0 || len(groups[0]) != 2 {
|
||||
return
|
||||
}
|
||||
ending := groups[0][1]
|
||||
if _, ok := languageMappings[ending]; !ok {
|
||||
return
|
||||
}
|
||||
h.Language, _ = languageMappings[ending]
|
||||
}
|
||||
|
||||
func (h *Heartbeat) GetKey(t uint8) (key string) {
|
||||
|
@ -26,17 +26,24 @@ func TestHeartbeat_Valid_MissingUser(t *testing.T) {
|
||||
|
||||
func TestHeartbeat_Augment(t *testing.T) {
|
||||
testMappings := map[string]string{
|
||||
"py": "Python3",
|
||||
"py": "Python3",
|
||||
"foo": "Foo Script",
|
||||
"blade.php": "Blade",
|
||||
}
|
||||
|
||||
sut := &Heartbeat{
|
||||
sut1, sut2 := &Heartbeat{
|
||||
Entity: "~/dev/file.py",
|
||||
Language: "Python",
|
||||
}, &Heartbeat{
|
||||
Entity: "~/dev/file.blade.php",
|
||||
Language: "unknown",
|
||||
}
|
||||
|
||||
sut.Augment(testMappings)
|
||||
sut1.Augment(testMappings)
|
||||
sut2.Augment(testMappings)
|
||||
|
||||
assert.Equal(t, "Python3", sut.Language)
|
||||
assert.Equal(t, "Python3", sut1.Language)
|
||||
assert.Equal(t, "Blade", sut2.Language)
|
||||
}
|
||||
|
||||
func TestHeartbeat_GetKey(t *testing.T) {
|
||||
|
45
models/mail.go
Normal file
45
models/mail.go
Normal file
@ -0,0 +1,45 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Mail struct {
|
||||
From MailAddress
|
||||
To MailAddresses
|
||||
Subject string
|
||||
Body string
|
||||
Type string
|
||||
}
|
||||
|
||||
func (m *Mail) WithText(text string) *Mail {
|
||||
m.Body = text
|
||||
m.Type = "text/plain; charset=UTF-8"
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Mail) WithHTML(html string) *Mail {
|
||||
m.Body = html
|
||||
m.Type = "text/html; charset=UTF-8"
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Mail) String() string {
|
||||
return fmt.Sprintf("To: %s\r\n"+
|
||||
"From: %s\r\n"+
|
||||
"Subject: %s\r\n"+
|
||||
"Content-Type: %s\r\n"+
|
||||
"\r\n"+
|
||||
"%s\r\n",
|
||||
strings.Join(m.To.RawStrings(), ", "),
|
||||
m.From.String(),
|
||||
m.Subject,
|
||||
m.Type,
|
||||
m.Body,
|
||||
)
|
||||
}
|
||||
|
||||
func (m *Mail) Reader() *strings.Reader {
|
||||
return strings.NewReader(m.String())
|
||||
}
|
66
models/mail_address.go
Normal file
66
models/mail_address.go
Normal file
@ -0,0 +1,66 @@
|
||||
package models
|
||||
|
||||
import "regexp"
|
||||
|
||||
const (
|
||||
MailPattern = "[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+"
|
||||
EmailAddrPattern = ".*\\s<(" + MailPattern + ")>|(" + MailPattern + ")"
|
||||
)
|
||||
|
||||
var (
|
||||
mailRegex *regexp.Regexp
|
||||
emailAddrRegex *regexp.Regexp
|
||||
)
|
||||
|
||||
func init() {
|
||||
mailRegex = regexp.MustCompile(MailPattern)
|
||||
emailAddrRegex = regexp.MustCompile(EmailAddrPattern)
|
||||
}
|
||||
|
||||
type MailAddress string
|
||||
|
||||
type MailAddresses []MailAddress
|
||||
|
||||
func (m MailAddress) String() string {
|
||||
return string(m)
|
||||
}
|
||||
|
||||
func (m MailAddress) Raw() string {
|
||||
match := emailAddrRegex.FindStringSubmatch(string(m))
|
||||
if len(match) == 3 {
|
||||
if match[2] != "" {
|
||||
return match[2]
|
||||
}
|
||||
return match[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m MailAddress) Valid() bool {
|
||||
return emailAddrRegex.Match([]byte(m))
|
||||
}
|
||||
|
||||
func (m MailAddresses) Strings() []string {
|
||||
out := make([]string, len(m))
|
||||
for i, s := range m {
|
||||
out[i] = s.String()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (m MailAddresses) RawStrings() []string {
|
||||
out := make([]string, len(m))
|
||||
for i, s := range m {
|
||||
out[i] = s.Raw()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (m MailAddresses) AllValid() bool {
|
||||
for _, a := range m {
|
||||
if !a.Valid() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
@ -35,7 +35,7 @@ type SummaryItem struct {
|
||||
ID uint `json:"-" gorm:"primary_key"`
|
||||
Summary *Summary `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
|
||||
SummaryID uint `json:"-"`
|
||||
Type uint8 `json:"-"`
|
||||
Type uint8 `json:"-" gorm:"index:idx_type"`
|
||||
Key string `json:"key"`
|
||||
Total time.Duration `json:"total" swaggertype:"primitive,integer"`
|
||||
}
|
||||
|
@ -1,8 +1,15 @@
|
||||
package models
|
||||
|
||||
import "regexp"
|
||||
|
||||
func init() {
|
||||
mailRegex = regexp.MustCompile(MailPattern)
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id" gorm:"primary_key"`
|
||||
ApiKey string `json:"api_key" gorm:"unique"`
|
||||
Email string `json:"email" gorm:"uniqueIndex:idx_user_email"`
|
||||
Password string `json:"-"`
|
||||
CreatedAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
|
||||
LastLoggedInAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"`
|
||||
@ -15,6 +22,7 @@ type User struct {
|
||||
IsAdmin bool `json:"-" gorm:"default:false; type:bool"`
|
||||
HasData bool `json:"-" gorm:"default:false; type:bool"`
|
||||
WakatimeApiKey string `json:"-"`
|
||||
ResetToken string `json:"-"`
|
||||
}
|
||||
|
||||
type Login struct {
|
||||
@ -24,16 +32,31 @@ type Login struct {
|
||||
|
||||
type Signup struct {
|
||||
Username string `schema:"username"`
|
||||
Email string `schema:"email"`
|
||||
Password string `schema:"password"`
|
||||
PasswordRepeat string `schema:"password_repeat"`
|
||||
}
|
||||
|
||||
type SetPasswordRequest struct {
|
||||
Password string `schema:"password"`
|
||||
PasswordRepeat string `schema:"password_repeat"`
|
||||
Token string `schema:"token"`
|
||||
}
|
||||
|
||||
type ResetPasswordRequest struct {
|
||||
Email string `schema:"email"`
|
||||
}
|
||||
|
||||
type CredentialsReset struct {
|
||||
PasswordOld string `schema:"password_old"`
|
||||
PasswordNew string `schema:"password_new"`
|
||||
PasswordRepeat string `schema:"password_repeat"`
|
||||
}
|
||||
|
||||
type UserDataUpdate struct {
|
||||
Email string `schema:"email"`
|
||||
}
|
||||
|
||||
type TimeByUser struct {
|
||||
User string
|
||||
Time CustomTime
|
||||
@ -45,20 +68,34 @@ type CountByUser struct {
|
||||
}
|
||||
|
||||
func (c *CredentialsReset) IsValid() bool {
|
||||
return validatePassword(c.PasswordNew) &&
|
||||
return ValidatePassword(c.PasswordNew) &&
|
||||
c.PasswordNew == c.PasswordRepeat
|
||||
}
|
||||
|
||||
func (c *SetPasswordRequest) IsValid() bool {
|
||||
return ValidatePassword(c.Password) &&
|
||||
c.Password == c.PasswordRepeat
|
||||
}
|
||||
|
||||
func (s *Signup) IsValid() bool {
|
||||
return validateUsername(s.Username) &&
|
||||
validatePassword(s.Password) &&
|
||||
return ValidateUsername(s.Username) &&
|
||||
ValidateEmail(s.Email) &&
|
||||
ValidatePassword(s.Password) &&
|
||||
s.Password == s.PasswordRepeat
|
||||
}
|
||||
|
||||
func validateUsername(username string) bool {
|
||||
func (r *UserDataUpdate) IsValid() bool {
|
||||
return ValidateEmail(r.Email)
|
||||
}
|
||||
|
||||
func ValidateUsername(username string) bool {
|
||||
return len(username) >= 1 && username != "current"
|
||||
}
|
||||
|
||||
func validatePassword(password string) bool {
|
||||
func ValidatePassword(password string) bool {
|
||||
return len(password) >= 6
|
||||
}
|
||||
|
||||
func ValidateEmail(email string) bool {
|
||||
return email == "" || mailRegex.Match([]byte(email))
|
||||
}
|
||||
|
@ -6,6 +6,11 @@ type LoginViewModel struct {
|
||||
TotalUsers int
|
||||
}
|
||||
|
||||
type SetPasswordViewModel struct {
|
||||
LoginViewModel
|
||||
Token string
|
||||
}
|
||||
|
||||
func (s *LoginViewModel) WithSuccess(m string) *LoginViewModel {
|
||||
s.Success = m
|
||||
return s
|
||||
|
346
postman/Wakapi.postman_collection.json
Normal file
346
postman/Wakapi.postman_collection.json
Normal file
@ -0,0 +1,346 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "3dcc346d-a9a8-4699-8a52-459eb978b382",
|
||||
"name": "Wakapi",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Misc",
|
||||
"item": [
|
||||
{
|
||||
"name": "Get health",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/health",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"health"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get metrics",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Basic {{TOKEN}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/metrics",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"metrics"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Heartbeats",
|
||||
"item": [
|
||||
{
|
||||
"name": "Create heartbeat",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Basic {{TOKEN}}",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "X-Machine-Name",
|
||||
"value": "devmachine",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "User-Agent",
|
||||
"value": "wakatime/13.0.7 (Linux-4.15.0-91-generic-x86_64-with-glibc2.4) Python3.8.0.final.0 generator/1.42.1 generator-wakatime/4.0.0",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "[{\n \"entity\": \"/home/user1/dev/proejct1/main.go\",\n \"project\": \"Project 1\",\n \"language\": \"Go\",\n \"is_write\": true,\n \"type\": \"file\",\n \"category\": null,\n \"branch\": null,\n \"time\": 1616680499.113417\n}]",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/heartbeat",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"heartbeat"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Summary",
|
||||
"item": [
|
||||
{
|
||||
"name": "Get summary",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Basic {{TOKEN}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/summary?interval=last_7_days",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"summary"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "interval",
|
||||
"value": "last_7_days"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Shields",
|
||||
"item": [
|
||||
{
|
||||
"name": "Get Shields data",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Basic {{TOKEN}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/compat/shields/v1/n1try/interval:today/language:Go",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"compat",
|
||||
"shields",
|
||||
"v1",
|
||||
"n1try",
|
||||
"interval:today",
|
||||
"language:Go"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "WakaTime",
|
||||
"item": [
|
||||
{
|
||||
"name": "Get all time",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Basic {{TOKEN}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/compat/wakatime/v1/users/current/all_time_since_today",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"compat",
|
||||
"wakatime",
|
||||
"v1",
|
||||
"users",
|
||||
"current",
|
||||
"all_time_since_today"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get stats",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Basic {{TOKEN}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/compat/wakatime/v1/users/current/stats",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"compat",
|
||||
"wakatime",
|
||||
"v1",
|
||||
"users",
|
||||
"current",
|
||||
"stats"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get stats with range",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Basic {{TOKEN}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/compat/wakatime/v1/users/current/stats/last_7_days",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"compat",
|
||||
"wakatime",
|
||||
"v1",
|
||||
"users",
|
||||
"current",
|
||||
"stats",
|
||||
"last_7_days"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get summaries",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Basic {{TOKEN}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{BASE_URL}}/api/compat/wakatime/v1/users/current/summaries?start=2020-03-01T15:04:05Z&end=2020-03-31T15:04:05Z",
|
||||
"host": [
|
||||
"{{BASE_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"compat",
|
||||
"wakatime",
|
||||
"v1",
|
||||
"users",
|
||||
"current",
|
||||
"summaries"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "start",
|
||||
"value": "2020-03-01T15:04:05Z"
|
||||
},
|
||||
{
|
||||
"key": "end",
|
||||
"value": "2020-03-31T15:04:05Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"event": [
|
||||
{
|
||||
"listen": "prerequest",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
"const apiKey = pm.variables.get('API_KEY')",
|
||||
"",
|
||||
"if (!apiKey) {",
|
||||
" throw new Error('no api key given')",
|
||||
"}",
|
||||
"",
|
||||
"const token = base64encode(apiKey)",
|
||||
"pm.variables.set('TOKEN', token)",
|
||||
"",
|
||||
"function base64encode(str) {",
|
||||
" return Buffer.from(str, 'utf-8').toString('base64')",
|
||||
"}"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
""
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "BASE_URL",
|
||||
"value": "http://localhost:3000"
|
||||
},
|
||||
{
|
||||
"key": "API_KEY",
|
||||
"value": ""
|
||||
}
|
||||
]
|
||||
}
|
@ -2,7 +2,6 @@ package repositories
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/muety/wakapi/models"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
@ -40,10 +39,6 @@ func (r *KeyValueRepository) PutString(kv *models.KeyStringValue) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.RowsAffected != 1 {
|
||||
logbuch.Warn("did not insert key '%s', maybe just updated?", kv.Key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,8 @@ type IUserRepository interface {
|
||||
GetById(string) (*models.User, error)
|
||||
GetByIds([]string) ([]*models.User, error)
|
||||
GetByApiKey(string) (*models.User, error)
|
||||
GetByEmail(string) (*models.User, error)
|
||||
GetByResetToken(string) (*models.User, error)
|
||||
GetAll() ([]*models.User, error)
|
||||
GetByLoggedInAfter(time.Time) ([]*models.User, error)
|
||||
GetByLastActiveAfter(time.Time) ([]*models.User, error)
|
||||
|
@ -42,6 +42,28 @@ func (r *UserRepository) GetByApiKey(key string) (*models.User, error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetByResetToken(resetToken string) (*models.User, error) {
|
||||
if resetToken == "" {
|
||||
return nil, errors.New("invalid input")
|
||||
}
|
||||
u := &models.User{}
|
||||
if err := r.db.Where(&models.User{ResetToken: resetToken}).First(u).Error; err != nil {
|
||||
return u, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetByEmail(email string) (*models.User, error) {
|
||||
if email == "" {
|
||||
return nil, errors.New("invalid input")
|
||||
}
|
||||
u := &models.User{}
|
||||
if err := r.db.Where(&models.User{Email: email}).First(u).Error; err != nil {
|
||||
return u, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetAll() ([]*models.User, error) {
|
||||
var users []*models.User
|
||||
if err := r.db.
|
||||
@ -109,6 +131,7 @@ func (r *UserRepository) Update(user *models.User) (*models.User, error) {
|
||||
updateMap := map[string]interface{}{
|
||||
"api_key": user.ApiKey,
|
||||
"password": user.Password,
|
||||
"email": user.Email,
|
||||
"last_logged_in_at": user.LastLoggedInAt,
|
||||
"share_data_max_days": user.ShareDataMaxDays,
|
||||
"share_editors": user.ShareEditors,
|
||||
@ -118,6 +141,7 @@ func (r *UserRepository) Update(user *models.User) (*models.User, error) {
|
||||
"share_machines": user.ShareMachines,
|
||||
"wakatime_api_key": user.WakatimeApiKey,
|
||||
"has_data": user.HasData,
|
||||
"reset_token": user.ResetToken,
|
||||
}
|
||||
|
||||
result := r.db.Model(user).Updates(updateMap)
|
||||
|
@ -53,7 +53,7 @@ func (h *HeartbeatApiHandler) RegisterRoutes(router *mux.Router) {
|
||||
// @Router /heartbeat [post]
|
||||
func (h *HeartbeatApiHandler) Post(w http.ResponseWriter, r *http.Request) {
|
||||
var heartbeats []*models.Heartbeat
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
opSys, editor, _ := utils.ParseUserAgent(r.Header.Get("User-Agent"))
|
||||
machineName := r.Header.Get("X-Machine-Name")
|
||||
|
||||
|
@ -68,7 +68,7 @@ func (h *MetricsHandler) RegisterRoutes(router *mux.Router) {
|
||||
}
|
||||
|
||||
func (h *MetricsHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
reqUser := r.Context().Value(models.UserKey).(*models.User)
|
||||
reqUser := middlewares.GetPrincipal(r)
|
||||
if reqUser == nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(conf.ErrUnauthorized))
|
||||
@ -108,7 +108,7 @@ func (h *MetricsHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error) {
|
||||
var metrics mm.Metrics
|
||||
|
||||
summaryAllTime, err := h.summarySrvc.Aliased(time.Time{}, time.Now(), user, h.summarySrvc.Retrieve)
|
||||
summaryAllTime, err := h.summarySrvc.Aliased(time.Time{}, time.Now(), user, h.summarySrvc.Retrieve, false)
|
||||
if err != nil {
|
||||
logbuch.Error("failed to retrieve all time summary for user '%s' for metric", user.ID)
|
||||
return nil, err
|
||||
@ -116,7 +116,7 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error)
|
||||
|
||||
from, to := utils.MustResolveIntervalRaw("today")
|
||||
|
||||
summaryToday, err := h.summarySrvc.Aliased(from, to, user, h.summarySrvc.Retrieve)
|
||||
summaryToday, err := h.summarySrvc.Aliased(from, to, user, h.summarySrvc.Retrieve, false)
|
||||
if err != nil {
|
||||
logbuch.Error("failed to retrieve today's summary for user '%s' for metric", user.ID)
|
||||
return nil, err
|
||||
|
@ -1,15 +1,16 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
v1 "github.com/muety/wakapi/models/compat/shields/v1"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -22,12 +23,14 @@ type BadgeHandler struct {
|
||||
config *conf.Config
|
||||
userSrvc services.IUserService
|
||||
summarySrvc services.ISummaryService
|
||||
cache *cache.Cache
|
||||
}
|
||||
|
||||
func NewBadgeHandler(summaryService services.ISummaryService, userService services.IUserService) *BadgeHandler {
|
||||
return &BadgeHandler{
|
||||
summarySrvc: summaryService,
|
||||
userSrvc: userService,
|
||||
cache: cache.New(time.Hour, time.Hour),
|
||||
config: conf.Get(),
|
||||
}
|
||||
}
|
||||
@ -52,11 +55,6 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
intervalReg := regexp.MustCompile(intervalPattern)
|
||||
entityFilterReg := regexp.MustCompile(entityFilterPattern)
|
||||
|
||||
if userAgent := r.Header.Get("user-agent"); !strings.HasPrefix(userAgent, "Shields.io/") && !h.config.IsDev() {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
var filterEntity, filterKey string
|
||||
if groups := entityFilterReg.FindStringSubmatch(r.URL.Path); len(groups) > 2 {
|
||||
filterEntity, filterKey = groups[1], groups[2]
|
||||
@ -101,6 +99,12 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
filters = &models.Filters{}
|
||||
}
|
||||
|
||||
cacheKey := fmt.Sprintf("%s_%v_%s_%s", user.ID, *interval, filterEntity, filterKey)
|
||||
if cacheResult, ok := h.cache.Get(cacheKey); ok {
|
||||
utils.RespondJSON(w, http.StatusOK, cacheResult.(*v1.BadgeData))
|
||||
return
|
||||
}
|
||||
|
||||
summary, err, status := h.loadUserSummary(user, interval)
|
||||
if err != nil {
|
||||
w.WriteHeader(status)
|
||||
@ -109,6 +113,7 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
vm := v1.NewBadgeDataFrom(summary, filters)
|
||||
h.cache.SetDefault(cacheKey, vm)
|
||||
utils.RespondJSON(w, http.StatusOK, vm)
|
||||
}
|
||||
|
||||
@ -129,7 +134,7 @@ func (h *BadgeHandler) loadUserSummary(user *models.User, interval *models.Inter
|
||||
retrieveSummary = h.summarySrvc.Summarize
|
||||
}
|
||||
|
||||
summary, err := h.summarySrvc.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary)
|
||||
summary, err := h.summarySrvc.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary, summaryParams.Recompute)
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ func (h *AllTimeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
values, _ := url.ParseQuery(r.URL.RawQuery)
|
||||
|
||||
requestedUser := vars["user"]
|
||||
authorizedUser := r.Context().Value(models.UserKey).(*models.User)
|
||||
authorizedUser := middlewares.GetPrincipal(r)
|
||||
|
||||
if requestedUser != authorizedUser.ID && requestedUser != "current" {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
@ -80,7 +80,7 @@ func (h *AllTimeHandler) loadUserSummary(user *models.User) (*models.Summary, er
|
||||
retrieveSummary = h.summarySrvc.Summarize
|
||||
}
|
||||
|
||||
summary, err := h.summarySrvc.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary)
|
||||
summary, err := h.summarySrvc.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary, summaryParams.Recompute)
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
|
@ -45,10 +45,7 @@ func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
var vars = mux.Vars(r)
|
||||
var authorizedUser, requestedUser *models.User
|
||||
|
||||
if u := r.Context().Value(models.UserKey); u != nil {
|
||||
authorizedUser = u.(*models.User)
|
||||
}
|
||||
|
||||
authorizedUser = middlewares.GetPrincipal(r)
|
||||
if authorizedUser != nil && vars["user"] == "current" {
|
||||
vars["user"] = authorizedUser.ID
|
||||
}
|
||||
@ -117,7 +114,7 @@ func (h *StatsHandler) loadUserSummary(user *models.User, start, end time.Time)
|
||||
Recompute: false,
|
||||
}
|
||||
|
||||
summary, err := h.summarySrvc.Aliased(overallParams.From, overallParams.To, user, h.summarySrvc.Retrieve)
|
||||
summary, err := h.summarySrvc.Aliased(overallParams.From, overallParams.To, user, h.summarySrvc.Retrieve, false)
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ func (h *SummariesHandler) RegisterRoutes(router *mux.Router) {
|
||||
func (h *SummariesHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
requestedUser := vars["user"]
|
||||
authorizedUser := r.Context().Value(models.UserKey).(*models.User)
|
||||
authorizedUser := middlewares.GetPrincipal(r)
|
||||
|
||||
if requestedUser != authorizedUser.ID && requestedUser != "current" {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
@ -80,7 +80,7 @@ func (h *SummariesHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary, error, int) {
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
params := r.URL.Query()
|
||||
rangeParam, startParam, endParam := params.Get("range"), params.Get("start"), params.Get("end")
|
||||
|
||||
@ -121,7 +121,7 @@ func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary
|
||||
summaries := make([]*models.Summary, len(intervals))
|
||||
|
||||
for i, interval := range intervals {
|
||||
summary, err := h.summarySrvc.Aliased(interval[0], interval[1], user, h.summarySrvc.Retrieve)
|
||||
summary, err := h.summarySrvc.Aliased(interval[0], interval[1], user, h.summarySrvc.Retrieve, false)
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ type HomeHandler struct {
|
||||
|
||||
var loginDecoder = schema.NewDecoder()
|
||||
var signupDecoder = schema.NewDecoder()
|
||||
var resetPasswordDecoder = schema.NewDecoder()
|
||||
|
||||
func NewHomeHandler(keyValueService services.IKeyValueService) *HomeHandler {
|
||||
return &HomeHandler{
|
||||
|
132
routes/login.go
132
routes/login.go
@ -2,6 +2,7 @@ package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/gorilla/mux"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
@ -9,18 +10,21 @@ import (
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LoginHandler struct {
|
||||
config *conf.Config
|
||||
userSrvc services.IUserService
|
||||
mailSrvc services.IMailService
|
||||
}
|
||||
|
||||
func NewLoginHandler(userService services.IUserService) *LoginHandler {
|
||||
func NewLoginHandler(userService services.IUserService, mailService services.IMailService) *LoginHandler {
|
||||
return &LoginHandler{
|
||||
config: conf.Get(),
|
||||
userSrvc: userService,
|
||||
mailSrvc: mailService,
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +34,10 @@ func (h *LoginHandler) RegisterRoutes(router *mux.Router) {
|
||||
router.Path("/logout").Methods(http.MethodPost).HandlerFunc(h.PostLogout)
|
||||
router.Path("/signup").Methods(http.MethodGet).HandlerFunc(h.GetSignup)
|
||||
router.Path("/signup").Methods(http.MethodPost).HandlerFunc(h.PostSignup)
|
||||
router.Path("/set-password").Methods(http.MethodGet).HandlerFunc(h.GetSetPassword)
|
||||
router.Path("/set-password").Methods(http.MethodPost).HandlerFunc(h.PostSetPassword)
|
||||
router.Path("/reset-password").Methods(http.MethodGet).HandlerFunc(h.GetResetPassword)
|
||||
router.Path("/reset-password").Methods(http.MethodPost).HandlerFunc(h.PostResetPassword)
|
||||
}
|
||||
|
||||
func (h *LoginHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
|
||||
@ -167,6 +175,128 @@ func (h *LoginHandler) PostSignup(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, fmt.Sprintf("%s/?success=%s", h.config.Server.BasePath, "account created successfully"), http.StatusFound)
|
||||
}
|
||||
|
||||
func (h *LoginHandler) GetResetPassword(w http.ResponseWriter, r *http.Request) {
|
||||
if h.config.IsDev() {
|
||||
loadTemplates()
|
||||
}
|
||||
templates[conf.ResetPasswordTemplate].Execute(w, h.buildViewModel(r))
|
||||
}
|
||||
|
||||
func (h *LoginHandler) GetSetPassword(w http.ResponseWriter, r *http.Request) {
|
||||
if h.config.IsDev() {
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
values, _ := url.ParseQuery(r.URL.RawQuery)
|
||||
token := values.Get("token")
|
||||
if token == "" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
templates[conf.SetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("invalid or missing token"))
|
||||
return
|
||||
}
|
||||
|
||||
vm := &view.SetPasswordViewModel{
|
||||
LoginViewModel: *h.buildViewModel(r),
|
||||
Token: token,
|
||||
}
|
||||
|
||||
templates[conf.SetPasswordTemplate].Execute(w, vm)
|
||||
}
|
||||
|
||||
func (h *LoginHandler) PostSetPassword(w http.ResponseWriter, r *http.Request) {
|
||||
if h.config.IsDev() {
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
var setRequest models.SetPasswordRequest
|
||||
if err := r.ParseForm(); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
templates[conf.SetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("missing parameters"))
|
||||
return
|
||||
}
|
||||
if err := signupDecoder.Decode(&setRequest, r.PostForm); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
templates[conf.SetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("missing parameters"))
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.userSrvc.GetUserByResetToken(setRequest.Token)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
templates[conf.SetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("invalid token"))
|
||||
return
|
||||
}
|
||||
|
||||
if !setRequest.IsValid() {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
templates[conf.SetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("invalid parameters"))
|
||||
return
|
||||
}
|
||||
|
||||
user.Password = setRequest.Password
|
||||
user.ResetToken = ""
|
||||
if hash, err := utils.HashBcrypt(user.Password, h.config.Security.PasswordSalt); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
templates[conf.SetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("failed to set new password"))
|
||||
return
|
||||
} else {
|
||||
user.Password = hash
|
||||
}
|
||||
|
||||
if _, err := h.userSrvc.Update(user); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
templates[conf.SetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("failed to save new password"))
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("%s/login?success=%s", h.config.Server.BasePath, "password updated successfully"), http.StatusFound)
|
||||
}
|
||||
|
||||
func (h *LoginHandler) PostResetPassword(w http.ResponseWriter, r *http.Request) {
|
||||
if h.config.IsDev() {
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
if !h.config.Mail.Enabled {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
templates[conf.ResetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("mailing is disabled on this server"))
|
||||
return
|
||||
}
|
||||
|
||||
var resetRequest models.ResetPasswordRequest
|
||||
if err := r.ParseForm(); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
templates[conf.ResetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("missing parameters"))
|
||||
return
|
||||
}
|
||||
if err := resetPasswordDecoder.Decode(&resetRequest, r.PostForm); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
templates[conf.ResetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("missing parameters"))
|
||||
return
|
||||
}
|
||||
|
||||
if user, err := h.userSrvc.GetUserByEmail(resetRequest.Email); user != nil && err == nil {
|
||||
if u, err := h.userSrvc.GenerateResetToken(user); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
templates[conf.ResetPasswordTemplate].Execute(w, h.buildViewModel(r).WithError("failed to generate password reset token"))
|
||||
return
|
||||
} else {
|
||||
go func(user *models.User) {
|
||||
link := fmt.Sprintf("%s/set-password?token=%s", h.config.Server.GetPublicUrl(), user.ResetToken)
|
||||
if err := h.mailSrvc.SendPasswordReset(user, link); err != nil {
|
||||
logbuch.Error("failed to send password reset mail to %s – %v", user.ID, err)
|
||||
} else {
|
||||
logbuch.Info("sent password reset mail to %s", user.ID)
|
||||
}
|
||||
}(u)
|
||||
}
|
||||
} else {
|
||||
logbuch.Warn("password reset requested for unregistered address '%s'", resetRequest.Email)
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("%s/?success=%s", h.config.Server.BasePath, "an e-mail was sent to you in case your e-mail address was registered"), http.StatusFound)
|
||||
}
|
||||
|
||||
func (h *LoginHandler) buildViewModel(r *http.Request) *view.LoginViewModel {
|
||||
numUsers, _ := h.userSrvc.Count()
|
||||
|
||||
|
@ -2,15 +2,17 @@ package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/markbates/pkger"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"github.com/muety/wakapi/views"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
@ -35,6 +37,9 @@ func loadTemplates() {
|
||||
"toRunes": utils.ToRunes,
|
||||
"entityTypes": models.SummaryTypes,
|
||||
"typeName": typeName,
|
||||
"isDev": func() bool {
|
||||
return config.Get().IsDev()
|
||||
},
|
||||
"getBasePath": func() string {
|
||||
return config.Get().Server.BasePath
|
||||
},
|
||||
@ -50,12 +55,7 @@ func loadTemplates() {
|
||||
})
|
||||
templates = make(map[string]*template.Template)
|
||||
|
||||
dir, err := pkger.Open(tplPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer dir.Close()
|
||||
files, err := dir.Readdir(0)
|
||||
files, err := fs.ReadDir(views.TemplateFiles, ".")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -66,7 +66,7 @@ func loadTemplates() {
|
||||
continue
|
||||
}
|
||||
|
||||
templateFile, err := pkger.Open(fmt.Sprintf("%s/%s", tplPath, tplName))
|
||||
templateFile, err := views.TemplateFiles.Open(tplName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ type SettingsHandler struct {
|
||||
aggregationSrvc services.IAggregationService
|
||||
languageMappingSrvc services.ILanguageMappingService
|
||||
keyValueSrvc services.IKeyValueService
|
||||
mailSrvc services.IMailService
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
@ -40,6 +41,7 @@ func NewSettingsHandler(
|
||||
aggregationService services.IAggregationService,
|
||||
languageMappingService services.ILanguageMappingService,
|
||||
keyValueService services.IKeyValueService,
|
||||
mailService services.IMailService,
|
||||
) *SettingsHandler {
|
||||
return &SettingsHandler{
|
||||
config: conf.Get(),
|
||||
@ -50,6 +52,7 @@ func NewSettingsHandler(
|
||||
userSrvc: userService,
|
||||
heartbeatSrvc: heartbeatService,
|
||||
keyValueSrvc: keyValueService,
|
||||
mailSrvc: mailService,
|
||||
httpClient: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
}
|
||||
@ -117,6 +120,8 @@ func (h *SettingsHandler) dispatchAction(action string) action {
|
||||
switch action {
|
||||
case "change_password":
|
||||
return h.actionChangePassword
|
||||
case "update_user":
|
||||
return h.actionUpdateUser
|
||||
case "reset_apikey":
|
||||
return h.actionResetApiKey
|
||||
case "delete_alias":
|
||||
@ -141,12 +146,40 @@ func (h *SettingsHandler) dispatchAction(action string) action {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) actionUpdateUser(w http.ResponseWriter, r *http.Request) (int, string, string) {
|
||||
if h.config.IsDev() {
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
user := middlewares.GetPrincipal(r)
|
||||
|
||||
var payload models.UserDataUpdate
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return http.StatusBadRequest, "", "missing parameters"
|
||||
}
|
||||
if err := credentialsDecoder.Decode(&payload, r.PostForm); err != nil {
|
||||
return http.StatusBadRequest, "", "missing parameters"
|
||||
}
|
||||
|
||||
if !payload.IsValid() {
|
||||
return http.StatusBadRequest, "", "invalid parameters"
|
||||
}
|
||||
|
||||
user.Email = payload.Email
|
||||
|
||||
if _, err := h.userSrvc.Update(user); err != nil {
|
||||
return http.StatusInternalServerError, "", conf.ErrInternalServerError
|
||||
}
|
||||
|
||||
return http.StatusOK, "user updated successfully", ""
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) actionChangePassword(w http.ResponseWriter, r *http.Request) (int, string, string) {
|
||||
if h.config.IsDev() {
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
|
||||
var credentials models.CredentialsReset
|
||||
if err := r.ParseForm(); err != nil {
|
||||
@ -193,7 +226,7 @@ func (h *SettingsHandler) actionResetApiKey(w http.ResponseWriter, r *http.Reque
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
if _, err := h.userSrvc.ResetApiKey(user); err != nil {
|
||||
return http.StatusInternalServerError, "", conf.ErrInternalServerError
|
||||
}
|
||||
@ -208,7 +241,7 @@ func (h *SettingsHandler) actionUpdateSharing(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
var err error
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
|
||||
defer h.userSrvc.FlushCache()
|
||||
|
||||
@ -235,7 +268,7 @@ func (h *SettingsHandler) actionDeleteAlias(w http.ResponseWriter, r *http.Reque
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
aliasKey := r.PostFormValue("key")
|
||||
aliasType, err := strconv.Atoi(r.PostFormValue("type"))
|
||||
if err != nil {
|
||||
@ -255,7 +288,7 @@ func (h *SettingsHandler) actionAddAlias(w http.ResponseWriter, r *http.Request)
|
||||
if h.config.IsDev() {
|
||||
loadTemplates()
|
||||
}
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
aliasKey := r.PostFormValue("key")
|
||||
aliasValue := r.PostFormValue("value")
|
||||
aliasType, err := strconv.Atoi(r.PostFormValue("type"))
|
||||
@ -283,19 +316,20 @@ func (h *SettingsHandler) actionDeleteLanguageMapping(w http.ResponseWriter, r *
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
id, err := strconv.Atoi(r.PostFormValue("mapping_id"))
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, "", "could not delete mapping"
|
||||
}
|
||||
|
||||
if mapping, err := h.languageMappingSrvc.GetById(uint(id)); err != nil || mapping == nil {
|
||||
mapping, err := h.languageMappingSrvc.GetById(uint(id))
|
||||
if err != nil || mapping == nil {
|
||||
return http.StatusNotFound, "", "mapping not found"
|
||||
} else if mapping.UserID != user.ID {
|
||||
return http.StatusForbidden, "", "not allowed to delete mapping"
|
||||
}
|
||||
|
||||
if err := h.languageMappingSrvc.Delete(&models.LanguageMapping{ID: uint(id)}); err != nil {
|
||||
if err := h.languageMappingSrvc.Delete(mapping); err != nil {
|
||||
return http.StatusInternalServerError, "", "could not delete mapping"
|
||||
}
|
||||
|
||||
@ -306,7 +340,7 @@ func (h *SettingsHandler) actionAddLanguageMapping(w http.ResponseWriter, r *htt
|
||||
if h.config.IsDev() {
|
||||
loadTemplates()
|
||||
}
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
extension := r.PostFormValue("extension")
|
||||
language := r.PostFormValue("language")
|
||||
|
||||
@ -332,7 +366,7 @@ func (h *SettingsHandler) actionSetWakatimeApiKey(w http.ResponseWriter, r *http
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
apiKey := r.PostFormValue("api_key")
|
||||
|
||||
// Healthcheck, if a new API key is set, i.e. the feature is activated
|
||||
@ -352,7 +386,7 @@ func (h *SettingsHandler) actionImportWaktime(w http.ResponseWriter, r *http.Req
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
if user.WakatimeApiKey == "" {
|
||||
return http.StatusForbidden, "", "not connected to wakatime"
|
||||
}
|
||||
@ -370,6 +404,7 @@ func (h *SettingsHandler) actionImportWaktime(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
go func(user *models.User) {
|
||||
start := time.Now()
|
||||
importer := imports.NewWakatimeHeartbeatImporter(user.WakatimeApiKey)
|
||||
|
||||
countBefore, err := h.heartbeatSrvc.CountByUser(user)
|
||||
@ -388,23 +423,38 @@ func (h *SettingsHandler) actionImportWaktime(w http.ResponseWriter, r *http.Req
|
||||
count := 0
|
||||
batch := make([]*models.Heartbeat, 0)
|
||||
|
||||
insert := func(batch []*models.Heartbeat) {
|
||||
if err := h.heartbeatSrvc.InsertBatch(batch); err != nil {
|
||||
logbuch.Warn("failed to insert imported heartbeat, already existing? – %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for hb := range stream {
|
||||
count++
|
||||
batch = append(batch, hb)
|
||||
|
||||
if len(batch) == h.config.App.ImportBatchSize {
|
||||
if err := h.heartbeatSrvc.InsertBatch(batch); err != nil {
|
||||
logbuch.Warn("failed to insert imported heartbeat, already existing? – %v", err)
|
||||
}
|
||||
|
||||
insert(batch)
|
||||
batch = make([]*models.Heartbeat, 0)
|
||||
}
|
||||
}
|
||||
|
||||
if len(batch) > 0 {
|
||||
insert(batch)
|
||||
}
|
||||
|
||||
countAfter, _ := h.heartbeatSrvc.CountByUser(user)
|
||||
logbuch.Info("downloaded %d heartbeats for user '%s' (%d actually imported)", count, user.ID, countAfter-countBefore)
|
||||
|
||||
h.regenerateSummaries(user)
|
||||
|
||||
if user.Email != "" {
|
||||
if err := h.mailSrvc.SendImportNotification(user, time.Now().Sub(start), int(countAfter-countBefore)); err != nil {
|
||||
logbuch.Error("failed to send import notification mail to %s – %v", user.ID, err)
|
||||
} else {
|
||||
logbuch.Info("sent import notification mail to %s", user.ID)
|
||||
}
|
||||
}
|
||||
}(user)
|
||||
|
||||
h.keyValueSrvc.PutString(&models.KeyStringValue{
|
||||
@ -412,7 +462,7 @@ func (h *SettingsHandler) actionImportWaktime(w http.ResponseWriter, r *http.Req
|
||||
Value: time.Now().Format(time.RFC822),
|
||||
})
|
||||
|
||||
return http.StatusAccepted, "ImportAll started. This may take a few minutes.", ""
|
||||
return http.StatusAccepted, "Import started. This will take several minutes. Please check back later.", ""
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) actionRegenerateSummaries(w http.ResponseWriter, r *http.Request) (int, string, string) {
|
||||
@ -420,13 +470,13 @@ func (h *SettingsHandler) actionRegenerateSummaries(w http.ResponseWriter, r *ht
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
go func(user *models.User) {
|
||||
if err := h.regenerateSummaries(user); err != nil {
|
||||
logbuch.Error("failed to regenerate summaries for user '%s' – %v", user.ID, err)
|
||||
}
|
||||
}(middlewares.GetPrincipal(r))
|
||||
|
||||
if err := h.regenerateSummaries(user); err != nil {
|
||||
return http.StatusInternalServerError, "", "failed to regenerate summaries"
|
||||
}
|
||||
|
||||
return http.StatusOK, "summaries are being regenerated – this may take a few seconds", ""
|
||||
return http.StatusAccepted, "summaries are being regenerated – this may take a up to a couple of minutes, please come back later", ""
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) actionDeleteUser(w http.ResponseWriter, r *http.Request) (int, string, string) {
|
||||
@ -434,7 +484,7 @@ func (h *SettingsHandler) actionDeleteUser(w http.ResponseWriter, r *http.Reques
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
go func(user *models.User) {
|
||||
logbuch.Info("deleting user '%s' shortly", user.ID)
|
||||
time.Sleep(5 * time.Minute)
|
||||
@ -493,7 +543,7 @@ func (h *SettingsHandler) regenerateSummaries(user *models.User) error {
|
||||
}
|
||||
|
||||
func (h *SettingsHandler) buildViewModel(r *http.Request) *view.SettingsViewModel {
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
mappings, _ := h.languageMappingSrvc.GetByUser(user.ID)
|
||||
aliases, _ := h.aliasSrvc.GetByUser(user.ID)
|
||||
aliasMap := make(map[string][]*models.Alias)
|
||||
|
@ -53,7 +53,7 @@ func (h *SummaryHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := middlewares.GetPrincipal(r)
|
||||
if user == nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
templates[conf.SummaryTemplate].Execute(w, h.buildViewModel(r).WithError("unauthorized"))
|
||||
|
@ -18,7 +18,7 @@ func LoadUserSummary(ss services.ISummaryService, r *http.Request) (*models.Summ
|
||||
retrieveSummary = ss.Summarize
|
||||
}
|
||||
|
||||
summary, err := ss.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary)
|
||||
summary, err := ss.Aliased(summaryParams.From, summaryParams.To, summaryParams.User, retrieveSummary, summaryParams.Recompute)
|
||||
if err != nil {
|
||||
return nil, err, http.StatusInternalServerError
|
||||
}
|
||||
|
@ -1,113 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Union
|
||||
|
||||
import requests
|
||||
from tqdm import tqdm
|
||||
|
||||
MACHINE = "devmachine"
|
||||
UA = 'wakatime/13.0.7 (Linux-4.15.0-91-generic-x86_64-with-glibc2.4) Python3.8.0.final.0 generator/1.42.1 generator-wakatime/4.0.0'
|
||||
LANGUAGES = {
|
||||
'Go': 'go',
|
||||
'Java': 'java',
|
||||
'JavaScript': 'js',
|
||||
'Python': 'py'
|
||||
}
|
||||
|
||||
|
||||
class Heartbeat:
|
||||
def __init__(
|
||||
self,
|
||||
entity: str,
|
||||
project: str,
|
||||
language: str,
|
||||
time: float,
|
||||
is_write: bool = True,
|
||||
branch: str = 'master',
|
||||
type: str = 'file'
|
||||
):
|
||||
self.entity: str = entity
|
||||
self.project: str = project
|
||||
self.language: str = language
|
||||
self.time: float = time
|
||||
self.is_write: bool = is_write
|
||||
self.branch: str = branch
|
||||
self.type: str = type
|
||||
self.category: Union[str, None] = None
|
||||
|
||||
|
||||
def generate_data(n: int, n_projects: int = 5, n_past_hours: int = 24) -> List[Heartbeat]:
|
||||
data: List[Heartbeat] = []
|
||||
now: datetime = datetime.today()
|
||||
projects: List[str] = [randomword(random.randint(5, 10)) for _ in range(n_projects)]
|
||||
languages: List[str] = list(LANGUAGES.keys())
|
||||
|
||||
for _ in range(n):
|
||||
p: str = random.choice(projects)
|
||||
l: str = random.choice(languages)
|
||||
f: str = randomword(random.randint(2, 8))
|
||||
delta: timedelta = timedelta(
|
||||
hours=random.randint(0, n_past_hours - 1),
|
||||
minutes=random.randint(0, 59),
|
||||
seconds=random.randint(0, 59),
|
||||
milliseconds=random.randint(0, 999),
|
||||
microseconds=random.randint(0, 999)
|
||||
)
|
||||
|
||||
data.append(Heartbeat(
|
||||
entity=f'/home/me/dev/{p}/{f}.{LANGUAGES[l]}',
|
||||
project=p,
|
||||
language=l,
|
||||
time=(now - delta).timestamp()
|
||||
))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def post_data_sync(data: List[Heartbeat], url: str, api_key: str):
|
||||
encoded_key: str = str(base64.b64encode(api_key.encode('utf-8')), 'utf-8')
|
||||
|
||||
for h in tqdm(data):
|
||||
r = requests.post(url, json=[h.__dict__], headers={
|
||||
'User-Agent': UA,
|
||||
'Authorization': f'Basic {encoded_key}',
|
||||
'X-Machine-Name': MACHINE,
|
||||
})
|
||||
if r.status_code != 201:
|
||||
print(r.text)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def randomword(length: int) -> str:
|
||||
letters = string.ascii_lowercase
|
||||
return ''.join(random.choice(letters) for _ in range(length))
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser(description='Wakapi test data insertion script.')
|
||||
parser.add_argument('-n', type=int, default=20, help='total number of random heartbeats to generate and insert')
|
||||
parser.add_argument('-u', '--url', type=str, default='http://localhost:3000/api/heartbeat',
|
||||
help='url of your api\'s heartbeats endpoint')
|
||||
parser.add_argument('-k', '--apikey', type=str, required=True,
|
||||
help='your api key (to get one, go to the web interface, create a new user, log in and copy the key)')
|
||||
parser.add_argument('-p', '--projects', type=int, default=5, help='number of different fake projects to generate')
|
||||
parser.add_argument('-o', '--offset', type=int, default=24,
|
||||
help='negative time offset in hours from now for to be used as an interval within which to generate heartbeats for')
|
||||
parser.add_argument('-s', '--seed', type=int, default=2020,
|
||||
help='a seed for initializing the pseudo-random number generator')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parse_arguments()
|
||||
|
||||
random.seed(args.seed)
|
||||
|
||||
data: List[Heartbeat] = generate_data(args.n, args.projects, args.offset)
|
||||
post_data_sync(data, args.url, args.apikey)
|
271
scripts/sample_data.py
Normal file
271
scripts/sample_data.py
Normal file
@ -0,0 +1,271 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Union, Callable
|
||||
|
||||
import requests
|
||||
from tqdm import tqdm
|
||||
|
||||
MACHINE = "devmachine"
|
||||
UA = 'wakatime/13.0.7 (Linux-4.15.0-91-generic-x86_64-with-glibc2.4) Python3.8.0.final.0 generator/1.42.1 generator-wakatime/4.0.0'
|
||||
LANGUAGES = {
|
||||
'Go': 'go',
|
||||
'Java': 'java',
|
||||
'JavaScript': 'js',
|
||||
'Python': 'py'
|
||||
}
|
||||
|
||||
|
||||
class Heartbeat:
|
||||
def __init__(
|
||||
self,
|
||||
entity: str,
|
||||
project: str,
|
||||
language: str,
|
||||
time: float,
|
||||
is_write: bool = True,
|
||||
branch: str = 'master',
|
||||
type: str = 'file'
|
||||
):
|
||||
self.entity: str = entity
|
||||
self.project: str = project
|
||||
self.language: str = language
|
||||
self.time: float = time
|
||||
self.is_write: bool = is_write
|
||||
self.branch: str = branch
|
||||
self.type: str = type
|
||||
self.category: Union[str, None] = None
|
||||
|
||||
|
||||
class ConfigParams:
|
||||
def __init__(self):
|
||||
self.api_url = ''
|
||||
self.api_key = ''
|
||||
self.n = 0
|
||||
self.n_projects = 0
|
||||
self.offset = 0
|
||||
self.seed = 0
|
||||
|
||||
|
||||
def generate_data(n: int, n_projects: int = 5, n_past_hours: int = 24) -> List[Heartbeat]:
|
||||
data: List[Heartbeat] = []
|
||||
now: datetime = datetime.today()
|
||||
projects: List[str] = [randomword(random.randint(5, 10)) for _ in range(n_projects)]
|
||||
languages: List[str] = list(LANGUAGES.keys())
|
||||
|
||||
for _ in range(n):
|
||||
p: str = random.choice(projects)
|
||||
l: str = random.choice(languages)
|
||||
f: str = randomword(random.randint(2, 8))
|
||||
delta: timedelta = timedelta(
|
||||
hours=random.randint(0, n_past_hours - 1),
|
||||
minutes=random.randint(0, 59),
|
||||
seconds=random.randint(0, 59),
|
||||
milliseconds=random.randint(0, 999),
|
||||
microseconds=random.randint(0, 999)
|
||||
)
|
||||
|
||||
data.append(Heartbeat(
|
||||
entity=f'/home/me/dev/{p}/{f}.{LANGUAGES[l]}',
|
||||
project=p,
|
||||
language=l,
|
||||
time=(now - delta).timestamp()
|
||||
))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def post_data_sync(data: List[Heartbeat], url: str, api_key: str):
|
||||
encoded_key: str = str(base64.b64encode(api_key.encode('utf-8')), 'utf-8')
|
||||
|
||||
for h in data:
|
||||
r = requests.post(url, json=[h.__dict__], headers={
|
||||
'User-Agent': UA,
|
||||
'Authorization': f'Basic {encoded_key}',
|
||||
'X-Machine-Name': MACHINE,
|
||||
})
|
||||
if r.status_code != 201:
|
||||
print(r.text)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def make_gui(callback: Callable[[ConfigParams, Callable[[int], None]], None]) -> ('QApplication', 'QWidget'):
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QApplication, QWidget, QFormLayout, QHBoxLayout, QVBoxLayout, QGroupBox, QLabel, \
|
||||
QLineEdit, QSpinBox, QProgressBar, QPushButton
|
||||
|
||||
# Main app
|
||||
app = QApplication([])
|
||||
|
||||
window = QWidget()
|
||||
window.setWindowTitle('Wakapi Sample Data Generator')
|
||||
window.setFixedSize(window.sizeHint())
|
||||
window.setMinimumWidth(350)
|
||||
|
||||
container_layout = QVBoxLayout()
|
||||
|
||||
# Top Controls
|
||||
form_layout_1 = QFormLayout()
|
||||
|
||||
url_input_label = QLabel('URL:')
|
||||
url_input = QLineEdit()
|
||||
url_input.setPlaceholderText('Wakatime API Url')
|
||||
url_input.setText('http://localhost:3000/api')
|
||||
|
||||
api_key_input_label = QLabel('API Key:')
|
||||
api_key_input = QLineEdit()
|
||||
api_key_input.setPlaceholderText(f'{"x"*8}-{"x"*4}-{"x"*4}-{"x"*4}-{"x"*12}')
|
||||
|
||||
form_layout_1.addRow(url_input_label, url_input)
|
||||
form_layout_1.addRow(api_key_input_label, api_key_input)
|
||||
|
||||
# Middle controls
|
||||
form_layout_2 = QFormLayout()
|
||||
params_container = QGroupBox('Parameters')
|
||||
params_container.setLayout(form_layout_2)
|
||||
|
||||
heartbeats_input_label = QLabel('# Heartbeats')
|
||||
heartbeats_input = QSpinBox()
|
||||
heartbeats_input.setMaximum(2147483647)
|
||||
heartbeats_input.setValue(100)
|
||||
|
||||
projects_input_label = QLabel('# Projects:')
|
||||
projects_input = QSpinBox()
|
||||
projects_input.setMinimum(1)
|
||||
projects_input.setValue(5)
|
||||
|
||||
offset_input_label = QLabel('Time Offset (hrs):')
|
||||
offset_input = QSpinBox()
|
||||
offset_input.setMinimum(-2147483647)
|
||||
offset_input.setMaximum(0)
|
||||
offset_input.setValue(-12)
|
||||
|
||||
seed_input_label = QLabel('Random Seed:')
|
||||
seed_input = QSpinBox()
|
||||
seed_input.setMaximum(2147483647)
|
||||
seed_input.setValue(1337)
|
||||
|
||||
form_layout_2.addRow(heartbeats_input_label, heartbeats_input)
|
||||
form_layout_2.addRow(projects_input_label, projects_input)
|
||||
form_layout_2.addRow(offset_input_label, offset_input)
|
||||
form_layout_2.addRow(seed_input_label, seed_input)
|
||||
|
||||
# Bottom controls
|
||||
bottom_layout = QHBoxLayout()
|
||||
|
||||
start_button = QPushButton('Generate')
|
||||
progress_bar = QProgressBar()
|
||||
|
||||
bottom_layout.addWidget(progress_bar)
|
||||
bottom_layout.addWidget(start_button)
|
||||
|
||||
# Wiring up
|
||||
container_layout.addLayout(form_layout_1)
|
||||
container_layout.addWidget(params_container)
|
||||
container_layout.addLayout(bottom_layout)
|
||||
container_layout.setStretch(1, 1)
|
||||
|
||||
window.setLayout(container_layout)
|
||||
|
||||
# Done dialog
|
||||
done_dialog = QWidget()
|
||||
done_dialog.setWindowTitle('Done')
|
||||
done_ok_button = QPushButton('Ok')
|
||||
done_layout = QVBoxLayout()
|
||||
done_layout.addWidget(QLabel('Done!'), alignment=Qt.AlignCenter)
|
||||
done_layout.addWidget(done_ok_button, alignment=Qt.AlignCenter)
|
||||
done_dialog.setFixedSize(done_dialog.sizeHint())
|
||||
done_ok_button.clicked.connect(done_dialog.close)
|
||||
done_dialog.setLayout(done_layout)
|
||||
|
||||
# Logic
|
||||
def parse_params() -> ConfigParams:
|
||||
params = ConfigParams()
|
||||
params.api_url = url_input.text()
|
||||
params.api_key = api_key_input.text()
|
||||
params.n = heartbeats_input.value()
|
||||
params.n_projects = projects_input.value()
|
||||
params.offset = offset_input.value()
|
||||
params.seed = seed_input.value()
|
||||
return params
|
||||
|
||||
def update_progress(inc=1):
|
||||
current = progress_bar.value()
|
||||
updated = current + inc
|
||||
if updated == progress_bar.maximum() - 1:
|
||||
progress_bar.setValue(0)
|
||||
start_button.setEnabled(True)
|
||||
done_dialog.show()
|
||||
return
|
||||
progress_bar.setValue(updated)
|
||||
|
||||
def call_back():
|
||||
params = parse_params()
|
||||
progress_bar.setMaximum(params.n)
|
||||
start_button.setEnabled(False)
|
||||
callback(params, update_progress)
|
||||
|
||||
start_button.clicked.connect(call_back)
|
||||
|
||||
return app, window
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser(description='Wakapi test data insertion script.')
|
||||
parser.add_argument('--headless', default=False, help='do not show a gui', action='store_true')
|
||||
parser.add_argument('-n', type=int, default=20, help='total number of random heartbeats to generate and insert')
|
||||
parser.add_argument('-u', '--url', type=str, default='http://localhost:3000/api',
|
||||
help='url of your api\'s heartbeats endpoint')
|
||||
parser.add_argument('-k', '--apikey', type=str,
|
||||
help='your api key (to get one, go to the web interface, create a new user, log in and copy the key)')
|
||||
parser.add_argument('-p', '--projects', type=int, default=5, help='number of different fake projects to generate')
|
||||
parser.add_argument('-o', '--offset', type=int, default=24,
|
||||
help='negative time offset in hours from now for to be used as an interval within which to generate heartbeats for')
|
||||
parser.add_argument('-s', '--seed', type=int, default=2020,
|
||||
help='a seed for initializing the pseudo-random number generator')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def args_to_params(parsed_args: argparse.Namespace) -> (ConfigParams, bool):
|
||||
params = ConfigParams()
|
||||
params.n = parsed_args.n
|
||||
params.n_projects = parsed_args.projects
|
||||
params.offset = parsed_args.offset
|
||||
params.seed = parsed_args.seed
|
||||
params.api_url = parsed_args.url
|
||||
params.api_key = parsed_args.apikey
|
||||
return params, not parsed_args.headless
|
||||
|
||||
|
||||
def randomword(length: int) -> str:
|
||||
letters = string.ascii_lowercase + 'äöü💩' # test utf8 and utf8mb4 characters as well
|
||||
return ''.join(random.choice(letters) for _ in range(length))
|
||||
|
||||
|
||||
def run(params: ConfigParams, update_progress: Callable[[int], None]):
|
||||
random.seed(params.seed)
|
||||
data: List[Heartbeat] = generate_data(
|
||||
params.n,
|
||||
params.n_projects,
|
||||
params.offset * -1 if params.offset < 0 else params.offset
|
||||
)
|
||||
|
||||
for d in data:
|
||||
post_data_sync([d], f'{params.api_url}/heartbeats', params.api_key)
|
||||
update_progress(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
params, show_gui = args_to_params(parse_arguments())
|
||||
if show_gui:
|
||||
app, window = make_gui(callback=run)
|
||||
window.show()
|
||||
app.exec()
|
||||
else:
|
||||
pbar = tqdm(total=params.n)
|
||||
run(params, pbar.update)
|
@ -149,7 +149,7 @@ func generateUserJobs(userId string, from time.Time, jobs chan<- *AggregationJob
|
||||
var to time.Time
|
||||
|
||||
// Go to next day of either user's first heartbeat or latest aggregation
|
||||
from.Add(-1 * time.Second)
|
||||
from = from.Add(-1 * time.Second)
|
||||
from = time.Date(
|
||||
from.Year(),
|
||||
from.Month(),
|
||||
|
@ -27,7 +27,18 @@ func (srv *HeartbeatService) Insert(heartbeat *models.Heartbeat) error {
|
||||
}
|
||||
|
||||
func (srv *HeartbeatService) InsertBatch(heartbeats []*models.Heartbeat) error {
|
||||
return srv.repository.InsertBatch(heartbeats)
|
||||
hashes := make(map[string]bool)
|
||||
|
||||
// https://github.com/muety/wakapi/issues/139
|
||||
filteredHeartbeats := make([]*models.Heartbeat, 0, len(heartbeats))
|
||||
for _, hb := range heartbeats {
|
||||
if _, ok := hashes[hb.Hash]; !ok {
|
||||
filteredHeartbeats = append(filteredHeartbeats, hb)
|
||||
hashes[hb.Hash] = true
|
||||
}
|
||||
}
|
||||
|
||||
return srv.repository.InsertBatch(filteredHeartbeats)
|
||||
}
|
||||
|
||||
func (srv *HeartbeatService) Count() (int64, error) {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/muety/wakapi/config"
|
||||
@ -17,7 +18,13 @@ import (
|
||||
)
|
||||
|
||||
const OriginWakatime = "wakatime"
|
||||
const maxWorkers = 6
|
||||
const (
|
||||
// wakatime api permits a max. rate of 10 req / sec
|
||||
// https://github.com/wakatime/wakatime/issues/261
|
||||
// with 5 workers, each sleeping slightly over 1/2 sec after every req., we should stay well below that limit
|
||||
maxWorkers = 5
|
||||
throttleDelay = 550 * time.Millisecond
|
||||
)
|
||||
|
||||
type WakatimeHeartbeatImporter struct {
|
||||
ApiKey string
|
||||
@ -72,11 +79,12 @@ func (w *WakatimeHeartbeatImporter) Import(user *models.User, minFrom time.Time,
|
||||
|
||||
go func(day time.Time) {
|
||||
defer sem.Release(1)
|
||||
defer time.Sleep(throttleDelay)
|
||||
|
||||
d := day.Format(config.SimpleDateFormat)
|
||||
heartbeats, err := w.fetchHeartbeats(d)
|
||||
if err != nil {
|
||||
logbuch.Error("failed to fetch heartbeats for day '%s' and user '%s' – &v", day, user.ID, err)
|
||||
logbuch.Error("failed to fetch heartbeats for day '%s' and user '%s' – &v", d, user.ID, err)
|
||||
}
|
||||
|
||||
for _, h := range heartbeats {
|
||||
@ -113,6 +121,8 @@ func (w *WakatimeHeartbeatImporter) fetchHeartbeats(day string) ([]*wakatime.Hea
|
||||
res, err := httpClient.Do(w.withHeaders(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if res.StatusCode >= 400 {
|
||||
return nil, errors.New(fmt.Sprintf("got status %d from wakatime api", res.StatusCode))
|
||||
}
|
||||
|
||||
var heartbeatsData wakatime.HeartbeatsViewModel
|
||||
|
81
services/mail/mail.go
Normal file
81
services/mail/mail.go
Normal file
@ -0,0 +1,81 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"text/template"
|
||||
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/views"
|
||||
)
|
||||
|
||||
const (
|
||||
tplNamePasswordReset = "reset_password"
|
||||
tplNameImportNotification = "import_finished"
|
||||
subjectPasswordReset = "Wakapi – Password Reset"
|
||||
subjectImportNotification = "Wakapi – Data Import Finished"
|
||||
)
|
||||
|
||||
type PasswordResetTplData struct {
|
||||
ResetLink string
|
||||
}
|
||||
|
||||
type ImportNotificationTplData struct {
|
||||
PublicUrl string
|
||||
Duration string
|
||||
NumHeartbeats int
|
||||
}
|
||||
|
||||
// Factory
|
||||
func NewMailService() services.IMailService {
|
||||
config := conf.Get()
|
||||
if config.Mail.Enabled {
|
||||
if config.Mail.Provider == conf.MailProviderMailWhale {
|
||||
return NewMailWhaleService(config.Mail.MailWhale, config.Server.PublicUrl)
|
||||
} else if config.Mail.Provider == conf.MailProviderSmtp {
|
||||
return NewSMTPMailService(config.Mail.Smtp, config.Server.PublicUrl)
|
||||
}
|
||||
}
|
||||
return &NoopMailService{}
|
||||
}
|
||||
|
||||
func getPasswordResetTemplate(data PasswordResetTplData) (*bytes.Buffer, error) {
|
||||
tpl, err := loadTemplate(tplNamePasswordReset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rendered bytes.Buffer
|
||||
if err := tpl.Execute(&rendered, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rendered, nil
|
||||
}
|
||||
|
||||
func getImportNotificationTemplate(data ImportNotificationTplData) (*bytes.Buffer, error) {
|
||||
tpl, err := loadTemplate(tplNameImportNotification)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rendered bytes.Buffer
|
||||
if err := tpl.Execute(&rendered, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rendered, nil
|
||||
}
|
||||
|
||||
func loadTemplate(tplName string) (*template.Template, error) {
|
||||
tplFile, err := views.TemplateFiles.Open(fmt.Sprintf("mail/%s.tpl.html", tplName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tplFile.Close()
|
||||
|
||||
tplData, err := ioutil.ReadAll(tplFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return template.New(tplName).Parse(string(tplData))
|
||||
}
|
92
services/mail/mailwhale.go
Normal file
92
services/mail/mailwhale.go
Normal file
@ -0,0 +1,92 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MailWhaleMailService struct {
|
||||
publicUrl string
|
||||
config conf.MailwhaleMailConfig
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
type MailWhaleSendRequest struct {
|
||||
To []string `json:"to"`
|
||||
Subject string `json:"subject"`
|
||||
Text string `json:"text"`
|
||||
Html string `json:"html"`
|
||||
TemplateId string `json:"template_id"`
|
||||
TemplateVars map[string]string `json:"template_vars"`
|
||||
}
|
||||
|
||||
func NewMailWhaleService(config conf.MailwhaleMailConfig, publicUrl string) *MailWhaleMailService {
|
||||
return &MailWhaleMailService{
|
||||
publicUrl: publicUrl,
|
||||
config: config,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MailWhaleMailService) SendPasswordReset(recipient *models.User, resetLink string) error {
|
||||
template, err := getPasswordResetTemplate(PasswordResetTplData{ResetLink: resetLink})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.send(recipient.Email, subjectPasswordReset, template.String(), true)
|
||||
}
|
||||
|
||||
func (s *MailWhaleMailService) SendImportNotification(recipient *models.User, duration time.Duration, numHeartbeats int) error {
|
||||
template, err := getImportNotificationTemplate(ImportNotificationTplData{
|
||||
PublicUrl: s.publicUrl,
|
||||
Duration: fmt.Sprintf("%.0f seconds", duration.Seconds()),
|
||||
NumHeartbeats: numHeartbeats,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.send(recipient.Email, subjectImportNotification, template.String(), true)
|
||||
}
|
||||
|
||||
func (s *MailWhaleMailService) send(to, subject, body string, isHtml bool) error {
|
||||
if to == "" {
|
||||
return errors.New("not sending mail as recipient mail address seems to be invalid")
|
||||
}
|
||||
|
||||
sendRequest := &MailWhaleSendRequest{
|
||||
To: []string{to},
|
||||
Subject: subject,
|
||||
}
|
||||
if isHtml {
|
||||
sendRequest.Html = body
|
||||
} else {
|
||||
sendRequest.Text = body
|
||||
}
|
||||
payload, _ := json.Marshal(sendRequest)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/mail", s.config.Url), bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(s.config.ClientId, s.config.ClientSecret)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
res, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.StatusCode >= 400 {
|
||||
return errors.New(fmt.Sprintf("got status %d from mailwhale", res.StatusCode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
19
services/mail/noop.go
Normal file
19
services/mail/noop.go
Normal file
@ -0,0 +1,19 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"github.com/emvi/logbuch"
|
||||
"github.com/muety/wakapi/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NoopMailService struct{}
|
||||
|
||||
func (n *NoopMailService) SendPasswordReset(recipient *models.User, resetLink string) error {
|
||||
logbuch.Info("noop mail service doing nothing instead of sending password reset mail to %s", recipient.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NoopMailService) SendImportNotification(recipient *models.User, duration time.Duration, numHeartbeats int) error {
|
||||
logbuch.Info("noop mail service doing nothing instead of sending import notification mail to %s", recipient.ID)
|
||||
return nil
|
||||
}
|
117
services/mail/smtp.go
Normal file
117
services/mail/smtp.go
Normal file
@ -0,0 +1,117 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/emersion/go-sasl"
|
||||
"github.com/emersion/go-smtp"
|
||||
conf "github.com/muety/wakapi/config"
|
||||
"github.com/muety/wakapi/models"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SMTPMailService struct {
|
||||
publicUrl string
|
||||
config conf.SMTPMailConfig
|
||||
auth sasl.Client
|
||||
}
|
||||
|
||||
func NewSMTPMailService(config conf.SMTPMailConfig, publicUrl string) *SMTPMailService {
|
||||
return &SMTPMailService{
|
||||
publicUrl: publicUrl,
|
||||
config: config,
|
||||
auth: sasl.NewPlainClient(
|
||||
"",
|
||||
config.Username,
|
||||
config.Password,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SMTPMailService) SendPasswordReset(recipient *models.User, resetLink string) error {
|
||||
template, err := getPasswordResetTemplate(PasswordResetTplData{ResetLink: resetLink})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mail := &models.Mail{
|
||||
From: models.MailAddress(s.config.Sender),
|
||||
To: models.MailAddresses([]models.MailAddress{models.MailAddress(recipient.Email)}),
|
||||
Subject: subjectPasswordReset,
|
||||
}
|
||||
mail.WithHTML(template.String())
|
||||
|
||||
return s.send(s.config.ConnStr(), s.config.TLS, s.auth, mail.From.Raw(), mail.To.RawStrings(), mail.Reader())
|
||||
}
|
||||
|
||||
func (s *SMTPMailService) SendImportNotification(recipient *models.User, duration time.Duration, numHeartbeats int) error {
|
||||
template, err := getImportNotificationTemplate(ImportNotificationTplData{
|
||||
PublicUrl: s.publicUrl,
|
||||
Duration: fmt.Sprintf("%.0f seconds", duration.Seconds()),
|
||||
NumHeartbeats: numHeartbeats,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mail := &models.Mail{
|
||||
From: models.MailAddress(s.config.Sender),
|
||||
To: models.MailAddresses([]models.MailAddress{models.MailAddress(recipient.Email)}),
|
||||
Subject: subjectImportNotification,
|
||||
}
|
||||
mail.WithHTML(template.String())
|
||||
|
||||
return s.send(s.config.ConnStr(), s.config.TLS, s.auth, mail.From.Raw(), mail.To.RawStrings(), mail.Reader())
|
||||
}
|
||||
|
||||
func (s *SMTPMailService) send(addr string, tls bool, a sasl.Client, from string, to []string, r io.Reader) error {
|
||||
dial := smtp.Dial
|
||||
if tls {
|
||||
dial = func(addr string) (*smtp.Client, error) {
|
||||
return smtp.DialTLS(addr, nil)
|
||||
}
|
||||
}
|
||||
|
||||
c, err := dial(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer c.Close()
|
||||
|
||||
if ok, _ := c.Extension("STARTTLS"); ok {
|
||||
if err = c.StartTLS(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if a != nil {
|
||||
if ok, _ := c.Extension("AUTH"); !ok {
|
||||
return errors.New("smtp: server doesn't support AUTH")
|
||||
}
|
||||
if err = c.Auth(a); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = c.Mail(from, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, addr := range to {
|
||||
if err = c.Rcpt(addr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w, err := c.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(w, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Quit()
|
||||
}
|
1
services/mail/utils.go
Normal file
1
services/mail/utils.go
Normal file
@ -0,0 +1 @@
|
||||
package mail
|
@ -79,7 +79,7 @@ func (srv *MiscService) runCountTotalTime() error {
|
||||
|
||||
func (srv *MiscService) countTotalTimeWorker(jobs <-chan *CountTotalTimeJob, results chan<- *CountTotalTimeResult) {
|
||||
for job := range jobs {
|
||||
if result, err := srv.summaryService.Aliased(time.Time{}, time.Now(), &models.User{ID: job.UserID}, srv.summaryService.Retrieve); err != nil {
|
||||
if result, err := srv.summaryService.Aliased(time.Time{}, time.Now(), &models.User{ID: job.UserID}, srv.summaryService.Retrieve, false); err != nil {
|
||||
logbuch.Error("failed to count total for user %s: %v", job.UserID, err)
|
||||
} else {
|
||||
logbuch.Info("successfully counted total for user %s", job.UserID)
|
||||
|
@ -53,7 +53,7 @@ type ILanguageMappingService interface {
|
||||
}
|
||||
|
||||
type ISummaryService interface {
|
||||
Aliased(time.Time, time.Time, *models.User, SummaryRetriever) (*models.Summary, error)
|
||||
Aliased(time.Time, time.Time, *models.User, SummaryRetriever, bool) (*models.Summary, error)
|
||||
Retrieve(time.Time, time.Time, *models.User) (*models.Summary, error)
|
||||
Summarize(time.Time, time.Time, *models.User) (*models.Summary, error)
|
||||
GetLatestByUser() ([]*models.TimeByUser, error)
|
||||
@ -64,6 +64,8 @@ type ISummaryService interface {
|
||||
type IUserService interface {
|
||||
GetUserById(string) (*models.User, error)
|
||||
GetUserByKey(string) (*models.User, error)
|
||||
GetUserByEmail(string) (*models.User, error)
|
||||
GetUserByResetToken(string) (*models.User, error)
|
||||
GetAll() ([]*models.User, error)
|
||||
GetActive() ([]*models.User, error)
|
||||
Count() (int64, error)
|
||||
@ -73,5 +75,11 @@ type IUserService interface {
|
||||
ResetApiKey(*models.User) (*models.User, error)
|
||||
SetWakatimeApiKey(*models.User, string) (*models.User, error)
|
||||
MigrateMd5Password(*models.User, *models.Login) (*models.User, error)
|
||||
GenerateResetToken(*models.User) (*models.User, error)
|
||||
FlushCache()
|
||||
}
|
||||
|
||||
type IMailService interface {
|
||||
SendPasswordReset(*models.User, string) error
|
||||
SendImportNotification(*models.User, time.Duration, int) error
|
||||
}
|
||||
|
@ -36,10 +36,10 @@ func NewSummaryService(summaryRepo repositories.ISummaryRepository, heartbeatSer
|
||||
|
||||
// Public summary generation methods
|
||||
|
||||
func (srv *SummaryService) Aliased(from, to time.Time, user *models.User, f SummaryRetriever) (*models.Summary, error) {
|
||||
func (srv *SummaryService) Aliased(from, to time.Time, user *models.User, f SummaryRetriever, skipCache bool) (*models.Summary, error) {
|
||||
// Check cache
|
||||
cacheKey := srv.getHash(from.String(), to.String(), user.ID, "--aliased")
|
||||
if cacheResult, ok := srv.cache.Get(cacheKey); ok {
|
||||
if cacheResult, ok := srv.cache.Get(cacheKey); ok && !skipCache {
|
||||
return cacheResult.(*models.Summary), nil
|
||||
}
|
||||
|
||||
@ -312,8 +312,17 @@ func (srv *SummaryService) getMissingIntervals(from, to time.Time, summaries []*
|
||||
}
|
||||
|
||||
// round to end of day / start of day, assuming that summaries are always generated on a per-day basis
|
||||
// we assume that, if summary for any time range within a day is present, no further heartbeats exist on that day before 'from' and after 'to' time of that summary
|
||||
// this requires that a summary exists for every single day in a year and none is skipped, which shouldn't ever happen
|
||||
td1 := time.Date(t1.Year(), t1.Month(), t1.Day()+1, 0, 0, 0, 0, t1.Location())
|
||||
td2 := time.Date(t2.Year(), t2.Month(), t2.Day(), 0, 0, 0, 0, t2.Location())
|
||||
|
||||
// we always want to jump to beginning of next day
|
||||
// however, if left summary ends already at midnight, we would instead jump to beginning of second-next day -> go back again
|
||||
if td1.Sub(t1) == 24*time.Hour {
|
||||
td1 = td1.Add(-1 * time.Hour)
|
||||
}
|
||||
|
||||
// one or more day missing in between?
|
||||
if td1.Before(td2) {
|
||||
intervals = append(intervals, &models.Interval{summaries[i].ToTime.T(), summaries[i+1].FromTime.T()})
|
||||
|
@ -235,6 +235,60 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Retrieve() {
|
||||
assert.Equal(suite.T(), 150*time.Second+90*time.Minute, result.TotalTime())
|
||||
assert.Equal(suite.T(), 150*time.Second+45*time.Minute, result.TotalTimeByKey(models.SummaryProject, TestProject1))
|
||||
assert.Equal(suite.T(), 45*time.Minute, result.TotalTimeByKey(models.SummaryProject, TestProject2))
|
||||
suite.HeartbeatService.AssertNumberOfCalls(suite.T(), "GetAllWithin", 2+1)
|
||||
|
||||
/* TEST 3 */
|
||||
from = time.Date(suite.TestStartTime.Year(), suite.TestStartTime.Month(), suite.TestStartTime.Day()+1, 0, 0, 0, 0, suite.TestStartTime.Location()) // start of next day
|
||||
to = time.Date(from.Year(), from.Month(), from.Day()+2, 13, 30, 0, 0, from.Location()) // noon of third-next day
|
||||
summaries = []*models.Summary{
|
||||
{
|
||||
ID: uint(rand.Uint32()),
|
||||
UserID: TestUserId,
|
||||
FromTime: models.CustomTime(from),
|
||||
ToTime: models.CustomTime(from.Add(24 * time.Hour)),
|
||||
Projects: []*models.SummaryItem{
|
||||
{
|
||||
Type: models.SummaryProject,
|
||||
Key: TestProject1,
|
||||
Total: 45 * time.Minute / time.Second, // hack
|
||||
},
|
||||
},
|
||||
Languages: []*models.SummaryItem{},
|
||||
Editors: []*models.SummaryItem{},
|
||||
OperatingSystems: []*models.SummaryItem{},
|
||||
Machines: []*models.SummaryItem{},
|
||||
},
|
||||
{
|
||||
ID: uint(rand.Uint32()),
|
||||
UserID: TestUserId,
|
||||
FromTime: models.CustomTime(to.Add(-2 * time.Hour)),
|
||||
ToTime: models.CustomTime(to),
|
||||
Projects: []*models.SummaryItem{
|
||||
{
|
||||
Type: models.SummaryProject,
|
||||
Key: TestProject2,
|
||||
Total: 45 * time.Minute / time.Second, // hack
|
||||
},
|
||||
},
|
||||
Languages: []*models.SummaryItem{},
|
||||
Editors: []*models.SummaryItem{},
|
||||
OperatingSystems: []*models.SummaryItem{},
|
||||
Machines: []*models.SummaryItem{},
|
||||
},
|
||||
}
|
||||
|
||||
suite.SummaryRepository.On("GetByUserWithin", suite.TestUser, from, to).Return(summaries, nil)
|
||||
suite.HeartbeatService.On("GetAllWithin", summaries[0].ToTime.T(), summaries[1].FromTime.T(), suite.TestUser).Return(filter(summaries[0].ToTime.T(), summaries[1].FromTime.T(), suite.TestHeartbeats), nil)
|
||||
|
||||
result, err = sut.Retrieve(from, to, suite.TestUser)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.NotNil(suite.T(), result)
|
||||
assert.Len(suite.T(), result.Projects, 2)
|
||||
assert.Equal(suite.T(), 90*time.Minute, result.TotalTime())
|
||||
assert.Equal(suite.T(), 45*time.Minute, result.TotalTimeByKey(models.SummaryProject, TestProject1))
|
||||
assert.Equal(suite.T(), 45*time.Minute, result.TotalTimeByKey(models.SummaryProject, TestProject2))
|
||||
suite.HeartbeatService.AssertNumberOfCalls(suite.T(), "GetAllWithin", 2+1+1)
|
||||
}
|
||||
|
||||
func (suite *SummaryServiceTestSuite) TestSummaryService_Aliased() {
|
||||
@ -253,7 +307,7 @@ func (suite *SummaryServiceTestSuite) TestSummaryService_Aliased() {
|
||||
suite.AliasService.On("GetAliasOrDefault", TestUserId, models.SummaryProject, TestProject1).Return(TestProject2, nil)
|
||||
suite.AliasService.On("GetAliasOrDefault", TestUserId, mock.Anything, mock.Anything).Return("", nil)
|
||||
|
||||
result, err = sut.Aliased(from, to, suite.TestUser, sut.Summarize)
|
||||
result, err = sut.Aliased(from, to, suite.TestUser, sut.Summarize, false)
|
||||
|
||||
assert.Nil(suite.T(), err)
|
||||
assert.NotNil(suite.T(), result)
|
||||
|
@ -52,6 +52,14 @@ func (srv *UserService) GetUserByKey(key string) (*models.User, error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (srv *UserService) GetUserByEmail(email string) (*models.User, error) {
|
||||
return srv.repository.GetByEmail(email)
|
||||
}
|
||||
|
||||
func (srv *UserService) GetUserByResetToken(resetToken string) (*models.User, error) {
|
||||
return srv.repository.GetByResetToken(resetToken)
|
||||
}
|
||||
|
||||
func (srv *UserService) GetAll() ([]*models.User, error) {
|
||||
return srv.repository.GetAll()
|
||||
}
|
||||
@ -68,6 +76,7 @@ func (srv *UserService) Count() (int64, error) {
|
||||
func (srv *UserService) CreateOrGet(signup *models.Signup, isAdmin bool) (*models.User, bool, error) {
|
||||
u := &models.User{
|
||||
ID: signup.Username,
|
||||
Email: signup.Email,
|
||||
ApiKey: uuid.NewV4().String(),
|
||||
Password: signup.Password,
|
||||
IsAdmin: isAdmin,
|
||||
@ -109,6 +118,10 @@ func (srv *UserService) MigrateMd5Password(user *models.User, login *models.Logi
|
||||
return srv.repository.UpdateField(user, "password", user.Password)
|
||||
}
|
||||
|
||||
func (srv *UserService) GenerateResetToken(user *models.User) (*models.User, error) {
|
||||
return srv.repository.UpdateField(user, "reset_token", uuid.NewV4())
|
||||
}
|
||||
|
||||
func (srv *UserService) Delete(user *models.User) error {
|
||||
srv.cache.Flush()
|
||||
return srv.repository.Delete(user)
|
||||
|
@ -1,3 +1,3 @@
|
||||
sonar.exclusions=**/*_test.go,.idea/**,.vscode/**,mocks/**
|
||||
sonar.exclusions=**/*_test.go,.idea/**,.vscode/**,mocks/**,static/**
|
||||
sonar.tests=.
|
||||
sonar.go.coverage.reportPaths=coverage/coverage.out
|
@ -112,7 +112,7 @@ function draw(subselection) {
|
||||
xAxes: [{
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: 'Minutes'
|
||||
labelString: 'Seconds'
|
||||
}
|
||||
}]
|
||||
},
|
||||
@ -371,21 +371,6 @@ function copyApiKey(event) {
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
// https://koddsson.com/posts/emoji-favicon/
|
||||
const favicon = document.querySelector('link[rel=icon]')
|
||||
if (favicon) {
|
||||
const emoji = favicon.getAttribute('data-emoji')
|
||||
if (emoji) {
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.height = 64
|
||||
canvas.width = 64
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx.font = '64px serif'
|
||||
ctx.fillText(emoji, 0, 64)
|
||||
favicon.href = canvas.toDataURL()
|
||||
}
|
||||
}
|
||||
|
||||
// Click outside
|
||||
window.addEventListener('click', function (event) {
|
||||
if (event.target.classList.contains('popup')) {
|
||||
|
105251
static/assets/vendor/tailwind.css
vendored
Normal file
105251
static/assets/vendor/tailwind.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1267
static/assets/vendor/tailwind.dist.css
vendored
Normal file
1267
static/assets/vendor/tailwind.dist.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
static/assets/vendor/tailwind.min.css
vendored
1
static/assets/vendor/tailwind.min.css
vendored
File diff suppressed because one or more lines are too long
7
tailwind.config.js
Normal file
7
tailwind.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
purge: {
|
||||
enabled: true,
|
||||
mode: 'all',
|
||||
content: ['./views/*.tpl.html']
|
||||
}
|
||||
}
|
@ -71,7 +71,7 @@ func ResolveInterval(interval *models.IntervalKey) (err error, from, to time.Tim
|
||||
}
|
||||
|
||||
func ParseSummaryParams(r *http.Request) (*models.SummaryParams, error) {
|
||||
user := r.Context().Value(models.UserKey).(*models.User)
|
||||
user := extractUser(r)
|
||||
params := r.URL.Query()
|
||||
|
||||
var err error
|
||||
@ -108,3 +108,13 @@ func ParseSummaryParams(r *http.Request) (*models.SummaryParams, error) {
|
||||
Recompute: recompute,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func extractUser(r *http.Request) *models.User {
|
||||
type principalGetter interface {
|
||||
GetPrincipal() *models.User
|
||||
}
|
||||
if p := r.Context().Value("principal"); p != nil {
|
||||
return p.(principalGetter).GetPrincipal()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
1.24.0
|
||||
1.26.3
|
||||
|
@ -1,2 +1,2 @@
|
||||
<script src="assets/vendor/seedrandom.min.js" integrity="sha384-bFS5CG904xYIgxBcrDF4KFNXuM7KeSGsSvS/QTaDqMTEdbaaxjg2Y2TSU3Ygs7wG" crossorigin="anonymous"></script>
|
||||
<script src="assets/vendor/Chart.bundle.min.js" integrity="sha384-mZ3q69BYmd4GxHp59G3RrsaFdWDxVSoqd7oVYuWRm2qiXrduT63lQtlhdD9lKbm3" crossorigin="anonymous"></script>
|
||||
<script src="assets/vendor/seedrandom.min.js"></script>
|
||||
<script src="assets/vendor/Chart.bundle.min.js"></script>
|
@ -2,11 +2,19 @@
|
||||
<title>Wakapi – Coding Statistics</title>
|
||||
<base href="{{ getBasePath }}/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
|
||||
<meta property="og:title" content="Wakapi - Coding Statistics" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:description" content="Wakapi is an open-source tool that helps you keep track of the time you have spent coding on different projects in different programming languages and more. Ideal for statistics freaks and anyone else.">
|
||||
<meta property="og:image" content="assets/images/screenshot.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/images/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="assets/images/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="assets/images/favicon-16x16.png">
|
||||
<link rel="manifest" href="assets/site.webmanifest">
|
||||
<link href="assets/vendor/roboto.css" rel="stylesheet">
|
||||
<link href="assets/vendor/tailwind.min.css" rel="stylesheet">
|
||||
{{ if isDev }}
|
||||
<link href="assets/vendor/tailwind.css" rel="stylesheet">
|
||||
{{ else }}
|
||||
<link href="assets/vendor/tailwind.dist.css" rel="stylesheet">
|
||||
{{ end }}
|
||||
<link href="assets/app.css" rel="stylesheet">
|
||||
</head>
|
@ -46,6 +46,10 @@
|
||||
<button type="button" class="py-1 px-3 h-8 rounded border border-green-700 text-white">📡 Host it
|
||||
</button>
|
||||
</a>
|
||||
<a href="https://liberapay.com/muety" target="_blank" rel="noopener noreferrer">
|
||||
<button type="button" class="py-1 px-3 h-8 rounded border border-green-700 text-white">🙏 Support it
|
||||
</button>
|
||||
</a>
|
||||
<a href="https://github.com/muety/wakapi" target="_blank" rel="noopener noreferrer">
|
||||
<button type="button" class="py-1 px-3 h-8 rounded border border-green-700 text-white">
|
||||
<img alt="GitHub Icon" src="assets/images/ghicon.svg" width="22px">
|
||||
|
@ -7,8 +7,12 @@
|
||||
|
||||
{{ template "header.tpl.html" . }}
|
||||
|
||||
<div class="flex items-center justify-center">
|
||||
<h1 class="font-semibold text-xl text-white m-0 border-b-4 border-green-700">Login</h1>
|
||||
<div class="w-full flex justify-center">
|
||||
<div class="flex items-center justify-between max-w-lg flex-grow">
|
||||
<div><a onclick="window.history.back()" class="text-gray-500 text-sm cursor-pointer">← Go back</a></div>
|
||||
<div><h1 class="font-semibold text-2xl text-white m-0 border-b-4 border-green-700">Login</h1></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "alerts.tpl.html" . }}
|
||||
@ -28,11 +32,16 @@
|
||||
type="password" id="password"
|
||||
name="password" placeholder="******" minlength="6" required>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<a href="signup">
|
||||
<button type="button" class="py-1 px-3 rounded border border-green-700 text-white text-sm">Sign up</button>
|
||||
<div class="flex justify-between items-center">
|
||||
<a href="reset-password" class="text-gray-500 text-sm">
|
||||
Forgot password?
|
||||
</a>
|
||||
<button type="submit" class="py-1 px-3 rounded bg-green-700 hover:bg-green-800 text-white text-sm">Log in</button>
|
||||
<div class="flex space-x-2">
|
||||
<a href="signup">
|
||||
<button type="button" class="py-1 px-3 rounded border border-green-700 text-white text-sm">Sign up</button>
|
||||
</a>
|
||||
<button type="submit" class="py-1 px-3 rounded bg-green-700 hover:bg-green-800 text-white text-sm">Log in</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
152
views/mail/import_finished.tpl.html
Normal file
152
views/mail/import_finished.tpl.html
Normal file
@ -0,0 +1,152 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Wakapi – Data Import Finished</title>
|
||||
<style>
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class=body] h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
table[class=body] p,
|
||||
table[class=body] ul,
|
||||
table[class=body] ol,
|
||||
table[class=body] td,
|
||||
table[class=body] span,
|
||||
table[class=body] a {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
table[class=body] .wrapper,
|
||||
table[class=body] .article {
|
||||
padding: 10px !important;
|
||||
}
|
||||
table[class=body] .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
table[class=body] .container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
table[class=body] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
table[class=body] .btn table {
|
||||
width: 100% !important;
|
||||
}
|
||||
table[class=body] .btn a {
|
||||
width: 100% !important;
|
||||
}
|
||||
table[class=body] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
PRESERVE THESE STYLES IN THE HEAD
|
||||
------------------------------------- */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
#MessageViewBody a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
.btn-primary table td:hover {
|
||||
background-color: #047857 !important;
|
||||
}
|
||||
.btn-primary a:hover {
|
||||
background-color: #047857 !important;
|
||||
border-color: #047857 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="" style="background-color: #f6f6f6; font-family: sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: #f6f6f6;">
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;"> </td>
|
||||
<td class="container" style="font-family: sans-serif; font-size: 14px; vertical-align: top; display: block; Margin: 0 auto; max-width: 580px; padding: 10px; width: 580px;">
|
||||
<div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 580px; padding: 10px;">
|
||||
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #ffffff; border-radius: 3px;">
|
||||
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;">
|
||||
<p style="font-family: sans-serif; font-size: 18px; font-weight: 500; margin: 0; Margin-bottom: 15px;">Data import finished</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">You have requested to import data from WakaTime to Wakapi. The import has now finished after {{ .Duration }} ({{ .NumHeartbeats }} new heartbeats imported).<br><br>You should be able to see the newly imported coding statistics in Wakapi.</p>
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-family: sans-serif; font-size: 14px; vertical-align: top; padding-bottom: 15px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top; background-color: #2F855A; border-radius: 5px; text-align: center;"> <a href="{{ .PublicUrl }}" target="_blank" style="display: inline-block; color: #ffffff; background-color: #2F855A; border: solid 1px #2F855A; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px; text-transform: capitalize; border-color: #2F855A;">Go to dashboard</a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<div class="footer" style="clear: both; Margin-top: 10px; text-align: center; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; color: #999999; text-align: center;">
|
||||
Powered by <a href="https://wakapi.dev" style="color: #999999; font-size: 12px; text-align: center; text-decoration: none;">Wakapi.dev</a>.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- END FOOTER -->
|
||||
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
153
views/mail/reset_password.tpl.html
Normal file
153
views/mail/reset_password.tpl.html
Normal file
@ -0,0 +1,153 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Wakapi – Reset Password</title>
|
||||
<style>
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class=body] h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
table[class=body] p,
|
||||
table[class=body] ul,
|
||||
table[class=body] ol,
|
||||
table[class=body] td,
|
||||
table[class=body] span,
|
||||
table[class=body] a {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
table[class=body] .wrapper,
|
||||
table[class=body] .article {
|
||||
padding: 10px !important;
|
||||
}
|
||||
table[class=body] .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
table[class=body] .container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
table[class=body] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
table[class=body] .btn table {
|
||||
width: 100% !important;
|
||||
}
|
||||
table[class=body] .btn a {
|
||||
width: 100% !important;
|
||||
}
|
||||
table[class=body] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
PRESERVE THESE STYLES IN THE HEAD
|
||||
------------------------------------- */
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
#MessageViewBody a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
.btn-primary table td:hover {
|
||||
background-color: #047857 !important;
|
||||
}
|
||||
.btn-primary a:hover {
|
||||
background-color: #047857 !important;
|
||||
border-color: #047857 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="" style="background-color: #f6f6f6; font-family: sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: #f6f6f6;">
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;"> </td>
|
||||
<td class="container" style="font-family: sans-serif; font-size: 14px; vertical-align: top; display: block; Margin: 0 auto; max-width: 580px; padding: 10px; width: 580px;">
|
||||
<div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 580px; padding: 10px;">
|
||||
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #ffffff; border-radius: 3px;">
|
||||
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;">
|
||||
<p style="font-family: sans-serif; font-size: 18px; font-weight: 500; margin: 0; Margin-bottom: 15px;">Password Reset</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">You have requested to reset your Wakapi password. Please click the following link to proceed.</p>
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-family: sans-serif; font-size: 14px; vertical-align: top; padding-bottom: 15px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top; background-color: #2F855A; border-radius: 5px; text-align: center;"> <a href="{{ .ResetLink }}" target="_blank" style="display: inline-block; color: #ffffff; background-color: #2F855A; border: solid 1px #2F855A; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px; text-transform: capitalize; border-color: #2F855A;">Reset Password</a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">If you did not request a password change, please just ignore this mail.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<div class="footer" style="clear: both; Margin-top: 10px; text-align: center; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; color: #999999; text-align: center;">
|
||||
Powered by <a href="https://wakapi.dev" style="color: #999999; font-size: 12px; text-align: center; text-decoration: none;">Wakapi.dev</a>.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- END FOOTER -->
|
||||
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
42
views/reset-password.tpl.html
Normal file
42
views/reset-password.tpl.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
{{ template "head.tpl.html" . }}
|
||||
|
||||
<body class="bg-gray-850 text-gray-700 p-4 pt-10 flex flex-col min-h-screen max-w-screen-lg mx-auto justify-center">
|
||||
|
||||
{{ template "header.tpl.html" . }}
|
||||
|
||||
<div class="w-full flex justify-center">
|
||||
<div class="flex items-center justify-between max-w-lg flex-grow">
|
||||
<div><a onclick="window.history.back()" class="text-gray-500 text-sm cursor-pointer">← Go back</a></div>
|
||||
<div><h1 class="font-semibold text-2xl text-white m-0 border-b-4 border-green-700">Reset Password</h1></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "alerts.tpl.html" . }}
|
||||
|
||||
<main class="mt-10 flex-grow flex justify-center w-full">
|
||||
<div class="flex-grow max-w-lg mt-10">
|
||||
<form action="reset-password" method="post">
|
||||
<p class="text-sm text-white mb-8">If you forgot your password, enter the e-mail address you associated with Wakapi to reset it and choose a new one. You will receive an e-mail afterwards. Make sure to check your spam folder if it does not arrive after a few minutes.</p>
|
||||
<div class="mb-8">
|
||||
<label class="inline-block text-sm mb-1 text-gray-500" for="email">E-Mail</label>
|
||||
<input class="shadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded w-full py-1 px-3"
|
||||
type="email" id="email"
|
||||
name="email" placeholder="Enter your e-mail address" minlength="1" required autofocus>
|
||||
</div>
|
||||
<div class="flex justify-end items-center">
|
||||
<button type="submit" class="py-1 px-3 rounded bg-green-700 hover:bg-green-800 text-white text-sm">Reset</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{{ template "footer.tpl.html" . }}
|
||||
|
||||
{{ template "foot.tpl.html" . }}
|
||||
</body>
|
||||
|
||||
</html>
|
44
views/set-password.tpl.html
Normal file
44
views/set-password.tpl.html
Normal file
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
{{ template "head.tpl.html" . }}
|
||||
|
||||
<body class="bg-gray-850 text-gray-700 p-4 pt-10 flex flex-col min-h-screen max-w-screen-lg mx-auto justify-center">
|
||||
|
||||
{{ template "header.tpl.html" . }}
|
||||
|
||||
<div class="flex items-center justify-center">
|
||||
<h1 class="font-semibold text-xl text-white m-0 border-b-4 border-green-700">Choose a new password</h1>
|
||||
</div>
|
||||
|
||||
{{ template "alerts.tpl.html" . }}
|
||||
|
||||
<main class="mt-10 flex-grow flex justify-center w-full">
|
||||
<div class="flex-grow max-w-lg mt-10">
|
||||
<form action="set-password" method="post">
|
||||
<div class="mb-8">
|
||||
<label class="inline-block text-sm mb-1 text-gray-500" for="password">Password</label>
|
||||
<input class="shadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded w-full py-1 px-3"
|
||||
type="password" id="password"
|
||||
name="password" placeholder="Choose a password" minlength="6" required>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<label class="inline-block text-sm mb-1 text-gray-500" for="password_repeat">And again ...</label>
|
||||
<input class="shadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded w-full py-1 px-3"
|
||||
type="password" id="password_repeat"
|
||||
name="password_repeat" placeholder="Repeat your password" minlength="6" required>
|
||||
</div>
|
||||
<div class="flex justify-end items-center">
|
||||
<input type="hidden" name="token" value="{{ .Token }}">
|
||||
<button type="submit" class="py-1 px-3 rounded bg-green-700 hover:bg-green-800 text-white text-sm">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{{ template "footer.tpl.html" . }}
|
||||
|
||||
{{ template "foot.tpl.html" . }}
|
||||
</body>
|
||||
|
||||
</html>
|
@ -23,7 +23,7 @@
|
||||
|
||||
<div class="w-full flex justify-center">
|
||||
<div class="flex items-center justify-between max-w-2xl flex-grow">
|
||||
<div><a href="" class="text-gray-500 text-sm">← Go back</a></div>
|
||||
<div><a href="/" class="text-gray-500 text-sm cursor-pointer">← Go back</a></div>
|
||||
<div><h1 class="font-semibold text-2xl text-white m-0 border-b-4 border-green-700">Settings</h1></div>
|
||||
<div> </div>
|
||||
</div>
|
||||
@ -35,6 +35,32 @@
|
||||
<div class="flex flex-col flex-grow max-w-2xl mt-8">
|
||||
|
||||
<details class="my-8 pb-8 border-b border-gray-700">
|
||||
<summary class="cursor-pointer">
|
||||
<h2 class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block"
|
||||
id="email-heading">
|
||||
Change E-Mail Address
|
||||
</h2>
|
||||
</summary>
|
||||
<div class="w-full">
|
||||
<form class="mt-10" action="" method="post">
|
||||
<input type="hidden" name="action" value="update_user">
|
||||
<div class="mb-8 flex justify-between items-center space-x-4">
|
||||
<label class="inline-block text-sm text-gray-500" for="password_old">E-Mail Address</label>
|
||||
<input class="shadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded flex-grow py-1 px-3"
|
||||
type="email" id="email"
|
||||
name="email" placeholder="Enter your e-mail address"
|
||||
value="{{ .User.Email }}">
|
||||
<button type="submit"
|
||||
class="py-1 px-3 rounded bg-green-700 hover:bg-green-800 text-white text-sm">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-gray-300 text-sm">E-Mail address is optional, but required for some features that you cannot use else. Also, if you do not add an e-mail address, you will not be able to reset your password in case you forget it.</div>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="mb-8 pb-8 border-b border-gray-700">
|
||||
<summary class="cursor-pointer">
|
||||
<h2 class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block" id="password">
|
||||
Change Password
|
||||
|
@ -3,10 +3,13 @@
|
||||
|
||||
{{ template "head.tpl.html" . }}
|
||||
|
||||
<body class="bg-gray-800 text-gray-700 p-4 pt-10 flex flex-col min-h-screen max-w-screen-xl mx-auto justify-center">
|
||||
<body class="bg-gray-850 text-gray-700 p-4 pt-10 flex flex-col min-h-screen max-w-screen-lg mx-auto justify-center">
|
||||
|
||||
{{ template "header.tpl.html" . }}
|
||||
|
||||
<div class="w-full flex justify-center">
|
||||
<div class="flex items-center justify-between max-w-4xl flex-grow">
|
||||
<div><a href="" class="text-gray-500 text-sm">← Go back</a></div>
|
||||
<div class="flex items-center justify-between max-w-lg flex-grow">
|
||||
<div><a onclick="window.history.back()" class="text-gray-500 text-sm cursor-pointer">← Go back</a></div>
|
||||
<div><h1 class="font-semibold text-2xl text-white m-0 border-b-4 border-green-700">Sign Up</h1></div>
|
||||
<div></div>
|
||||
</div>
|
||||
@ -37,6 +40,13 @@
|
||||
type="text" id="username"
|
||||
name="username" placeholder="Choose a username" minlength="1" required autofocus>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<label class="inline-block text-sm mb-1 text-gray-500" for="email">E-Mail</label>
|
||||
<input class="shadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded w-full py-1 px-3"
|
||||
type="email" id="email"
|
||||
name="email" placeholder="Optionally add your e-mail address">
|
||||
<div class="text-xs text-gray-500 mt-2 italic">E-Mail address is optional, but required for some features that you cannot use else. Also, if you do not add an e-mail address, you will not be able to reset your password in case you forget it.</div>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<label class="inline-block text-sm mb-1 text-gray-500" for="password">Password</label>
|
||||
<input class="shadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded w-full py-1 px-3"
|
||||
|
@ -45,12 +45,12 @@
|
||||
<form class="text-white flex flex-nowrap items-center justify-center self-center max-w-xl flex-wrap space-x-8">
|
||||
<div class="flex space-x-1">
|
||||
<label for="from-date-picker" class="text-gray-300 pl-1">▶️ Start:</label>
|
||||
<input id="from-date-picker" type="date" name="from" class="text-sm text-gray-300 bg-gray-800 rounded-md text-center cursor-pointer"
|
||||
<input id="from-date-picker" type="date" name="from" max="{{ .ToTime.T | simpledate }}" class="text-sm text-gray-300 bg-gray-800 rounded-md text-center cursor-pointer"
|
||||
value="{{ .FromTime.T | simpledate }}" required>
|
||||
</div>
|
||||
<div class="flex space-x-1">
|
||||
<label for="to-date-picker" class="text-gray-300 pl-1">⏹️ End:</label>
|
||||
<input id="to-date-picker" type="date" name="to" class="text-sm text-gray-300 bg-gray-800 rounded-md text-center cursor-pointer"
|
||||
<input id="to-date-picker" type="date" name="to" min="{{ .FromTime.T | simpledate }}" class="text-sm text-gray-300 bg-gray-800 rounded-md text-center cursor-pointer"
|
||||
value="{{ .ToTime.T | simpledate }}" required>
|
||||
</div>
|
||||
<div>
|
||||
@ -88,11 +88,11 @@
|
||||
</span>
|
||||
|
||||
<div class="flex flex-wrap justify-center">
|
||||
<div class="w-full lg:w-1/2 p-1">
|
||||
<div class="w-full lg:w-1/2 p-1" style="max-width: 100vw;">
|
||||
<div class="p-4 pb-10 bg-gray-900 border border-gray-700 text-gray-300 rounded-md shadow m-2 flex flex-col" id="project-container" style="height: 300px">
|
||||
<div class="flex justify-between">
|
||||
<div class="w-1/4 flex-1"></div>
|
||||
<span class="font-semibold w-1/2 text-center flex-1">Projects</span>
|
||||
<span class="font-semibold w-1/2 text-center flex-1 whitespace-no-wrap">Projects</span>
|
||||
<div class="flex justify-end flex-1 text-xs items-center">
|
||||
<label for="project-top-picker" class="mr-1">Show: </label>
|
||||
<input type="number" min="1" id="project-top-picker" data-entity="0" class="w-1/4 top-picker bg-gray-800 rounded-md text-center" value="10">
|
||||
@ -104,11 +104,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full lg:w-1/2 p-1">
|
||||
<div class="w-full lg:w-1/2 p-1" style="max-width: 100vw;">
|
||||
<div class="p-4 pb-10 bg-gray-900 border border-gray-700 text-gray-300 rounded-md shadow m-2 flex flex-col" id="os-container" style="height: 300px">
|
||||
<div class="flex justify-between">
|
||||
<div class="w-1/4 flex-1"></div>
|
||||
<span class="font-semibold w-1/2 text-center flex-1">Operating Systems</span>
|
||||
<span class="font-semibold w-1/2 text-center flex-1 whitespace-no-wrap">Operating Systems</span>
|
||||
<div class="flex justify-end flex-1 text-xs items-center">
|
||||
<label for="os-top-picker" class="mr-1">Show: </label>
|
||||
<input type="number" min="1" id="os-top-picker" data-entity="1" class="w-1/4 top-picker bg-gray-800 rounded-md text-center" value="10">
|
||||
@ -120,11 +120,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full lg:w-1/2 p-1">
|
||||
<div class="w-full lg:w-1/2 p-1" style="max-width: 100vw;">
|
||||
<div class="p-4 pb-10 bg-gray-900 border border-gray-700 text-gray-300 rounded-md shadow m-2 flex flex-col relative" id="language-container" style="height: 300px">
|
||||
<div class="flex justify-between">
|
||||
<div class="w-1/4 flex-1"></div>
|
||||
<span class="font-semibold w-1/2 text-center flex-1">Languages</span>
|
||||
<span class="font-semibold w-1/2 text-center flex-1 whitespace-no-wrap">Languages</span>
|
||||
<div class="flex justify-end flex-1 text-xs items-center">
|
||||
<label for="language-top-picker" class="mr-1">Show: </label>
|
||||
<input type="number" min="1" id="language-top-picker" data-entity="3" class="w-1/4 top-picker bg-gray-800 rounded-md text-center" value="10">
|
||||
@ -136,11 +136,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full lg:w-1/2 p-1">
|
||||
<div class="w-full lg:w-1/2 p-1" style="max-width: 100vw;">
|
||||
<div class="p-4 pb-10 bg-gray-900 border border-gray-700 text-gray-300 rounded-md shadow m-2 flex flex-col" id="editor-container" style="height: 300px">
|
||||
<div class="flex justify-between">
|
||||
<div class="w-1/4 flex-1"></div>
|
||||
<span class="font-semibold w-1/2 text-center flex-1">Editors</span>
|
||||
<span class="font-semibold w-1/2 text-center flex-1 whitespace-no-wrap">Editors</span>
|
||||
<div class="flex justify-end flex-1 text-xs items-center">
|
||||
<label for="editor-top-picker" class="mr-1">Show: </label>
|
||||
<input type="number" min="1" id="editor-top-picker" data-entity="2" class="w-1/4 top-picker bg-gray-800 rounded-md text-center" value="10">
|
||||
@ -152,11 +152,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full lg:w-1/2 p-1">
|
||||
<div class="w-full lg:w-1/2 p-1" style="max-width: 100vw;">
|
||||
<div class="p-4 pb-10 bg-gray-900 border border-gray-700 text-gray-300 rounded-md shadow m-2 flex flex-col" id="machine-container" style="height: 300px">
|
||||
<div class="flex justify-between">
|
||||
<div class="w-1/4 flex-1"></div>
|
||||
<span class="font-semibold w-1/2 text-center flex-1">Machines</span>
|
||||
<span class="font-semibold w-1/2 text-center flex-1 whitespace-no-wrap">Machines</span>
|
||||
<div class="flex justify-end flex-1 text-xs items-center">
|
||||
<label for="machine-top-picker" class="mr-1">Show: </label>
|
||||
<input type="number" min="1" id="machine-top-picker" data-entity="4" class="w-1/4 top-picker bg-gray-800 rounded-md text-center" value="10">
|
||||
@ -209,8 +209,10 @@
|
||||
|
||||
{{ template "foot.tpl.html" . }}
|
||||
|
||||
<script type="text/javascript">
|
||||
document.querySelector('#api-key-instruction').innerHTML = document.querySelector('#api-key-container').value
|
||||
<script>
|
||||
document.addEventListener('load', function() {
|
||||
document.getElementById('api-key-instruction').innerHTML = document.getElementById('api-key-container').value
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@ -224,6 +226,17 @@
|
||||
wakapiData.editors = {{ .Editors | json }}
|
||||
wakapiData.languages = {{ .Languages | json }}
|
||||
wakapiData.machines = {{ .Machines | json }}
|
||||
|
||||
document.getElementById("to-date-picker").onchange = function () {
|
||||
var input = document.getElementById("from-date-picker");
|
||||
input.setAttribute("max", this.value);
|
||||
}
|
||||
|
||||
document.getElementById("from-date-picker").onchange = function () {
|
||||
var input = document.getElementById("to-date-picker");
|
||||
input.setAttribute("min", this.value);
|
||||
}
|
||||
|
||||
</script>
|
||||
<script src="assets/app.js"></script>
|
||||
|
||||
|
6
views/views.go
Normal file
6
views/views.go
Normal file
@ -0,0 +1,6 @@
|
||||
package views
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed *.html mail/*.html
|
||||
var TemplateFiles embed.FS
|
Reference in New Issue
Block a user