214 lines
5.4 KiB
Go
214 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"io"
|
|
"mime"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.icedream.tech/icedream/oneandone-billing-mailer/pkg/api"
|
|
"git.icedream.tech/icedream/oneandone-billing-mailer/pkg/appcontext"
|
|
"git.icedream.tech/icedream/oneandone-billing-mailer/pkg/auth"
|
|
"git.icedream.tech/icedream/oneandone-billing-mailer/pkg/auth/authcache"
|
|
"git.icedream.tech/icedream/oneandone-billing-mailer/pkg/auth/authcache/fileauthcache"
|
|
"git.icedream.tech/icedream/oneandone-billing-mailer/pkg/backend"
|
|
"git.icedream.tech/icedream/oneandone-billing-mailer/pkg/environment"
|
|
"github.com/go-logr/logr"
|
|
"github.com/go-logr/zapr"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
const (
|
|
// webviewDebug toggles the debug mode in the Webview library.
|
|
webviewDebug = false
|
|
|
|
// apiDebug toggles the debug mode in the OpenAPI client.
|
|
apiDebug = false
|
|
)
|
|
|
|
// dateFormat is the format used to pass dates to the 1&1 API
|
|
const dateFormat = "2006-01-02"
|
|
|
|
// main is the main entrypoint.
|
|
func main() {
|
|
ctx := context.Background()
|
|
|
|
// load logger
|
|
zapLogger, err := zap.NewDevelopment()
|
|
if err != nil {
|
|
os.Stderr.WriteString("Could not initialize zap logger\n")
|
|
os.Exit(1)
|
|
return
|
|
}
|
|
generalLog := zapr.NewLogger(zapLogger)
|
|
ctx = logr.NewContext(ctx, generalLog)
|
|
log := generalLog.WithName("app")
|
|
|
|
// parse cmdline
|
|
flag.Parse()
|
|
|
|
// initialize oauth stuff for API
|
|
oauth, err := auth.NewOAuthFromEnvironment(ctx)
|
|
if err != nil {
|
|
log.Error(err, "Failed to initialize central login helper")
|
|
return
|
|
}
|
|
|
|
// Try and get an initial token first
|
|
tokenCache := fileauthcache.NewFileAuthCache("token.json")
|
|
token, err := tokenCache.Fetch()
|
|
if err != nil {
|
|
log.Error(err, "Failed to fetch token from cache")
|
|
return
|
|
}
|
|
if token == nil {
|
|
// We need to fetch an initial token
|
|
tokenSource, err := oauth.TokenSource(nil, requestUserConsentFunc(ctx))
|
|
if err != nil {
|
|
log.Error(err, "Failed to set up OAuth token source")
|
|
return
|
|
}
|
|
cacheTokenSource := authcache.NewCacheTokenSource(ctx, tokenCache, tokenSource)
|
|
token, err = cacheTokenSource.Token()
|
|
if err != nil {
|
|
log.Error(err, "Failed to fetch initial token")
|
|
return
|
|
}
|
|
}
|
|
|
|
// Set up new token source only for refreshing
|
|
tokenSource, err := oauth.TokenSource(token, nil)
|
|
if err != nil {
|
|
log.Error(err, "Failed to set up OAuth token source")
|
|
return
|
|
}
|
|
cacheTokenSource := authcache.NewCacheTokenSource(ctx, tokenCache, tokenSource)
|
|
|
|
reuseTokenSource := oauth2.ReuseTokenSource(token, cacheTokenSource)
|
|
|
|
client := oauth2.NewClient(ctx, reuseTokenSource)
|
|
client.Transport = backend.BackendRequestTransport(client.Transport)
|
|
|
|
// MSSA invoice requests from here
|
|
|
|
appctx, err := appcontext.GetEnvironmentContext()
|
|
if err != nil {
|
|
log.Error(err, "Failed to fetch app env context")
|
|
return
|
|
}
|
|
mssaBaseURL, err := appctx.MSSA.BaseURL()
|
|
if err != nil {
|
|
log.Error(err, "Failed to parse MSSA base URL")
|
|
return
|
|
}
|
|
|
|
auth := context.WithValue(ctx, api.ContextOAuth2, reuseTokenSource)
|
|
|
|
apiConfig := api.NewConfiguration()
|
|
apiConfig.HTTPClient = client
|
|
apiConfig.Debug = apiDebug
|
|
apiConfig.UserAgent = environment.UserAgent()
|
|
|
|
apiClient := api.NewAPIClient(apiConfig)
|
|
|
|
now := time.Now()
|
|
thisMonthStartDate := now.
|
|
// go to first day of this month
|
|
AddDate(0, 0, 1-now.Day())
|
|
nextMonthStartDate := thisMonthStartDate.
|
|
// add 1 month
|
|
AddDate(0, 1, 0)
|
|
|
|
apiReq := apiClient.InvoicesApi.GetInvoicesInInterval(auth,
|
|
thisMonthStartDate.Format(dateFormat),
|
|
nextMonthStartDate.Format(dateFormat)).
|
|
PerPage(1)
|
|
apiResp, resp, err := apiReq.Page(1).Execute()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
panic(resp.Status)
|
|
}
|
|
for _, invoice := range apiResp.GetInvoice() {
|
|
// skip entries without invoice document
|
|
doc := invoice.Links.InvoiceDocument
|
|
if doc == nil {
|
|
continue
|
|
}
|
|
|
|
ext := ".pdf"
|
|
if doc.Type != nil {
|
|
exts, err := mime.ExtensionsByType(*doc.Type)
|
|
if err != nil {
|
|
log.V(1).Error(err, "Failed to fetch extensions for type",
|
|
"type", *doc.Type)
|
|
} else if len(exts) > 0 {
|
|
ext = exts[0]
|
|
}
|
|
}
|
|
// make sure extension starts with dot
|
|
if !strings.HasPrefix(ext, ".") {
|
|
ext = "." + ext
|
|
}
|
|
|
|
title := "Rechnung"
|
|
if doc.Title != nil {
|
|
title = *doc.Title
|
|
}
|
|
fileName := filepath.Clean(title) + ext
|
|
|
|
u, err := url.Parse(doc.Href)
|
|
if err != nil {
|
|
log.V(1).Error(err, "Failed to parse invoice document hyperlink reference, skipping",
|
|
"url", doc.Href)
|
|
continue
|
|
}
|
|
u = mssaBaseURL.ResolveReference(u)
|
|
|
|
// open PDF on server
|
|
resp, err := apiClient.GetConfig().HTTPClient.Get(u.String())
|
|
if err != nil {
|
|
log.Error(err, "Invoice PDF download request failed, skipping",
|
|
"fileName", fileName)
|
|
continue
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
resp.Body.Close()
|
|
log.Error(err, "Unexpected status code for invoice PDF download request, skipping",
|
|
"fileName", fileName,
|
|
"statusCode", resp.StatusCode)
|
|
continue
|
|
}
|
|
|
|
// write PDF to file as we download
|
|
f, err := os.Create(fileName)
|
|
if err != nil {
|
|
resp.Body.Close()
|
|
log.Error(err, "Could not create invoice PDF",
|
|
"fileName", fileName)
|
|
continue
|
|
}
|
|
_, err = io.Copy(f, resp.Body)
|
|
resp.Body.Close()
|
|
if err != nil {
|
|
os.Remove(f.Name())
|
|
log.Error(err, "Could not write to invoice PDF",
|
|
"fileName", fileName)
|
|
}
|
|
err = f.Close()
|
|
if err != nil {
|
|
os.Remove(f.Name())
|
|
log.Error(err, "Could not complete writing to invoice PDF",
|
|
"fileName", fileName)
|
|
}
|
|
}
|
|
}
|