blob: 701807a2c8ff44d85341b3f5e68d818d805152b8 [file] [log] [blame]
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +02001package main
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7 "strconv"
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +02008 "time"
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +02009
10 "github.com/golang/glog"
11 "github.com/golang/protobuf/proto"
12 _ "github.com/mattn/go-sqlite3"
13 "google.golang.org/grpc/codes"
14 "google.golang.org/grpc/status"
15
16 pb "code.hackerspace.pl/hscloud/proto/invoice"
17)
18
19type model struct {
20 db *sql.DB
21}
22
23func newModel(dsn string) (*model, error) {
24 db, err := sql.Open("sqlite3", dsn)
25 if err != nil {
26 return nil, err
27 }
28 return &model{
29 db: db,
30 }, nil
31}
32
33func (m *model) init() error {
34 _, err := m.db.Exec(`
35 create table invoice (
36 id integer primary key not null,
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020037 created_time integer not null,
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020038 proto blob not null
39 );
40 create table invoice_seal (
41 id integer primary key not null,
42 invoice_id integer not null,
43 final_uid text not null unique,
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020044 sealed_time integer not null,
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020045 foreign key (invoice_id) references invoice(id)
46 );
47 create table invoice_blob (
48 id integer primary key not null,
49 invoice_id integer not null,
50 pdf blob not null,
51 foreign key (invoice_id) references invoice(id)
52 );
53 `)
54 return err
55}
56
57func (m *model) sealInvoice(ctx context.Context, uid string) error {
58 id, err := strconv.Atoi(uid)
59 if err != nil {
60 return status.Error(codes.InvalidArgument, "invalid uid")
61 }
62
63 invoice, err := m.getInvoice(ctx, uid)
64 if err != nil {
65 return err
66 }
67
68 tx, err := m.db.BeginTx(ctx, nil)
69 if err != nil {
70 return err
71 }
72
73 q := `
74 insert into invoice_seal (
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020075 invoice_id, final_uid, sealed_time
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020076 ) values (
77 ?,
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020078 ( select printf("%04d", ifnull( (select final_uid as v from invoice_seal order by final_uid desc limit 1), 19000) + 1 )),
79 ?
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020080 )
81
82 `
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +020083 sealTime := time.Now()
84 res, err := tx.Exec(q, id, sealTime.UnixNano())
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +020085 if err != nil {
86 return err
87 }
88
89 lastInvoiceSealId, err := res.LastInsertId()
90 if err != nil {
91 return err
92 }
93
94 q = `
95 select final_uid from invoice_seal where id = ?
96 `
97
98 var finalUid string
99 if err := tx.QueryRow(q, lastInvoiceSealId).Scan(&finalUid); err != nil {
100 return err
101 }
102
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200103 invoice.State = pb.Invoice_STATE_SEALED
104 invoice.FinalUid = fmt.Sprintf("FV/%s", finalUid)
105 invoice.Date = sealTime.UnixNano()
106 calculateInvoiceData(invoice)
107
108 pdfBlob, err := renderInvoicePDF(invoice)
109 if err != nil {
110 return err
111 }
112
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200113 q = `
114 insert into invoice_blob (
115 invoice_id, pdf
116 ) values (
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200117 ?, ?
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200118 )
119 `
120
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200121 if _, err := tx.Exec(q, id, pdfBlob); err != nil {
122 return err
123 }
124
125 if err := tx.Commit(); err != nil {
126 return err
127 }
128
129 return nil
130}
131
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200132func (m *model) createInvoice(ctx context.Context, id *pb.InvoiceData) (string, error) {
133 data, err := proto.Marshal(id)
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200134 if err != nil {
135 return "", err
136 }
137
138 sql := `
139 insert into invoice (
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200140 proto, created_time
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200141 ) values (
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200142 ?, ?
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200143 )
144 `
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200145 res, err := m.db.Exec(sql, data, time.Now().UnixNano())
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200146 if err != nil {
147 return "", err
148 }
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200149 uid, err := res.LastInsertId()
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200150 if err != nil {
151 return "", err
152 }
153
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200154 glog.Infof("%+v", uid)
155 return fmt.Sprintf("%d", uid), nil
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200156}
157
158func (m *model) getRendered(ctx context.Context, uid string) ([]byte, error) {
159 id, err := strconv.Atoi(uid)
160 if err != nil {
161 return nil, status.Error(codes.InvalidArgument, "invalid uid")
162 }
163
164 q := `
165 select invoice_blob.pdf from invoice_blob where invoice_blob.invoice_id = ?
166 `
167 res := m.db.QueryRow(q, id)
168
169 data := []byte{}
170 if err := res.Scan(&data); err != nil {
171 if err == sql.ErrNoRows {
172 return nil, status.Error(codes.InvalidArgument, "no such invoice")
173 }
174 return nil, err
175 }
176 return data, nil
177}
178
179func (m *model) getSealedUid(ctx context.Context, uid string) (string, error) {
180 id, err := strconv.Atoi(uid)
181 if err != nil {
182 return "", status.Error(codes.InvalidArgument, "invalid uid")
183 }
184
185 q := `
186 select invoice_seal.final_uid from invoice_seal where invoice_seal.invoice_id = ?
187 `
188 res := m.db.QueryRow(q, id)
189 finalUid := ""
190 if err := res.Scan(&finalUid); err != nil {
191 if err == sql.ErrNoRows {
192 return "", nil
193 }
194 return "", err
195 }
196 return finalUid, nil
197}
198
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200199type sqlInvoiceSealRow struct {
200 proto []byte
201 createdTime int64
202 sealedTime sql.NullInt64
203 finalUid sql.NullString
204 uid int64
205}
206
207func (s *sqlInvoiceSealRow) Proto() (*pb.Invoice, error) {
208 data := &pb.InvoiceData{}
209 if err := proto.Unmarshal(s.proto, data); err != nil {
210 return nil, err
211 }
212
213 p := &pb.Invoice{
214 Uid: fmt.Sprintf("%d", s.uid),
215 Data: data,
216 }
217 if s.finalUid.Valid {
218 p.State = pb.Invoice_STATE_SEALED
219 p.FinalUid = fmt.Sprintf("FV/%s", s.finalUid.String)
220 p.Date = s.sealedTime.Int64
221 } else {
222 p.State = pb.Invoice_STATE_PROFORMA
223 p.FinalUid = fmt.Sprintf("PROFORMA/%d", s.uid)
224 p.Date = s.createdTime
225 }
226 calculateInvoiceData(p)
227 return p, nil
228}
229
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200230func (m *model) getInvoice(ctx context.Context, uid string) (*pb.Invoice, error) {
231 id, err := strconv.Atoi(uid)
232 if err != nil {
233 return nil, status.Error(codes.InvalidArgument, "invalid uid")
234 }
235
236 q := `
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200237 select
238 invoice.id, invoice.proto, invoice.created_time, invoice_seal.sealed_time, invoice_seal.final_uid
239 from invoice
240 left join invoice_seal
241 on invoice_seal.invoice_id = invoice.id
242 where invoice.id = ?
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200243 `
244 res := m.db.QueryRow(q, id)
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200245 row := sqlInvoiceSealRow{}
246 if err := res.Scan(&row.uid, &row.proto, &row.createdTime, &row.sealedTime, &row.finalUid); err != nil {
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200247 if err == sql.ErrNoRows {
248 return nil, status.Error(codes.NotFound, "no such invoice")
249 }
250 return nil, err
251 }
252
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200253 return row.Proto()
Sergiusz Bazanskifb18c992019-05-01 12:27:03 +0200254}
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200255
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200256func (m *model) getInvoices(ctx context.Context) ([]*pb.Invoice, error) {
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200257 q := `
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200258 select
259 invoice.id, invoice.proto, invoice.created_time, invoice_seal.sealed_time, invoice_seal.final_uid
260 from invoice
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200261 left join invoice_seal
262 on invoice_seal.invoice_id = invoice.id
263 `
264 rows, err := m.db.QueryContext(ctx, q)
265 if err != nil {
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200266 return nil, err
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200267 }
268 defer rows.Close()
269
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200270 res := []*pb.Invoice{}
271
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200272 for rows.Next() {
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200273 row := sqlInvoiceSealRow{}
274 if err := rows.Scan(&row.uid, &row.proto, &row.createdTime, &row.sealedTime, &row.finalUid); err != nil {
275 return nil, err
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200276 }
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200277 p, err := row.Proto()
278 if err != nil {
279 return nil, err
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200280 }
Sergiusz Bazanski3976e3c2019-05-01 15:27:49 +0200281 res = append(res, p)
Sergiusz Bazanski57ef6b02019-05-01 14:08:29 +0200282 }
283
284 return res, nil
285}