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) } }