2019-11-05 13:16:02 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package satellitedb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
2020-07-14 14:04:38 +01:00
|
|
|
"errors"
|
2019-11-05 13:16:02 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
|
2020-03-30 10:08:50 +01:00
|
|
|
"storj.io/common/uuid"
|
2023-05-08 12:15:09 +01:00
|
|
|
"storj.io/private/tagsql"
|
2023-04-06 12:41:14 +01:00
|
|
|
"storj.io/storj/satellite/payments/stripe"
|
2020-01-15 02:29:51 +00:00
|
|
|
"storj.io/storj/satellite/satellitedb/dbx"
|
2019-11-05 13:16:02 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// ensure that invoiceProjectRecords implements stripecoinpayments.ProjectRecordsDB.
|
2023-04-06 12:41:14 +01:00
|
|
|
var _ stripe.ProjectRecordsDB = (*invoiceProjectRecords)(nil)
|
2019-11-05 13:16:02 +00:00
|
|
|
|
|
|
|
// invoiceProjectRecordState defines states of the invoice project record.
|
|
|
|
type invoiceProjectRecordState int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// invoice project record is not yet applied to customer invoice.
|
|
|
|
invoiceProjectRecordStateUnapplied invoiceProjectRecordState = 0
|
|
|
|
// invoice project record has been used during creating customer invoice.
|
|
|
|
invoiceProjectRecordStateConsumed invoiceProjectRecordState = 1
|
|
|
|
)
|
|
|
|
|
|
|
|
// Int returns intent state as int.
|
|
|
|
func (intent invoiceProjectRecordState) Int() int {
|
|
|
|
return int(intent)
|
|
|
|
}
|
|
|
|
|
|
|
|
// invoiceProjectRecords is stripecoinpayments project records DB.
|
|
|
|
//
|
|
|
|
// architecture: Database
|
|
|
|
type invoiceProjectRecords struct {
|
2019-12-14 02:29:54 +00:00
|
|
|
db *satelliteDB
|
2019-11-05 13:16:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create creates new invoice project record in the DB.
|
2023-04-06 12:41:14 +01:00
|
|
|
func (db *invoiceProjectRecords) Create(ctx context.Context, records []stripe.CreateProjectRecord, start, end time.Time) (err error) {
|
2019-11-05 13:16:02 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
return db.db.WithTx(ctx, func(ctx context.Context, tx *dbx.Tx) error {
|
|
|
|
for _, record := range records {
|
|
|
|
id, err := uuid.New()
|
|
|
|
if err != nil {
|
|
|
|
return Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2022-06-14 14:16:44 +01:00
|
|
|
_, err = tx.Create_StripecoinpaymentsInvoiceProjectRecord(ctx,
|
2019-11-05 13:16:02 +00:00
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_Id(id[:]),
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_ProjectId(record.ProjectID[:]),
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_Storage(record.Storage),
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_Egress(record.Egress),
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodStart(start),
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodEnd(end),
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_State(invoiceProjectRecordStateUnapplied.Int()),
|
2021-10-20 23:43:32 +01:00
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_Create_Fields{
|
2021-10-20 23:54:34 +01:00
|
|
|
Segments: dbx.StripecoinpaymentsInvoiceProjectRecord_Segments(int64(record.Segments)),
|
2021-10-20 23:43:32 +01:00
|
|
|
},
|
2019-11-05 13:16:02 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check checks if invoice project record for specified project and billing period exists.
|
|
|
|
func (db *invoiceProjectRecords) Check(ctx context.Context, projectID uuid.UUID, start, end time.Time) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
_, err = db.db.Get_StripecoinpaymentsInvoiceProjectRecord_By_ProjectId_And_PeriodStart_And_PeriodEnd(ctx,
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_ProjectId(projectID[:]),
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodStart(start),
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodEnd(end),
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
2020-07-14 14:04:38 +01:00
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
2019-11-05 13:16:02 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-04-06 12:41:14 +01:00
|
|
|
return stripe.ErrProjectRecordExists
|
2019-11-05 13:16:02 +00:00
|
|
|
}
|
|
|
|
|
2020-01-07 10:41:19 +00:00
|
|
|
// Get returns record for specified project and billing period.
|
2023-04-06 12:41:14 +01:00
|
|
|
func (db *invoiceProjectRecords) Get(ctx context.Context, projectID uuid.UUID, start, end time.Time) (record *stripe.ProjectRecord, err error) {
|
2020-01-07 10:41:19 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
dbxRecord, err := db.db.Get_StripecoinpaymentsInvoiceProjectRecord_By_ProjectId_And_PeriodStart_And_PeriodEnd(ctx,
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_ProjectId(projectID[:]),
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodStart(start),
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_PeriodEnd(end),
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
2020-07-14 14:04:38 +01:00
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
2020-05-26 16:09:43 +01:00
|
|
|
return nil, nil
|
|
|
|
}
|
2020-01-07 10:41:19 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return fromDBXInvoiceProjectRecord(dbxRecord)
|
|
|
|
}
|
|
|
|
|
2019-11-05 13:16:02 +00:00
|
|
|
// Consume consumes invoice project record.
|
|
|
|
func (db *invoiceProjectRecords) Consume(ctx context.Context, id uuid.UUID) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
_, err = db.db.Update_StripecoinpaymentsInvoiceProjectRecord_By_Id(ctx,
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_Id(id[:]),
|
|
|
|
dbx.StripecoinpaymentsInvoiceProjectRecord_Update_Fields{
|
|
|
|
State: dbx.StripecoinpaymentsInvoiceProjectRecord_State(invoiceProjectRecordStateConsumed.Int()),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListUnapplied returns project records page with unapplied project records.
|
2023-05-08 12:15:09 +01:00
|
|
|
// Cursor is not included into listing results.
|
|
|
|
func (db *invoiceProjectRecords) ListUnapplied(ctx context.Context, cursor uuid.UUID, limit int, start, end time.Time) (page stripe.ProjectRecordsPage, err error) {
|
2019-11-05 13:16:02 +00:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2023-05-08 12:15:09 +01:00
|
|
|
err = withRows(db.db.QueryContext(ctx, db.db.Rebind(`
|
|
|
|
SELECT
|
|
|
|
id, project_id, storage, egress, segments, period_start, period_end, state
|
|
|
|
FROM
|
|
|
|
stripecoinpayments_invoice_project_records
|
|
|
|
WHERE
|
|
|
|
id > ? AND period_start = ? AND period_end = ? AND state = ?
|
|
|
|
LIMIT ?
|
|
|
|
`), cursor, start, end, invoiceProjectRecordStateUnapplied.Int(), limit+1))(func(rows tagsql.Rows) error {
|
|
|
|
for rows.Next() {
|
|
|
|
var record stripe.ProjectRecord
|
|
|
|
err := rows.Scan(&record.ID, &record.ProjectID, &record.Storage, &record.Egress, &record.Segments, &record.PeriodStart, &record.PeriodEnd, &record.State)
|
|
|
|
if err != nil {
|
|
|
|
return Error.New("failed to scan stripe invoice project records: %w", err)
|
|
|
|
}
|
2019-11-05 13:16:02 +00:00
|
|
|
|
2023-05-08 12:15:09 +01:00
|
|
|
page.Records = append(page.Records, record)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
2019-11-05 13:16:02 +00:00
|
|
|
if err != nil {
|
2023-04-06 12:41:14 +01:00
|
|
|
return stripe.ProjectRecordsPage{}, err
|
2019-11-05 13:16:02 +00:00
|
|
|
}
|
|
|
|
|
2023-05-08 12:15:09 +01:00
|
|
|
if len(page.Records) == limit+1 {
|
2019-11-05 13:16:02 +00:00
|
|
|
page.Next = true
|
|
|
|
|
2023-05-08 12:15:09 +01:00
|
|
|
page.Records = page.Records[:len(page.Records)-1]
|
2019-11-05 13:16:02 +00:00
|
|
|
|
2023-05-08 12:15:09 +01:00
|
|
|
page.Cursor = page.Records[len(page.Records)-1].ID
|
2019-11-05 13:16:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return page, nil
|
|
|
|
}
|
|
|
|
|
2020-07-16 15:18:02 +01:00
|
|
|
// fromDBXInvoiceProjectRecord converts *dbx.StripecoinpaymentsInvoiceProjectRecord to *stripecoinpayments.ProjectRecord.
|
2023-04-06 12:41:14 +01:00
|
|
|
func fromDBXInvoiceProjectRecord(dbxRecord *dbx.StripecoinpaymentsInvoiceProjectRecord) (*stripe.ProjectRecord, error) {
|
2020-03-31 17:49:16 +01:00
|
|
|
id, err := uuid.FromBytes(dbxRecord.Id)
|
2019-11-05 13:16:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errs.Wrap(err)
|
|
|
|
}
|
2020-03-31 17:49:16 +01:00
|
|
|
projectID, err := uuid.FromBytes(dbxRecord.ProjectId)
|
2019-11-05 13:16:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errs.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2021-10-20 23:54:34 +01:00
|
|
|
var segments float64
|
|
|
|
if dbxRecord.Segments != nil {
|
|
|
|
segments = float64(*dbxRecord.Segments)
|
2021-10-20 23:43:32 +01:00
|
|
|
}
|
|
|
|
|
2023-04-06 12:41:14 +01:00
|
|
|
return &stripe.ProjectRecord{
|
2019-11-05 13:16:02 +00:00
|
|
|
ID: id,
|
|
|
|
ProjectID: projectID,
|
|
|
|
Storage: dbxRecord.Storage,
|
|
|
|
Egress: dbxRecord.Egress,
|
2021-10-20 23:54:34 +01:00
|
|
|
Segments: segments,
|
2019-11-05 13:16:02 +00:00
|
|
|
PeriodStart: dbxRecord.PeriodStart,
|
|
|
|
PeriodEnd: dbxRecord.PeriodEnd,
|
2020-07-06 21:15:55 +01:00
|
|
|
State: dbxRecord.State,
|
2019-11-05 13:16:02 +00:00
|
|
|
}, nil
|
|
|
|
}
|