216 lines
6.0 KiB
Go
216 lines
6.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"net"
|
|
|
|
"github.com/golang/glog"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/reflection"
|
|
"google.golang.org/grpc/status"
|
|
|
|
pb "code.hackerspace.pl/q3k/inboice/proto"
|
|
)
|
|
|
|
var (
|
|
flagListenAddress string
|
|
flagInit bool
|
|
flagDisablePKI bool
|
|
)
|
|
|
|
type service struct {
|
|
m *model
|
|
}
|
|
|
|
func (s *service) CreateInvoice(ctx context.Context, req *pb.CreateInvoiceRequest) (*pb.CreateInvoiceResponse, error) {
|
|
if req.Invoice == nil {
|
|
return nil, status.Error(codes.InvalidArgument, "invoice must be given")
|
|
}
|
|
if len(req.Invoice.Item) < 1 {
|
|
return nil, status.Error(codes.InvalidArgument, "invoice must contain at least one item")
|
|
}
|
|
for i, item := range req.Invoice.Item {
|
|
if item.Title == "" {
|
|
return nil, status.Errorf(codes.InvalidArgument, "invoice item %d must have title set", i)
|
|
}
|
|
if item.Count == 0 || item.Count > 1000000 {
|
|
return nil, status.Errorf(codes.InvalidArgument, "invoice item %d must have correct count", i)
|
|
}
|
|
if item.UnitPrice == 0 {
|
|
return nil, status.Errorf(codes.InvalidArgument, "invoice item %d must have correct unit price", i)
|
|
}
|
|
if item.Vat > 100000 {
|
|
return nil, status.Errorf(codes.InvalidArgument, "invoice item %d must have correct vat set", i)
|
|
}
|
|
}
|
|
if len(req.Invoice.CustomerBilling) < 1 {
|
|
return nil, status.Error(codes.InvalidArgument, "invoice must contain at least one line of the customer's billing address")
|
|
}
|
|
if len(req.Invoice.InvoicerBilling) < 1 {
|
|
return nil, status.Error(codes.InvalidArgument, "invoice must contain at least one line of the invoicer's billing address")
|
|
}
|
|
for i, c := range req.Invoice.InvoicerContact {
|
|
if c.Medium == "" {
|
|
return nil, status.Errorf(codes.InvalidArgument, "contact point %d must have medium set", i)
|
|
}
|
|
if c.Contact == "" {
|
|
return nil, status.Errorf(codes.InvalidArgument, "contact point %d must have contact set", i)
|
|
}
|
|
}
|
|
if req.Invoice.InvoicerVatId == "" {
|
|
return nil, status.Error(codes.InvalidArgument, "invoice must contain invoicer's vat id")
|
|
}
|
|
|
|
uid, err := s.m.createInvoice(ctx, req.Invoice)
|
|
if err != nil {
|
|
if _, ok := status.FromError(err); ok {
|
|
return nil, err
|
|
}
|
|
glog.Errorf("createInvoice(_, _): %v", err)
|
|
return nil, status.Error(codes.Unavailable, "could not create invoice")
|
|
}
|
|
return &pb.CreateInvoiceResponse{
|
|
Uid: uid,
|
|
}, nil
|
|
}
|
|
|
|
func (s *service) GetInvoice(ctx context.Context, req *pb.GetInvoiceRequest) (*pb.GetInvoiceResponse, error) {
|
|
invoice, err := s.m.getInvoice(ctx, req.Uid)
|
|
if err != nil {
|
|
if _, ok := status.FromError(err); ok {
|
|
return nil, err
|
|
}
|
|
glog.Errorf("getInvoice(_, %q): %v", req.Uid, err)
|
|
return nil, status.Error(codes.Unavailable, "internal server error")
|
|
}
|
|
sealedUid, err := s.m.getSealedUid(ctx, req.Uid)
|
|
if err != nil {
|
|
if _, ok := status.FromError(err); ok {
|
|
return nil, err
|
|
}
|
|
glog.Errorf("getSealedUid(_, %q): %v", req.Uid, err)
|
|
return nil, status.Error(codes.Unavailable, "internal server error")
|
|
}
|
|
|
|
res := &pb.GetInvoiceResponse{
|
|
Invoice: invoice,
|
|
}
|
|
if sealedUid == "" {
|
|
res.State = pb.GetInvoiceResponse_STATE_PROFORMA
|
|
} else {
|
|
res.State = pb.GetInvoiceResponse_STATE_SEALED
|
|
res.FinalUid = sealedUid
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func newService(m *model) *service {
|
|
return &service{
|
|
m: m,
|
|
}
|
|
}
|
|
|
|
func (s *service) RenderInvoice(req *pb.RenderInvoiceRequest, srv pb.Inboice_RenderInvoiceServer) error {
|
|
sealed, err := s.m.getSealedUid(srv.Context(), req.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")
|
|
}
|
|
|
|
var rendered []byte
|
|
if sealed != "" {
|
|
// Invoice is sealed, return stored PDF.
|
|
rendered, err = s.m.getRendered(srv.Context(), req.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")
|
|
}
|
|
} else {
|
|
// Invoice is proforma, render.
|
|
invoice, err := s.m.getInvoice(srv.Context(), req.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")
|
|
}
|
|
|
|
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")
|
|
}
|
|
}
|
|
|
|
chunkSize := 16 * 1024
|
|
chunk := &pb.RenderInvoiceResponse{}
|
|
for i := 0; i < len(rendered); i += chunkSize {
|
|
if i+chunkSize > len(rendered) {
|
|
chunk.Data = rendered[i:len(rendered)]
|
|
} else {
|
|
chunk.Data = rendered[i : i+chunkSize]
|
|
}
|
|
if err := srv.Send(chunk); err != nil {
|
|
glog.Errorf("srv.Send: %v", err)
|
|
return status.Error(codes.Unavailable, "stream broken")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *service) SealInvoice(ctx context.Context, req *pb.SealInvoiceRequest) (*pb.SealInvoiceResponse, error) {
|
|
if err := s.m.sealInvoice(ctx, req.Uid); err != nil {
|
|
if _, ok := status.FromError(err); ok {
|
|
return nil, err
|
|
}
|
|
glog.Errorf("sealInvoice(_, %q): %v", req.Uid, err)
|
|
return nil, status.Error(codes.Unavailable, "internal server error")
|
|
}
|
|
return &pb.SealInvoiceResponse{}, nil
|
|
}
|
|
|
|
func init() {
|
|
flag.Set("logtostderr", "true")
|
|
}
|
|
|
|
func main() {
|
|
flag.StringVar(&flagListenAddress, "listen_address", "127.0.0.1:42000", "gRPC listen address")
|
|
flag.BoolVar(&flagInit, "init_db", false, "init database and exit")
|
|
flag.Parse()
|
|
|
|
m, err := newModel("./foo.db")
|
|
if err != nil {
|
|
glog.Exitf("newModel: %v", err)
|
|
}
|
|
if flagInit {
|
|
glog.Exit(m.init())
|
|
}
|
|
s := newService(m)
|
|
|
|
grpc.EnableTracing = true
|
|
grpcLis, err := net.Listen("tcp", flagListenAddress)
|
|
if err != nil {
|
|
glog.Exitf("net.Listen(tcp, %q): %v", flagListenAddress, err)
|
|
}
|
|
|
|
grpcSrv := grpc.NewServer()
|
|
pb.RegisterInboiceServer(grpcSrv, s)
|
|
reflection.Register(grpcSrv)
|
|
|
|
glog.Infof("Starting gRPC on %v", flagListenAddress)
|
|
if err := grpcSrv.Serve(grpcLis); err != nil {
|
|
glog.Exitf("grpcServ.Serve: %v", err)
|
|
}
|
|
}
|