Skip to content
This repository has been archived by the owner on Aug 26, 2022. It is now read-only.

Commit

Permalink
file-transfers: add (admin) HTTP endpoints for modifying configs
Browse files Browse the repository at this point in the history
Issue: #147
  • Loading branch information
adamdecaf committed Jun 12, 2019
1 parent 80a05f5 commit 533fdf8
Show file tree
Hide file tree
Showing 7 changed files with 672 additions and 376 deletions.
187 changes: 13 additions & 174 deletions file_transfer_async.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"bufio"
"bytes"
"context"
"database/sql"
"encoding/json"
"fmt"
"io"
"io/ioutil"
Expand Down Expand Up @@ -86,6 +86,18 @@ func (c *cutoffTime) diff(when time.Time) time.Duration {
return cutoffTime.Sub(when)
}

func (c cutoffTime) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
RoutingNumber string
Cutoff int
Location string
}{
RoutingNumber: c.routingNumber,
Cutoff: c.cutoff,
Location: c.loc.String(), // *time.Location doesn't marshal to JSON, so just write the IANA name
})
}

// fileTransferController is a controller which is responsible for periodic sync'ing of ACH files
// with their remote SFTP destination. The ACH network operates on uploading and downloding files
// from hosts during the business day.
Expand Down Expand Up @@ -968,176 +980,3 @@ func groupTransfers(xfers []*groupableTransfer, err error) ([][]*groupableTransf
//
// so, we can only route to ^ if we have a config for it (configs are only written to the DB if a physical contract exists)
// If the eastern bank is past the cutoff send to the western bank

type fileTransferRepository interface {
getCutoffTimes() ([]*cutoffTime, error)
getSFTPConfigs() ([]*sftpConfig, error)
getFileTransferConfigs() ([]*fileTransferConfig, error)

close() error
}

func newFileTransferRepository(db *sql.DB, dbType string) fileTransferRepository {
if db == nil {
return &localFileTransferRepository{}
}

sqliteRepo := &sqliteFileTransferRepository{db}
if strings.EqualFold(dbType, "mysql") {
// On 'mysql' database setups return that over the local (hardcoded) values.
return sqliteRepo
}

cutoffCount, sftpCount, fileTransferCount := sqliteRepo.getCounts()
if (cutoffCount + sftpCount + fileTransferCount) == 0 {
return &localFileTransferRepository{}
}

return sqliteRepo
}

type sqliteFileTransferRepository struct {
// TODO(adam): admin endpoints to read and write these configs? (w/ masked passwords)
db *sql.DB
}

func (r *sqliteFileTransferRepository) close() error {
return r.db.Close()
}

// getCounts returns the count of cutoffTime's, sftpConfig's, and fileTransferConfig's in the sqlite database.
//
// This is used to return localFileTransferRepository if the counts are empty (so local dev "just works").
func (r *sqliteFileTransferRepository) getCounts() (int, int, int) {
count := func(table string) int {
query := fmt.Sprintf(`select count(*) from %s`, table)
stmt, err := r.db.Prepare(query)
if err != nil {
return 0
}
defer stmt.Close()

row := stmt.QueryRow()
var n int
row.Scan(&n)
return n
}
return count("cutoff_times"), count("sftp_configs"), count("file_transfer_configs")
}

func (r *sqliteFileTransferRepository) getCutoffTimes() ([]*cutoffTime, error) {
query := `select routing_number, cutoff, location from cutoff_times;`
stmt, err := r.db.Prepare(query)
if err != nil {
return nil, err
}
defer stmt.Close()

var times []*cutoffTime
rows, err := stmt.Query()
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var cutoff cutoffTime
var loc string
if err := rows.Scan(&cutoff.routingNumber, &cutoff.cutoff, &loc); err != nil {
return nil, fmt.Errorf("getCutoffTimes: scan: %v", err)
}
if l, err := time.LoadLocation(loc); err != nil {
return nil, fmt.Errorf("getCutoffTimes: parsing %q failed: %v", loc, err)
} else {
cutoff.loc = l
}
times = append(times, &cutoff)
}
return times, rows.Err()
}

func (r *sqliteFileTransferRepository) getSFTPConfigs() ([]*sftpConfig, error) {
query := `select routing_number, hostname, username, password from sftp_configs;`
stmt, err := r.db.Prepare(query)
if err != nil {
return nil, err
}
defer stmt.Close()

var configs []*sftpConfig
rows, err := stmt.Query()
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var cfg sftpConfig
if err := rows.Scan(&cfg.RoutingNumber, &cfg.Hostname, &cfg.Username, &cfg.Password); err != nil {
return nil, fmt.Errorf("getSFTPConfigs: scan: %v", err)
}
configs = append(configs, &cfg)
}
return configs, rows.Err()
}

func (r *sqliteFileTransferRepository) getFileTransferConfigs() ([]*fileTransferConfig, error) {
query := `select routing_number, inbound_path, outbound_path, return_path from file_transfer_configs;`
stmt, err := r.db.Prepare(query)
if err != nil {
return nil, err
}
defer stmt.Close()

var configs []*fileTransferConfig
rows, err := stmt.Query()
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var cfg fileTransferConfig
if err := rows.Scan(&cfg.RoutingNumber, &cfg.InboundPath, &cfg.OutboundPath, &cfg.ReturnPath); err != nil {
return nil, fmt.Errorf("getFileTransferConfigs: scan: %v", err)
}
configs = append(configs, &cfg)
}
return configs, rows.Err()
}

// localFileTransferRepository is a fileTransferRepository for local dev with values that match
// apitest, testdata/ftp-server/ paths, files and FTP (fsftp) defaults.
type localFileTransferRepository struct{}

func (r *localFileTransferRepository) close() error { return nil }

func (r *localFileTransferRepository) getCutoffTimes() ([]*cutoffTime, error) {
nyc, _ := time.LoadLocation("America/New_York")
return []*cutoffTime{
{
routingNumber: "121042882",
cutoff: 1700,
loc: nyc,
},
}, nil
}

func (r *localFileTransferRepository) getSFTPConfigs() ([]*sftpConfig, error) {
return []*sftpConfig{
{
RoutingNumber: "121042882", // from 'go run ./cmd/server' in Accounts
Hostname: "localhost:2121", // below configs for moov/fsftp:v0.1.0
Username: "admin",
Password: "123456",
},
}, nil
}

func (r *localFileTransferRepository) getFileTransferConfigs() ([]*fileTransferConfig, error) {
return []*fileTransferConfig{
{
RoutingNumber: "121042882",
InboundPath: "inbound/", // below configs match paygate's testdata/ftp-server/
OutboundPath: "outbound/",
ReturnPath: "returned/",
},
}, nil
}
Loading

0 comments on commit 533fdf8

Please sign in to comment.