blob: 33bc125b36f86231f923da5eb3cdebe903c45b17 [file] [log] [blame]
package main
import (
"context"
"flag"
"github.com/golang/glog"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"code.hackerspace.pl/hscloud/go/mirko"
pb "code.hackerspace.pl/hscloud/proto/invoice"
)
var (
flagDatabasePath 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) invoicePDF(ctx context.Context, uid string) ([]byte, error) {
sealed, err := s.m.getSealedUid(ctx, uid)
if err != nil {
return nil, err
}
var rendered []byte
if sealed != "" {
// Invoice is sealed, return stored PDF.
rendered, err = s.m.getRendered(ctx, uid)
if err != nil {
return nil, err
}
} else {
// Invoice is proforma, render.
invoice, err := s.m.getInvoice(ctx, uid)
if err != nil {
return nil, err
}
rendered, err = renderInvoicePDF(invoice, "xxxx", true)
if err != nil {
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{}
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(&flagDatabasePath, "db_path", "./foo.db", "path to sqlite database")
flag.BoolVar(&flagInit, "init_db", false, "init database and exit")
flag.Parse()
m, err := newModel(flagDatabasePath)
if err != nil {
glog.Exitf("newModel: %v", err)
}
if flagInit {
glog.Exit(m.init())
}
mi := mirko.New()
if err := mi.Listen(); err != nil {
glog.Exitf("Listen failed: %v", err)
}
s := newService(m)
s.setupStatusz(mi)
pb.RegisterInvoicerServer(mi.GRPC(), s)
if err := mi.Serve(); err != nil {
glog.Exitf("Serve failed: %v", err)
}
<-mi.Done()
}