blob: 4a80441b1ded29070633edcbe5443872cebc3268 [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) {
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020026 if req.InvoiceData == nil {
27 return nil, status.Error(codes.InvalidArgument, "invoice data must be given")
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020028 }
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020029 if len(req.InvoiceData.Item) < 1 {
30 return nil, status.Error(codes.InvalidArgument, "invoice data must contain at least one item")
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020031 }
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020032 for i, item := range req.InvoiceData.Item {
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020033 if item.Title == "" {
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020034 return nil, status.Errorf(codes.InvalidArgument, "invoice data item %d must have title set", i)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020035 }
36 if item.Count == 0 || item.Count > 1000000 {
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020037 return nil, status.Errorf(codes.InvalidArgument, "invoice data item %d must have correct count", i)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020038 }
39 if item.UnitPrice == 0 {
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020040 return nil, status.Errorf(codes.InvalidArgument, "invoice data item %d must have correct unit price", i)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020041 }
42 if item.Vat > 100000 {
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020043 return nil, status.Errorf(codes.InvalidArgument, "invoice data item %d must have correct vat set", i)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020044 }
45 }
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020046 if len(req.InvoiceData.CustomerBilling) < 1 {
47 return nil, status.Error(codes.InvalidArgument, "invoice data must contain at least one line of the customer's billing address")
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020048 }
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020049 if len(req.InvoiceData.InvoicerBilling) < 1 {
50 return nil, status.Error(codes.InvalidArgument, "invoice data must contain at least one line of the invoicer's billing address")
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020051 }
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020052 for i, c := range req.InvoiceData.InvoicerContact {
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020053 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 }
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020060 if req.InvoiceData.InvoicerVatId == "" {
61 return nil, status.Error(codes.InvalidArgument, "invoice data must contain invoicer's vat id")
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020062 }
63
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020064 uid, err := s.m.createInvoice(ctx, req.InvoiceData)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020065 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 }
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020086 res := &pb.GetInvoiceResponse{
87 Invoice: invoice,
88 }
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020089 return res, nil
90}
91
92func newService(m *model) *service {
93 return &service{
94 m: m,
95 }
96}
97
Sergiusz Bazanskia818ef22019-06-07 10:37:22 +020098func (s *service) invoicePDF(ctx context.Context, uid, language string) ([]byte, error) {
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +020099 sealed, err := s.m.getSealedUid(ctx, uid)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200100 if err != nil {
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200101 return nil, err
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200102 }
103
104 var rendered []byte
105 if sealed != "" {
106 // Invoice is sealed, return stored PDF.
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200107 rendered, err = s.m.getRendered(ctx, uid)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200108 if err != nil {
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200109 return nil, err
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200110 }
111 } else {
112 // Invoice is proforma, render.
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200113 invoice, err := s.m.getInvoice(ctx, uid)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200114 if err != nil {
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200115 return nil, err
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200116 }
117
Sergiusz Bazanskia818ef22019-06-07 10:37:22 +0200118 rendered, err = renderInvoicePDF(invoice, language)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200119 if err != nil {
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200120 return nil, err
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200121 }
122 }
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200123 return rendered, nil
124}
125
126func (s *service) RenderInvoice(req *pb.RenderInvoiceRequest, srv pb.Invoicer_RenderInvoiceServer) error {
Sergiusz Bazanskia818ef22019-06-07 10:37:22 +0200127 rendered, err := s.invoicePDF(srv.Context(), req.Uid, req.Language)
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200128 if err != nil {
129 if _, ok := status.FromError(err); ok {
130 return err
131 }
132 glog.Errorf("invoicePDF(_, %q): %v", req.Uid, err)
133 return status.Error(codes.Unavailable, "internal server error")
134 }
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200135
136 chunkSize := 16 * 1024
137 chunk := &pb.RenderInvoiceResponse{}
138 for i := 0; i < len(rendered); i += chunkSize {
139 if i+chunkSize > len(rendered) {
140 chunk.Data = rendered[i:len(rendered)]
141 } else {
142 chunk.Data = rendered[i : i+chunkSize]
143 }
144 if err := srv.Send(chunk); err != nil {
145 glog.Errorf("srv.Send: %v", err)
146 return status.Error(codes.Unavailable, "stream broken")
147 }
148 }
149 return nil
150}
151
152func (s *service) SealInvoice(ctx context.Context, req *pb.SealInvoiceRequest) (*pb.SealInvoiceResponse, error) {
Sergiusz Bazanskia818ef22019-06-07 10:37:22 +0200153 useProformaTime := false
154 if req.DateSource == pb.SealInvoiceRequest_DATE_SOURCE_PROFORMA {
155 useProformaTime = true
156 }
157 if err := s.m.sealInvoice(ctx, req.Uid, req.Language, useProformaTime); err != nil {
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200158 if _, ok := status.FromError(err); ok {
159 return nil, err
160 }
161 glog.Errorf("sealInvoice(_, %q): %v", req.Uid, err)
162 return nil, status.Error(codes.Unavailable, "internal server error")
163 }
164 return &pb.SealInvoiceResponse{}, nil
165}
166
167func init() {
168 flag.Set("logtostderr", "true")
169}
170
171func main() {
172 flag.StringVar(&flagDatabasePath, "db_path", "./foo.db", "path to sqlite database")
173 flag.BoolVar(&flagInit, "init_db", false, "init database and exit")
174 flag.Parse()
175
176 m, err := newModel(flagDatabasePath)
177 if err != nil {
178 glog.Exitf("newModel: %v", err)
179 }
180 if flagInit {
181 glog.Exit(m.init())
182 }
183
184 mi := mirko.New()
185 if err := mi.Listen(); err != nil {
186 glog.Exitf("Listen failed: %v", err)
187 }
188
189 s := newService(m)
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200190 s.setupStatusz(mi)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200191 pb.RegisterInvoicerServer(mi.GRPC(), s)
192
193 if err := mi.Serve(); err != nil {
194 glog.Exitf("Serve failed: %v", err)
195 }
196
197 <-mi.Done()
198}