@@ -2,8 +2,21 @@ package main
22
33import (
44 "context"
5+ "encoding/csv"
56 "flag"
67 "fmt"
8+ "io"
9+ "os"
10+
11+ "github.com/DIMO-Network/device-definitions-api/internal/core/common"
12+ coremodels "github.com/DIMO-Network/device-definitions-api/internal/core/models"
13+ "github.com/DIMO-Network/device-definitions-api/internal/core/services"
14+ "github.com/DIMO-Network/device-definitions-api/internal/infrastructure/db/models"
15+ "github.com/DIMO-Network/shared/pkg/db"
16+ vinutil "github.com/DIMO-Network/shared/pkg/vin"
17+ "github.com/ethereum/go-ethereum/ethclient"
18+ "github.com/volatiletech/null/v8"
19+ "github.com/volatiletech/sqlboiler/v4/boil"
720
821 "github.com/goccy/go-json"
922
@@ -17,86 +30,233 @@ type decodeVINCmd struct {
1730 logger * zerolog.Logger
1831 settings * config.Settings
1932
20- datGroup bool
21- drivly bool
22- vincario bool
23- japan17vin bool
33+ datGroup bool
34+ drivly bool
35+ vincario bool
36+ japan17vin bool
37+ fromFile bool
38+ persistToDB bool
2439}
2540
2641func (* decodeVINCmd ) Name () string { return "decodevin" }
2742func (* decodeVINCmd ) Synopsis () string {
2843 return "tries decoding a vin with chosen provider - does not insert in our db"
2944}
3045func (* decodeVINCmd ) Usage () string {
31- return `decodevin [-dat|-drivly|-vincario|-japan17vin] <vin 17 chars> <country two letter iso>`
46+ return `decodevin [-dat|-drivly|-vincario|-japan17vin|-from-file ] <vin 17 chars OR filaname in /tmp > <country two letter iso>`
3247}
3348
3449func (p * decodeVINCmd ) SetFlags (f * flag.FlagSet ) {
3550 f .BoolVar (& p .datGroup , "dat" , false , "use dat group vin decoder" )
3651 f .BoolVar (& p .drivly , "drivly" , false , "use drivly vin decoder" )
3752 f .BoolVar (& p .vincario , "vincario" , false , "use vincario vin decoder" )
3853 f .BoolVar (& p .japan17vin , "japan17vin" , false , "use japan17vin vin decoder" )
54+ f .BoolVar (& p .fromFile , "from-file" , false , "read vin from file in /tmp directory" )
55+ f .BoolVar (& p .persistToDB , "persist-to-db" , false , "persist successful vin decodings to db, table vin_numbers" )
3956}
4057
41- func (p * decodeVINCmd ) Execute (_ context.Context , f * flag.FlagSet , _ ... interface {}) subcommands.ExitStatus {
58+ func (p * decodeVINCmd ) Execute (ctx context.Context , f * flag.FlagSet , _ ... interface {}) subcommands.ExitStatus {
4259 if len (f .Args ()) == 0 {
43- fmt .Println ("missing vin parameter" )
60+ if p .fromFile {
61+ fmt .Println ("missing filename parameter" )
62+ } else {
63+ fmt .Println ("missing vin parameter" )
64+ }
4465 return subcommands .ExitUsageError
4566 }
46- vin := f .Args ()[0 ]
67+ vinOrFile := f .Args ()[0 ]
68+
69+ pdb := db .NewDbConnectionFromSettings (context .Background (), & p .settings .DB , true )
70+ pdb .WaitForDB (* p .logger )
4771
4872 country := "USA"
4973 if len (f .Args ()) == 2 {
5074 country = f .Args ()[1 ]
5175 }
52- fmt .Printf ("VIN: %s\n " , vin )
76+ vins := []string {}
77+ if p .fromFile {
78+ fmt .Printf ("Filename: %s\n " , vinOrFile )
79+ vins = loadVINsFromFile (vinOrFile )
80+ } else {
81+ fmt .Printf ("VIN: %s\n " , vinOrFile )
82+ vins = append (vins , vinOrFile )
83+ }
5384 fmt .Printf ("Country: %s\n " , country )
5485
55- if p .datGroup {
56- // use the dat group service to decode
57- datAPI := gateways .NewDATGroupAPIService (p .settings , p .logger )
58- vinInfo , err := datAPI .GetVINv2 (vin , country )
86+ fmt .Printf ("total VINs found: %d\n " , len (vins ))
87+ if len (vins ) == 0 {
88+ fmt .Println ("no vins found" )
89+ return subcommands .ExitFailure
90+ }
91+
92+ vinDecodingService := instantiateVINDecodingSvc (ctx , p .settings , p .logger , pdb )
93+
94+ for _ , vin := range vins {
95+ // in case want to insert
96+ vinObj := vinutil .VIN (vin )
97+ dbVin := & models.VinNumber {
98+ Vin : vin ,
99+ Wmi : null .StringFrom (vinObj .Wmi ()),
100+ VDS : null .StringFrom (vinObj .VDS ()),
101+ SerialNumber : vinObj .SerialNumber (),
102+ CheckDigit : null .StringFrom (vinObj .CheckDigit ()),
103+ Vis : null .StringFrom (vinObj .VIS ()),
104+ }
105+ wmi , _ := models .Wmis (models .WmiWhere .Wmi .EQ (vinObj .Wmi ())).One (ctx , pdb .DBS ().Reader )
106+ if wmi != nil {
107+ dbVin .ManufacturerName = wmi .ManufacturerName
108+ }
109+ dt , err := models .DeviceTypes (models .DeviceTypeWhere .ID .EQ (common .DefaultDeviceType )).One (ctx , pdb .DBS ().Reader )
59110 if err != nil {
60111 fmt .Println (err .Error ())
61112 return subcommands .ExitFailure
62113 }
114+ vinInfo := & coremodels.VINDecodingInfoData {VIN : vin }
63115
64- fmt .Printf ("\n \n VIN Response: %+v\n " , * vinInfo )
65- }
66- if p .drivly {
67- drivlyAPI := gateways .NewDrivlyAPIService (p .settings )
68- vinInfo , err := drivlyAPI .GetVINInfo (vin )
69- if err != nil {
70- fmt .Println (err .Error ())
71- return subcommands .ExitFailure
116+ if p .datGroup {
117+ vinInfo , err = vinDecodingService .GetVIN (ctx , vin , dt , coremodels .DATGroupProvider , country )
118+ // use the dat group service to decode
119+ if err != nil {
120+ fmt .Println (err .Error ())
121+ }
122+
123+ fmt .Printf ("\n \n VIN Response: %+v\n " , vinInfo )
124+ }
125+ if p .drivly {
126+ vinInfo , err = vinDecodingService .GetVIN (ctx , vin , dt , coremodels .DrivlyProvider , country )
127+ if err != nil {
128+ fmt .Println (err .Error ())
129+ return subcommands .ExitFailure
130+ }
131+
132+ fmt .Printf ("VIN Response: %+v\n " , vinInfo )
72133 }
134+ if p .vincario {
135+ vinInfo , err = vinDecodingService .GetVIN (ctx , vin , dt , coremodels .VincarioProvider , country )
136+ if err != nil {
137+ fmt .Println (err .Error ())
138+ return subcommands .ExitFailure
139+ }
73140
74- fmt .Printf ("VIN Response: %+v\n " , vinInfo )
141+ fmt .Printf ("VIN Response: %+v\n " , vinInfo )
142+ }
143+ if p .japan17vin {
144+ vinInfo , err = vinDecodingService .GetVIN (ctx , vin , dt , coremodels .Japan17VIN , country )
145+ if err != nil {
146+ fmt .Println (err .Error ())
147+ return subcommands .ExitFailure
148+ }
149+ jsonBytes , _ := json .MarshalIndent (vinInfo , "" , " " )
150+ fmt .Println ("VIN Info:" )
151+ fmt .Println (string (jsonBytes ))
152+ }
153+ fmt .Println ()
154+ if p .persistToDB {
155+ if vinInfo == nil || vinInfo .Model == "" {
156+ fmt .Println ("no decoding info found, skipping: " + vin )
157+ continue
158+ }
159+ dbVin .Year = int (vinInfo .Year )
160+ if dbVin .ManufacturerName == "" {
161+ dbVin .ManufacturerName = vinInfo .Make
162+ }
163+ dbVin .DatgroupData = null .JSONFrom (vinInfo .Raw )
164+ dbVin .DefinitionID = common .DeviceDefinitionSlug (vinInfo .Make , vinInfo .Model , int16 (vinInfo .Year ))
165+ dbVin .DecodeProvider = null .StringFrom (string (vinInfo .Source ))
166+ // todo future change to add field with StyleName
167+
168+ err := dbVin .Insert (ctx , pdb .DBS ().Writer , boil .Infer ())
169+ if err != nil {
170+ fmt .Println (err .Error ())
171+ return subcommands .ExitFailure
172+ }
173+ }
75174 }
76- if p .vincario {
77- vincarioAPI := gateways .NewVincarioAPIService (p .settings , p .logger )
78- vinInfo , err := vincarioAPI .DecodeVIN (vin )
79- if err != nil {
80- fmt .Println (err .Error ())
81- return subcommands .ExitFailure
175+ return subcommands .ExitSuccess
176+ }
177+
178+ func loadVINsFromFile (file string ) []string {
179+ // files are assumed to be in the tmp directory
180+ // pull out vins from csv file from column "vin"
181+ vinFile := "/tmp/" + file
182+ vinFileContents , err := readVINFile (vinFile )
183+ if err != nil {
184+ fmt .Println (err .Error ())
185+ return []string {}
186+ }
187+ return vinFileContents
188+ }
189+
190+ func readVINFile (filename string ) ([]string , error ) {
191+ file , err := os .Open (filename )
192+ if err != nil {
193+ return nil , fmt .Errorf ("failed to open file: %w" , err )
194+ }
195+ defer file .Close ()
196+
197+ reader := csv .NewReader (file )
198+
199+ // Read header row to find the index of "csv" column
200+ header , err := reader .Read ()
201+ if err != nil {
202+ return nil , fmt .Errorf ("failed to read CSV header: %w" , err )
203+ }
204+
205+ csvColumnIndex := - 1
206+ for i , columnName := range header {
207+ if columnName == "csv" {
208+ csvColumnIndex = i
209+ break
82210 }
211+ }
83212
84- fmt .Printf ("VIN Response: %+v\n " , vinInfo )
213+ if csvColumnIndex == - 1 {
214+ csvColumnIndex = 0 // default to first column if "csv" column not found
215+ fmt .Println ("defaulting to first column as 'csv' column not found, please ensure your CSV file has a column named 'csv' with VINs in it." )
85216 }
86- if p .japan17vin {
87- jp17vinAPI := gateways .NewJapan17VINAPI (p .logger , p .settings )
88- vinInfo , payload , err := jp17vinAPI .GetVINInfo (vin )
217+
218+ // Read all rows and extract values from the "csv" column
219+ var values []string
220+ for {
221+ row , err := reader .Read ()
222+ if err == io .EOF {
223+ break
224+ }
89225 if err != nil {
90- fmt .Println (err .Error ())
91- return subcommands .ExitFailure
226+ return nil , fmt .Errorf ("failed to read CSV row: %w" , err )
227+ }
228+
229+ if csvColumnIndex < len (row ) {
230+ values = append (values , row [csvColumnIndex ])
92231 }
93- jsonBytes , _ := json .MarshalIndent (vinInfo , "" , " " )
94- fmt .Println ("VIN Info:" )
95- fmt .Println (string (jsonBytes ))
96- fmt .Println ("Raw JSON Payload:" )
97- fmt .Println (string (payload ))
98232 }
99233
100- fmt .Println ()
101- return subcommands .ExitSuccess
234+ return values , nil
235+ }
236+
237+ func instantiateVINDecodingSvc (ctx context.Context , settings * config.Settings , logger * zerolog.Logger , pdb db.Store ) services.VINDecodingService {
238+ datAPI := gateways .NewDATGroupAPIService (settings , logger )
239+ drivlyAPI := gateways .NewDrivlyAPIService (settings )
240+ vincarioAPI := gateways .NewVincarioAPIService (settings , logger )
241+ jp17vinAPI := gateways .NewJapan17VINAPI (logger , settings )
242+
243+ send , err := createSender (ctx , settings , logger )
244+ if err != nil {
245+ logger .Fatal ().Err (err ).Msg ("Failed to create sender." )
246+ }
247+
248+ ethClient , err := ethclient .Dial (settings .EthereumRPCURL .String ())
249+ if err != nil {
250+ logger .Fatal ().Err (err ).Msg ("Failed to create Ethereum client." )
251+ }
252+
253+ chainID , err := ethClient .ChainID (ctx )
254+ if err != nil {
255+ logger .Fatal ().Err (err ).Msg ("Couldn't retrieve chain id." )
256+ }
257+ deviceDefinitionOnChainService := gateways .NewDeviceDefinitionOnChainService (settings , logger , ethClient , chainID , send , pdb .DBS )
258+
259+ vinDecodingService := services .NewVINDecodingService (drivlyAPI , vincarioAPI , nil , logger , deviceDefinitionOnChainService , datAPI , pdb .DBS , jp17vinAPI )
260+
261+ return vinDecodingService
102262}
0 commit comments