From 57ef6b0d7fd8b7b4478fd6d63da71413dfff088d Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Wed, 1 May 2019 14:08:29 +0200 Subject: [PATCH] go/svc/invoice: add statusz --- go/svc/invoice/BUILD.bazel | 2 + go/svc/invoice/main.go | 43 ++++++++------- go/svc/invoice/model.go | 59 ++++++++++++++++++++ go/svc/invoice/proto/BUILD.bazel | 10 +--- go/svc/invoice/statusz.go | 93 ++++++++++++++++++++++++++++++++ 5 files changed, 176 insertions(+), 31 deletions(-) create mode 100644 go/svc/invoice/statusz.go diff --git a/go/svc/invoice/BUILD.bazel b/go/svc/invoice/BUILD.bazel index d654bc94..0f8bd8bf 100644 --- a/go/svc/invoice/BUILD.bazel +++ b/go/svc/invoice/BUILD.bazel @@ -6,11 +6,13 @@ go_library( "main.go", "model.go", "render.go", + "statusz.go", ], importpath = "code.hackerspace.pl/hscloud/go/svc/invoice", visibility = ["//visibility:private"], deps = [ "//go/mirko:go_default_library", + "//go/statusz:go_default_library", "//go/svc/invoice/templates:go_default_library", "//proto/invoice:go_default_library", "@com_github_golang_glog//:go_default_library", diff --git a/go/svc/invoice/main.go b/go/svc/invoice/main.go index 8a5ddbe9..33bc125b 100644 --- a/go/svc/invoice/main.go +++ b/go/svc/invoice/main.go @@ -111,45 +111,43 @@ func newService(m *model) *service { } } -func (s *service) RenderInvoice(req *pb.RenderInvoiceRequest, srv pb.Invoicer_RenderInvoiceServer) error { - sealed, err := s.m.getSealedUid(srv.Context(), req.Uid) +func (s *service) invoicePDF(ctx context.Context, uid string) ([]byte, error) { + sealed, err := s.m.getSealedUid(ctx, uid) if err != nil { - if _, ok := status.FromError(err); ok { - return err - } - glog.Errorf("getSealedUid(_, %q): %v", req.Uid, err) - return status.Error(codes.Unavailable, "internal server error") + return nil, err } var rendered []byte if sealed != "" { // Invoice is sealed, return stored PDF. - rendered, err = s.m.getRendered(srv.Context(), req.Uid) + rendered, err = s.m.getRendered(ctx, uid) if err != nil { - if _, ok := status.FromError(err); ok { - return err - } - glog.Errorf("getRendered(_, %q): %v", req.Uid, err) - return status.Error(codes.Unavailable, "internal server error") + return nil, err } } else { // Invoice is proforma, render. - invoice, err := s.m.getInvoice(srv.Context(), req.Uid) + invoice, err := s.m.getInvoice(ctx, uid) if err != nil { - if _, ok := status.FromError(err); ok { - return err - } - glog.Errorf("getInvoice(_, %q): %v", req.Uid, err) - return status.Error(codes.Unavailable, "internal server error") + return nil, err } - glog.Infof("%+v", invoice) rendered, err = renderInvoicePDF(invoice, "xxxx", true) if err != nil { - glog.Errorf("renderProformaPDF(_): %v", err) - return status.Error(codes.Unavailable, "internal server error") + return nil, err } } + return rendered, nil +} + +func (s *service) RenderInvoice(req *pb.RenderInvoiceRequest, srv pb.Invoicer_RenderInvoiceServer) error { + rendered, err := s.invoicePDF(srv.Context(), req.Uid) + if err != nil { + if _, ok := status.FromError(err); ok { + return err + } + glog.Errorf("invoicePDF(_, %q): %v", req.Uid, err) + return status.Error(codes.Unavailable, "internal server error") + } chunkSize := 16 * 1024 chunk := &pb.RenderInvoiceResponse{} @@ -201,6 +199,7 @@ func main() { } s := newService(m) + s.setupStatusz(mi) pb.RegisterInvoicerServer(mi.GRPC(), s) if err := mi.Serve(); err != nil { diff --git a/go/svc/invoice/model.go b/go/svc/invoice/model.go index 0ed8245c..cb15a166 100644 --- a/go/svc/invoice/model.go +++ b/go/svc/invoice/model.go @@ -211,3 +211,62 @@ func (m *model) getInvoice(ctx context.Context, uid string) (*pb.Invoice, error) } return p, nil } + +type invoice struct { + ID int64 + Number string + Sealed bool + Proto *pb.Invoice + TotalNet int + Total int +} + +func (m *model) getInvoices(ctx context.Context) ([]invoice, error) { + q := ` + select invoice_seal.final_uid, invoice.id, invoice.proto from invoice + left join invoice_seal + on invoice_seal.invoice_id = invoice.id + ` + rows, err := m.db.QueryContext(ctx, q) + if err != nil { + return []invoice{}, err + } + defer rows.Close() + + res := []invoice{} + for rows.Next() { + i := invoice{ + Proto: &pb.Invoice{}, + } + buf := []byte{} + + number := sql.NullString{} + if err := rows.Scan(&number, &i.ID, &buf); err != nil { + return []invoice{}, err + } + + if err := proto.Unmarshal(buf, i.Proto); err != nil { + return []invoice{}, err + } + + if number.Valid { + i.Sealed = true + i.Number = number.String + } else { + i.Number = "proforma" + } + + i.Total = 0 + i.TotalNet = 0 + for _, it := range i.Proto.Item { + rowTotalNet := int(it.UnitPrice * it.Count) + rowTotal := int(float64(rowTotalNet) * (float64(1) + float64(it.Vat)/100000)) + i.TotalNet += rowTotalNet + i.Total += rowTotal + } + + res = append(res, i) + } + + return res, nil +} diff --git a/go/svc/invoice/proto/BUILD.bazel b/go/svc/invoice/proto/BUILD.bazel index 63c82cc5..6c78fa3d 100644 --- a/go/svc/invoice/proto/BUILD.bazel +++ b/go/svc/invoice/proto/BUILD.bazel @@ -2,15 +2,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", - srcs = [ - "generate.go", - "inboice.pb.go", - ], + srcs = ["generate.go"], importpath = "code.hackerspace.pl/hscloud/go/svc/invoice/proto", visibility = ["//visibility:public"], - deps = [ - "@com_github_golang_protobuf//proto:go_default_library", - "@org_golang_google_grpc//:go_default_library", - "@org_golang_x_net//context:go_default_library", - ], ) diff --git a/go/svc/invoice/statusz.go b/go/svc/invoice/statusz.go new file mode 100644 index 00000000..dbc59f10 --- /dev/null +++ b/go/svc/invoice/statusz.go @@ -0,0 +1,93 @@ +package main + +import ( + "context" + "fmt" + "net/http" + + "code.hackerspace.pl/hscloud/go/mirko" + "code.hackerspace.pl/hscloud/go/statusz" + "github.com/golang/glog" +) + +const invoicesFragment = ` + +
+ {{ .Msg }} + + + + + + + + + {{ range .Invoices }} + {{ if .Sealed }} + + {{ else }} + + {{ end }} + + + + + + + {{ end }} +
Internal IDNumberCustomerAmount (net)Actions
{{ .ID }}{{ .Number }}{{ index .Proto.CustomerBilling 0 }}{{ .TotalNetPretty }} + View +
+
+` + +type templateInvoice struct { + invoice + TotalNetPretty string +} + +func (s *service) setupStatusz(m *mirko.Mirko) { + statusz.AddStatusPart("Invoices", invoicesFragment, func(ctx context.Context) interface{} { + var res struct { + Invoices []templateInvoice + Msg string + } + invoices, err := s.m.getInvoices(ctx) + res.Invoices = make([]templateInvoice, len(invoices)) + for i, inv := range invoices { + res.Invoices[i] = templateInvoice{ + invoice: inv, + TotalNetPretty: fmt.Sprintf("%.2f %s", float64(inv.TotalNet)/100, inv.Proto.Unit), + } + } + + if err != nil { + glog.Errorf("Could not get invoices for statusz: %v", err) + res.Msg = fmt.Sprintf("Could not get invoices: %v", err) + } + return res + }) + + m.HTTPMux().HandleFunc("/debug/view", func(w http.ResponseWriter, r *http.Request) { + rendered, err := s.invoicePDF(r.Context(), r.URL.Query().Get("id")) + if err != nil { + fmt.Fprintf(w, "error: %v", err) + } + w.Header().Set("Content-type", "application/pdf") + w.Write(rendered) + }) +}