blob: 33bc125b36f86231f923da5eb3cdebe903c45b17 [file] [log] [blame]
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +02001package main
2
3import (
4 "context"
5 "flag"
6
7 "github.com/golang/glog"
8 "google.golang.org/grpc/codes"
9 "google.golang.org/grpc/status"
10
11 "code.hackerspace.pl/hscloud/go/mirko"
12 pb "code.hackerspace.pl/hscloud/proto/invoice"
13)
14
15var (
16 flagDatabasePath string
17 flagInit bool
18 flagDisablePKI bool
19)
20
21type service struct {
22 m *model
23}
24
25func (s *service) CreateInvoice(ctx context.Context, req *pb.CreateInvoiceRequest) (*pb.CreateInvoiceResponse, error) {
26 if req.Invoice == nil {
27 return nil, status.Error(codes.InvalidArgument, "invoice must be given")
28 }
29 if len(req.Invoice.Item) < 1 {
30 return nil, status.Error(codes.InvalidArgument, "invoice must contain at least one item")
31 }
32 for i, item := range req.Invoice.Item {
33 if item.Title == "" {
34 return nil, status.Errorf(codes.InvalidArgument, "invoice item %d must have title set", i)
35 }
36 if item.Count == 0 || item.Count > 1000000 {
37 return nil, status.Errorf(codes.InvalidArgument, "invoice item %d must have correct count", i)
38 }
39 if item.UnitPrice == 0 {
40 return nil, status.Errorf(codes.InvalidArgument, "invoice item %d must have correct unit price", i)
41 }
42 if item.Vat > 100000 {
43 return nil, status.Errorf(codes.InvalidArgument, "invoice item %d must have correct vat set", i)
44 }
45 }
46 if len(req.Invoice.CustomerBilling) < 1 {
47 return nil, status.Error(codes.InvalidArgument, "invoice must contain at least one line of the customer's billing address")
48 }
49 if len(req.Invoice.InvoicerBilling) < 1 {
50 return nil, status.Error(codes.InvalidArgument, "invoice must contain at least one line of the invoicer's billing address")
51 }
52 for i, c := range req.Invoice.InvoicerContact {
53 if c.Medium == "" {
54 return nil, status.Errorf(codes.InvalidArgument, "contact point %d must have medium set", i)
55 }
56 if c.Contact == "" {
57 return nil, status.Errorf(codes.InvalidArgument, "contact point %d must have contact set", i)
58 }
59 }
60 if req.Invoice.InvoicerVatId == "" {
61 return nil, status.Error(codes.InvalidArgument, "invoice must contain invoicer's vat id")
62 }
63
64 uid, err := s.m.createInvoice(ctx, req.Invoice)
65 if err != nil {
66 if _, ok := status.FromError(err); ok {
67 return nil, err
68 }
69 glog.Errorf("createInvoice(_, _): %v", err)
70 return nil, status.Error(codes.Unavailable, "could not create invoice")
71 }
72 return &pb.CreateInvoiceResponse{
73 Uid: uid,
74 }, nil
75}
76
77func (s *service) GetInvoice(ctx context.Context, req *pb.GetInvoiceRequest) (*pb.GetInvoiceResponse, error) {
78 invoice, err := s.m.getInvoice(ctx, req.Uid)
79 if err != nil {
80 if _, ok := status.FromError(err); ok {
81 return nil, err
82 }
83 glog.Errorf("getInvoice(_, %q): %v", req.Uid, err)
84 return nil, status.Error(codes.Unavailable, "internal server error")
85 }
86 sealedUid, err := s.m.getSealedUid(ctx, req.Uid)
87 if err != nil {
88 if _, ok := status.FromError(err); ok {
89 return nil, err
90 }
91 glog.Errorf("getSealedUid(_, %q): %v", req.Uid, err)
92 return nil, status.Error(codes.Unavailable, "internal server error")
93 }
94
95 res := &pb.GetInvoiceResponse{
96 Invoice: invoice,
97 }
98 if sealedUid == "" {
99 res.State = pb.GetInvoiceResponse_STATE_PROFORMA
100 } else {
101 res.State = pb.GetInvoiceResponse_STATE_SEALED
102 res.FinalUid = sealedUid
103 }
104
105 return res, nil
106}
107
108func newService(m *model) *service {
109 return &service{
110 m: m,
111 }
112}
113
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200114func (s *service) invoicePDF(ctx context.Context, uid string) ([]byte, error) {
115 sealed, err := s.m.getSealedUid(ctx, uid)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200116 if err != nil {
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200117 return nil, err
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200118 }
119
120 var rendered []byte
121 if sealed != "" {
122 // Invoice is sealed, return stored PDF.
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200123 rendered, err = s.m.getRendered(ctx, uid)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200124 if err != nil {
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200125 return nil, err
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200126 }
127 } else {
128 // Invoice is proforma, render.
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200129 invoice, err := s.m.getInvoice(ctx, uid)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200130 if err != nil {
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200131 return nil, err
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200132 }
133
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200134 rendered, err = renderInvoicePDF(invoice, "xxxx", true)
135 if err != nil {
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200136 return nil, err
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200137 }
138 }
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200139 return rendered, nil
140}
141
142func (s *service) RenderInvoice(req *pb.RenderInvoiceRequest, srv pb.Invoicer_RenderInvoiceServer) error {
143 rendered, err := s.invoicePDF(srv.Context(), req.Uid)
144 if err != nil {
145 if _, ok := status.FromError(err); ok {
146 return err
147 }
148 glog.Errorf("invoicePDF(_, %q): %v", req.Uid, err)
149 return status.Error(codes.Unavailable, "internal server error")
150 }
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200151
152 chunkSize := 16 * 1024
153 chunk := &pb.RenderInvoiceResponse{}
154 for i := 0; i < len(rendered); i += chunkSize {
155 if i+chunkSize > len(rendered) {
156 chunk.Data = rendered[i:len(rendered)]
157 } else {
158 chunk.Data = rendered[i : i+chunkSize]
159 }
160 if err := srv.Send(chunk); err != nil {
161 glog.Errorf("srv.Send: %v", err)
162 return status.Error(codes.Unavailable, "stream broken")
163 }
164 }
165 return nil
166}
167
168func (s *service) SealInvoice(ctx context.Context, req *pb.SealInvoiceRequest) (*pb.SealInvoiceResponse, error) {
169 if err := s.m.sealInvoice(ctx, req.Uid); err != nil {
170 if _, ok := status.FromError(err); ok {
171 return nil, err
172 }
173 glog.Errorf("sealInvoice(_, %q): %v", req.Uid, err)
174 return nil, status.Error(codes.Unavailable, "internal server error")
175 }
176 return &pb.SealInvoiceResponse{}, nil
177}
178
179func init() {
180 flag.Set("logtostderr", "true")
181}
182
183func main() {
184 flag.StringVar(&flagDatabasePath, "db_path", "./foo.db", "path to sqlite database")
185 flag.BoolVar(&flagInit, "init_db", false, "init database and exit")
186 flag.Parse()
187
188 m, err := newModel(flagDatabasePath)
189 if err != nil {
190 glog.Exitf("newModel: %v", err)
191 }
192 if flagInit {
193 glog.Exit(m.init())
194 }
195
196 mi := mirko.New()
197 if err := mi.Listen(); err != nil {
198 glog.Exitf("Listen failed: %v", err)
199 }
200
201 s := newService(m)
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200202 s.setupStatusz(mi)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200203 pb.RegisterInvoicerServer(mi.GRPC(), s)
204
205 if err := mi.Serve(); err != nil {
206 glog.Exitf("Serve failed: %v", err)
207 }
208
209 <-mi.Done()
210}