Compare commits
26 Commits
pkg/dev/ub
...
develop
Author | SHA1 | Date |
---|---|---|
wpetit | 888ff2ec47 | |
wpetit | 040a9d0e46 | |
wpetit | 4d2ca6bd3a | |
wpetit | 805c873695 | |
wpetit | 13d3991a10 | |
wpetit | 4871876bf0 | |
wpetit | d47b819e1d | |
Philippe Caseiro | f7bff1b697 | |
wpetit | 1ce43d2c76 | |
Benjamin Gaudé | 75213ebe6e | |
Benjamin Gaudé | 6a9c2fd13f | |
wpetit | 19e15d3fe7 | |
wpetit | db0a3ac98f | |
wpetit | b335a825a3 | |
wpetit | f959fdb93f | |
wpetit | 6a52595fa7 | |
wpetit | 450a9de82d | |
wpetit | 3afd252c71 | |
wpetit | 8027ba7bcc | |
wpetit | 61c3cd33be | |
wpetit | 62344993f5 | |
wpetit | 9035280818 | |
wpetit | 82f21d0298 | |
wpetit | 92baa742c4 | |
wpetit | 14770d7146 | |
wpetit | c07753b21a |
|
@ -6,3 +6,4 @@
|
||||||
/misc/docker/Dockerfile
|
/misc/docker/Dockerfile
|
||||||
/.env
|
/.env
|
||||||
/.env.dist
|
/.env.dist
|
||||||
|
/tools
|
|
@ -4,3 +4,5 @@
|
||||||
/bin
|
/bin
|
||||||
/node_modules
|
/node_modules
|
||||||
/.env
|
/.env
|
||||||
|
/tools
|
||||||
|
.mktools/
|
||||||
|
|
47
Makefile
47
Makefile
|
@ -1,7 +1,20 @@
|
||||||
build: vendor
|
DOCKER_REPOSITORY ?= reg.cadoles.com/cadoles
|
||||||
CGO_ENABLED=0 go build -mod=vendor -v -o bin/fake-smtp ./cmd/fake-smtp
|
|
||||||
|
YQ_VERSION ?= v4.30.4
|
||||||
|
YQ_BINARY ?= yq_linux_amd64
|
||||||
|
|
||||||
|
tools: tools/yq/bin/yq
|
||||||
|
|
||||||
|
tools/yq/bin/yq:
|
||||||
|
mkdir -p tools/yq/bin
|
||||||
|
wget https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY} -O tools/yq/bin/yq &&\
|
||||||
|
chmod +x tools/yq/bin/yq
|
||||||
|
|
||||||
|
build:
|
||||||
|
CGO_ENABLED=0 go build -v -o bin/fake-smtp ./cmd/fake-smtp
|
||||||
|
|
||||||
docker-image:
|
docker-image:
|
||||||
|
git tag -a $(MKT_PROJECT_VERSION) -m "v$(MKT_PROJECT_VERSION)" || exit 0
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg HTTP_PROXY=$(HTTP_PROXY) \
|
--build-arg HTTP_PROXY=$(HTTP_PROXY) \
|
||||||
--build-arg HTTPS_PROXY=$(HTTPS_PROXY) \
|
--build-arg HTTPS_PROXY=$(HTTPS_PROXY) \
|
||||||
|
@ -19,28 +32,30 @@ docker-run:
|
||||||
--tmpfs /app/data \
|
--tmpfs /app/data \
|
||||||
fake-smtp:latest
|
fake-smtp:latest
|
||||||
|
|
||||||
docker-release:
|
docker-release: .mktools
|
||||||
docker tag fake-smtp:latest bornholm/fake-smtp:latest
|
docker tag fake-smtp:latest $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_VERSION)
|
||||||
docker login
|
docker tag fake-smtp:latest $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_SHORT_VERSION)
|
||||||
docker push bornholm/fake-smtp:latest
|
docker tag fake-smtp:latest $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_VERSION_CHANNEL)-latest
|
||||||
|
docker tag fake-smtp:latest $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_SHORT_VERSION_CHANNEL)-latest
|
||||||
|
docker push $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_VERSION)
|
||||||
|
docker push $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_SHORT_VERSION)
|
||||||
|
docker push $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_VERSION_CHANNEL)-latest
|
||||||
|
docker push $(DOCKER_REPOSITORY)/fake-smtp:$(MKT_PROJECT_SHORT_VERSION_CHANNEL)-latest
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -v -race ./...
|
go test -v -race ./...
|
||||||
|
|
||||||
release: dist
|
release: dist tools
|
||||||
@./misc/script/release.sh
|
@./misc/script/release.sh
|
||||||
|
|
||||||
dist:
|
dist:
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
vendor:
|
|
||||||
go mod vendor
|
|
||||||
|
|
||||||
tidy:
|
tidy:
|
||||||
go mod tidy
|
go mod tidy
|
||||||
|
|
||||||
watch:
|
watch:
|
||||||
modd
|
go run github.com/cortesi/modd/cmd/modd@v0.8.1
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
golangci-lint run --enable-all
|
golangci-lint run --enable-all
|
||||||
|
@ -54,4 +69,14 @@ clean:
|
||||||
rm -rf vendor
|
rm -rf vendor
|
||||||
rm -rf bin
|
rm -rf bin
|
||||||
|
|
||||||
|
.PHONY: mktools
|
||||||
|
mktools:
|
||||||
|
rm -rf .mktools
|
||||||
|
curl -q https://forge.cadoles.com/Cadoles/mktools/raw/branch/master/install.sh | $(SHELL)
|
||||||
|
|
||||||
|
.mktools:
|
||||||
|
$(MAKE) mktools
|
||||||
|
|
||||||
|
-include .mktools/*.mk
|
||||||
|
|
||||||
.PHONY: lint watch build vendor tidy release
|
.PHONY: lint watch build vendor tidy release
|
56
README.md
56
README.md
|
@ -11,7 +11,7 @@ Serveur SMTP factice pour le développement avec interface web.
|
||||||
### Avec Docker
|
### Avec Docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -it --rm -p 8080:8080 -p 2525:2525 bornholm/fake-smtp
|
docker run -it --rm -p 8080:8080 -p 2525:2525 reg.cadoles.com/cadoles/fake-smtp
|
||||||
```
|
```
|
||||||
|
|
||||||
L'interface Web sera accessible à l'adresse http://localhost:8080/.
|
L'interface Web sera accessible à l'adresse http://localhost:8080/.
|
||||||
|
@ -55,6 +55,18 @@ smtp:
|
||||||
# Configuration du stockage
|
# Configuration du stockage
|
||||||
data:
|
data:
|
||||||
path: fakesmtp.db
|
path: fakesmtp.db
|
||||||
|
|
||||||
|
# Configuration du relais SMTP
|
||||||
|
relay:
|
||||||
|
enabled: false # Activer/désactiver le relais SMTP
|
||||||
|
address: "" # Adresse du serveur au format "host:port"
|
||||||
|
identity: "" # Identité du compte utilisateur, peut être laissé vide
|
||||||
|
username: "" # Identifiant du compte SMTP, non utilisé sur anonymous = true
|
||||||
|
password: "" # Mot de passe du compte SMTP, non utilisé sur anonymous = true
|
||||||
|
anonymous: false # Utiliser le mode d'authentification "anonyme"
|
||||||
|
useTLS: false # Utiliser TLS pour se connecter au serveur SMTP
|
||||||
|
insecureSkipVerify: true # Ne pas vérifier le certificat du serveur pour les connexions TLS/STARTTLS
|
||||||
|
fromOverride: "" # Surcharger l'adresse émetteur des courriels transmis
|
||||||
```
|
```
|
||||||
|
|
||||||
### Variables d'environnement
|
### Variables d'environnement
|
||||||
|
@ -63,28 +75,36 @@ La configuration de FakeSMTP peut être personnalisée via des variables d'envir
|
||||||
|
|
||||||
Les valeurs des variables d'environnement surchargent les valeurs présentes dans le fichier de configuration.
|
Les valeurs des variables d'environnement surchargent les valeurs présentes dans le fichier de configuration.
|
||||||
|
|
||||||
|Variable|Correspondance dans le fichier de configuration|
|
| Variable | Correspondance dans le fichier de configuration |
|
||||||
|--------|-----------------------------------------------|
|
| ------------------------------------- | ----------------------------------------------- |
|
||||||
|`FAKESMTP_HTTP_ADDRESS`|`http.address`|
|
| `FAKESMTP_HTTP_ADDRESS` | `http.address` |
|
||||||
|`FAKESMTP_HTTP_TEMPLATEDIR`|`http.templateDir`|
|
| `FAKESMTP_HTTP_TEMPLATEDIR` | `http.templateDir` |
|
||||||
|`FAKESMTP_HTTP_PUBLICDIR`|`http.publicDir`|
|
| `FAKESMTP_HTTP_PUBLICDIR` | `http.publicDir` |
|
||||||
|`FAKESMTP_SMTP_ADDRESS`|`smtp.address`|
|
| `FAKESMTP_SMTP_ADDRESS` | `smtp.address` |
|
||||||
|`FAKESMTP_SMTP_USERNAME`|`smtp.username`|
|
| `FAKESMTP_SMTP_USERNAME` | `smtp.username` |
|
||||||
|`FAKESMTP_SMTP_PASSWORD`|`smtp.password`|
|
| `FAKESMTP_SMTP_PASSWORD` | `smtp.password` |
|
||||||
|`FAKESMTP_SMTP_DOMAIN`|`smtp.domain`|
|
| `FAKESMTP_SMTP_DOMAIN` | `smtp.domain` |
|
||||||
|`FAKESMTP_SMTP_READTIMEOUT`|`smtp.readTimeout`|
|
| `FAKESMTP_SMTP_READTIMEOUT` | `smtp.readTimeout` |
|
||||||
|`FAKESMTP_SMTP_WRITETIMEOUT`|`smtp.writeTimeout`|
|
| `FAKESMTP_SMTP_WRITETIMEOUT` | `smtp.writeTimeout` |
|
||||||
|`FAKESMTP_SMTP_MAXMESSAGEBYTES`|`smtp.maxMessageBytes`|
|
| `FAKESMTP_SMTP_MAXMESSAGEBYTES` | `smtp.maxMessageBytes` |
|
||||||
|`FAKESMTP_SMTP_MAXRECIPIENTS`|`smtp.maxRecipients`|
|
| `FAKESMTP_SMTP_MAXRECIPIENTS` | `smtp.maxRecipients` |
|
||||||
|`FAKESMTP_SMTP_ALLOWINSECUREAUTH`|`smtp.allowInsecureAuth`|
|
| `FAKESMTP_SMTP_ALLOWINSECUREAUTH` | `smtp.allowInsecureAuth` |
|
||||||
|`FAKESMTP_SMTP_DEBUG`|`smtp.debug`|
|
| `FAKESMTP_SMTP_DEBUG` | `smtp.debug` |
|
||||||
|`FAKESMTP_DATA_PATH`|`data.path`|
|
| `FAKESMTP_DATA_PATH` | `data.path` |
|
||||||
|
| `FAKESMTP_RELAY_ENABLED` | `relay.enabled` |
|
||||||
|
| `FAKESMTP_RELAY_ADDRESS` | `relay.address` |
|
||||||
|
| `FAKESMTP_RELAY_IDENTITY` | `relay.identity` |
|
||||||
|
| `FAKESMTP_RELAY_USERNAME` | `relay.username` |
|
||||||
|
| `FAKESMTP_RELAY_PASSWORD` | `relay.password` |
|
||||||
|
| `FAKESMTP_RELAY_ANONYMOUS` | `relay.anonymous` |
|
||||||
|
| `FAKESMTP_RELAY_INSECURE_SKIP_VERIFY` | `relay.insecureSkipVerify` |
|
||||||
|
| `FAKESMTP_RELAY_FROM_OVERRIDE` | `relay.fromOverride` |
|
||||||
|
|
||||||
## Démarrer avec les sources
|
## Démarrer avec les sources
|
||||||
|
|
||||||
### Dépendances
|
### Dépendances
|
||||||
|
|
||||||
- Go 1.13
|
- Go 1.21
|
||||||
- modd
|
- modd
|
||||||
- make
|
- make
|
||||||
- NodeJS/npm
|
- NodeJS/npm
|
||||||
|
|
|
@ -3,10 +3,10 @@ package main
|
||||||
import (
|
import (
|
||||||
"gitlab.com/wpetit/goweb/template/html"
|
"gitlab.com/wpetit/goweb/template/html"
|
||||||
|
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/command"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/command"
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/config"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/config"
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/query"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/query"
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
"gitlab.com/wpetit/goweb/cqrs"
|
||||||
"gitlab.com/wpetit/goweb/service"
|
"gitlab.com/wpetit/goweb/service"
|
||||||
"gitlab.com/wpetit/goweb/service/build"
|
"gitlab.com/wpetit/goweb/service/build"
|
||||||
|
@ -21,7 +21,7 @@ func getServiceContainer(conf *config.Config) (*service.Container, error) {
|
||||||
|
|
||||||
// Create and expose template service provider
|
// Create and expose template service provider
|
||||||
ctn.Provide(template.ServiceName, html.ServiceProvider(
|
ctn.Provide(template.ServiceName, html.ServiceProvider(
|
||||||
conf.HTTP.TemplateDir,
|
html.NewDirectoryLoader(conf.HTTP.TemplateDir),
|
||||||
))
|
))
|
||||||
|
|
||||||
// Create and expose config service provider
|
// Create and expose config service provider
|
||||||
|
@ -53,6 +53,11 @@ func getServiceContainer(conf *config.Config) (*service.Container, error) {
|
||||||
cqrs.CommandHandlerFunc(command.HandleDeleteEmail),
|
cqrs.CommandHandlerFunc(command.HandleDeleteEmail),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bus.RegisterCommand(
|
||||||
|
cqrs.MatchCommandRequest(&command.RelayEmailRequest{}),
|
||||||
|
cqrs.CommandHandlerFunc(command.HandleRelayEmail),
|
||||||
|
)
|
||||||
|
|
||||||
bus.RegisterQuery(
|
bus.RegisterQuery(
|
||||||
cqrs.MatchQueryRequest(&query.GetInboxRequest{}),
|
cqrs.MatchQueryRequest(&query.GetInboxRequest{}),
|
||||||
cqrs.QueryHandlerFunc(query.HandleGetInbox),
|
cqrs.QueryHandlerFunc(query.HandleGetInbox),
|
||||||
|
|
|
@ -3,9 +3,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/route"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/route"
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
|
|
||||||
"flag"
|
"flag"
|
||||||
|
@ -14,11 +14,11 @@ import (
|
||||||
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/config"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/config"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint: gochecknoglobals
|
// nolint: gochecknoglobals
|
||||||
var (
|
var (
|
||||||
configFile = ""
|
configFile = ""
|
||||||
workdir = ""
|
workdir = ""
|
||||||
|
@ -33,7 +33,7 @@ var (
|
||||||
BuildDate = "unknown"
|
BuildDate = "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint: gochecknoinits
|
// nolint: gochecknoinits
|
||||||
func init() {
|
func init() {
|
||||||
flag.StringVar(&configFile, "config", configFile, "configuration file")
|
flag.StringVar(&configFile, "config", configFile, "configuration file")
|
||||||
flag.StringVar(&workdir, "workdir", workdir, "working directory")
|
flag.StringVar(&workdir, "workdir", workdir, "working directory")
|
||||||
|
|
|
@ -6,12 +6,13 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/command"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/command"
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/config"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/config"
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
"github.com/jhillyerd/enmime"
|
"github.com/jhillyerd/enmime"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
"gitlab.com/wpetit/goweb/cqrs"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
"gitlab.com/wpetit/goweb/service"
|
"gitlab.com/wpetit/goweb/service"
|
||||||
)
|
)
|
||||||
|
@ -74,12 +75,31 @@ func (s *Session) Data(r io.Reader) error {
|
||||||
return errors.Wrap(err, "could not retrieve cqrs service")
|
return errors.Wrap(err, "could not retrieve cqrs service")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conf, err := config.From(s.ctn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not retrieve config service")
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.Relay.Enabled {
|
||||||
|
cmd := &command.RelayEmailRequest{
|
||||||
|
Envelope: env,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := bus.Exec(s.ctx, cmd); err != nil {
|
||||||
|
logger.Error(s.ctx, "could not exec command", logger.E(err))
|
||||||
|
|
||||||
|
return errors.Wrapf(err, "could not exec '%T' command", cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cmd := &command.StoreEmailRequest{
|
cmd := &command.StoreEmailRequest{
|
||||||
Envelope: env,
|
Envelope: env,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := bus.Exec(s.ctx, cmd); err != nil {
|
if _, err := bus.Exec(s.ctx, cmd); err != nil {
|
||||||
return errors.Wrap(err, "could not exec command")
|
logger.Error(s.ctx, "could not exec command", logger.E(err))
|
||||||
|
|
||||||
|
return errors.Wrapf(err, "could not exec '%T' command", cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{{block "title" . -}}{{- end}}</title>
|
<title>{{block "title" . -}}{{- end}}</title>
|
||||||
{{- block "head_style" . -}}
|
{{- block "head_style" . -}}
|
||||||
<link rel="stylesheet" href="/css/main.css" />
|
<link rel="stylesheet" href="{{ .BaseURL }}/css/main.css" />
|
||||||
{{end}}
|
{{end}}
|
||||||
{{- block "head_script" . -}}
|
{{- block "head_script" . -}}
|
||||||
<script type="text/javascript" src="/main.js"></script>
|
<script type="text/javascript" src="{{ .BaseURL }}/main.js"></script>
|
||||||
{{end}}
|
{{end}}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -5,6 +5,6 @@
|
||||||
Date de construction: {{ .BuildInfo.BuildDate }}
|
Date de construction: {{ .BuildInfo.BuildDate }}
|
||||||
</p>
|
</p>
|
||||||
<p class="has-text-right is-size-7 has-text-grey">
|
<p class="has-text-right is-size-7 has-text-grey">
|
||||||
Propulsé par <a target="_blank" href="https://forge.cadoles.com/wpetit/fake-smtp">FakeSMTP</a> et publié sous licence <a href="https://www.gnu.org/licenses/agpl-3.0.txt">AGPL-3.0</a>.
|
Propulsé par <a target="_blank" href="https://forge.cadoles.com/Cadoles/fake-smtp">FakeSMTP</a> et publié sous licence <a href="https://www.gnu.org/licenses/agpl-3.0.txt">AGPL-3.0</a>.
|
||||||
</p>
|
</p>
|
||||||
{{end}}
|
{{end}}
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="columns is-mobile">
|
<div class="columns is-mobile">
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<h1 class="is-size-3 title">
|
<h1 class="is-size-3 title">
|
||||||
<a href="/" rel="Inbox" class="has-text-grey-dark">
|
<a href="{{ .BaseURL }}/" rel="Inbox" class="has-text-grey-dark">
|
||||||
{{if or .Emails .Email}}
|
{{if or .Emails .Email}}
|
||||||
📬
|
📬
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
{{define "header_buttons"}}
|
{{define "header_buttons"}}
|
||||||
<button class="button is-danger"
|
<button class="button is-danger"
|
||||||
data-controller="restful"
|
data-controller="restful"
|
||||||
data-restful-endpoint="./{{ .Email.ID }}"
|
data-restful-endpoint="{{ .BaseURL }}/emails/{{ .Email.ID }}"
|
||||||
data-restful-method="DELETE"
|
data-restful-method="DELETE"
|
||||||
data-restful-redirect="../">
|
data-restful-redirect="{{ .BaseURL }}/">
|
||||||
🗑️ Delete
|
🗑️ Delete
|
||||||
</button>
|
</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -24,8 +24,9 @@
|
||||||
<h4 class="title is-size-4">Attachments ({{len .Email.Attachments}})</h4>
|
<h4 class="title is-size-4">Attachments ({{len .Email.Attachments}})</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{{ $email := .Email }}
|
{{ $email := .Email }}
|
||||||
|
{{ $baseURL := .BaseURL }}
|
||||||
{{range $i, $a := .Email.Attachments}}
|
{{range $i, $a := .Email.Attachments}}
|
||||||
<li><a href="{{ $email.ID }}/attachments/{{ $i }}" download="{{ $a.Name }}">{{ $a.Name }}</a></li>
|
<li><a href="{{ $baseURL }}/emails/{{ $email.ID }}/attachments/{{ $i }}" download="{{ $a.Name }}">{{ $a.Name }}</a></li>
|
||||||
{{end}}
|
{{end}}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,7 +45,7 @@
|
||||||
data-controller="iframe"
|
data-controller="iframe"
|
||||||
data-action="load->iframe#onLoad"
|
data-action="load->iframe#onLoad"
|
||||||
style="width:100%;{{if not .Email.HTML}}display:none;{{end}}"
|
style="width:100%;{{if not .Email.HTML}}display:none;{{end}}"
|
||||||
src="{{ .Email.ID }}/html">
|
src="{{ .BaseURL }}/emails/{{ .Email.ID }}/html">
|
||||||
</iframe>
|
</iframe>
|
||||||
<div data-target="tabs.tabContent" data-tabs-for="text" style="{{if .Email.HTML}}display:none;{{end}}width:100%;overflow:hidden;">
|
<div data-target="tabs.tabContent" data-tabs-for="text" style="{{if .Email.HTML}}display:none;{{end}}width:100%;overflow:hidden;">
|
||||||
<pre style="white-space:pre-line;">{{ .Email.Text }}</pre>
|
<pre style="white-space:pre-line;">{{ .Email.Text }}</pre>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{{define "header_buttons"}}
|
{{define "header_buttons"}}
|
||||||
<button
|
<button
|
||||||
data-controller="restful"
|
data-controller="restful"
|
||||||
data-restful-endpoint="/emails"
|
data-restful-endpoint="{{ .BaseURL }}/emails"
|
||||||
data-restful-method="DELETE"
|
data-restful-method="DELETE"
|
||||||
class="button is-danger">
|
class="button is-danger">
|
||||||
🗑️ Clear
|
🗑️ Clear
|
||||||
|
@ -24,10 +24,11 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
{{ $baseURL := .BaseURL }}
|
||||||
{{range .Emails}}
|
{{range .Emails}}
|
||||||
<tr data-controller="inbox-entry"
|
<tr data-controller="inbox-entry"
|
||||||
data-action="click->inbox-entry#onClick"
|
data-action="click->inbox-entry#onClick"
|
||||||
data-inbox-entry-link="./emails/{{ .ID }}">
|
data-inbox-entry-link="{{ $baseURL }}/emails/{{ .ID }}">
|
||||||
<td class="email-subject"><div>{{ .Subject }}</div></td>
|
<td class="email-subject"><div>{{ .Subject }}</div></td>
|
||||||
<td class="email-from">
|
<td class="email-from">
|
||||||
{{range .From}}
|
{{range .From}}
|
||||||
|
@ -44,7 +45,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="email-actions">
|
<td class="email-actions">
|
||||||
<div class="buttons is-right">
|
<div class="buttons is-right">
|
||||||
<a href="./emails/{{ .ID }}" class="button is-small is-link">👁️ See</a>
|
<a href="{{ $baseURL }}/emails/{{ .ID }}" class="button is-small is-link">👁️ See</a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
47
go.mod
47
go.mod
|
@ -1,19 +1,52 @@
|
||||||
module forge.cadoles.com/wpetit/fake-smtp
|
module forge.cadoles.com/Cadoles/fake-smtp
|
||||||
|
|
||||||
go 1.14
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/asdine/storm/v3 v3.1.1
|
github.com/asdine/storm/v3 v3.1.1
|
||||||
github.com/caarlos0/env/v6 v6.2.1
|
github.com/caarlos0/env/v6 v6.2.1
|
||||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b // indirect
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
||||||
github.com/emersion/go-smtp v0.12.1
|
github.com/emersion/go-smtp v0.12.1
|
||||||
github.com/go-chi/chi v4.1.1+incompatible
|
github.com/go-chi/chi/v5 v5.0.12
|
||||||
github.com/jhillyerd/enmime v0.8.0
|
github.com/jhillyerd/enmime v0.8.0
|
||||||
github.com/microcosm-cc/bluemonday v1.0.2
|
github.com/microcosm-cc/bluemonday v1.0.26
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
gitlab.com/wpetit/goweb v0.0.0-20200418152305-76dea96a46ce
|
gitlab.com/wpetit/goweb v0.0.0-20200707070104-985ce3eba3c2
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.2.8
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cdr.dev/slog v1.3.0 // indirect
|
||||||
|
github.com/alecthomas/chroma v0.7.0 // indirect
|
||||||
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
|
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||||
|
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
|
||||||
|
github.com/dlclark/regexp2 v1.2.0 // indirect
|
||||||
|
github.com/fatih/color v1.7.0 // indirect
|
||||||
|
github.com/go-chi/chi v4.1.1+incompatible // indirect
|
||||||
|
github.com/go-playground/locales v0.12.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.16.0 // indirect
|
||||||
|
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect
|
||||||
|
github.com/google/uuid v1.1.1 // indirect
|
||||||
|
github.com/gorilla/css v1.0.0 // indirect
|
||||||
|
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43 // indirect
|
||||||
|
github.com/leodido/go-urn v1.1.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.11 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||||
|
github.com/olekukonko/tablewriter v0.0.1 // indirect
|
||||||
|
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||||
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||||
|
go.etcd.io/bbolt v1.3.4 // indirect
|
||||||
|
go.opencensus.io v0.22.2 // indirect
|
||||||
|
golang.org/x/crypto v0.20.0 // indirect
|
||||||
|
golang.org/x/net v0.21.0 // indirect
|
||||||
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
|
golang.org/x/term v0.17.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
|
||||||
|
gopkg.in/go-playground/validator.v9 v9.29.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
// replace gitlab.com/wpetit/goweb => ../goweb
|
// replace gitlab.com/wpetit/goweb => ../goweb
|
||||||
|
|
64
go.sum
64
go.sum
|
@ -1,3 +1,4 @@
|
||||||
|
cdr.dev/slog v1.3.0 h1:MYN1BChIaVEGxdS7I5cpdyMC0+WfJfK8BETAfzfLUGQ=
|
||||||
cdr.dev/slog v1.3.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns=
|
cdr.dev/slog v1.3.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns=
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
@ -6,6 +7,7 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A
|
||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go v0.49.0 h1:CH+lkubJzcPYB1Ggupcq0+k8Ni2ILdG2lYjDIgavDBQ=
|
||||||
cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY=
|
cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY=
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
@ -21,15 +23,21 @@ github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtix
|
||||||
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
|
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
|
||||||
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
||||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||||
|
github.com/alecthomas/chroma v0.7.0 h1:z+0HgTUmkpRDRz0SRSdMaqOLfJV4F+N1FPDZUZIDUzw=
|
||||||
github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY=
|
github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY=
|
||||||
|
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||||
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||||
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||||
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
||||||
|
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||||
github.com/asdine/storm/v3 v3.1.1 h1:5ESJvmcNhQQOFcvpxkIHcZs7mp8Z6XGdBqEoAgf+11g=
|
github.com/asdine/storm/v3 v3.1.1 h1:5ESJvmcNhQQOFcvpxkIHcZs7mp8Z6XGdBqEoAgf+11g=
|
||||||
github.com/asdine/storm/v3 v3.1.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
|
github.com/asdine/storm/v3 v3.1.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
|
||||||
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
github.com/caarlos0/env/v6 v6.2.1 h1:/bFpX1dg4TNioJjg7mrQaSrBoQvRfLUHNfXivdFbbEo=
|
github.com/caarlos0/env/v6 v6.2.1 h1:/bFpX1dg4TNioJjg7mrQaSrBoQvRfLUHNfXivdFbbEo=
|
||||||
github.com/caarlos0/env/v6 v6.2.1/go.mod h1:3LpmfcAYCG6gCiSgDLaFR5Km1FRpPwFvBbRcjHar6Sw=
|
github.com/caarlos0/env/v6 v6.2.1/go.mod h1:3LpmfcAYCG6gCiSgDLaFR5Km1FRpPwFvBbRcjHar6Sw=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
@ -37,26 +45,32 @@ github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/
|
||||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
|
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||||
|
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
|
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e h1:ba7YsgX5OV8FjGi5ZWml8Jng6oBrJAb3ahqWMJ5Ce8Q=
|
|
||||||
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
||||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b h1:uhWtEWBHgop1rqEk2klKaxPAkVDCXexai6hSuRQ7Nvs=
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-smtp v0.12.1 h1:1R8BDqrR2HhlGwgFYcOi+BVTvK1bMjAB65QcVpJ5sNA=
|
github.com/emersion/go-smtp v0.12.1 h1:1R8BDqrR2HhlGwgFYcOi+BVTvK1bMjAB65QcVpJ5sNA=
|
||||||
github.com/emersion/go-smtp v0.12.1/go.mod h1:SD9V/xa4ndMw77lR3Mf7htkp8RBNYuPh9UeuBs9tpUQ=
|
github.com/emersion/go-smtp v0.12.1/go.mod h1:SD9V/xa4ndMw77lR3Mf7htkp8RBNYuPh9UeuBs9tpUQ=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||||
github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4=
|
github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4=
|
||||||
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
|
||||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||||
|
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
|
||||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||||
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
|
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
|
||||||
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
|
@ -64,6 +78,7 @@ github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 h1:aBzukfDxQlCTVS0NBU
|
||||||
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE=
|
||||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
@ -78,6 +93,7 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
|
||||||
github.com/google/btree v1.0.0/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.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.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e h1:4WfjkTUTsO6siF8ghDQQk6t7x/FPsv3w6MXkc47do7Q=
|
||||||
github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
@ -88,11 +104,11 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
||||||
|
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
|
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||||
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
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/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
|
|
||||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.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/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
@ -109,16 +125,19 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
|
||||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-isatty v0.0.4/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.8/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.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
|
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||||
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
|
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
|
||||||
|
@ -135,13 +154,13 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
|
||||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||||
|
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
@ -149,17 +168,20 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
|
||||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
||||||
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||||
gitlab.com/wpetit/goweb v0.0.0-20200418152305-76dea96a46ce h1:B3inZUHFr/FpA3jb+ZeSSHk3FSpB0xkQ0TjePhRokxw=
|
gitlab.com/wpetit/goweb v0.0.0-20200707070104-985ce3eba3c2 h1:9WJw0v6BzHV8fP8EywjcqAz8PyCZxLn8ioTiMP4SBog=
|
||||||
gitlab.com/wpetit/goweb v0.0.0-20200418152305-76dea96a46ce/go.mod h1:Gfv7cBOw1T2XwXMsLm1d9kAjMAdNtLMjPv+yCzRO9qk=
|
gitlab.com/wpetit/goweb v0.0.0-20200707070104-985ce3eba3c2/go.mod h1:Gfv7cBOw1T2XwXMsLm1d9kAjMAdNtLMjPv+yCzRO9qk=
|
||||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
||||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
|
||||||
|
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
@ -180,7 +202,6 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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-20180826012351-8a410e7b638d/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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-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-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
@ -191,10 +212,9 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/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-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -215,14 +235,16 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||||
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
@ -242,6 +264,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
@ -263,19 +286,22 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1 h1:aQktFqmDE2yjveXJlVIfslDFmFnUXSqG0i6KRcJAeMc=
|
||||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
|
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
|
||||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||||
gopkg.in/yaml.v2 v2.2.2/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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/model"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
"gitlab.com/wpetit/goweb/cqrs"
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/model"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
"gitlab.com/wpetit/goweb/cqrs"
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/emersion/go-sasl"
|
||||||
|
|
||||||
|
"github.com/emersion/go-smtp"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/config"
|
||||||
|
"github.com/jhillyerd/enmime"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/cqrs"
|
||||||
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RelayEmailRequest struct {
|
||||||
|
Envelope *enmime.Envelope
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleRelayEmail(ctx context.Context, cmd cqrs.Command) error {
|
||||||
|
req, ok := cmd.Request().(*RelayEmailRequest)
|
||||||
|
if !ok {
|
||||||
|
return cqrs.ErrUnexpectedRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
ctn, err := container.From(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not retrieve service container")
|
||||||
|
}
|
||||||
|
|
||||||
|
conf, err := config.From(ctn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not retrieve config service")
|
||||||
|
}
|
||||||
|
|
||||||
|
relay := conf.Relay
|
||||||
|
|
||||||
|
if err := forwardMail(req.Envelope, relay); err != nil {
|
||||||
|
return errors.Wrap(err, "could not forward mail")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func forwardMail(env *enmime.Envelope, conf config.RelayConfig) error {
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
|
||||||
|
if conf.InsecureSkipVerify {
|
||||||
|
tlsConfig = &tls.Config{
|
||||||
|
// nolint: gosec
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := conf.Address
|
||||||
|
|
||||||
|
var (
|
||||||
|
client *smtp.Client
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if conf.UseTLS {
|
||||||
|
client, err = smtp.DialTLS(addr, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client, err = smtp.Dial(addr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
if err = client.Hello("localhost"); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, _ := client.Extension("STARTTLS"); ok {
|
||||||
|
if err = client.StartTLS(tlsConfig); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.Username != "" || conf.Password != "" {
|
||||||
|
if ok, _ := client.Extension("AUTH"); ok {
|
||||||
|
var auth sasl.Client
|
||||||
|
if conf.Anonymous {
|
||||||
|
auth = sasl.NewAnonymousClient("fakesmtp")
|
||||||
|
} else {
|
||||||
|
auth = sasl.NewPlainClient(conf.Identity, conf.Username, conf.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Auth(auth); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var from string
|
||||||
|
|
||||||
|
if conf.FromOverride != "" {
|
||||||
|
from = conf.FromOverride
|
||||||
|
} else {
|
||||||
|
from = env.GetHeader("From")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = client.Mail(from, nil); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
to := env.GetHeaderValues("To")
|
||||||
|
|
||||||
|
for _, addr := range to {
|
||||||
|
if err = client.Rcpt(addr); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := client.Data()
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pr, pw := io.Pipe()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
defer pw.Close()
|
||||||
|
|
||||||
|
if err = env.Root.Encode(pw); err != nil {
|
||||||
|
err = errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err = io.Copy(w, pr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = w.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Quit(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -6,8 +6,8 @@ import (
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/model"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
|
||||||
"github.com/jhillyerd/enmime"
|
"github.com/jhillyerd/enmime"
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
"gitlab.com/wpetit/goweb/cqrs"
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
|
|
|
@ -14,13 +14,15 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
HTTP HTTPConfig `yaml:"http"`
|
HTTP HTTPConfig `yaml:"http"`
|
||||||
SMTP SMTPConfig `yaml:"smtp"`
|
SMTP SMTPConfig `yaml:"smtp"`
|
||||||
Data DataConfig `ymal:"data"`
|
Data DataConfig `yaml:"data"`
|
||||||
|
Relay RelayConfig `yaml:"relay"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
Address string `yaml:"address" env:"FAKESMTP_HTTP_ADDRESS"`
|
Address string `yaml:"address" env:"FAKESMTP_HTTP_ADDRESS"`
|
||||||
TemplateDir string `yaml:"templateDir" env:"FAKESMTP_HTTP_TEMPLATEDIR"`
|
TemplateDir string `yaml:"templateDir" env:"FAKESMTP_HTTP_TEMPLATEDIR"`
|
||||||
PublicDir string `yaml:"publicDir" env:"FAKESMTP_HTTP_PUBLICDIR"`
|
PublicDir string `yaml:"publicDir" env:"FAKESMTP_HTTP_PUBLICDIR"`
|
||||||
|
BaseURL string `yaml:"baseUrl" env:"FAKESMTP_HTTP_BASEURL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SMTPConfig struct {
|
type SMTPConfig struct {
|
||||||
|
@ -36,6 +38,18 @@ type SMTPConfig struct {
|
||||||
Debug bool `yaml:"debug" env:"FAKESMTP_SMTP_DEBUG"`
|
Debug bool `yaml:"debug" env:"FAKESMTP_SMTP_DEBUG"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RelayConfig struct {
|
||||||
|
Enabled bool `yaml:"enabled" env:"FAKESMTP_RELAY_ENABLED"`
|
||||||
|
Address string `yaml:"address" env:"FAKESMTP_RELAY_ADDRESS"`
|
||||||
|
Identity string `yaml:"identity" env:"FAKESMTP_RELAY_IDENTITY"`
|
||||||
|
Username string `yaml:"username" env:"FAKESMTP_RELAY_USERNAME"`
|
||||||
|
Password string `yaml:"password" env:"FAKESMTP_RELAY_PASSWORD"`
|
||||||
|
Anonymous bool `yaml:"anonymous" env:"FAKESMTP_RELAY_ANONYMOUS"`
|
||||||
|
UseTLS bool `yaml:"useTLS" env:"FAKESMTP_RELAY_USE_TLS"`
|
||||||
|
InsecureSkipVerify bool `yaml:"insecureSkipVerify" env:"FAKESMTP_RELAY_INSECURE_SKIP_VERIFY"`
|
||||||
|
FromOverride string `yaml:"fromOverride" env:"FAKESMTP_RELAY_FROM_OVERRIDE"`
|
||||||
|
}
|
||||||
|
|
||||||
type DataConfig struct {
|
type DataConfig struct {
|
||||||
Path string `yaml:"path" env:"FAKESMTP_DATA_PATH"`
|
Path string `yaml:"path" env:"FAKESMTP_DATA_PATH"`
|
||||||
}
|
}
|
||||||
|
@ -91,6 +105,9 @@ func NewDefault() *Config {
|
||||||
Data: DataConfig{
|
Data: DataConfig{
|
||||||
Path: "fakesmtp.db",
|
Path: "fakesmtp.db",
|
||||||
},
|
},
|
||||||
|
Relay: RelayConfig{
|
||||||
|
Enabled: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,195 @@
|
||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/mail"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type emailMatcherTestCase struct {
|
||||||
|
Name string
|
||||||
|
Search *InboxSearch
|
||||||
|
Email *model.Email
|
||||||
|
Matcher emailMatcherFunc
|
||||||
|
Expect bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmailMatcher(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
johnDoeAddr, err := mail.ParseAddress("john.doe@test.local")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
adaLovelaceAddr, err := mail.ParseAddress("ada.lovelace@test.local")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
testCases := []emailMatcherTestCase{
|
||||||
|
{
|
||||||
|
Name: "Simple matching header",
|
||||||
|
Email: &model.Email{
|
||||||
|
Headers: map[string][]string{
|
||||||
|
"X-Swift-To": {johnDoeAddr.Address},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Search: &InboxSearch{
|
||||||
|
Headers: map[string]string{
|
||||||
|
"X-Swift-To": johnDoeAddr.Address,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Matcher: matchHeaders,
|
||||||
|
Expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Multiple matching header",
|
||||||
|
Email: &model.Email{
|
||||||
|
Headers: map[string][]string{
|
||||||
|
"X-Swift-To": {johnDoeAddr.Address},
|
||||||
|
"Content-Type": {"multipart/alternative; boundary=\"_=_swift_1645181013_7b80ab8ab386ba4fcaff4f6f79593adb_=_\""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Search: &InboxSearch{
|
||||||
|
Headers: map[string]string{
|
||||||
|
"X-Swift-To": johnDoeAddr.Address,
|
||||||
|
"Content-Type": "multipart/alternative",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Matcher: matchHeaders,
|
||||||
|
Expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Simple non matching header",
|
||||||
|
Email: &model.Email{
|
||||||
|
Headers: map[string][]string{
|
||||||
|
"X-Swift-To": {johnDoeAddr.Address},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Search: &InboxSearch{
|
||||||
|
Headers: map[string]string{
|
||||||
|
"X-Swift-To": adaLovelaceAddr.Address,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Matcher: matchHeaders,
|
||||||
|
Expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Multiple non matching headers",
|
||||||
|
Email: &model.Email{
|
||||||
|
Headers: map[string][]string{
|
||||||
|
"X-Swift-To": {johnDoeAddr.Address},
|
||||||
|
"Content-Type": {"foo/bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Search: &InboxSearch{
|
||||||
|
Headers: map[string]string{
|
||||||
|
"X-Swift-To": johnDoeAddr.Address,
|
||||||
|
"Content-Type": "multipart/alternative",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Matcher: matchHeaders,
|
||||||
|
Expect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Simple to",
|
||||||
|
Email: &model.Email{
|
||||||
|
To: []*mail.Address{johnDoeAddr},
|
||||||
|
},
|
||||||
|
Search: &InboxSearch{
|
||||||
|
To: johnDoeAddr.Address,
|
||||||
|
},
|
||||||
|
Matcher: matchTo,
|
||||||
|
Expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Simple from",
|
||||||
|
Email: &model.Email{
|
||||||
|
From: []*mail.Address{johnDoeAddr},
|
||||||
|
},
|
||||||
|
Search: &InboxSearch{
|
||||||
|
From: johnDoeAddr.Address,
|
||||||
|
},
|
||||||
|
Matcher: matchFrom,
|
||||||
|
Expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Simple after",
|
||||||
|
Email: &model.Email{
|
||||||
|
SentAt: now,
|
||||||
|
},
|
||||||
|
Search: &InboxSearch{
|
||||||
|
After: now.Add(-5 * time.Second),
|
||||||
|
},
|
||||||
|
Matcher: matchAfter,
|
||||||
|
Expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Simple before",
|
||||||
|
Email: &model.Email{
|
||||||
|
SentAt: now,
|
||||||
|
},
|
||||||
|
Search: &InboxSearch{
|
||||||
|
Before: now.Add(5 * time.Second),
|
||||||
|
},
|
||||||
|
Matcher: matchBefore,
|
||||||
|
Expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Matching composite",
|
||||||
|
Email: &model.Email{
|
||||||
|
SentAt: now,
|
||||||
|
Headers: map[string][]string{
|
||||||
|
"X-Swift-To": {johnDoeAddr.Address},
|
||||||
|
"Content-Type": {"multipart/alternative; boundary=\"_=_swift_1645181013_7b80ab8ab386ba4fcaff4f6f79593adb_=_\""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Search: &InboxSearch{
|
||||||
|
Before: now.Add(5 * time.Second),
|
||||||
|
Headers: map[string]string{
|
||||||
|
"X-Swift-To": johnDoeAddr.Address,
|
||||||
|
"Content-Type": "multipart/alternative",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Matcher: and(matchBefore, matchHeaders),
|
||||||
|
Expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Non matching composite",
|
||||||
|
Email: &model.Email{
|
||||||
|
SentAt: now,
|
||||||
|
Headers: map[string][]string{
|
||||||
|
"X-Swift-To": {johnDoeAddr.Address},
|
||||||
|
"Content-Type": {"multipart/alternative; boundary=\"_=_swift_1645181013_7b80ab8ab386ba4fcaff4f6f79593adb_=_\""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Search: &InboxSearch{
|
||||||
|
Before: now.Add(5 * time.Second),
|
||||||
|
Headers: map[string]string{
|
||||||
|
"X-Swift-To": adaLovelaceAddr.Address,
|
||||||
|
"Content-Type": "multipart/alternative",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Matcher: and(matchBefore, matchHeaders),
|
||||||
|
Expect: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
func(tc emailMatcherTestCase) {
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if e, g := tc.Expect, tc.Matcher(tc.Email, tc.Search); e != g {
|
||||||
|
t.Errorf("'%s': expected '%v', got '%v'", tc.Name, e, g)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}(tc)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,19 +2,34 @@ package query
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/model"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
|
||||||
|
stormdb "github.com/asdine/storm/v3"
|
||||||
|
"github.com/asdine/storm/v3/q"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
"gitlab.com/wpetit/goweb/cqrs"
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type InboxSearch struct {
|
||||||
|
To string
|
||||||
|
From string
|
||||||
|
Body string
|
||||||
|
Subject string
|
||||||
|
Headers map[string]string
|
||||||
|
After time.Time
|
||||||
|
Before time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type GetInboxRequest struct {
|
type GetInboxRequest struct {
|
||||||
OrderBy string
|
OrderBy string
|
||||||
Limit int
|
Limit int
|
||||||
Skip int
|
Skip int
|
||||||
Reverse bool
|
Reverse bool
|
||||||
|
Search *InboxSearch
|
||||||
}
|
}
|
||||||
|
|
||||||
type InboxData struct {
|
type InboxData struct {
|
||||||
|
@ -39,7 +54,26 @@ func HandleGetInbox(ctx context.Context, qry cqrs.Query) (interface{}, error) {
|
||||||
|
|
||||||
emails := make([]*model.Email, 0)
|
emails := make([]*model.Email, 0)
|
||||||
|
|
||||||
query := db.Select()
|
var query stormdb.Query
|
||||||
|
|
||||||
|
if req.Search != nil {
|
||||||
|
matchers := make([]q.Matcher, 0)
|
||||||
|
|
||||||
|
if req.Search.Body != "" {
|
||||||
|
matchers = append(matchers, q.Or(
|
||||||
|
q.Re("HTML", req.Search.Body),
|
||||||
|
q.Re("Text", req.Search.Body),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Search.Subject != "" {
|
||||||
|
matchers = append(matchers, q.Re("Subject", req.Search.Subject))
|
||||||
|
}
|
||||||
|
|
||||||
|
query = db.Select(matchers...)
|
||||||
|
} else {
|
||||||
|
query = db.Select()
|
||||||
|
}
|
||||||
|
|
||||||
if req.OrderBy != "" {
|
if req.OrderBy != "" {
|
||||||
query = query.OrderBy(req.OrderBy)
|
query = query.OrderBy(req.OrderBy)
|
||||||
|
@ -51,6 +85,14 @@ func HandleGetInbox(ctx context.Context, qry cqrs.Query) (interface{}, error) {
|
||||||
query = query.Reverse()
|
query = query.Reverse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.Limit != 0 {
|
||||||
|
query = query.Limit(req.Limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Skip != 0 {
|
||||||
|
query = query.Limit(req.Skip)
|
||||||
|
}
|
||||||
|
|
||||||
if err := query.Find(&emails); err != nil {
|
if err := query.Find(&emails); err != nil {
|
||||||
if err == storm.ErrNotFound {
|
if err == storm.ErrNotFound {
|
||||||
return &InboxData{emails}, nil
|
return &InboxData{emails}, nil
|
||||||
|
@ -59,5 +101,139 @@ func HandleGetInbox(ctx context.Context, qry cqrs.Query) (interface{}, error) {
|
||||||
return nil, errors.Wrap(err, "could not retrieve emails")
|
return nil, errors.Wrap(err, "could not retrieve emails")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.Search == nil {
|
||||||
return &InboxData{emails}, nil
|
return &InboxData{emails}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := filterEmails(emails, req.Search)
|
||||||
|
|
||||||
|
return &InboxData{filtered}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchers = []emailMatcherFunc{
|
||||||
|
matchTo,
|
||||||
|
matchFrom,
|
||||||
|
matchBefore,
|
||||||
|
matchAfter,
|
||||||
|
matchHeaders,
|
||||||
|
}
|
||||||
|
|
||||||
|
type emailMatcherFunc func(*model.Email, *InboxSearch) bool
|
||||||
|
|
||||||
|
func matchTo(eml *model.Email, search *InboxSearch) bool {
|
||||||
|
if search.To == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
|
||||||
|
for _, addr := range eml.To {
|
||||||
|
if strings.Contains(addr.Name, search.To) || strings.Contains(addr.Address, search.To) {
|
||||||
|
found = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchFrom(eml *model.Email, search *InboxSearch) bool {
|
||||||
|
if search.From == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
|
||||||
|
for _, addr := range eml.From {
|
||||||
|
if strings.Contains(addr.Name, search.From) || strings.Contains(addr.Address, search.From) {
|
||||||
|
found = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchAfter(eml *model.Email, search *InboxSearch) bool {
|
||||||
|
if search.After.IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return eml.SentAt.After(search.After)
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchBefore(eml *model.Email, search *InboxSearch) bool {
|
||||||
|
if search.Before.IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return eml.SentAt.Before(search.Before)
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchHeaders(eml *model.Email, search *InboxSearch) bool {
|
||||||
|
if eml.Headers == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := true
|
||||||
|
|
||||||
|
for searchKey, searchValue := range search.Headers {
|
||||||
|
for headerKey, headerValues := range eml.Headers {
|
||||||
|
if searchKey != headerKey {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
matchesHeader := true
|
||||||
|
|
||||||
|
for _, hv := range headerValues {
|
||||||
|
if !strings.Contains(hv, searchValue) {
|
||||||
|
matchesHeader = false
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matchesHeader {
|
||||||
|
matches = false
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matches {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
|
func and(matchers ...emailMatcherFunc) emailMatcherFunc {
|
||||||
|
return func(eml *model.Email, search *InboxSearch) bool {
|
||||||
|
for _, match := range matchers {
|
||||||
|
if !match(eml, search) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterEmails(emails []*model.Email, search *InboxSearch) []*model.Email {
|
||||||
|
filtered := make([]*model.Email, 0)
|
||||||
|
|
||||||
|
match := and(matchers...)
|
||||||
|
|
||||||
|
for _, eml := range emails {
|
||||||
|
if !match(eml, search) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = append(filtered, eml)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ package query
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/model"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
"gitlab.com/wpetit/goweb/cqrs"
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/query"
|
||||||
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/api"
|
||||||
|
"gitlab.com/wpetit/goweb/cqrs"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
func browseAPIV1Emails(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctn := container.Must(r.Context())
|
||||||
|
bus := cqrs.Must(ctn)
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
getInbox, err := createInboxQueryFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "bad request", logger.E(err))
|
||||||
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := bus.Query(ctx, getInbox)
|
||||||
|
if err != nil {
|
||||||
|
panic(errors.Wrap(err, "could not retrieve inbox"))
|
||||||
|
}
|
||||||
|
|
||||||
|
inboxData, ok := result.Data().(*query.InboxData)
|
||||||
|
if !ok {
|
||||||
|
panic(errors.New("unexpected data"))
|
||||||
|
}
|
||||||
|
|
||||||
|
api.DataResponse(w, http.StatusOK, inboxData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveAPIV1Email(w http.ResponseWriter, r *http.Request) {
|
||||||
|
emailID, err := getEmailID(r)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
email, err := openEmail(ctx, emailID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, storm.ErrNotFound) {
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(errors.Wrap(err, "could not open email"))
|
||||||
|
}
|
||||||
|
|
||||||
|
api.DataResponse(w, http.StatusOK, email)
|
||||||
|
}
|
|
@ -7,11 +7,11 @@ import (
|
||||||
|
|
||||||
"github.com/microcosm-cc/bluemonday"
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/command"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/command"
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/model"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/model"
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/query"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/query"
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/storm"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/storm"
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
"gitlab.com/wpetit/goweb/cqrs"
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/config"
|
||||||
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/query"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
|
"gitlab.com/wpetit/goweb/service"
|
||||||
"gitlab.com/wpetit/goweb/service/template"
|
"gitlab.com/wpetit/goweb/service/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,11 +18,119 @@ func extendTemplateData(w http.ResponseWriter, r *http.Request, data template.Da
|
||||||
ctn := container.Must(r.Context())
|
ctn := container.Must(r.Context())
|
||||||
data, err := template.Extend(data,
|
data, err := template.Extend(data,
|
||||||
template.WithBuildInfo(w, r, ctn),
|
template.WithBuildInfo(w, r, ctn),
|
||||||
|
withBaseURL(ctn),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(errors.Wrap(err, "could not extend template data"))
|
panic(errors.Wrap(err, "could not extend template data"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createInboxQueryFromRequest(r *http.Request) (*query.GetInboxRequest, error) {
|
||||||
|
orderBy := r.URL.Query().Get("orderBy")
|
||||||
|
reverse := r.URL.Query().Get("reverse")
|
||||||
|
to := r.URL.Query().Get("to")
|
||||||
|
from := r.URL.Query().Get("from")
|
||||||
|
body := r.URL.Query().Get("body")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var limit int64 = 0
|
||||||
|
|
||||||
|
rawLimit := r.URL.Query().Get("limit")
|
||||||
|
if rawLimit != "" {
|
||||||
|
limit, err = strconv.ParseInt(rawLimit, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var skip int64 = 0
|
||||||
|
|
||||||
|
rawSkip := r.URL.Query().Get("skip")
|
||||||
|
if rawSkip != "" {
|
||||||
|
skip, err = strconv.ParseInt(rawSkip, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var after time.Time
|
||||||
|
|
||||||
|
rawAfter := r.URL.Query().Get("after")
|
||||||
|
if rawAfter != "" {
|
||||||
|
after, err = time.Parse(time.RFC3339, rawAfter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var before time.Time
|
||||||
|
|
||||||
|
rawBefore := r.URL.Query().Get("before")
|
||||||
|
if rawBefore != "" {
|
||||||
|
before, err = time.Parse(time.RFC3339, rawBefore)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var headers map[string]string
|
||||||
|
|
||||||
|
rawHeaders := r.URL.Query().Get("headers")
|
||||||
|
if rawHeaders != "" {
|
||||||
|
headers = make(map[string]string)
|
||||||
|
if err := json.Unmarshal([]byte(rawHeaders), &headers); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
search := &query.InboxSearch{}
|
||||||
|
if to != "" {
|
||||||
|
search.To = to
|
||||||
|
}
|
||||||
|
|
||||||
|
if from != "" {
|
||||||
|
search.From = from
|
||||||
|
}
|
||||||
|
|
||||||
|
if body != "" {
|
||||||
|
search.Body = body
|
||||||
|
}
|
||||||
|
|
||||||
|
if rawAfter != "" {
|
||||||
|
search.After = after
|
||||||
|
}
|
||||||
|
|
||||||
|
if rawBefore != "" {
|
||||||
|
search.Before = before
|
||||||
|
}
|
||||||
|
|
||||||
|
if rawHeaders != "" {
|
||||||
|
search.Headers = headers
|
||||||
|
}
|
||||||
|
|
||||||
|
inboxRequest := &query.GetInboxRequest{
|
||||||
|
OrderBy: orderBy,
|
||||||
|
Reverse: reverse == "y",
|
||||||
|
Skip: int(skip),
|
||||||
|
Limit: int(limit),
|
||||||
|
Search: search,
|
||||||
|
}
|
||||||
|
|
||||||
|
return inboxRequest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func withBaseURL(ctn *service.Container) template.DataExtFunc {
|
||||||
|
return func(data template.Data) (template.Data, error) {
|
||||||
|
conf, err := config.From(ctn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data["BaseURL"] = conf.HTTP.BaseURL
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,10 +3,11 @@ package route
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/command"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/command"
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/query"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/query"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/cqrs"
|
"gitlab.com/wpetit/goweb/cqrs"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
"gitlab.com/wpetit/goweb/middleware/container"
|
||||||
"gitlab.com/wpetit/goweb/service/template"
|
"gitlab.com/wpetit/goweb/service/template"
|
||||||
)
|
)
|
||||||
|
@ -14,12 +15,18 @@ import (
|
||||||
func serveInboxPage(w http.ResponseWriter, r *http.Request) {
|
func serveInboxPage(w http.ResponseWriter, r *http.Request) {
|
||||||
ctn := container.Must(r.Context())
|
ctn := container.Must(r.Context())
|
||||||
tmpl := template.Must(ctn)
|
tmpl := template.Must(ctn)
|
||||||
|
|
||||||
bus := cqrs.Must(ctn)
|
bus := cqrs.Must(ctn)
|
||||||
|
|
||||||
getInbox := &query.GetInboxRequest{}
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
|
getInbox, err := createInboxQueryFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "bad request", logger.E(err))
|
||||||
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
result, err := bus.Query(ctx, getInbox)
|
result, err := bus.Query(ctx, getInbox)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(errors.Wrap(err, "could not retrieve inbox"))
|
panic(errors.Wrap(err, "could not retrieve inbox"))
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"forge.cadoles.com/wpetit/fake-smtp/internal/config"
|
"forge.cadoles.com/Cadoles/fake-smtp/internal/config"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi/v5"
|
||||||
"gitlab.com/wpetit/goweb/static"
|
"gitlab.com/wpetit/goweb/static"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,6 +17,13 @@ func Mount(r *chi.Mux, config *config.Config) error {
|
||||||
r.Delete("/emails/{id}", handleEmailDelete)
|
r.Delete("/emails/{id}", handleEmailDelete)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
r.Route("/api", func(r chi.Router) {
|
||||||
|
r.Route("/v1", func(r chi.Router) {
|
||||||
|
r.Get("/emails", browseAPIV1Emails)
|
||||||
|
r.Get("/emails/{id}", serveAPIV1Email)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
notFoundHandler := r.NotFoundHandler()
|
notFoundHandler := r.NotFoundHandler()
|
||||||
r.Get("/*", static.Dir(config.HTTP.PublicDir, "", notFoundHandler))
|
r.Get("/*", static.Dir(config.HTTP.PublicDir, "", notFoundHandler))
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
@baseURL = http://localhost:8080
|
||||||
|
|
||||||
|
### Filter emails via headers
|
||||||
|
|
||||||
|
GET {{ baseURL }}/api/v1/emails?headers={"Mime-Version":"1.0"}
|
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.13 AS build
|
FROM reg.cadoles.com/proxy_cache/library/golang:1.21 AS build
|
||||||
|
|
||||||
ARG HTTP_PROXY=
|
ARG HTTP_PROXY=
|
||||||
ARG HTTPS_PROXY=
|
ARG HTTPS_PROXY=
|
||||||
|
@ -7,28 +7,26 @@ ARG https_proxy=
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y build-essential git bash curl
|
RUN apt-get update && apt-get install -y build-essential git bash curl
|
||||||
|
|
||||||
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \
|
RUN curl -sL https://deb.nodesource.com/setup_20.x | bash - \
|
||||||
&& apt-get install -y nodejs
|
&& apt-get install -y nodejs
|
||||||
|
|
||||||
COPY . /src
|
COPY . /src
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
RUN cp -f misc/docker/config-patch.yml misc/release/config-patch.yml
|
RUN cp -f misc/docker/config-patch.txt misc/release/config-patch.txt \
|
||||||
|
&& npm ci \
|
||||||
RUN go get github.com/krishicks/yaml-patch/cmd/yaml-patch
|
|
||||||
|
|
||||||
RUN npm install \
|
|
||||||
&& make vendor \
|
|
||||||
&& echo "---" > ./misc/release/config-patch.yml \
|
|
||||||
&& make ARCH_TARGETS=amd64 release
|
&& make ARCH_TARGETS=amd64 release
|
||||||
|
|
||||||
FROM busybox
|
FROM reg.cadoles.com/proxy_cache/library/busybox
|
||||||
|
|
||||||
|
RUN adduser -D -h /app fsmtp
|
||||||
|
|
||||||
COPY --from=build /src/release/fake-smtp-linux-amd64 /app
|
COPY --from=build /src/release/fake-smtp-linux-amd64 /app
|
||||||
|
RUN chown -R fsmtp:fsmtp /app
|
||||||
|
|
||||||
|
EXPOSE 8080 2525
|
||||||
|
USER fsmtp
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN mkdir -p /app
|
|
||||||
|
|
||||||
CMD ["bin/fake-smtp", "--config", "config.yml"]
|
CMD ["bin/fake-smtp", "--config", "config.yml"]
|
|
@ -0,0 +1 @@
|
||||||
|
.smtp.debug = false
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
- op: replace
|
|
||||||
path: /smtp/debug
|
|
||||||
value: false
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
.data.path = "/var/lib/fake-smtp/data.db"
|
||||||
|
.smtp.address = "127.0.0.1:2525"
|
||||||
|
.smtp.debug = false
|
|
@ -1,10 +0,0 @@
|
||||||
---
|
|
||||||
- op: replace
|
|
||||||
path: /data/path
|
|
||||||
value: /var/lib/fake-smtp/data.db
|
|
||||||
- op: replace
|
|
||||||
path: /smtp/address
|
|
||||||
value: 127.0.0.1:2525
|
|
||||||
- op: replace
|
|
||||||
path: /smtp/debug
|
|
||||||
value: false
|
|
|
@ -24,7 +24,6 @@ function build {
|
||||||
echo "building $dirname..."
|
echo "building $dirname..."
|
||||||
|
|
||||||
CGO_ENABLED=0 GOOS="$os" GOARCH="$arch" go build \
|
CGO_ENABLED=0 GOOS="$os" GOARCH="$arch" go build \
|
||||||
-mod=vendor \
|
|
||||||
-ldflags="-s -w -X 'main.GitRef=$(current_commit_ref)' -X 'main.ProjectVersion=$(current_version)' -X 'main.BuildDate=$(current_date)'" \
|
-ldflags="-s -w -X 'main.GitRef=$(current_commit_ref)' -X 'main.ProjectVersion=$(current_version)' -X 'main.BuildDate=$(current_date)'" \
|
||||||
-gcflags=-trimpath="${PWD}" \
|
-gcflags=-trimpath="${PWD}" \
|
||||||
-asmflags=-trimpath="${PWD}" \
|
-asmflags=-trimpath="${PWD}" \
|
||||||
|
@ -74,13 +73,17 @@ function dump_default_conf {
|
||||||
local command=$1
|
local command=$1
|
||||||
local os=$2
|
local os=$2
|
||||||
local arch=$3
|
local arch=$3
|
||||||
local tmp_conf=$(mktemp)
|
|
||||||
local patched_conf=$(mktemp)
|
local patched_conf=$(mktemp)
|
||||||
|
|
||||||
go run "$PROJECT_DIR/cmd/$command" -dump-config > "$tmp_conf"
|
go run "$PROJECT_DIR/cmd/$command" -dump-config > "$patched_conf"
|
||||||
cat "$tmp_conf" | yaml-patch -o misc/release/config-patch.yml > "$patched_conf"
|
|
||||||
|
while IFS= read -r yq_cmd; do
|
||||||
|
echo "patching configuration with '$yq_cmd'..."
|
||||||
|
tools/yq/bin/yq -i "$yq_cmd" "$patched_conf"
|
||||||
|
done < misc/release/config-patch.txt
|
||||||
|
|
||||||
copy "$command" $os $arch "$patched_conf" "config.yml"
|
copy "$command" $os $arch "$patched_conf" "config.yml"
|
||||||
rm -f "$tmp_conf" "$patched_conf"
|
rm -f "$patched_conf"
|
||||||
}
|
}
|
||||||
|
|
||||||
function compress {
|
function compress {
|
||||||
|
|
|
@ -6,7 +6,7 @@ for i in {1..10}; do
|
||||||
-s "Test ${i}" \
|
-s "Test ${i}" \
|
||||||
-a README.md \
|
-a README.md \
|
||||||
-a package.json \
|
-a package.json \
|
||||||
foo_${i}@bar_${i}.com \
|
foo_${i}@bar${i}.com \
|
||||||
<<EOF
|
<<EOF
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus eget metus ornare, placerat mi nec, ornare magna. Vivamus volutpat nisi et aliquet mollis. Phasellus eu malesuada erat, vel molestie ipsum. Etiam tincidunt ligula eget scelerisque blandit. Vivamus nec quam vitae felis rutrum cursus a eget elit. Integer vestibulum ultrices iaculis. Integer varius sapien ac ante accumsan euismod. Quisque fermentum dui nec porttitor pellentesque. Vivamus lobortis mauris eget metus sagittis tempor.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus eget metus ornare, placerat mi nec, ornare magna. Vivamus volutpat nisi et aliquet mollis. Phasellus eu malesuada erat, vel molestie ipsum. Etiam tincidunt ligula eget scelerisque blandit. Vivamus nec quam vitae felis rutrum cursus a eget elit. Integer vestibulum ultrices iaculis. Integer varius sapien ac ante accumsan euismod. Quisque fermentum dui nec porttitor pellentesque. Vivamus lobortis mauris eget metus sagittis tempor.
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
|
@ -11,21 +11,21 @@
|
||||||
"author": "William Petit <wpetit@cadoles.com>",
|
"author": "William Petit <wpetit@cadoles.com>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.2.0",
|
"@babel/core": "^7.23.9",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.2.1",
|
"@babel/plugin-proposal-class-properties": "^7.2.1",
|
||||||
"@babel/preset-env": "^7.2.0",
|
"@babel/preset-env": "^7.23.9",
|
||||||
"babel-loader": "^8.0.4",
|
"babel-loader": "^9.1.3",
|
||||||
"bulma": "^0.8.2",
|
"bulma": "^0.9.4",
|
||||||
"bulma-switch": "^2.0.0",
|
"bulma-switch": "^2.0.4",
|
||||||
"css-loader": "^3.5.2",
|
"css-loader": "^6.10.0",
|
||||||
"file-loader": "^6.0.0",
|
"file-loader": "^6.2.0",
|
||||||
"mini-css-extract-plugin": "^0.9.0",
|
"mini-css-extract-plugin": "^2.8.0",
|
||||||
"node-sass": "^4.10.0",
|
"node-sass": "^9.0.0",
|
||||||
"resolve-url-loader": "^3.0.0",
|
"resolve-url-loader": "^5.0.0",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^14.1.1",
|
||||||
"stimulus": "^1.1.0",
|
"stimulus": "^3.2.2",
|
||||||
"style-loader": "^1.1.4",
|
"style-loader": "^3.3.4",
|
||||||
"webpack": "^4.25.0",
|
"webpack": "^5.90.3",
|
||||||
"webpack-cli": "^3.1.2"
|
"webpack-cli": "^5.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,8 @@ module.exports = {
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: "/css/[name].css",
|
filename: "./css/[name].css",
|
||||||
chunkFilename: "/css/[id].css"
|
chunkFilename: "./css/[id].css"
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
}
|
}
|
Loading…
Reference in New Issue