From c1f8737920ed8c94059020ccf4aef6d5c6b57755 Mon Sep 17 00:00:00 2001 From: Daniel Tonks Date: Fri, 16 Feb 2024 14:40:34 -0700 Subject: [PATCH 1/7] refactor api logic into packages --- cmd/server/main.go | 13 ++-- cmd/server/storage_test.go | 71 ----------------- go.mod | 3 - go.sum | 78 +------------------ {cmd/server => internal/files}/files.go | 73 ++++++++--------- {cmd/server => internal/files}/files_test.go | 55 ++++++++++--- {cmd/server => internal/metrics}/http.go | 4 +- {cmd/server => internal/metrics}/http_test.go | 2 +- {cmd/server => internal/storage}/storage.go | 24 +++--- internal/storage/storage_test.go | 52 +++++++++++++ 10 files changed, 157 insertions(+), 218 deletions(-) delete mode 100644 cmd/server/storage_test.go rename {cmd/server => internal/files}/files.go (85%) rename {cmd/server => internal/files}/files_test.go (90%) rename {cmd/server => internal/metrics}/http.go (94%) rename {cmd/server => internal/metrics}/http_test.go (98%) rename {cmd/server => internal/storage}/storage.go (63%) create mode 100644 internal/storage/storage_test.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 4f38a48e..e0b63ef5 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -16,13 +16,14 @@ import ( "syscall" "time" + "github.com/gorilla/mux" "github.com/moov-io/base/admin" moovhttp "github.com/moov-io/base/http" "github.com/moov-io/base/http/bind" - "github.com/moov-io/imagecashletter" - - "github.com/gorilla/mux" "github.com/moov-io/base/log" + "github.com/moov-io/imagecashletter" + "github.com/moov-io/imagecashletter/internal/files" + "github.com/moov-io/imagecashletter/internal/storage" ) var ( @@ -75,15 +76,11 @@ func main() { }() defer adminServer.Shutdown() - repo := &memoryICLFileRepository{ - files: make(map[string]*imagecashletter.File), - } - // Setup business HTTP routes router := mux.NewRouter() moovhttp.AddCORSHandler(router) addPingRoute(router) - addFileRoutes(logger, router, repo) + files.AppendRoutes(logger, router, storage.NewInMemoryRepo()) // Start business HTTP server readTimeout, _ := time.ParseDuration("30s") diff --git a/cmd/server/storage_test.go b/cmd/server/storage_test.go deleted file mode 100644 index 62a45b55..00000000 --- a/cmd/server/storage_test.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2020 The Moov Authors -// Use of this source code is governed by an Apache License -// license that can be found in the LICENSE file. - -package main - -import ( - "testing" - - "github.com/moov-io/base" - "github.com/moov-io/imagecashletter" - "github.com/stretchr/testify/require" -) - -type testICLFileRepository struct { - err error - - file *imagecashletter.File -} - -func (r *testICLFileRepository) getFiles() ([]*imagecashletter.File, error) { - if r.err != nil { - return nil, r.err - } - return []*imagecashletter.File{r.file}, nil -} - -func (r *testICLFileRepository) getFile(fileId string) (*imagecashletter.File, error) { - if r.err != nil { - return nil, r.err - } - return r.file, nil -} - -func (r *testICLFileRepository) saveFile(file *imagecashletter.File) error { - if r.err == nil { // only persist if we're not error'ing - r.file = file - } - return r.err -} - -func (r *testICLFileRepository) deleteFile(fileId string) error { - return r.err -} - -func TestMemoryStorage(t *testing.T) { - repo := &memoryICLFileRepository{ - files: make(map[string]*imagecashletter.File), - } - - files, err := repo.getFiles() - require.NoError(t, err) - require.Equal(t, 0, len(files)) - - f := readFile(t, "BNK20180905121042882-A.icl") - f.ID = base.ID() - require.NoError(t, repo.saveFile(f)) - - files, err = repo.getFiles() - require.NoError(t, err) - require.Equal(t, 1, len(files)) - - file, err := repo.getFile(f.ID) - require.NoError(t, err) - require.Equal(t, f.ID, file.ID) - - require.NoError(t, repo.deleteFile(f.ID)) - files, err = repo.getFiles() - require.NoError(t, err) - require.Equal(t, 0, len(files)) -} diff --git a/go.mod b/go.mod index f5429854..6a22ddec 100644 --- a/go.mod +++ b/go.mod @@ -22,15 +22,12 @@ require ( github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/kr/text v0.2.0 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rickar/cal/v2 v2.1.13 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect - golang.org/x/net v0.21.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/go.sum b/go.sum index ccbdd7eb..2c3c670d 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= @@ -18,74 +16,35 @@ github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= -github.com/moov-io/base v0.46.0 h1:4sgFjfD9EELJ+i6cGfPtezoPAAxwE3imLJ64bbHtBwM= -github.com/moov-io/base v0.46.0/go.mod h1:AFoLPJA1uuylLp33g1nGzTnGk8zyqYH7KfBS7flc0P8= -github.com/moov-io/base v0.47.0 h1:r4PALYPSMwCB/smDUcMUk3a2JW6TA4mT17oOBx9ZpEE= -github.com/moov-io/base v0.47.0/go.mod h1:jz9m/WyTr6RTbm1DP0V5j4RVUuXqmq3bUnzJ6NwCPo8= -github.com/moov-io/base v0.47.1 h1:kQXBbTesyqb9ETem/jCeG2bYGuOpZYFbTHHVrwcpkrY= -github.com/moov-io/base v0.47.1/go.mod h1:AVMPYsHbPg+TxqOpfwIIHuT1rLsjxEhIekIKRj91i4I= -github.com/moov-io/base v0.48.1 h1:BVKjtEdR79ZjBeTF8Dl56ko43w3HaAnsUDhUoSVV9g4= -github.com/moov-io/base v0.48.1/go.mod h1:cFHUtEDjzxjc4SBjkntYNF4LB166iLN5eGR2BBSJenI= -github.com/moov-io/base v0.48.2 h1:BPSNgmwokOVaVzAMJg71L48LCrDYelMfVXJEiZb2zOY= -github.com/moov-io/base v0.48.2/go.mod h1:u1/WC3quR6otC9NrM1TtXSwNti1A/m7MR49RIXY1ee4= -github.com/moov-io/base v0.48.3 h1:0VH5+a1yH+6CCwnMVa8+XUbu7Rth7l0LzHMq3xnE6/I= -github.com/moov-io/base v0.48.3/go.mod h1:5/LTQ3Sy+c39yTCs9vgsHEhjEQHmDinhwIxBkBLNL20= github.com/moov-io/base v0.48.5 h1:QaTyTo6eFFFV35R9l/GdePQN40IJti9knD5hqdWPnnM= github.com/moov-io/base v0.48.5/go.mod h1:D5ZV9COV/qtCjTQuYpq7gGInCk64AhOQI6UY4kt4Rq8= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= -github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= -github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rickar/cal/v2 v2.1.13 h1:FENBPXxDPyL1OWGf9ZdpWGcEiGoSjt0UZED8VOxvK0c= github.com/rickar/cal/v2 v2.1.13/go.mod h1:/fdlMcx7GjPlIBibMzOM9gMvDBsrK+mOtRXdTzUqV/A= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI= @@ -94,29 +53,11 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= -golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0= -golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -124,25 +65,14 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -150,14 +80,10 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/cmd/server/files.go b/internal/files/files.go similarity index 85% rename from cmd/server/files.go rename to internal/files/files.go index ba2dc936..7507e537 100644 --- a/cmd/server/files.go +++ b/internal/files/files.go @@ -2,7 +2,7 @@ // Use of this source code is governed by an Apache License // license that can be found in the LICENSE file. -package main +package files import ( "bufio" @@ -16,12 +16,13 @@ import ( "strconv" "strings" + "github.com/gorilla/mux" "github.com/moov-io/base" moovhttp "github.com/moov-io/base/http" - "github.com/moov-io/imagecashletter" - - "github.com/gorilla/mux" "github.com/moov-io/base/log" + "github.com/moov-io/imagecashletter" + "github.com/moov-io/imagecashletter/internal/metrics" + "github.com/moov-io/imagecashletter/internal/storage" ) var ( @@ -29,7 +30,7 @@ var ( errNoCashLetterId = errors.New("no CashLetter ID found") ) -func addFileRoutes(logger log.Logger, r *mux.Router, repo ICLFileRepository) { +func AppendRoutes(logger log.Logger, r *mux.Router, repo storage.ICLFileRepository) { r.Methods("GET").Path("/files").HandlerFunc(getFiles(logger, repo)) r.Methods("POST").Path("/files/create").HandlerFunc(createFile(logger, repo)) r.Methods("GET").Path("/files/{fileId}").HandlerFunc(getFile(logger, repo)) @@ -61,15 +62,15 @@ func getCashLetterId(w http.ResponseWriter, r *http.Request) string { return v } -func getFiles(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { +func getFiles(logger log.Logger, repo storage.ICLFileRepository) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if requestID := moovhttp.GetRequestID(r); requestID != "" { logger = logger.Set("requestID", log.String(requestID)) } - w = wrapResponseWriter(logger, w, r) + w = metrics.WrapResponseWriter(logger, w, r) - files, err := repo.getFiles() // TODO(adam): implement soft and hard limits + files, err := repo.GetFiles() // TODO(adam): implement soft and hard limits if err != nil { err = logger.LogErrorf("error getting ICL files: %v", err).Err() moovhttp.Problem(w, err) @@ -97,13 +98,13 @@ func determineBufferSize(env string, nominal int) int { return nominal } -func createFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { +func createFile(logger log.Logger, repo storage.ICLFileRepository) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if requestID := moovhttp.GetRequestID(r); requestID != "" { logger = logger.Set("requestID", log.String(requestID)) } - w = wrapResponseWriter(logger, w, r) + w = metrics.WrapResponseWriter(logger, w, r) req := imagecashletter.NewFile() if req.ID == "" { @@ -148,7 +149,7 @@ func createFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { } // Save the ICL file - if err := repo.saveFile(req); err != nil { + if err := repo.SaveFile(req); err != nil { err = logger.LogErrorf("problem saving file %s: %v", req.ID, err).Err() moovhttp.Problem(w, err) return @@ -161,20 +162,20 @@ func createFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { } } -func getFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { +func getFile(logger log.Logger, repo storage.ICLFileRepository) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if requestID := moovhttp.GetRequestID(r); requestID != "" { logger = logger.Set("requestID", log.String(requestID)) } - w = wrapResponseWriter(logger, w, r) + w = metrics.WrapResponseWriter(logger, w, r) fileId := getFileId(w, r) if fileId == "" { return } - file, err := repo.getFile(fileId) + file, err := repo.GetFile(fileId) if err != nil { err = logger.LogErrorf("problem reading file=%s: %v", fileId, err).Err() moovhttp.Problem(w, err) @@ -195,13 +196,13 @@ func getFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { } } -func updateFileHeader(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { +func updateFileHeader(logger log.Logger, repo storage.ICLFileRepository) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if requestID := moovhttp.GetRequestID(r); requestID != "" { logger = logger.Set("requestID", log.String(requestID)) } - w = wrapResponseWriter(logger, w, r) + w = metrics.WrapResponseWriter(logger, w, r) var req imagecashletter.FileHeader if err := json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -217,7 +218,7 @@ func updateFileHeader(logger log.Logger, repo ICLFileRepository) http.HandlerFun } logger = logger.Set("fileID", log.String(fileId)) - file, err := repo.getFile(fileId) + file, err := repo.GetFile(fileId) if err != nil { err = logger.LogErrorf("error retrieving file: %v", err).Err() moovhttp.Problem(w, err) @@ -231,7 +232,7 @@ func updateFileHeader(logger log.Logger, repo ICLFileRepository) http.HandlerFun } file.Header = req - if err := repo.saveFile(file); err != nil { + if err := repo.SaveFile(file); err != nil { err = logger.LogErrorf("error saving file: %v", err).Err() moovhttp.Problem(w, err) return @@ -244,13 +245,13 @@ func updateFileHeader(logger log.Logger, repo ICLFileRepository) http.HandlerFun } } -func deleteFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { +func deleteFile(logger log.Logger, repo storage.ICLFileRepository) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if requestID := moovhttp.GetRequestID(r); requestID != "" { logger = logger.Set("requestID", log.String(requestID)) } - w = wrapResponseWriter(logger, w, r) + w = metrics.WrapResponseWriter(logger, w, r) fileId := getFileId(w, r) if fileId == "" { @@ -259,7 +260,7 @@ func deleteFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { } logger = logger.Set("fileID", log.String(fileId)) - file, err := repo.getFile(fileId) + file, err := repo.GetFile(fileId) if err != nil { err = logger.LogErrorf("error retrieving file: %v", err).Err() moovhttp.Problem(w, err) @@ -272,7 +273,7 @@ func deleteFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { return } - if err := repo.deleteFile(fileId); err != nil { + if err := repo.DeleteFile(fileId); err != nil { err = logger.LogErrorf("error deleting file: %v", err).Err() moovhttp.Problem(w, err) return @@ -286,13 +287,13 @@ func deleteFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { } } -func getFileContents(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { +func getFileContents(logger log.Logger, repo storage.ICLFileRepository) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if requestID := moovhttp.GetRequestID(r); requestID != "" { logger = logger.Set("requestID", log.String(requestID)) } - w = wrapResponseWriter(logger, w, r) + w = metrics.WrapResponseWriter(logger, w, r) fileId := getFileId(w, r) if fileId == "" { @@ -301,7 +302,7 @@ func getFileContents(logger log.Logger, repo ICLFileRepository) http.HandlerFunc } logger = logger.Set("fileID", log.String(fileId)) - file, err := repo.getFile(fileId) + file, err := repo.GetFile(fileId) if err != nil { err = logger.LogErrorf("error retrieving file: %v", err).Err() moovhttp.Problem(w, err) @@ -331,13 +332,13 @@ func getFileContents(logger log.Logger, repo ICLFileRepository) http.HandlerFunc } } -func validateFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { +func validateFile(logger log.Logger, repo storage.ICLFileRepository) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if requestID := moovhttp.GetRequestID(r); requestID != "" { logger = logger.Set("requestID", log.String(requestID)) } - w = wrapResponseWriter(logger, w, r) + w = metrics.WrapResponseWriter(logger, w, r) fileId := getFileId(w, r) if fileId == "" { @@ -346,7 +347,7 @@ func validateFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { } logger = logger.Set("fileID", log.String(fileId)) - file, err := repo.getFile(fileId) + file, err := repo.GetFile(fileId) if err != nil { err = logger.LogErrorf("error retrieving file: %v", err).Err() moovhttp.Problem(w, err) @@ -373,13 +374,13 @@ func validateFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { } } -func addCashLetterToFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { +func addCashLetterToFile(logger log.Logger, repo storage.ICLFileRepository) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if requestID := moovhttp.GetRequestID(r); requestID != "" { logger = logger.Set("requestID", log.String(requestID)) } - w = wrapResponseWriter(logger, w, r) + w = metrics.WrapResponseWriter(logger, w, r) var req imagecashletter.CashLetter if err := json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -395,7 +396,7 @@ func addCashLetterToFile(logger log.Logger, repo ICLFileRepository) http.Handler } logger = logger.Set("fileID", log.String(fileId)) - file, err := repo.getFile(fileId) + file, err := repo.GetFile(fileId) if err != nil { err = logger.LogErrorf("error retrieving file: %v", err).Err() moovhttp.Problem(w, err) @@ -409,7 +410,7 @@ func addCashLetterToFile(logger log.Logger, repo ICLFileRepository) http.Handler } file.CashLetters = append(file.CashLetters, req) - if err := repo.saveFile(file); err != nil { + if err := repo.SaveFile(file); err != nil { err = logger.LogErrorf("error saving file: %v", err).Err() moovhttp.Problem(w, err) return @@ -423,13 +424,13 @@ func addCashLetterToFile(logger log.Logger, repo ICLFileRepository) http.Handler } } -func removeCashLetterFromFile(logger log.Logger, repo ICLFileRepository) http.HandlerFunc { +func removeCashLetterFromFile(logger log.Logger, repo storage.ICLFileRepository) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if requestID := moovhttp.GetRequestID(r); requestID != "" { logger = logger.Set("requestID", log.String(requestID)) } - w = wrapResponseWriter(logger, w, r) + w = metrics.WrapResponseWriter(logger, w, r) fileId := getFileId(w, r) if fileId == "" { @@ -445,7 +446,7 @@ func removeCashLetterFromFile(logger log.Logger, repo ICLFileRepository) http.Ha } logger = logger.Set("cashLetterID", log.String(cashLetterId)) - file, err := repo.getFile(fileId) + file, err := repo.GetFile(fileId) if err != nil { err = logger.LogErrorf("error retrieving file: %v", err).Err() moovhttp.Problem(w, err) @@ -464,7 +465,7 @@ func removeCashLetterFromFile(logger log.Logger, repo ICLFileRepository) http.Ha i-- } } - if err := repo.saveFile(file); err != nil { + if err := repo.SaveFile(file); err != nil { err = logger.LogErrorf("error saving file: %v", err).Err() moovhttp.Problem(w, err) return diff --git a/cmd/server/files_test.go b/internal/files/files_test.go similarity index 90% rename from cmd/server/files_test.go rename to internal/files/files_test.go index 0266d3c3..8e464f2e 100644 --- a/cmd/server/files_test.go +++ b/internal/files/files_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by an Apache License // license that can be found in the LICENSE file. -package main +package files import ( "bytes" @@ -55,7 +55,7 @@ func TestFiles_getFiles(t *testing.T) { }, } router := mux.NewRouter() - addFileRoutes(log.NewNopLogger(), router, repo) + AppendRoutes(log.NewNopLogger(), router, repo) t.Run("returns one file", func(t *testing.T) { w := httptest.NewRecorder() @@ -96,7 +96,7 @@ func TestFiles_createFile(t *testing.T) { req := httptest.NewRequest("POST", "/files/create", fd) repo := &testICLFileRepository{} router := mux.NewRouter() - addFileRoutes(log.NewNopLogger(), router, repo) + AppendRoutes(log.NewNopLogger(), router, repo) router.ServeHTTP(w, req) w.Flush() @@ -124,7 +124,7 @@ func TestFiles_createFileJSON(t *testing.T) { req.Header.Set("Content-Type", "application/json") repo := &testICLFileRepository{} router := mux.NewRouter() - addFileRoutes(log.NewNopLogger(), router, repo) + AppendRoutes(log.NewNopLogger(), router, repo) router.ServeHTTP(w, req) w.Flush() @@ -147,7 +147,7 @@ func TestFiles_createFileJSON(t *testing.T) { func TestFiles_getFile(t *testing.T) { repo := &testICLFileRepository{} router := mux.NewRouter() - addFileRoutes(log.NewNopLogger(), router, repo) + AppendRoutes(log.NewNopLogger(), router, repo) req := httptest.NewRequest("GET", "/files/foo", nil) t.Run("file not found", func(t *testing.T) { @@ -185,7 +185,7 @@ func TestFiles_getFile(t *testing.T) { func TestFiles_updateFileHeader(t *testing.T) { repo := &testICLFileRepository{} router := mux.NewRouter() - addFileRoutes(log.NewNopLogger(), router, repo) + AppendRoutes(log.NewNopLogger(), router, repo) f := readFile(t, "BNK20180905121042882-A.icl") f.ID = base.ID() @@ -220,7 +220,7 @@ func TestFiles_deleteFile(t *testing.T) { req := httptest.NewRequest("DELETE", "/files/foo", nil) repo := &testICLFileRepository{} router := mux.NewRouter() - addFileRoutes(log.NewNopLogger(), router, repo) + AppendRoutes(log.NewNopLogger(), router, repo) t.Run("file not found", func(t *testing.T) { w := httptest.NewRecorder() @@ -254,7 +254,7 @@ func TestFiles_getFileContents(t *testing.T) { req := httptest.NewRequest("GET", "/files/foo/contents", nil) router := mux.NewRouter() repo := &testICLFileRepository{} - addFileRoutes(log.NewNopLogger(), router, repo) + AppendRoutes(log.NewNopLogger(), router, repo) t.Run("file not found", func(t *testing.T) { w := httptest.NewRecorder() @@ -291,7 +291,7 @@ func TestFiles_validateFile(t *testing.T) { req := httptest.NewRequest("GET", "/files/foo/validate", nil) repo := &testICLFileRepository{} router := mux.NewRouter() - addFileRoutes(log.NewNopLogger(), router, repo) + AppendRoutes(log.NewNopLogger(), router, repo) f := readFile(t, "BNK20180905121042882-A.icl") t.Run("file not found", func(t *testing.T) { @@ -335,7 +335,7 @@ func TestFiles_validateFile(t *testing.T) { func TestFiles_addCashLetterToFile(t *testing.T) { repo := &testICLFileRepository{} router := mux.NewRouter() - addFileRoutes(log.NewNopLogger(), router, repo) + AppendRoutes(log.NewNopLogger(), router, repo) f := readFile(t, "BNK20180905121042882-A.icl") cashLetter := f.CashLetters[0] f.CashLetters = nil @@ -383,7 +383,7 @@ func TestFiles_addCashLetterToFile(t *testing.T) { func TestFiles_removeCashLetterFromFile(t *testing.T) { repo := &testICLFileRepository{} router := mux.NewRouter() - addFileRoutes(log.NewNopLogger(), router, repo) + AppendRoutes(log.NewNopLogger(), router, repo) f := readFile(t, "BNK20180905121042882-A.icl") cashLetterId := base.ID() f.CashLetters[0].ID = cashLetterId @@ -420,7 +420,7 @@ func TestFiles_removeCashLetterFromFile(t *testing.T) { func TestFiles_createFile_Issue228(t *testing.T) { repo := &testICLFileRepository{} router := mux.NewRouter() - addFileRoutes(log.NewNopLogger(), router, repo) + AppendRoutes(log.NewNopLogger(), router, repo) w := httptest.NewRecorder() fd, _ := os.Open(filepath.Join("..", "..", "test", "testdata", "issue228.json")) @@ -447,3 +447,34 @@ func readFile(t *testing.T, filename string) *imagecashletter.File { require.NoError(t, err) return &f } + +type testICLFileRepository struct { + err error + + file *imagecashletter.File +} + +func (r *testICLFileRepository) GetFiles() ([]*imagecashletter.File, error) { + if r.err != nil { + return nil, r.err + } + return []*imagecashletter.File{r.file}, nil +} + +func (r *testICLFileRepository) GetFile(fileId string) (*imagecashletter.File, error) { + if r.err != nil { + return nil, r.err + } + return r.file, nil +} + +func (r *testICLFileRepository) SaveFile(file *imagecashletter.File) error { + if r.err == nil { // only persist if we're not error'ing + r.file = file + } + return r.err +} + +func (r *testICLFileRepository) DeleteFile(fileId string) error { + return r.err +} diff --git a/cmd/server/http.go b/internal/metrics/http.go similarity index 94% rename from cmd/server/http.go rename to internal/metrics/http.go index c406446c..d426fe07 100644 --- a/cmd/server/http.go +++ b/internal/metrics/http.go @@ -2,7 +2,7 @@ // Use of this source code is governed by an Apache License // license that can be found in the LICENSE file. -package main +package metrics import ( "fmt" @@ -25,7 +25,7 @@ var ( }, []string{"route"}) ) -func wrapResponseWriter(logger log.Logger, w http.ResponseWriter, r *http.Request) http.ResponseWriter { +func WrapResponseWriter(logger log.Logger, w http.ResponseWriter, r *http.Request) http.ResponseWriter { route := fmt.Sprintf("%s-%s", strings.ToLower(r.Method), cleanMetricsPath(r.URL.Path)) return moovhttp.Wrap(logger, routeHistogram.With("route", route), w, r) } diff --git a/cmd/server/http_test.go b/internal/metrics/http_test.go similarity index 98% rename from cmd/server/http_test.go rename to internal/metrics/http_test.go index 58a2a7ad..fb3d9bf8 100644 --- a/cmd/server/http_test.go +++ b/internal/metrics/http_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by an Apache License // license that can be found in the LICENSE file. -package main +package metrics import ( "testing" diff --git a/cmd/server/storage.go b/internal/storage/storage.go similarity index 63% rename from cmd/server/storage.go rename to internal/storage/storage.go index 8b5b9682..1209f815 100644 --- a/cmd/server/storage.go +++ b/internal/storage/storage.go @@ -2,7 +2,7 @@ // Use of this source code is governed by an Apache License // license that can be found in the LICENSE file. -package main +package storage import ( "errors" @@ -12,11 +12,11 @@ import ( ) type ICLFileRepository interface { - getFiles() ([]*imagecashletter.File, error) - getFile(fileId string) (*imagecashletter.File, error) + GetFiles() ([]*imagecashletter.File, error) + GetFile(fileId string) (*imagecashletter.File, error) - saveFile(file *imagecashletter.File) error - deleteFile(fileId string) error + SaveFile(file *imagecashletter.File) error + DeleteFile(fileId string) error } type memoryICLFileRepository struct { @@ -24,7 +24,13 @@ type memoryICLFileRepository struct { files map[string]*imagecashletter.File } -func (r *memoryICLFileRepository) getFiles() ([]*imagecashletter.File, error) { +func NewInMemoryRepo() ICLFileRepository { + return &memoryICLFileRepository{ + files: make(map[string]*imagecashletter.File), + } +} + +func (r *memoryICLFileRepository) GetFiles() ([]*imagecashletter.File, error) { r.mu.Lock() defer r.mu.Unlock() @@ -36,7 +42,7 @@ func (r *memoryICLFileRepository) getFiles() ([]*imagecashletter.File, error) { return out, nil } -func (r *memoryICLFileRepository) getFile(fileId string) (*imagecashletter.File, error) { +func (r *memoryICLFileRepository) GetFile(fileId string) (*imagecashletter.File, error) { r.mu.Lock() defer r.mu.Unlock() @@ -49,7 +55,7 @@ func (r *memoryICLFileRepository) getFile(fileId string) (*imagecashletter.File, return nil, nil } -func (r *memoryICLFileRepository) saveFile(file *imagecashletter.File) error { +func (r *memoryICLFileRepository) SaveFile(file *imagecashletter.File) error { r.mu.Lock() defer r.mu.Unlock() @@ -60,7 +66,7 @@ func (r *memoryICLFileRepository) saveFile(file *imagecashletter.File) error { return nil } -func (r *memoryICLFileRepository) deleteFile(fileId string) error { +func (r *memoryICLFileRepository) DeleteFile(fileId string) error { r.mu.Lock() defer r.mu.Unlock() diff --git a/internal/storage/storage_test.go b/internal/storage/storage_test.go new file mode 100644 index 00000000..27e0e43d --- /dev/null +++ b/internal/storage/storage_test.go @@ -0,0 +1,52 @@ +// Copyright 2020 The Moov Authors +// Use of this source code is governed by an Apache License +// license that can be found in the LICENSE file. + +package storage + +import ( + "os" + "path/filepath" + "testing" + + "github.com/moov-io/base" + "github.com/moov-io/imagecashletter" + "github.com/stretchr/testify/require" +) + +func TestMemoryStorage(t *testing.T) { + repo := &memoryICLFileRepository{ + files: make(map[string]*imagecashletter.File), + } + + files, err := repo.GetFiles() + require.NoError(t, err) + require.Equal(t, 0, len(files)) + + f := readFile(t, "BNK20180905121042882-A.icl") + f.ID = base.ID() + require.NoError(t, repo.SaveFile(f)) + + files, err = repo.GetFiles() + require.NoError(t, err) + require.Equal(t, 1, len(files)) + + file, err := repo.GetFile(f.ID) + require.NoError(t, err) + require.Equal(t, f.ID, file.ID) + + require.NoError(t, repo.DeleteFile(f.ID)) + files, err = repo.GetFiles() + require.NoError(t, err) + require.Equal(t, 0, len(files)) +} + +func readFile(t *testing.T, filename string) *imagecashletter.File { + t.Helper() + + fd, err := os.Open(filepath.Join("..", "..", "test", "testdata", filename)) + require.NoError(t, err) + f, err := imagecashletter.NewReader(fd, imagecashletter.ReadVariableLineLengthOption()).Read() + require.NoError(t, err) + return &f +} From ae49dd78e58303bac646d9a4b72ccab4b402d231 Mon Sep 17 00:00:00 2001 From: Daniel Tonks Date: Sat, 17 Feb 2024 21:41:32 -0700 Subject: [PATCH 2/7] implement POST /v2/files endpoint --- cmd/server/main.go | 7 +- go.mod | 1 + go.sum | 1 + internal/files/v2/files.go | 141 ++++++++++++++++++ internal/files/v2/files_test.go | 251 ++++++++++++++++++++++++++++++++ internal/responder/responder.go | 103 +++++++++++++ 6 files changed, 503 insertions(+), 1 deletion(-) create mode 100644 internal/files/v2/files.go create mode 100644 internal/files/v2/files_test.go create mode 100644 internal/responder/responder.go diff --git a/cmd/server/main.go b/cmd/server/main.go index e0b63ef5..fe6caf90 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -23,6 +23,7 @@ import ( "github.com/moov-io/base/log" "github.com/moov-io/imagecashletter" "github.com/moov-io/imagecashletter/internal/files" + v2files "github.com/moov-io/imagecashletter/internal/files/v2" "github.com/moov-io/imagecashletter/internal/storage" ) @@ -76,11 +77,15 @@ func main() { }() defer adminServer.Shutdown() + // Persistence layer shared between API versions for interoperability + repository := storage.NewInMemoryRepo() + // Setup business HTTP routes router := mux.NewRouter() moovhttp.AddCORSHandler(router) addPingRoute(router) - files.AppendRoutes(logger, router, storage.NewInMemoryRepo()) + files.AppendRoutes(logger, router, repository) + v2files.NewController(logger, repository).AddRoutes(router) // Start business HTTP server readTimeout, _ := time.ParseDuration("30s") diff --git a/go.mod b/go.mod index 6a22ddec..194a9b22 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/antihax/optional v1.0.0 github.com/gdamore/encoding v1.0.0 github.com/go-kit/kit v0.13.0 + github.com/google/uuid v1.5.0 github.com/gorilla/mux v1.8.1 github.com/moov-io/base v0.48.5 github.com/prometheus/client_golang v1.18.0 diff --git a/go.sum b/go.sum index 2c3c670d..ceeca832 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,7 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= diff --git a/internal/files/v2/files.go b/internal/files/v2/files.go new file mode 100644 index 00000000..bd7efd7f --- /dev/null +++ b/internal/files/v2/files.go @@ -0,0 +1,141 @@ +package v2 + +import ( + "bufio" + "fmt" + "io" + "net/http" + "os" + "strconv" + "strings" + + "github.com/google/uuid" + "github.com/gorilla/mux" + "github.com/moov-io/base/log" + "github.com/moov-io/imagecashletter" + "github.com/moov-io/imagecashletter/internal/metrics" + "github.com/moov-io/imagecashletter/internal/responder" + "github.com/moov-io/imagecashletter/internal/storage" +) + +var ( + maxReaderBufferSize = func() int { + v, exists := os.LookupEnv("READER_BUFFER_SIZE") + if exists { + n, _ := strconv.ParseInt(v, 10, 32) + return int(n) + } + return bufio.MaxScanTokenSize + }() +) + +type Controller struct { + logger log.Logger + repo storage.ICLFileRepository +} + +func NewController(logger log.Logger, fileRepo storage.ICLFileRepository) Controller { + return Controller{ + logger: logger, + repo: fileRepo, + } +} + +func (c Controller) AddRoutes(router *mux.Router) { + v2Routes := router.PathPrefix("/v2").Subrouter() + + v2Routes. + Path("/files"). + Methods(http.MethodPost). + HandlerFunc(c.createFile) + +} + +func (c Controller) createFile(w http.ResponseWriter, r *http.Request) { + w = metrics.WrapResponseWriter(c.logger, w, r) + respond := responder.NewResponder(c.logger, w, r) + + var created *imagecashletter.File + var err error + + contentType := r.Header.Get("Content-Type") + switch { + case strings.Contains(contentType, "application/json"): + created, err = c.fileFromJSON(r) + case strings.Contains(contentType, "multipart/form-data"): + created, err = c.fileFromForm(r) + default: + err = fmt.Errorf("missing or unsupported Content-Type: %s", contentType) + } + + if err != nil { + c.logger.Error().LogErrorf("creating file: %v", err) + respond.Error(http.StatusBadRequest, err) + return + } + + if err = c.repo.SaveFile(created); err != nil { + c.logger.Error().LogErrorf("saving created file: %v", err) + respond.Error(http.StatusInternalServerError, err) + return + } + + respond = respond.WithLocation(created.ID) + if expectingFile(r) { + respond.File(http.StatusCreated, *created, fmt.Sprintf("%s.x9", created.ID)) + return + } + + respond.JSON(http.StatusCreated, created) +} + +func expectingFile(r *http.Request) bool { + mimeType := r.Header.Get("Accept") + return mimeType == "application/octet-stream" || mimeType == "text/plain" +} + +func (c Controller) fileFromJSON(r *http.Request) (*imagecashletter.File, error) { + contents, err := io.ReadAll(r.Body) + if err != nil { + return nil, fmt.Errorf("reading request body: %w", err) + } + + file, err := imagecashletter.FileFromJSON(contents) + if err != nil { + return nil, fmt.Errorf("parsing request body: %w", err) + } + file.ID = uuid.NewString() + + return file, nil +} + +func (c Controller) fileFromForm(r *http.Request) (*imagecashletter.File, error) { + if err := r.ParseMultipartForm(int64(maxReaderBufferSize)); err != nil { + return nil, fmt.Errorf("parsing multipart form: %v", err) + } + + formFile, hdr, err := r.FormFile("file") + if err != nil { + return nil, fmt.Errorf("reading form file: %w", err) + } + + opts := []imagecashletter.ReaderOption{ + imagecashletter.ReadVariableLineLengthOption(), + imagecashletter.BufferSizeOption(maxReaderBufferSize), + } + + // Industry standard encoding is EBCDIC, so unless plain/text was + // explicitly requested, default to EBCDIC. + contentType := hdr.Header.Get("Content-Type") + if contentType != "text/plain" { + opts = append(opts, imagecashletter.ReadEbcdicEncodingOption()) + } + + file, err := imagecashletter.NewReader(formFile, opts...).Read() + if err != nil { + return nil, fmt.Errorf("parsing file: %w", err) + } + file.ID = uuid.NewString() + + return &file, nil +} diff --git a/internal/files/v2/files_test.go b/internal/files/v2/files_test.go new file mode 100644 index 00000000..e69fbc8b --- /dev/null +++ b/internal/files/v2/files_test.go @@ -0,0 +1,251 @@ +package v2_test + +import ( + "bytes" + "encoding/json" + "io" + "mime/multipart" + "net/http" + "net/http/httptest" + "net/textproto" + "os" + "path/filepath" + "testing" + + "github.com/gorilla/mux" + "github.com/moov-io/base/log" + "github.com/moov-io/imagecashletter" + openapi "github.com/moov-io/imagecashletter/client" + v2 "github.com/moov-io/imagecashletter/internal/files/v2" + "github.com/moov-io/imagecashletter/internal/storage" + "github.com/stretchr/testify/require" +) + +func TestController_createJSONFile(t *testing.T) { + router := newRouter(t) + + t.Run("returns JSON", func(t *testing.T) { + rdr := getTestData(t, "icl-valid.json") + + resp, apiErr := createFile(t, router, rdr, "application/json") + require.Empty(t, apiErr) + + var created imagecashletter.File + require.NoError(t, json.NewDecoder(resp.Body).Decode(&created)) + + require.Contains(t, resp.Header().Get("Location"), created.ID) + require.NotEmpty(t, created.ID) + require.NotEmpty(t, created) + require.Equal(t, "231380104", created.Header.ImmediateDestination) + require.Len(t, created.CashLetters, 2) + require.Equal(t, 400000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount) + }) + + t.Run("returns EBCDIC file", func(t *testing.T) { + rdr := getTestData(t, "icl-valid.json") + + resp, apiErr := createFile(t, router, rdr, "application/octet-stream") + require.Empty(t, apiErr) + + opts := []imagecashletter.ReaderOption{ + imagecashletter.ReadVariableLineLengthOption(), + imagecashletter.ReadEbcdicEncodingOption(), + } + created, err := imagecashletter.NewReader(resp.Body, opts...).Read() + require.NoError(t, err) + + require.Contains(t, resp.Header().Get("Content-Type"), "application/octet-stream") + require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9") + require.NotEmpty(t, created) + require.Equal(t, "231380104", created.Header.ImmediateDestination) + require.Len(t, created.CashLetters, 2) + require.Equal(t, 400000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount) + }) +} + +func TestController_uploadEBCDICFile(t *testing.T) { + router := newRouter(t) + + t.Run("uploads EBCDIC; returns ASCII", func(t *testing.T) { + rdr := getTestData(t, "valid-ebcdic.x937") + + resp, apiErr := uploadFile(t, router, rdr, "application/octet-stream", "text/plain") + require.Empty(t, apiErr) + + // now read back in without EBCDIC option + created, err := imagecashletter.NewReader(resp.Body, imagecashletter.ReadVariableLineLengthOption()).Read() + require.NoError(t, err) + require.Contains(t, resp.Header().Get("Content-Type"), "text/plain") + require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9") + require.NotEmpty(t, created) + require.Equal(t, "061000146", created.Header.ImmediateDestination) + require.Len(t, created.CashLetters, 1) + require.Equal(t, 10000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount) + }) + + t.Run("uploads EBCDIC; returns EBCDIC", func(t *testing.T) { + rdr := getTestData(t, "valid-ebcdic.x937") + + resp, apiErr := uploadFile(t, router, rdr, "application/octet-stream", "application/octet-stream") + require.Empty(t, apiErr) + + // now read back in with EBCDIC option + created, err := imagecashletter.NewReader(resp.Body, + imagecashletter.ReadVariableLineLengthOption(), + imagecashletter.ReadEbcdicEncodingOption(), + ).Read() + require.NoError(t, err) + require.Contains(t, resp.Header().Get("Content-Type"), "application/octet-stream") + require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9") + require.NotEmpty(t, created) + require.Equal(t, "061000146", created.Header.ImmediateDestination) + require.Len(t, created.CashLetters, 1) + require.Equal(t, 10000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount) + }) + + t.Run("uploads EBCDIC; returns JSON", func(t *testing.T) { + rdr := getTestData(t, "valid-ebcdic.x937") + + resp, apiErr := uploadFile(t, router, rdr, "application/octet-stream", "application/json") + require.Empty(t, apiErr) + + // now read back in without EBCDIC option + var created imagecashletter.File + require.NoError(t, json.NewDecoder(resp.Body).Decode(&created)) + require.Contains(t, resp.Header().Get("Content-Type"), "application/json") + require.NotEmpty(t, created.ID) + require.Equal(t, "061000146", created.Header.ImmediateDestination) + require.Len(t, created.CashLetters, 1) + require.Equal(t, 10000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount) + }) +} + +func TestController_uploadASCIIFile(t *testing.T) { + router := newRouter(t) + + t.Run("uploads ASCII; returns ASCII", func(t *testing.T) { + rdr := getTestData(t, "valid-ascii.x937") + + resp, apiErr := uploadFile(t, router, rdr, "text/plain", "text/plain") + require.Empty(t, apiErr) + + // now read back in without EBCDIC option + created, err := imagecashletter.NewReader(resp.Body, imagecashletter.ReadVariableLineLengthOption()).Read() + require.NoError(t, err) + require.Contains(t, resp.Header().Get("Content-Type"), "text/plain") + require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9") + require.NotEmpty(t, created) + require.Equal(t, "061000146", created.Header.ImmediateDestination) + require.Len(t, created.CashLetters, 1) + require.Equal(t, 10000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount) + }) + + t.Run("uploads EBCDIC; returns EBCDIC", func(t *testing.T) { + rdr := getTestData(t, "valid-ascii.x937") + + resp, apiErr := uploadFile(t, router, rdr, "text/plain", "application/octet-stream") + require.Empty(t, apiErr) + + // now read back in with EBCDIC option + created, err := imagecashletter.NewReader(resp.Body, + imagecashletter.ReadVariableLineLengthOption(), + imagecashletter.ReadEbcdicEncodingOption(), + ).Read() + require.NoError(t, err) + require.Contains(t, resp.Header().Get("Content-Type"), "application/octet-stream") + require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9") + require.NotEmpty(t, created) + require.Equal(t, "061000146", created.Header.ImmediateDestination) + require.Len(t, created.CashLetters, 1) + require.Equal(t, 10000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount) + }) + + t.Run("uploads EBCDIC; returns JSON", func(t *testing.T) { + rdr := getTestData(t, "valid-ascii.x937") + + resp, apiErr := uploadFile(t, router, rdr, "text/plain", "application/json") + require.Empty(t, apiErr) + + // now read back in without EBCDIC option + var created imagecashletter.File + require.NoError(t, json.NewDecoder(resp.Body).Decode(&created)) + require.Contains(t, resp.Header().Get("Content-Type"), "application/json") + require.NotEmpty(t, created.ID) + require.Equal(t, "061000146", created.Header.ImmediateDestination) + require.Len(t, created.CashLetters, 1) + require.Equal(t, 10000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount) + }) +} + +func createFile(t *testing.T, router *mux.Router, body io.Reader, accept string) (*httptest.ResponseRecorder, openapi.Error) { + req, err := http.NewRequest(http.MethodPost, "/v2/files", body) + require.NoError(t, err) + + req.Header.Set("Accept", accept) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + w.Flush() + + var apiErr openapi.Error + if w.Code >= 400 && w.Code < 500 { + require.NoError(t, json.NewDecoder(w.Body).Decode(&apiErr)) + } + + return w, apiErr +} + +func uploadFile(t *testing.T, router *mux.Router, file io.Reader, contentType, accept string) (*httptest.ResponseRecorder, openapi.Error) { + // create the multipart-form writer + body := new(bytes.Buffer) + mw := multipart.NewWriter(body) + + // set up headers for the "file" portion + partHeaders := make(textproto.MIMEHeader) + partHeaders.Set("Content-Disposition", `form-data; name="file"; filename="cashletter.x9"`) + partHeaders.Set("Content-Type", contentType) + + // create a new multipart-form section with the headers + part, err := mw.CreatePart(partHeaders) + require.NoError(t, err) + + // copy the file contents to the form section + _, err = io.Copy(part, file) + require.NoError(t, err) + require.NoError(t, mw.Close()) + + req, err := http.NewRequest(http.MethodPost, "/v2/files", body) + require.NoError(t, err) + req.Header.Set("Content-Type", mw.FormDataContentType()) + req.Header.Set("Accept", accept) + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + w.Flush() + + var apiErr openapi.Error + if w.Code >= 400 && w.Code < 500 { + require.NoError(t, json.NewDecoder(w.Body).Decode(&apiErr)) + } + + return w, apiErr +} + +func newRouter(t *testing.T) *mux.Router { + c := v2.NewController(log.NewTestLogger(), storage.NewInMemoryRepo()) + router := mux.NewRouter() + c.AddRoutes(router) + + return router +} + +func getTestData(t *testing.T, filename string) io.Reader { + fd, err := os.Open(filepath.Join("..", "..", "..", "test", "testdata", filename)) + require.NoError(t, err) + t.Cleanup(func() { + fd.Close() + }) + + return fd +} diff --git a/internal/responder/responder.go b/internal/responder/responder.go new file mode 100644 index 00000000..75c520de --- /dev/null +++ b/internal/responder/responder.go @@ -0,0 +1,103 @@ +package responder + +import ( + "encoding/json" + "net/http" + + "github.com/moov-io/base/log" + "github.com/moov-io/imagecashletter" + client "github.com/moov-io/imagecashletter/client" +) + +// Responder is a helper for writing responses to an http.ResponseWriter. +type Responder struct { + logger log.Logger + w http.ResponseWriter + r *http.Request + optionalHeaders +} + +type optionalHeaders struct { + location string +} + +func (h optionalHeaders) apply(w http.ResponseWriter) { + if h.location != "" { + w.Header().Set("Location", h.location) + } +} + +func NewResponder(logger log.Logger, w http.ResponseWriter, r *http.Request) *Responder { + return &Responder{ + logger: logger, + w: w, + r: r, + } +} + +// WithLocation sets the Location header on the response. +func (r *Responder) WithLocation(resourceID string) *Responder { + r.optionalHeaders.location = r.r.URL.String() + "/" + resourceID + return r +} + +// File writes the file to the http.ResponseWriter as an attachment. It should +// only be called when the request's Accept header was "application/octet-stream" or "text/plain". +func (r *Responder) File(status int, file imagecashletter.File, name string) { + opts := []imagecashletter.WriterOption{ + imagecashletter.WriteVariableLineLengthOption(), + } + + // determine which encoding the caller expects to set up the writer and response headers + mimeType := r.r.Header.Get("Accept") + switch mimeType { + case "application/octet-stream": + r.w.Header().Set("Content-Type", "application/octet-stream") + opts = append(opts, imagecashletter.WriteEbcdicEncodingOption()) + case "text/plain": + r.w.Header().Set("Content-Type", "text/plain") + default: + r.logger.LogErrorf("renderer: file method called for unsupported mime-type: %s", mimeType) + r.w.WriteHeader(http.StatusInternalServerError) + return + } + + r.optionalHeaders.apply(r.w) + r.w.Header().Set("Content-Disposition", "attachment; filename="+name) + r.w.WriteHeader(status) + + if err := imagecashletter.NewWriter(r.w, opts...).Write(&file); err != nil { + r.logger.LogErrorf("rendering file: %v", err) + r.w.WriteHeader(http.StatusInternalServerError) + return + } +} + +// JSON writes the resource to the http.ResponseWriter as JSON. +func (r *Responder) JSON(status int, resource any) { + r.optionalHeaders.apply(r.w) + r.w.Header().Set("Content-Type", "application/json; charset=UTF-8") + r.w.WriteHeader(status) + if err := json.NewEncoder(r.w).Encode(resource); err != nil { + r.logger.LogErrorf("problem encoding response: %v", err) + r.w.WriteHeader(http.StatusInternalServerError) + return + } +} + +// Error sets the status code and writes an error to the http.ResponseWriter. Response body is +// omitted for 5xx errors. +func (r *Responder) Error(status int, err error) { + if status >= 500 { // don't return body for internal errors + r.w.WriteHeader(status) + return + } + + r.w.Header().Set("Content-Type", "application/json; charset=UTF-8") + r.w.WriteHeader(status) + if err := json.NewEncoder(r.w).Encode(client.Error{Error: err.Error()}); err != nil { + r.logger.LogErrorf("problem encoding response: %v", err) + r.w.WriteHeader(http.StatusInternalServerError) + return + } +} From 817fe3620845d18009814ceb6477bdec17755426 Mon Sep 17 00:00:00 2001 From: Daniel Tonks Date: Sat, 17 Feb 2024 22:08:59 -0700 Subject: [PATCH 3/7] new openapi documentation and client code --- client/README.md | 1 + client/api/openapi.yaml | 393 ++++++++++++++----------- client/api_image_cash_letter_files.go | 87 ++++++ client/docs/BundleHeader.md | 2 +- client/docs/CashLetterControl.md | 2 +- client/docs/CashLetterHeader.md | 2 +- client/docs/Checks.md | 2 +- client/docs/ImageCashLetterFilesApi.md | 33 +++ client/docs/ImageViewData.md | 2 +- client/docs/Returns.md | 2 +- client/model_bundle_header.go | 2 +- client/model_cash_letter_control.go | 2 +- client/model_cash_letter_header.go | 2 +- client/model_checks.go | 2 +- client/model_image_view_data.go | 2 +- client/model_returns.go | 2 +- makefile | 5 + openapi.yaml | 53 ++++ 18 files changed, 415 insertions(+), 181 deletions(-) diff --git a/client/README.md b/client/README.md index 9a4bd39a..e3289ea2 100644 --- a/client/README.md +++ b/client/README.md @@ -35,6 +35,7 @@ Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- *ImageCashLetterFilesApi* | [**AddICLToFile**](docs/ImageCashLetterFilesApi.md#addicltofile) | **Post** /files/{fileID}/cashLetters | Add cash letter to file *ImageCashLetterFilesApi* | [**CreateICLFile**](docs/ImageCashLetterFilesApi.md#createiclfile) | **Post** /files/create | Create file +*ImageCashLetterFilesApi* | [**CreateICLFileV2**](docs/ImageCashLetterFilesApi.md#createiclfilev2) | **Post** /v2/files | Create file *ImageCashLetterFilesApi* | [**DeleteICLFile**](docs/ImageCashLetterFilesApi.md#deleteiclfile) | **Delete** /files/{fileID} | Delete file *ImageCashLetterFilesApi* | [**DeleteICLFromFile**](docs/ImageCashLetterFilesApi.md#deleteiclfromfile) | **Delete** /files/{fileID}/cashLetters/{cashLetterID} | Delete cash letter from file *ImageCashLetterFilesApi* | [**GetICLFileByID**](docs/ImageCashLetterFilesApi.md#geticlfilebyid) | **Get** /files/{fileID} | Retrieve file diff --git a/client/api/openapi.yaml b/client/api/openapi.yaml index 56dca895..20c2508f 100644 --- a/client/api/openapi.yaml +++ b/client/api/openapi.yaml @@ -404,6 +404,61 @@ paths: summary: Delete cash letter from file tags: - Image Cash Letter Files + /v2/files: + post: + operationId: createICLFileV2 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CreateICLFile' + multipart/form-data: + schema: + properties: + file: + description: "The file to upload. Set this section's `Content-Type`\ + \ to `text/plain` for \nASCII-encoded files, or `application/octet-stream`\ + \ for binary files. \n" + format: binary + type: string + type: object + description: | + Content of the ImageCashLetter file in JSON, or X9 (ASCII or EBCDIC) format. + Use the `Accept` header to specify the response format. + required: true + responses: + "201": + content: + application/json: + schema: + $ref: '#/components/schemas/ICLFile' + application/octet-stream: + schema: + format: binary + type: string + text/plain: + schema: + format: binary + type: string + description: The file was created successfully + headers: + Location: + description: The location of the new resource + explode: false + required: true + schema: + format: url + type: string + style: simple + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: The request was invalid + summary: Create file + tags: + - Image Cash Letter Files components: schemas: CreateICLFile: @@ -455,6 +510,7 @@ components: cashLetterHeader: collectionTypeIndicator: "00" destinationRoutingNumber: "999282654" + eceInstitutionRoutingNumber: "217876527" originatorContactPhoneNumber: "5685551212" returnsIndicator: E cashLetterID: "1" @@ -466,7 +522,6 @@ components: userField: V originatorContactName: John Smith ID: d1e26288 - eceInstitutionRoutingNumber: "217876527" documentationTypeIndicator: A bundles: - bundleHeader: @@ -476,13 +531,14 @@ components: destinationRoutingNumber: "231987654" bundleID: "9999" userField: B1234 + eceInstitutionRoutingNumber: "233431587" ID: d1e26288 bundleBusinessDate: 2018-10-03T00:00:00Z returnLocationRoutingNumber: "987654320" - eceInstitutionRoutingNumber: "233431587" cycleNumber: "01" checks: - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -490,6 +546,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -501,7 +558,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -509,6 +565,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -520,14 +577,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -687,7 +742,8 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -695,6 +751,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -706,7 +763,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -714,6 +770,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -725,14 +782,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -901,12 +956,14 @@ components: ID: d1e26288 creditTotalIndicator: 1 returns: - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -918,7 +975,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -926,6 +982,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -937,7 +994,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -982,7 +1038,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -1118,12 +1173,14 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -1135,7 +1192,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -1143,6 +1199,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -1154,7 +1211,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -1199,7 +1255,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -1342,13 +1397,14 @@ components: destinationRoutingNumber: "231987654" bundleID: "9999" userField: B1234 + eceInstitutionRoutingNumber: "233431587" ID: d1e26288 bundleBusinessDate: 2018-10-03T00:00:00Z returnLocationRoutingNumber: "987654320" - eceInstitutionRoutingNumber: "233431587" cycleNumber: "01" checks: - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -1356,6 +1412,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -1367,7 +1424,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -1375,6 +1431,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -1386,14 +1443,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -1553,7 +1608,8 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -1561,6 +1617,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -1572,7 +1629,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -1580,6 +1636,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -1591,14 +1648,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -1767,12 +1822,14 @@ components: ID: d1e26288 creditTotalIndicator: 1 returns: - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -1784,7 +1841,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -1792,6 +1848,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -1803,7 +1860,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -1848,7 +1904,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -1984,12 +2039,14 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -2001,7 +2058,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -2009,6 +2065,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -2020,7 +2077,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -2065,7 +2121,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -2215,12 +2270,12 @@ components: cashLetterControl: cashLetterTotalAmount: 100 cashLetterItemsCount: 1 - eceInstitutionName: Citadel cashLetterBundleCount: 1 cashLetterImagesCount: 1 ID: d1e26288 settlementDate: 2018-10-03T00:00:00Z creditTotalIndicator: 1 + eceInstitutionName: Citadel - creditItems: - externalProcessingCode: "9" creditItemSequenceNumber: "1" @@ -2247,6 +2302,7 @@ components: cashLetterHeader: collectionTypeIndicator: "00" destinationRoutingNumber: "999282654" + eceInstitutionRoutingNumber: "217876527" originatorContactPhoneNumber: "5685551212" returnsIndicator: E cashLetterID: "1" @@ -2258,7 +2314,6 @@ components: userField: V originatorContactName: John Smith ID: d1e26288 - eceInstitutionRoutingNumber: "217876527" documentationTypeIndicator: A bundles: - bundleHeader: @@ -2268,13 +2323,14 @@ components: destinationRoutingNumber: "231987654" bundleID: "9999" userField: B1234 + eceInstitutionRoutingNumber: "233431587" ID: d1e26288 bundleBusinessDate: 2018-10-03T00:00:00Z returnLocationRoutingNumber: "987654320" - eceInstitutionRoutingNumber: "233431587" cycleNumber: "01" checks: - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -2282,6 +2338,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -2293,7 +2350,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -2301,6 +2357,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -2312,14 +2369,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -2479,7 +2534,8 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -2487,6 +2543,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -2498,7 +2555,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -2506,6 +2562,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -2517,14 +2574,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -2693,12 +2748,14 @@ components: ID: d1e26288 creditTotalIndicator: 1 returns: - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -2710,7 +2767,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -2718,6 +2774,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -2729,7 +2786,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -2774,7 +2830,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -2910,12 +2965,14 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -2927,7 +2984,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -2935,6 +2991,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -2946,7 +3003,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -2991,7 +3047,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -3134,13 +3189,14 @@ components: destinationRoutingNumber: "231987654" bundleID: "9999" userField: B1234 + eceInstitutionRoutingNumber: "233431587" ID: d1e26288 bundleBusinessDate: 2018-10-03T00:00:00Z returnLocationRoutingNumber: "987654320" - eceInstitutionRoutingNumber: "233431587" cycleNumber: "01" checks: - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -3148,6 +3204,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -3159,7 +3216,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -3167,6 +3223,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -3178,14 +3235,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -3345,7 +3400,8 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -3353,6 +3409,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -3364,7 +3421,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -3372,6 +3428,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -3383,14 +3440,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -3559,12 +3614,14 @@ components: ID: d1e26288 creditTotalIndicator: 1 returns: - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -3576,7 +3633,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -3584,6 +3640,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -3595,7 +3652,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -3640,7 +3696,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -3776,12 +3831,14 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -3793,7 +3850,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -3801,6 +3857,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -3812,7 +3869,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -3857,7 +3913,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -4007,12 +4062,12 @@ components: cashLetterControl: cashLetterTotalAmount: 100 cashLetterItemsCount: 1 - eceInstitutionName: Citadel cashLetterBundleCount: 1 cashLetterImagesCount: 1 ID: d1e26288 settlementDate: 2018-10-03T00:00:00Z creditTotalIndicator: 1 + eceInstitutionName: Citadel fileControl: cashLetterCount: 1 immediateOriginContactName: John Smith @@ -4030,13 +4085,14 @@ components: destinationRoutingNumber: "231987654" bundleID: "9999" userField: B1234 + eceInstitutionRoutingNumber: "233431587" ID: d1e26288 bundleBusinessDate: 2018-10-03T00:00:00Z returnLocationRoutingNumber: "987654320" - eceInstitutionRoutingNumber: "233431587" cycleNumber: "01" checks: - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -4044,6 +4100,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -4055,7 +4112,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -4063,6 +4119,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -4074,14 +4131,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -4241,7 +4296,8 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -4249,6 +4305,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -4260,7 +4317,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -4268,6 +4324,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -4279,14 +4336,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -4455,12 +4510,14 @@ components: ID: d1e26288 creditTotalIndicator: 1 returns: - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -4472,7 +4529,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -4480,6 +4536,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -4491,7 +4548,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -4536,7 +4592,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -4672,12 +4727,14 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -4689,7 +4746,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -4697,6 +4753,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -4708,7 +4765,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -4753,7 +4809,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -4896,13 +4951,14 @@ components: destinationRoutingNumber: "231987654" bundleID: "9999" userField: B1234 + eceInstitutionRoutingNumber: "233431587" ID: d1e26288 bundleBusinessDate: 2018-10-03T00:00:00Z returnLocationRoutingNumber: "987654320" - eceInstitutionRoutingNumber: "233431587" cycleNumber: "01" checks: - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -4910,6 +4966,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -4921,7 +4978,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -4929,6 +4985,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -4940,14 +4997,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -5107,7 +5162,8 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -5115,6 +5171,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -5126,7 +5183,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -5134,6 +5190,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -5145,14 +5202,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -5321,12 +5376,14 @@ components: ID: d1e26288 creditTotalIndicator: 1 returns: - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -5338,7 +5395,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -5346,6 +5402,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -5357,7 +5414,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -5402,7 +5458,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -5538,12 +5593,14 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -5555,7 +5612,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -5563,6 +5619,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -5574,7 +5631,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -5619,7 +5675,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -6023,6 +6078,7 @@ components: cashLetterHeader: collectionTypeIndicator: "00" destinationRoutingNumber: "999282654" + eceInstitutionRoutingNumber: "217876527" originatorContactPhoneNumber: "5685551212" returnsIndicator: E cashLetterID: "1" @@ -6034,7 +6090,6 @@ components: userField: V originatorContactName: John Smith ID: d1e26288 - eceInstitutionRoutingNumber: "217876527" documentationTypeIndicator: A bundles: - bundleHeader: @@ -6044,13 +6099,14 @@ components: destinationRoutingNumber: "231987654" bundleID: "9999" userField: B1234 + eceInstitutionRoutingNumber: "233431587" ID: d1e26288 bundleBusinessDate: 2018-10-03T00:00:00Z returnLocationRoutingNumber: "987654320" - eceInstitutionRoutingNumber: "233431587" cycleNumber: "01" checks: - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -6058,6 +6114,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -6069,7 +6126,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -6077,6 +6133,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -6088,14 +6145,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -6255,7 +6310,8 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -6263,6 +6319,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -6274,7 +6331,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -6282,6 +6338,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -6293,14 +6350,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -6469,12 +6524,14 @@ components: ID: d1e26288 creditTotalIndicator: 1 returns: - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -6486,7 +6543,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -6494,6 +6550,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -6505,7 +6562,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -6550,7 +6606,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -6686,12 +6741,14 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -6703,7 +6760,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -6711,6 +6767,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -6722,7 +6779,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -6767,7 +6823,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -6910,13 +6965,14 @@ components: destinationRoutingNumber: "231987654" bundleID: "9999" userField: B1234 + eceInstitutionRoutingNumber: "233431587" ID: d1e26288 bundleBusinessDate: 2018-10-03T00:00:00Z returnLocationRoutingNumber: "987654320" - eceInstitutionRoutingNumber: "233431587" cycleNumber: "01" checks: - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -6924,6 +6980,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -6935,7 +6992,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -6943,6 +6999,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -6954,14 +7011,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -7121,7 +7176,8 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -7129,6 +7185,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -7140,7 +7197,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -7148,6 +7204,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -7159,14 +7216,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -7335,12 +7390,14 @@ components: ID: d1e26288 creditTotalIndicator: 1 returns: - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -7352,7 +7409,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -7360,6 +7416,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -7371,7 +7428,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -7416,7 +7472,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -7552,12 +7607,14 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -7569,7 +7626,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -7577,6 +7633,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -7588,7 +7645,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -7633,7 +7689,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -7783,12 +7838,12 @@ components: cashLetterControl: cashLetterTotalAmount: 100 cashLetterItemsCount: 1 - eceInstitutionName: Citadel cashLetterBundleCount: 1 cashLetterImagesCount: 1 ID: d1e26288 settlementDate: 2018-10-03T00:00:00Z creditTotalIndicator: 1 + eceInstitutionName: Citadel properties: cashLetterHeader: $ref: '#/components/schemas/CashLetterHeader' @@ -7810,6 +7865,7 @@ components: example: collectionTypeIndicator: "00" destinationRoutingNumber: "999282654" + eceInstitutionRoutingNumber: "217876527" originatorContactPhoneNumber: "5685551212" returnsIndicator: E cashLetterID: "1" @@ -7821,7 +7877,6 @@ components: userField: V originatorContactName: John Smith ID: d1e26288 - eceInstitutionRoutingNumber: "217876527" documentationTypeIndicator: A properties: ID: @@ -7988,12 +8043,12 @@ components: example: cashLetterTotalAmount: 100 cashLetterItemsCount: 1 - eceInstitutionName: Citadel cashLetterBundleCount: 1 cashLetterImagesCount: 1 ID: d1e26288 settlementDate: 2018-10-03T00:00:00Z creditTotalIndicator: 1 + eceInstitutionName: Citadel properties: ID: description: CashLetterControl ID @@ -8061,13 +8116,14 @@ components: destinationRoutingNumber: "231987654" bundleID: "9999" userField: B1234 + eceInstitutionRoutingNumber: "233431587" ID: d1e26288 bundleBusinessDate: 2018-10-03T00:00:00Z returnLocationRoutingNumber: "987654320" - eceInstitutionRoutingNumber: "233431587" cycleNumber: "01" checks: - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -8075,6 +8131,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -8086,7 +8143,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -8094,6 +8150,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -8105,14 +8162,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -8272,7 +8327,8 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - bOFDIndicator: "Y" + - eceInstitutionItemSequenceNumber: "24531" + bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: - ivData.LengthImageData: "0000001" @@ -8280,6 +8336,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -8291,7 +8348,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -8299,6 +8355,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -8310,14 +8367,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -8486,12 +8541,14 @@ components: ID: d1e26288 creditTotalIndicator: 1 returns: - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -8503,7 +8560,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -8511,6 +8567,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -8522,7 +8579,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -8567,7 +8623,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -8703,12 +8758,14 @@ components: imageRecreateIndicator: 0 overrideIndicator: "0" documentationTypeIndicator: A - - imageViewData: + - eceInstitutionItemSequenceNumber: "24531" + imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -8720,7 +8777,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -8728,6 +8784,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -8739,7 +8796,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -8784,7 +8840,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -8941,10 +8996,10 @@ components: destinationRoutingNumber: "231987654" bundleID: "9999" userField: B1234 + eceInstitutionRoutingNumber: "233431587" ID: d1e26288 bundleBusinessDate: 2018-10-03T00:00:00Z returnLocationRoutingNumber: "987654320" - eceInstitutionRoutingNumber: "233431587" cycleNumber: "01" properties: ID: @@ -9087,6 +9142,7 @@ components: - bundleTotalAmount Checks: example: + eceInstitutionItemSequenceNumber: "24531" bOFDIndicator: "Y" mICRValidIndicator: 1 imageViewData: @@ -9095,6 +9151,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -9106,7 +9163,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -9114,6 +9170,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -9125,14 +9182,12 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name returnAcceptanceIndicator: "1" archiveTypeIndicator: A onUs: "5558881" addendumCount: 3 - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "1" auxiliaryOnUs: "1001" itemAmount: 100000 @@ -9535,12 +9590,14 @@ components: type: array Returns: example: + eceInstitutionItemSequenceNumber: "24531" imageViewData: - ivData.LengthImageData: "0000001" ivData.ClippingCoordinateV2: "0001" ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -9552,7 +9609,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name - ivData.LengthImageData: "0000001" @@ -9560,6 +9616,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -9571,7 +9628,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name forwardBundleDate: 2018-10-03T00:00:00Z @@ -9616,7 +9672,6 @@ components: ID: d1e26288 returnLocationRoutingNumber: "231486745" bOFDItemSequenceNumber: "1" - eceInstitutionItemSequenceNumber: "24531" externalProcessingCode: "9" timesReturned: 0 returnDetailAddendumD: @@ -10707,6 +10762,7 @@ components: ivData.LengthImageReferenceKey: "0001" ivData.ClippingCoordinateV1: "0001" ivData.SecurityKeyName: SECURE + eceInstitutionRoutingNumber: "2318769876" ivData.DigitalSignature: "0101" ivData.ImageReferenceKey: "0" ivData.LengthDigitalSignature: '0 ' @@ -10718,7 +10774,6 @@ components: bundleBusinessDate: 2018-10-03T00:00:00Z ivData.ClippingOrigin: 1 ivData.ImageData: "0101" - eceInstitutionRoutingNumber: "2318769876" cycleNumber: "1" ivData.SecurityAuthenticatorName: Name properties: diff --git a/client/api_image_cash_letter_files.go b/client/api_image_cash_letter_files.go index cd5f24ba..93e0e19f 100644 --- a/client/api_image_cash_letter_files.go +++ b/client/api_image_cash_letter_files.go @@ -202,6 +202,93 @@ func (a *ImageCashLetterFilesApiService) CreateICLFile(ctx _context.Context, cre return localVarReturnValue, localVarHTTPResponse, nil } +/* +CreateICLFileV2 Create file + - @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + - @param createIclFile Content of the ImageCashLetter file in JSON, or X9 (ASCII or EBCDIC) format. Use the `Accept` header to specify the response format. + +@return IclFile +*/ +func (a *ImageCashLetterFilesApiService) CreateICLFileV2(ctx _context.Context, createIclFile CreateIclFile) (IclFile, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodPost + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue IclFile + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/v2/files" + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{"application/json", "multipart/form-data"} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json", "application/octet-stream", "text/plain"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + // body params + localVarPostBody = &createIclFile + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 400 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + // DeleteICLFileOpts Optional parameters for the method 'DeleteICLFile' type DeleteICLFileOpts struct { XRequestID optional.String diff --git a/client/docs/BundleHeader.md b/client/docs/BundleHeader.md index 156eb67a..b923c056 100644 --- a/client/docs/BundleHeader.md +++ b/client/docs/BundleHeader.md @@ -7,7 +7,7 @@ Name | Type | Description | Notes **ID** | **string** | BundleHeader ID | [optional] **CollectionTypeIndicator** | **string** | A code that identifies the type of bundle. It is the same value as the CollectionTypeIndicator in the CashLetterHeader within which the bundle is contained, unless the CollectionTypeIndicator in the CashLetterHeader is 99. * `00` - Preliminary Forward Information * `01` - Forward Presentment * `02` - Forward Presentment - Same-Day Settlement * `03` - Return * `04` - Return Notification * `05` - Preliminary Return Notification * `06` - Final Return Notification | [optional] **DestinationRoutingNumber** | **string** | DestinationRoutingNumber contains the routing and transit number of the institution that receives and processes the cash letter or the bundle. | [optional] -**ECEInstitutionRoutingNumber** | **string** | ECEInstitutionRoutingNumber contains the routing and transit number of the institution that that creates the bundle header. | [optional] +**EceInstitutionRoutingNumber** | **string** | ECEInstitutionRoutingNumber contains the routing and transit number of the institution that that creates the bundle header. | [optional] **BundleBusinessDate** | [**time.Time**](time.Time.md) | BundleBusinessDate is the business date of the bundle. | [optional] **BundleCreationDate** | [**time.Time**](time.Time.md) | BundleCreationDate is the date that the bundle is created. | [optional] **BundleID** | **string** | BundleID is a number that identifies the bundle, assigned by the institution that creates the bundle. | [optional] diff --git a/client/docs/CashLetterControl.md b/client/docs/CashLetterControl.md index ef1e1a57..5a3313c4 100644 --- a/client/docs/CashLetterControl.md +++ b/client/docs/CashLetterControl.md @@ -9,7 +9,7 @@ Name | Type | Description | Notes **CashLetterItemsCount** | **int32** | CashLetterItemsCount identifies the total number of items within the cash letter. | **CashLetterTotalAmount** | **int32** | CashLetterTotalAmount identifies the total dollar value of all item amounts within the cash letter. | **CashLetterImagesCount** | **int32** | CashLetterImagesCount identifies the total number of ImageViewDetail(s) within the CashLetter. | [optional] -**ECEInstitutionName** | **string** | ECEInstitutionName identifies the short name of the institution that creates the CashLetterControl. | [optional] +**EceInstitutionName** | **string** | ECEInstitutionName identifies the short name of the institution that creates the CashLetterControl. | [optional] **SettlementDate** | [**time.Time**](time.Time.md) | SettlementDate identifies the date that the institution that creates the cash letter expects settlement. | **CreditTotalIndicator** | **int32** | CreditTotalIndicator is a code that indicates whether Credit Items are included in this record’s totals. If so, they will be included in TotalItemCount and FileTotalAmount. TotalRecordCount includes all records of all types regardless of the value of this field. * ` ` - No Credit Items * `0` - Credit Items are not included in totals * `1` - Credit Items are included in totals | [optional] diff --git a/client/docs/CashLetterHeader.md b/client/docs/CashLetterHeader.md index 4cf19e0a..6283a89c 100644 --- a/client/docs/CashLetterHeader.md +++ b/client/docs/CashLetterHeader.md @@ -7,7 +7,7 @@ Name | Type | Description | Notes **ID** | **string** | CashLetterHeader ID | [optional] **CollectionTypeIndicator** | **string** | CollectionTypeIndicator is a code that identifies the type of cash letter. * `00` - Preliminary Forward Information * `01` - Forward Presentment * `02` - Forward Presentment - Same-Day Settlement * `03` - Return * `04` - Return Notification * `05` - Preliminary Return Notification * `06` - Final Return Notification * `20` - No Detail * `99` - Bundles not the same collection type. Use of the value is only allowed by clearing arrangement. | [optional] **DestinationRoutingNumber** | **string** | DestinationRoutingNumber is the routing and transit number of the institution that receives and processes the cash letter or the bundle. | [optional] -**ECEInstitutionRoutingNumber** | **string** | ECEInstitutionRoutingNumber is the routing and transit number of the institution that creates the Cash Letter Header record. | [optional] +**EceInstitutionRoutingNumber** | **string** | ECEInstitutionRoutingNumber is the routing and transit number of the institution that creates the Cash Letter Header record. | [optional] **CashLetterBusinessDate** | [**time.Time**](time.Time.md) | cashLetterBusinessDate is the business date of the cash letter. | [optional] **CashLetterCreationDate** | [**time.Time**](time.Time.md) | cashLetterCreationDate is the date that the cash letter is created. | [optional] **CashLetterCreationTime** | [**time.Time**](time.Time.md) | CashLetterCreationTime is the time that the cash letter is created. | [optional] diff --git a/client/docs/Checks.md b/client/docs/Checks.md index 92074e61..75d99156 100644 --- a/client/docs/Checks.md +++ b/client/docs/Checks.md @@ -11,7 +11,7 @@ Name | Type | Description | Notes **PayorBankCheckDigit** | **string** | PayorBankCheckDigit identifies the routing number check digit. The combination of Payor Bank Routing Number and Payor Bank Routing Number Check Digit must be a mod-checked routing number with a valid check digit. | [optional] **OnUs** | **string** | OnUs identifies data specified by the payor bank. On-Us data usually consists of the payor’s account number, a serial number or transaction code, or both. | [optional] **ItemAmount** | **int32** | Amount identifies the amount of the check. All amounts fields have two implied decimal points. e.g., 100000 is $1,000.00. | [optional] -**ECEInstitutionItemSequenceNumber** | **string** | ECEInstitutionItemSequenceNumber identifies a number assigned by the institution that creates the Check. Field must contain a numeric value. It cannot be all blanks. | [optional] +**EceInstitutionItemSequenceNumber** | **string** | ECEInstitutionItemSequenceNumber identifies a number assigned by the institution that creates the Check. Field must contain a numeric value. It cannot be all blanks. | [optional] **DocumentationTypeIndicator** | **string** | DocumentationTypeIndicator identifies a code that indicates the type of documentation that supports the check record. This field is superseded by the Cash Letter Documentation Type Indicator in the Cash Letter Header Record for all Defined Values except ‘Z’ Not Same Type. In the case of Defined Value of ‘Z’, the Documentation Type Indicator in this record takes precedent. Shall be present when Cash Letter Documentation Type Indicator in the Cash Letter Header Record is Defined Value of ‘Z’. * `A` - No image provided, paper provided separately * `B` - No image provided, paper provided separately, image upon request * `C` - Image provided separately, no paper provided * `D` - Image provided separately, no paper provided, image upon request * `E` - Image and paper provided separately * `F` - Image and paper provided separately, image upon request * `G` - Image included, no paper provided * `H` - Image included, no paper provided, image upon request * `I` - Image included, paper provided separately * `J` - Image included, paper provided separately, image upon request * `K` - No image provided, no paper provided * `L` - No image provided, no paper provided, image upon request * `M` - No image provided, Electronic Check provided separately | [optional] **ReturnAcceptanceIndicator** | **string** | ReturnAcceptanceIndicator is a code that indicates whether the institution that creates the Check will or will not support electronic return processing. * `0` - Will not accept any electronic information * `1` - Will accept preliminary return notifications, returns, and final return notifications * `2` - Will accept preliminary return notifications and returns * `3` - Will accept preliminary return notifications and final return notifications * `4` - Will accept returns and final return notifications * `5` - Will accept preliminary return notifications only * `6` - Will accept returns only * `7` - Will accept final return notifications only * `8` - Will accept preliminary return notifications, returns, final return notifications, and image returns * `9` - Will accept preliminary return notifications, returns and image returns * `A` - Will accept preliminary return notifications, final return notifications and image returns * `B` - Will accept returns, final return notifications and image returns * `C` - Will accept preliminary return notifications and image returns * `D` - Will accept returns and image returns * `E` - Will accept final return notifications and image returns * `F` - Will accept image returns only | [optional] **MICRValidIndicator** | **int32** | MICRValidIndicator is a code that indicates whether any character in the Magnetic Ink Character Recognition (MICR) property is unreadable, or the OnUs property is missing from the Check. * `1` - Good read * `2` - Good read, missing field * `3` - Read error encountered * `4` - Missing field and read error encountered | [optional] diff --git a/client/docs/ImageCashLetterFilesApi.md b/client/docs/ImageCashLetterFilesApi.md index 2abdedca..1b34746a 100644 --- a/client/docs/ImageCashLetterFilesApi.md +++ b/client/docs/ImageCashLetterFilesApi.md @@ -6,6 +6,7 @@ Method | HTTP request | Description ------------- | ------------- | ------------- [**AddICLToFile**](ImageCashLetterFilesApi.md#AddICLToFile) | **Post** /files/{fileID}/cashLetters | Add cash letter to file [**CreateICLFile**](ImageCashLetterFilesApi.md#CreateICLFile) | **Post** /files/create | Create file +[**CreateICLFileV2**](ImageCashLetterFilesApi.md#CreateICLFileV2) | **Post** /v2/files | Create file [**DeleteICLFile**](ImageCashLetterFilesApi.md#DeleteICLFile) | **Delete** /files/{fileID} | Delete file [**DeleteICLFromFile**](ImageCashLetterFilesApi.md#DeleteICLFromFile) | **Delete** /files/{fileID}/cashLetters/{cashLetterID} | Delete cash letter from file [**GetICLFileByID**](ImageCashLetterFilesApi.md#GetICLFileByID) | **Get** /files/{fileID} | Retrieve file @@ -105,6 +106,38 @@ No authorization required [[Back to README]](../README.md) +## CreateICLFileV2 + +> IclFile CreateICLFileV2(ctx, createIclFile) + +Create file + +### Required Parameters + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- +**ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. +**createIclFile** | [**CreateIclFile**](CreateIclFile.md)| Content of the ImageCashLetter file in JSON, or X9 (ASCII or EBCDIC) format. Use the `Accept` header to specify the response format. | + +### Return type + +[**IclFile**](ICLFile.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: application/json, multipart/form-data +- **Accept**: application/json, application/octet-stream, text/plain + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) +[[Back to Model list]](../README.md#documentation-for-models) +[[Back to README]](../README.md) + + ## DeleteICLFile > DeleteICLFile(ctx, fileID, optional) diff --git a/client/docs/ImageViewData.md b/client/docs/ImageViewData.md index 1d0cb787..42d7b686 100644 --- a/client/docs/ImageViewData.md +++ b/client/docs/ImageViewData.md @@ -5,7 +5,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **ID** | **string** | ImageViewData ID | [optional] -**ECEInstitutionRoutingNumber** | **string** | ECEInstitutionRoutingNumber contains the routing and transit number of the institution that creates the bundle header. This number is imported from the Bundle Header Record (Clause 9.4) associated with the image view conveyed in this Image View Data Property. | [optional] +**EceInstitutionRoutingNumber** | **string** | ECEInstitutionRoutingNumber contains the routing and transit number of the institution that creates the bundle header. This number is imported from the Bundle Header Record (Clause 9.4) associated with the image view conveyed in this Image View Data Property. | [optional] **BundleBusinessDate** | [**time.Time**](time.Time.md) | BundleBusinessDate is the business date of the bundle. | [optional] **CycleNumber** | **string** | CycleNumber is a code assigned by the institution that creates the bundle. Denotes the cycle under which the bundle is created. | [optional] **IvDataECEInstitutionItemSequenceNumber** | **string** | ECEInstitutionItemSequenceNumber is a number assigned by the institution that creates the Check or Return. This number is imported from the Check.ECEInstitutionItemSequenceNumber or Return.ECEInstitutionItemSequenceNumber associated with the image view conveyed in this Image View Data Record. The ECE institution must construct the sequence number to guarantee uniqueness for a given routing number, business day, and cycle number. Must contain a numeric value. | [optional] diff --git a/client/docs/Returns.md b/client/docs/Returns.md index db623c0e..1851c4aa 100644 --- a/client/docs/Returns.md +++ b/client/docs/Returns.md @@ -13,7 +13,7 @@ Name | Type | Description | Notes **AddendumCount** | **int32** | AddendumCount is a number of Check Detail Record Addenda to follow. This represents the number of CheckDetailAddendumA, CheckDetailAddendumB, and CheckDetailAddendumC types. It matches the total number of addendum records associated with this item. The standard supports up to 99 addendum records. | [optional] **DocumentationTypeIndicator** | **string** | DocumentationTypeIndicator identifies a code that indicates the type of documentation that supports the check record. This field is superseded by the Cash Letter Documentation Type Indicator in the Cash Letter Header Record for all Defined Values except ‘Z’ Not Same Type. In the case of Defined Value of ‘Z’, the Documentation Type Indicator in this record takes precedent. Shall be present when Cash Letter Documentation Type Indicator in the Cash Letter Header Record is Defined Value of ‘Z’. * `A` - No image provided, paper provided separately * `B` - No image provided, paper provided separately, image upon request * `C` - Image provided separately, no paper provided * `D` - Image provided separately, no paper provided, image upon request * `E` - Image and paper provided separately * `F` - Image and paper provided separately, image upon request * `G` - Image included, no paper provided * `H` - Image included, no paper provided, image upon request * `I` - Image included, paper provided separately * `J` - Image included, paper provided separately, image upon request * `K` - No image provided, no paper provided * `L` - No image provided, no paper provided, image upon request * `M` - No image provided, Electronic Check provided separately | [optional] **ForwardBundleDate** | [**time.Time**](time.Time.md) | ForwardBundleDate represents for electronic check exchange items, the year, month, and day that designate the business date of the original forward bundle. This data is transferred from the BundleHeader BundleBusinessDate. For items presented in paper cash letters, the year, month, and day that the cash letter was created. | [optional] -**ECEInstitutionItemSequenceNumber** | **string** | ECEInstitutionItemSequenceNumber identifies a number assigned by the institution that creates the CheckDetail. Field must contain a numeric value. It cannot be all blanks. | [optional] +**EceInstitutionItemSequenceNumber** | **string** | ECEInstitutionItemSequenceNumber identifies a number assigned by the institution that creates the CheckDetail. Field must contain a numeric value. It cannot be all blanks. | [optional] **ExternalProcessingCode** | **string** | ExternalProcessingCode identifies a code used for special purposes as authorized by the Accredited Standards Committee X9. Also known as Position 44. | [optional] **ReturnNotificationIndicator** | **string** | ReturnNotificationIndicator is a code that identifies the type of notification. The CashLetterHeader.CollectionTypeIndicator and BundleHeader.CollectionTypeIndicator equalling `05` or `06` takes precedence over this field. * `1` - Preliminary notification * `2` - Final notification | [optional] **ArchiveTypeIndicator** | **string** | ArchiveTypeIndicator is a code that indicates the type of archive that supports this Check. Access method, availability, and time frames shall be defined by clearing arrangements. * `A` - Microfilm * `B` - Image * `C` - Paper * `D` - Microfilm and image * `E` - Microfilm and paper * `F` - Image and paper * `G` - Microfilm, image, and paper * `H` - Electronic Check Instrument * `I` - None | [optional] diff --git a/client/model_bundle_header.go b/client/model_bundle_header.go index a1976460..c680a1f2 100644 --- a/client/model_bundle_header.go +++ b/client/model_bundle_header.go @@ -22,7 +22,7 @@ type BundleHeader struct { // DestinationRoutingNumber contains the routing and transit number of the institution that receives and processes the cash letter or the bundle. DestinationRoutingNumber string `json:"destinationRoutingNumber,omitempty"` // ECEInstitutionRoutingNumber contains the routing and transit number of the institution that that creates the bundle header. - ECEInstitutionRoutingNumber string `json:"eceInstitutionRoutingNumber,omitempty"` + EceInstitutionRoutingNumber string `json:"eceInstitutionRoutingNumber,omitempty"` // BundleBusinessDate is the business date of the bundle. BundleBusinessDate time.Time `json:"bundleBusinessDate,omitempty"` // BundleCreationDate is the date that the bundle is created. diff --git a/client/model_cash_letter_control.go b/client/model_cash_letter_control.go index 7d875c60..7ab240f3 100644 --- a/client/model_cash_letter_control.go +++ b/client/model_cash_letter_control.go @@ -26,7 +26,7 @@ type CashLetterControl struct { // CashLetterImagesCount identifies the total number of ImageViewDetail(s) within the CashLetter. CashLetterImagesCount int32 `json:"cashLetterImagesCount,omitempty"` // ECEInstitutionName identifies the short name of the institution that creates the CashLetterControl. - ECEInstitutionName string `json:"eceInstitutionName,omitempty"` + EceInstitutionName string `json:"eceInstitutionName,omitempty"` // SettlementDate identifies the date that the institution that creates the cash letter expects settlement. SettlementDate time.Time `json:"settlementDate"` // CreditTotalIndicator is a code that indicates whether Credit Items are included in this record’s totals. If so, they will be included in TotalItemCount and FileTotalAmount. TotalRecordCount includes all records of all types regardless of the value of this field. * ` ` - No Credit Items * `0` - Credit Items are not included in totals * `1` - Credit Items are included in totals diff --git a/client/model_cash_letter_header.go b/client/model_cash_letter_header.go index 707d516d..3f48f23c 100644 --- a/client/model_cash_letter_header.go +++ b/client/model_cash_letter_header.go @@ -22,7 +22,7 @@ type CashLetterHeader struct { // DestinationRoutingNumber is the routing and transit number of the institution that receives and processes the cash letter or the bundle. DestinationRoutingNumber string `json:"destinationRoutingNumber,omitempty"` // ECEInstitutionRoutingNumber is the routing and transit number of the institution that creates the Cash Letter Header record. - ECEInstitutionRoutingNumber string `json:"eceInstitutionRoutingNumber,omitempty"` + EceInstitutionRoutingNumber string `json:"eceInstitutionRoutingNumber,omitempty"` // cashLetterBusinessDate is the business date of the cash letter. CashLetterBusinessDate time.Time `json:"cashLetterBusinessDate,omitempty"` // cashLetterCreationDate is the date that the cash letter is created. diff --git a/client/model_checks.go b/client/model_checks.go index 8453718e..409c4d62 100644 --- a/client/model_checks.go +++ b/client/model_checks.go @@ -26,7 +26,7 @@ type Checks struct { // Amount identifies the amount of the check. All amounts fields have two implied decimal points. e.g., 100000 is $1,000.00. ItemAmount int32 `json:"itemAmount,omitempty"` // ECEInstitutionItemSequenceNumber identifies a number assigned by the institution that creates the Check. Field must contain a numeric value. It cannot be all blanks. - ECEInstitutionItemSequenceNumber string `json:"eceInstitutionItemSequenceNumber,omitempty"` + EceInstitutionItemSequenceNumber string `json:"eceInstitutionItemSequenceNumber,omitempty"` // DocumentationTypeIndicator identifies a code that indicates the type of documentation that supports the check record. This field is superseded by the Cash Letter Documentation Type Indicator in the Cash Letter Header Record for all Defined Values except ‘Z’ Not Same Type. In the case of Defined Value of ‘Z’, the Documentation Type Indicator in this record takes precedent. Shall be present when Cash Letter Documentation Type Indicator in the Cash Letter Header Record is Defined Value of ‘Z’. * `A` - No image provided, paper provided separately * `B` - No image provided, paper provided separately, image upon request * `C` - Image provided separately, no paper provided * `D` - Image provided separately, no paper provided, image upon request * `E` - Image and paper provided separately * `F` - Image and paper provided separately, image upon request * `G` - Image included, no paper provided * `H` - Image included, no paper provided, image upon request * `I` - Image included, paper provided separately * `J` - Image included, paper provided separately, image upon request * `K` - No image provided, no paper provided * `L` - No image provided, no paper provided, image upon request * `M` - No image provided, Electronic Check provided separately DocumentationTypeIndicator string `json:"documentationTypeIndicator,omitempty"` // ReturnAcceptanceIndicator is a code that indicates whether the institution that creates the Check will or will not support electronic return processing. * `0` - Will not accept any electronic information * `1` - Will accept preliminary return notifications, returns, and final return notifications * `2` - Will accept preliminary return notifications and returns * `3` - Will accept preliminary return notifications and final return notifications * `4` - Will accept returns and final return notifications * `5` - Will accept preliminary return notifications only * `6` - Will accept returns only * `7` - Will accept final return notifications only * `8` - Will accept preliminary return notifications, returns, final return notifications, and image returns * `9` - Will accept preliminary return notifications, returns and image returns * `A` - Will accept preliminary return notifications, final return notifications and image returns * `B` - Will accept returns, final return notifications and image returns * `C` - Will accept preliminary return notifications and image returns * `D` - Will accept returns and image returns * `E` - Will accept final return notifications and image returns * `F` - Will accept image returns only diff --git a/client/model_image_view_data.go b/client/model_image_view_data.go index ee7c0077..5fceb6f1 100644 --- a/client/model_image_view_data.go +++ b/client/model_image_view_data.go @@ -18,7 +18,7 @@ type ImageViewData struct { // ImageViewData ID ID string `json:"ID,omitempty"` // ECEInstitutionRoutingNumber contains the routing and transit number of the institution that creates the bundle header. This number is imported from the Bundle Header Record (Clause 9.4) associated with the image view conveyed in this Image View Data Property. - ECEInstitutionRoutingNumber string `json:"eceInstitutionRoutingNumber,omitempty"` + EceInstitutionRoutingNumber string `json:"eceInstitutionRoutingNumber,omitempty"` // BundleBusinessDate is the business date of the bundle. BundleBusinessDate time.Time `json:"bundleBusinessDate,omitempty"` // CycleNumber is a code assigned by the institution that creates the bundle. Denotes the cycle under which the bundle is created. diff --git a/client/model_returns.go b/client/model_returns.go index ab1ad4de..3eb59fa1 100644 --- a/client/model_returns.go +++ b/client/model_returns.go @@ -34,7 +34,7 @@ type Returns struct { // ForwardBundleDate represents for electronic check exchange items, the year, month, and day that designate the business date of the original forward bundle. This data is transferred from the BundleHeader BundleBusinessDate. For items presented in paper cash letters, the year, month, and day that the cash letter was created. ForwardBundleDate time.Time `json:"forwardBundleDate,omitempty"` // ECEInstitutionItemSequenceNumber identifies a number assigned by the institution that creates the CheckDetail. Field must contain a numeric value. It cannot be all blanks. - ECEInstitutionItemSequenceNumber string `json:"eceInstitutionItemSequenceNumber,omitempty"` + EceInstitutionItemSequenceNumber string `json:"eceInstitutionItemSequenceNumber,omitempty"` // ExternalProcessingCode identifies a code used for special purposes as authorized by the Accredited Standards Committee X9. Also known as Position 44. ExternalProcessingCode string `json:"externalProcessingCode,omitempty"` // ReturnNotificationIndicator is a code that identifies the type of notification. The CashLetterHeader.CollectionTypeIndicator and BundleHeader.CollectionTypeIndicator equalling `05` or `06` takes precedence over this field. * `1` - Preliminary notification * `2` - Final notification diff --git a/makefile b/makefile index 5a9ebf15..57c135dd 100644 --- a/makefile +++ b/makefile @@ -102,3 +102,8 @@ AUTHORS: .PHONY: tagged-release tagged-release: @./tagged-release.sh $(VERSION) + +.PHONY: preview-openapi +preview-openapi: + @docker run --rm -p 8080:8080 -e SWAGGER_JSON=/openapi.yaml -v $(shell pwd)/openapi.yaml:/openapi.yaml swaggerapi/swagger-ui + \ No newline at end of file diff --git a/openapi.yaml b/openapi.yaml index 577e2cd9..fdceaa57 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -331,6 +331,59 @@ paths: description: CashLetter deleted '404': description: CashLetter or File not found + /v2/files: + post: + tags: ['Image Cash Letter Files'] + summary: Create file + operationId: createICLFileV2 + requestBody: + description: | + Content of the ImageCashLetter file in JSON, or X9 (ASCII or EBCDIC) format. + Use the `Accept` header to specify the response format. + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateICLFile' + multipart/form-data: + schema: + type: object + properties: + file: + description: | + The file to upload. Set this section's `Content-Type` to `text/plain` for + ASCII-encoded files, or `application/octet-stream` for binary files. + type: string + format: binary + responses: + '201': + description: The file was created successfully + headers: + Location: + description: The location of the new resource + required: true + schema: + type: string + format: url + content: + application/json: + schema: + $ref: '#/components/schemas/ICLFile' + application/octet-stream: + schema: + type: string + format: binary + text/plain: + schema: + type: string + format: binary + '400': + description: The request was invalid + content: + application/json: + schema: + $ref: 'https://raw.githubusercontent.com/moov-io/base/master/api/common.yaml#/components/schemas/Error' + components: schemas: From 6a34057722aa59b9e0ac53d335d1da3c7c1f2d65 Mon Sep 17 00:00:00 2001 From: Daniel Tonks Date: Sat, 17 Feb 2024 22:21:14 -0700 Subject: [PATCH 4/7] add more tests --- internal/files/v2/files_test.go | 78 +++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/internal/files/v2/files_test.go b/internal/files/v2/files_test.go index e69fbc8b..c7069561 100644 --- a/internal/files/v2/files_test.go +++ b/internal/files/v2/files_test.go @@ -10,6 +10,7 @@ import ( "net/textproto" "os" "path/filepath" + "strings" "testing" "github.com/gorilla/mux" @@ -21,13 +22,62 @@ import ( "github.com/stretchr/testify/require" ) +func TestController_createFile_errors(t *testing.T) { + router := newRouter(t) + + t.Run("unsupported content type", func(t *testing.T) { + rdr := strings.NewReader("real file") + resp, apiErr := createFile(t, router, rdr, "application/msword", "application/json") + require.Equal(t, http.StatusBadRequest, resp.Code) + require.Contains(t, apiErr.Error, "unsupported Content-Type") + }) + + t.Run("invalid json file", func(t *testing.T) { + rdr := strings.NewReader("real file") + resp, apiErr := createFile(t, router, rdr, "application/json", "application/json") + require.Equal(t, http.StatusBadRequest, resp.Code) + require.Contains(t, apiErr.Error, "problem reading file") + }) + + t.Run("invalid ascii file", func(t *testing.T) { + rdr := strings.NewReader("real file") + resp, apiErr := uploadFile(t, router, rdr, "text/plain", "application/json") + require.Equal(t, http.StatusBadRequest, resp.Code) + require.Contains(t, apiErr.Error, "parsing file") + }) + + t.Run("invalid ebcdic file", func(t *testing.T) { + rdr := strings.NewReader("real file") + resp, apiErr := uploadFile(t, router, rdr, "application/octet-stream", "application/json") + require.Equal(t, http.StatusBadRequest, resp.Code) + require.Contains(t, apiErr.Error, "parsing file") + }) +} + func TestController_createJSONFile(t *testing.T) { router := newRouter(t) t.Run("returns JSON", func(t *testing.T) { rdr := getTestData(t, "icl-valid.json") - resp, apiErr := createFile(t, router, rdr, "application/json") + resp, apiErr := createFile(t, router, rdr, "application/json", "application/json") + require.Empty(t, apiErr) + + var created imagecashletter.File + require.NoError(t, json.NewDecoder(resp.Body).Decode(&created)) + + require.Contains(t, resp.Header().Get("Location"), created.ID) + require.NotEmpty(t, created.ID) + require.NotEmpty(t, created) + require.Equal(t, "231380104", created.Header.ImmediateDestination) + require.Len(t, created.CashLetters, 2) + require.Equal(t, 400000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount) + }) + + t.Run("invalid accept header returns JSON", func(t *testing.T) { + rdr := getTestData(t, "icl-valid.json") + + resp, apiErr := createFile(t, router, rdr, "application/json", "foo/bar") require.Empty(t, apiErr) var created imagecashletter.File @@ -44,7 +94,7 @@ func TestController_createJSONFile(t *testing.T) { t.Run("returns EBCDIC file", func(t *testing.T) { rdr := getTestData(t, "icl-valid.json") - resp, apiErr := createFile(t, router, rdr, "application/octet-stream") + resp, apiErr := createFile(t, router, rdr, "application/json", "application/octet-stream") require.Empty(t, apiErr) opts := []imagecashletter.ReaderOption{ @@ -61,6 +111,26 @@ func TestController_createJSONFile(t *testing.T) { require.Len(t, created.CashLetters, 2) require.Equal(t, 400000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount) }) + + t.Run("returns ASCII file", func(t *testing.T) { + rdr := getTestData(t, "icl-valid.json") + + resp, apiErr := createFile(t, router, rdr, "application/json", "text/plain") + require.Empty(t, apiErr) + + opts := []imagecashletter.ReaderOption{ + imagecashletter.ReadVariableLineLengthOption(), + } + created, err := imagecashletter.NewReader(resp.Body, opts...).Read() + require.NoError(t, err) + + require.Contains(t, resp.Header().Get("Content-Type"), "text/plain") + require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9") + require.NotEmpty(t, created) + require.Equal(t, "231380104", created.Header.ImmediateDestination) + require.Len(t, created.CashLetters, 2) + require.Equal(t, 400000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount) + }) } func TestController_uploadEBCDICFile(t *testing.T) { @@ -177,12 +247,12 @@ func TestController_uploadASCIIFile(t *testing.T) { }) } -func createFile(t *testing.T, router *mux.Router, body io.Reader, accept string) (*httptest.ResponseRecorder, openapi.Error) { +func createFile(t *testing.T, router *mux.Router, body io.Reader, contentType string, accept string) (*httptest.ResponseRecorder, openapi.Error) { req, err := http.NewRequest(http.MethodPost, "/v2/files", body) require.NoError(t, err) + req.Header.Set("Content-Type", contentType) req.Header.Set("Accept", accept) - req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) From 168eda34de95325c4276386346beb962265a860c Mon Sep 17 00:00:00 2001 From: Daniel Tonks Date: Sat, 17 Feb 2024 22:36:17 -0700 Subject: [PATCH 5/7] collapse generated code files --- .gitattributes | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..79b654dd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# This is for preventing generated code from showing up in GitHub diffs +# https://docs.github.com/en/github/administering-a-repository/customizing-how-changed-files-appear-on-github +/client/** linguist-generated \ No newline at end of file From 3bf9c64c3643e81daf20b1e501724fc62891b481 Mon Sep 17 00:00:00 2001 From: Adam Shannon Date: Mon, 19 Feb 2024 13:16:00 -0600 Subject: [PATCH 6/7] build: fix OpenShift image creation --- Dockerfile.openshift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile.openshift b/Dockerfile.openshift index 53f396c6..cdfddf01 100644 --- a/Dockerfile.openshift +++ b/Dockerfile.openshift @@ -2,7 +2,9 @@ FROM registry.access.redhat.com/ubi9/go-toolset as builder COPY go.mod go.mod COPY go.sum go.sum COPY *.go ./ +COPY ./client ./client COPY ./cmd/server ./cmd/server +COPY ./internal ./internal COPY makefile makefile RUN make build-server From 8fc9513f96ce97888dc97c0b586864cc65e3e6e4 Mon Sep 17 00:00:00 2001 From: Daniel Tonks Date: Mon, 19 Feb 2024 12:33:12 -0700 Subject: [PATCH 7/7] return old API endpoint in Location header --- internal/files/v2/files.go | 4 +++- internal/files/v2/files_test.go | 19 ++++++++++++++----- internal/responder/responder.go | 8 +++++--- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/internal/files/v2/files.go b/internal/files/v2/files.go index bd7efd7f..d9f4e271 100644 --- a/internal/files/v2/files.go +++ b/internal/files/v2/files.go @@ -80,7 +80,9 @@ func (c Controller) createFile(w http.ResponseWriter, r *http.Request) { return } - respond = respond.WithLocation(created.ID) + // TODO: Update to the v2 API endpoint once the GET file endpoint is implemented + location := fmt.Sprintf("%s://%s/files/%s", r.URL.Scheme, r.URL.Host, created.ID) + respond = respond.WithLocation(location) if expectingFile(r) { respond.File(http.StatusCreated, *created, fmt.Sprintf("%s.x9", created.ID)) return diff --git a/internal/files/v2/files_test.go b/internal/files/v2/files_test.go index c7069561..fcac4a86 100644 --- a/internal/files/v2/files_test.go +++ b/internal/files/v2/files_test.go @@ -13,6 +13,7 @@ import ( "strings" "testing" + "github.com/google/uuid" "github.com/gorilla/mux" "github.com/moov-io/base/log" "github.com/moov-io/imagecashletter" @@ -66,8 +67,8 @@ func TestController_createJSONFile(t *testing.T) { var created imagecashletter.File require.NoError(t, json.NewDecoder(resp.Body).Decode(&created)) - require.Contains(t, resp.Header().Get("Location"), created.ID) require.NotEmpty(t, created.ID) + require.Equal(t, "https://some.domain.io/files/"+created.ID, resp.Header().Get("Location")) require.NotEmpty(t, created) require.Equal(t, "231380104", created.Header.ImmediateDestination) require.Len(t, created.CashLetters, 2) @@ -199,11 +200,19 @@ func TestController_uploadASCIIFile(t *testing.T) { resp, apiErr := uploadFile(t, router, rdr, "text/plain", "text/plain") require.Empty(t, apiErr) + // inspect headers + require.Contains(t, resp.Header().Get("Content-Type"), "text/plain") + require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9") + location := resp.Header().Get("Location") + require.True(t, strings.HasPrefix(location, "https://some.domain.io/files/")) + resourceID := strings.TrimPrefix(location, "https://some.domain.io/files/") + require.NotEmpty(t, resourceID) + _, err := uuid.Parse(resourceID) + require.NoError(t, err) + // now read back in without EBCDIC option created, err := imagecashletter.NewReader(resp.Body, imagecashletter.ReadVariableLineLengthOption()).Read() require.NoError(t, err) - require.Contains(t, resp.Header().Get("Content-Type"), "text/plain") - require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9") require.NotEmpty(t, created) require.Equal(t, "061000146", created.Header.ImmediateDestination) require.Len(t, created.CashLetters, 1) @@ -248,7 +257,7 @@ func TestController_uploadASCIIFile(t *testing.T) { } func createFile(t *testing.T, router *mux.Router, body io.Reader, contentType string, accept string) (*httptest.ResponseRecorder, openapi.Error) { - req, err := http.NewRequest(http.MethodPost, "/v2/files", body) + req, err := http.NewRequest(http.MethodPost, "https://some.domain.io/v2/files", body) require.NoError(t, err) req.Header.Set("Content-Type", contentType) @@ -285,7 +294,7 @@ func uploadFile(t *testing.T, router *mux.Router, file io.Reader, contentType, a require.NoError(t, err) require.NoError(t, mw.Close()) - req, err := http.NewRequest(http.MethodPost, "/v2/files", body) + req, err := http.NewRequest(http.MethodPost, "https://some.domain.io/v2/files", body) require.NoError(t, err) req.Header.Set("Content-Type", mw.FormDataContentType()) req.Header.Set("Accept", accept) diff --git a/internal/responder/responder.go b/internal/responder/responder.go index 75c520de..54190522 100644 --- a/internal/responder/responder.go +++ b/internal/responder/responder.go @@ -35,9 +35,11 @@ func NewResponder(logger log.Logger, w http.ResponseWriter, r *http.Request) *Re } } -// WithLocation sets the Location header on the response. -func (r *Responder) WithLocation(resourceID string) *Responder { - r.optionalHeaders.location = r.r.URL.String() + "/" + resourceID +// WithLocation sets the Location header on the response. The Location header should be used for +// HTTP 201 responses to indicate the location of the newly created resource. While both absolute +// and relative URIs are allowed, the absolute URI is preferred. +func (r *Responder) WithLocation(location string) *Responder { + r.optionalHeaders.location = location return r }