├── VERSION ├── pkg ├── api │ ├── http │ │ ├── server_test.go │ │ ├── routes.go │ │ ├── error.go │ │ ├── server.go │ │ └── api.go │ └── grpc │ │ ├── interseptor.go │ │ ├── server.go │ │ ├── converting.go │ │ ├── error.go │ │ └── api.go ├── domain │ ├── service │ │ ├── utils.go │ │ ├── customer_service.go │ │ ├── internal_service.go │ │ ├── govin.go │ │ └── countries.go │ ├── model │ │ ├── error.go │ │ ├── manufacturer.go │ │ ├── testing.go │ │ └── types.go │ ├── command │ │ ├── command.go │ │ ├── decode_vin_internal.go │ │ └── decode_vin.go │ └── service.go ├── version │ └── version.go ├── config │ ├── duration.go │ └── config.go └── store │ └── sqlstore │ ├── sqlstore_test.go │ ├── testing.go │ ├── manufacturer_repository_test.go │ ├── manufacturer_repository.go │ └── store.go ├── migrations ├── 000001_initialize_schema.down.sql └── 000001_initialize_schema.up.sql ├── config ├── nats.conf └── config.yaml ├── .gitignore ├── Makefile ├── Dockerfile ├── docker-compose.yaml ├── README.md ├── LICENSE ├── go.mod ├── .github └── workflows │ ├── pull-request.yml │ └── master.yml ├── cmd └── server │ └── main.go ├── go.sum └── manufacturers.csv /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.10 -------------------------------------------------------------------------------- /pkg/api/http/server_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | -------------------------------------------------------------------------------- /pkg/domain/service/utils.go: -------------------------------------------------------------------------------- 1 | package service 2 | -------------------------------------------------------------------------------- /migrations/000001_initialize_schema.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE manufacturers; 2 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | var ( 4 | // Version holds the current version of OpenCars. 5 | Version = "dev" 6 | ) 7 | -------------------------------------------------------------------------------- /pkg/domain/model/error.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/opencars/seedwork" 4 | 5 | var ( 6 | ErrManufacturerNotFound = seedwork.NewError("manufacturer.not_found") 7 | ) 8 | -------------------------------------------------------------------------------- /pkg/domain/command/command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import "github.com/opencars/schema" 4 | 5 | var ( 6 | source = schema.Source{ 7 | Service: "vin-decoder", 8 | Version: "1.0", 9 | } 10 | ) 11 | -------------------------------------------------------------------------------- /migrations/000001_initialize_schema.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE manufacturers 2 | ( 3 | "wmi" VARCHAR NOT NULL, 4 | "name" TEXT NOT NULL 5 | ); 6 | 7 | CREATE UNIQUE INDEX manufacturers_wmi_idx ON manufacturers("wmi"); 8 | -------------------------------------------------------------------------------- /pkg/domain/model/manufacturer.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // Manufacturer represents entities from manufacturer table. 4 | type Manufacturer struct { 5 | WMI string `json:"wmi" db:"wmi"` 6 | Name string `json:"name" db:"name"` 7 | } 8 | -------------------------------------------------------------------------------- /config/nats.conf: -------------------------------------------------------------------------------- 1 | jetstream { 2 | store_dir: "/data/nats-server" 3 | 4 | max_memory_store: 107374182 5 | max_file_store: 5368709120 6 | } 7 | 8 | authorization: { 9 | users: [ 10 | { user: opencars, password: opencars }, 11 | ] 12 | } 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | data/ 15 | bin/* 16 | -------------------------------------------------------------------------------- /pkg/domain/model/testing.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "testing" 4 | 5 | // TestManufacturer returns valid manufacturer entity for testing. 6 | func TestManufacturer(t *testing.T) *Manufacturer { 7 | t.Helper() 8 | 9 | return &Manufacturer{ 10 | WMI: "5YJ", 11 | Name: "Tesla Inc.", 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/api/http/routes.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import "github.com/opencars/seedwork/httputil" 4 | 5 | func (s *server) configureRouter() { 6 | v1 := s.router.PathPrefix("/api/v1/").Subrouter() 7 | v1.Use( 8 | httputil.CustomerTokenMiddleware(), 9 | ) 10 | 11 | v1.Handle("/vin-decoder/{vin}", s.DecodeVIN()) 12 | } 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default all clean 2 | APPS := server 3 | BLDDIR := bin 4 | VERSION := $(shell cat VERSION) 5 | 6 | .EXPORT_ALL_VARIABLES: 7 | GO111MODULE = on 8 | 9 | default: clean all 10 | 11 | all: $(APPS) 12 | 13 | $(BLDDIR)/%: 14 | go build -o $@ ./cmd/$* 15 | 16 | $(APPS): %: $(BLDDIR)/% 17 | 18 | clean: 19 | @mkdir -p $(BLDDIR) 20 | @for app in $(APPS) ; do \ 21 | rm -f $(BLDDIR)/$$app ; \ 22 | done 23 | -------------------------------------------------------------------------------- /pkg/config/duration.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Duration represents custom type for unmarshalling string. 8 | // For example: "500ms", "1s", "2m", etc. 9 | type Duration struct { 10 | time.Duration 11 | } 12 | 13 | // UnmarshalText implements yaml unmarshaler. 14 | func (d *Duration) UnmarshalText(text []byte) error { 15 | var err error 16 | d.Duration, err = time.ParseDuration(string(text)) 17 | 18 | return err 19 | } 20 | -------------------------------------------------------------------------------- /config/config.yaml: -------------------------------------------------------------------------------- 1 | log: 2 | level: "debug" 3 | mode: "dev" 4 | 5 | server: 6 | shutdown_timeout: "15s" 7 | read_timeout: "10s" 8 | write_timeout: "10s" 9 | idle_timeout: "10s" 10 | 11 | database: 12 | host: "127.0.0.1" 13 | port: 5432 14 | username: "postgres" 15 | password: "password" 16 | database: "vin-decoder" 17 | ssl_mode: "disable" 18 | 19 | nats: 20 | nodes: 21 | - host: 0.0.0.0 22 | port: 4222 23 | user: "opencars" 24 | password: "opencars" -------------------------------------------------------------------------------- /pkg/store/sqlstore/sqlstore_test.go: -------------------------------------------------------------------------------- 1 | package sqlstore_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/opencars/vin-decoder-api/pkg/config" 8 | ) 9 | 10 | var ( 11 | conf *config.Database 12 | ) 13 | 14 | func TestMain(m *testing.M) { 15 | conf = &config.Database{ 16 | Host: os.Getenv("DATABASE_HOST"), 17 | Port: 5432, 18 | User: "postgres", 19 | Password: "password", 20 | Name: "vin-decoder", 21 | SSLMode: "disable", 22 | } 23 | 24 | if conf.Host == "" { 25 | conf.Host = "127.0.0.1" 26 | } 27 | 28 | code := m.Run() 29 | os.Exit(code) 30 | } 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-alpine AS build 2 | 3 | ENV GO111MODULE=on 4 | 5 | WORKDIR /go/src/app 6 | 7 | LABEL maintainer="ashanaakh@gmail.com" 8 | 9 | RUN apk add bash ca-certificates git gcc g++ libc-dev 10 | 11 | COPY go.mod go.sum ./ 12 | 13 | RUN go mod download 14 | 15 | COPY . . 16 | 17 | RUN export VERSION=$(cat VERSION) && \ 18 | go build -ldflags "-X github.com/opencars/vin-decoder-api/pkg/version.Version=$VERSION" -o /go/bin/server ./cmd/server/main.go 19 | 20 | FROM alpine 21 | 22 | RUN apk update && apk upgrade && apk add curl 23 | 24 | WORKDIR /app 25 | 26 | COPY --from=build /go/bin/ ./ 27 | 28 | EXPOSE 8080 3000 29 | CMD ["./server"] 30 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | 3 | services: 4 | postgres: 5 | image: postgres:12.0 6 | restart: on-failure 7 | environment: 8 | POSTGRES_USER: "postgres" 9 | POSTGRES_PASSWORD: "password" 10 | POSTGRES_DB: "vin-decoder" 11 | volumes: 12 | - postgres-data:/var/lib/postgresql/data 13 | ports: 14 | - "5432:5432" 15 | 16 | nats: 17 | image: nats:2.6.6 18 | ports: 19 | - "8222:8222" 20 | - "4222:4222" 21 | - "6222:6222" 22 | volumes: 23 | - ./config/nats.conf:/config/nats.conf 24 | - nats-data:/data/nats-server 25 | command: "-c config/nats.conf" 26 | 27 | volumes: 28 | postgres-data: {} 29 | nats-data: {} -------------------------------------------------------------------------------- /pkg/domain/service.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/opencars/vin-decoder-api/pkg/domain/command" 7 | "github.com/opencars/vin-decoder-api/pkg/domain/model" 8 | ) 9 | 10 | // ManufacturerRepository is responsible for interaction with manufacturers entities. 11 | type ManufacturerRepository interface { 12 | Create(manufacturer *model.Manufacturer) error 13 | FindByWMI(wmi string) (*model.Manufacturer, error) 14 | } 15 | 16 | type CustomerService interface { 17 | DecodeVIN(context.Context, *command.DecodeVIN) (*model.Result, error) 18 | } 19 | 20 | type InternalService interface { 21 | Decode(context.Context, *command.DecodeVINInternal) (*model.BulkResult, error) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/api/grpc/interseptor.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | 7 | "github.com/opencars/seedwork/logger" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | // RequestLoggingInterceptor write request body to logs. 12 | func RequestLoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 13 | log := logger.WithFields(logger.Fields{ 14 | "method": info.FullMethod, 15 | }) 16 | 17 | reqBody, err := json.Marshal(req) 18 | if err != nil { 19 | log.Errorf("failed to unmarshal request: %s", err) 20 | return nil, err 21 | } 22 | 23 | log.Debugf("start handling new request: %s", reqBody) 24 | 25 | return handler(ctx, req) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/store/sqlstore/testing.go: -------------------------------------------------------------------------------- 1 | package sqlstore 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | 8 | _ "github.com/lib/pq" 9 | 10 | "github.com/opencars/vin-decoder-api/pkg/config" 11 | ) 12 | 13 | // TestDB returns special test connection and teardown function. 14 | func TestDB(t *testing.T, conf *config.Database) (*Store, func(...string)) { 15 | t.Helper() 16 | 17 | store, err := New(conf) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | return store, func(tables ...string) { 23 | if len(tables) > 0 { 24 | _, err = store.db.Exec(fmt.Sprintf("TRUNCATE %s RESTART IDENTITY CASCADE", strings.Join(tables, ", "))) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | } 29 | 30 | if err := store.db.Close(); err != nil { 31 | t.Fatal(err) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkg/api/http/error.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/opencars/seedwork" 9 | "github.com/opencars/seedwork/httputil" 10 | "github.com/opencars/seedwork/logger" 11 | ) 12 | 13 | func handleErr(err error) error { 14 | logger.Errorf("%s", err) 15 | 16 | var e seedwork.Error 17 | if errors.As(err, &e) { 18 | return httputil.NewError(http.StatusBadRequest, e.Error()) 19 | } 20 | 21 | var vErr seedwork.ValidationError 22 | if errors.As(err, &vErr) { 23 | errMessage := make([]string, 0) 24 | for k, vv := range vErr.Messages { 25 | for _, v := range vv { 26 | errMessage = append(errMessage, fmt.Sprintf("%s.%s", k, v)) 27 | } 28 | } 29 | 30 | return httputil.NewError(http.StatusBadRequest, errMessage...) 31 | } 32 | 33 | return err 34 | } 35 | -------------------------------------------------------------------------------- /pkg/domain/command/decode_vin_internal.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "strings" 5 | 6 | validation "github.com/go-ozzo/ozzo-validation/v4" 7 | "github.com/opencars/seedwork" 8 | "github.com/opencars/vin-decoder-api/pkg/domain/model" 9 | ) 10 | 11 | type Item struct { 12 | VIN string 13 | } 14 | 15 | func (c *Item) Validate() error { 16 | return validation.ValidateStruct(c, 17 | validation.Field( 18 | &c.VIN, 19 | validation.Required.Error(seedwork.Required), 20 | validation.Match(model.IsVIN).Error(seedwork.Invalid), 21 | ), 22 | ) 23 | } 24 | 25 | type DecodeVINInternal struct { 26 | Items []Item 27 | } 28 | 29 | func (c *DecodeVINInternal) Prepare() { 30 | for i := range c.Items { 31 | c.Items[i].VIN = strings.ReplaceAll(strings.ToUpper(c.Items[i].VIN), "-", "") 32 | } 33 | } 34 | 35 | func (c *DecodeVINInternal) Validate() error { 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /pkg/store/sqlstore/manufacturer_repository_test.go: -------------------------------------------------------------------------------- 1 | package sqlstore_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/opencars/vin-decoder-api/pkg/domain/model" 9 | "github.com/opencars/vin-decoder-api/pkg/store/sqlstore" 10 | ) 11 | 12 | func TestManufacturerRepository_Create(t *testing.T) { 13 | s, teardown := sqlstore.TestDB(t, conf) 14 | defer teardown("manufacturers") 15 | 16 | manufacturer := model.TestManufacturer(t) 17 | assert.NoError(t, s.Manufacturer().Create(manufacturer)) 18 | } 19 | 20 | func TestManufacturerRepository_FindByWMI(t *testing.T) { 21 | s, teardown := sqlstore.TestDB(t, conf) 22 | defer teardown("manufacturers") 23 | 24 | manufacturer := model.TestManufacturer(t) 25 | assert.NoError(t, s.Manufacturer().Create(manufacturer)) 26 | 27 | actual, err := s.Manufacturer().FindByWMI(manufacturer.WMI) 28 | assert.NoError(t, err) 29 | assert.Equal(t, manufacturer, actual) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/api/grpc/server.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/opencars/grpc/pkg/vin_decoding" 7 | 8 | "github.com/opencars/vin-decoder-api/pkg/domain/command" 9 | ) 10 | 11 | type vinDecodingHandler struct { 12 | vin_decoding.UnimplementedServiceServer 13 | api *API 14 | } 15 | 16 | func (h *vinDecodingHandler) Decode(ctx context.Context, r *vin_decoding.DecodeRequest) (*vin_decoding.DecodeResultList, error) { 17 | c := command.DecodeVINInternal{ 18 | Items: make([]command.Item, 0, len(r.Vins)), 19 | } 20 | 21 | for _, vin := range r.Vins { 22 | c.Items = append(c.Items, command.Item{VIN: vin}) 23 | } 24 | 25 | result, err := h.api.svc.Decode(ctx, &c) 26 | if err != nil { 27 | return nil, handleErr(err) 28 | } 29 | 30 | dto := vin_decoding.DecodeResultList{ 31 | Items: make([]*vin_decoding.DecodeResultItem, 0, len(result.Results)), 32 | } 33 | 34 | for i := range result.Results { 35 | dto.Items = append(dto.Items, ResultItemFromDomain(&result.Results[i])) 36 | } 37 | 38 | return &dto, nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/store/sqlstore/manufacturer_repository.go: -------------------------------------------------------------------------------- 1 | package sqlstore 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | 7 | "github.com/opencars/vin-decoder-api/pkg/domain/model" 8 | ) 9 | 10 | type ManufacturerRepository struct { 11 | store *Store 12 | } 13 | 14 | func (r *ManufacturerRepository) Create(manufacturer *model.Manufacturer) error { 15 | _, err := r.store.db.NamedExec( 16 | `INSERT INTO manufacturers ( 17 | wmi, name 18 | ) VALUES ( 19 | :wmi, :name 20 | )`, 21 | manufacturer, 22 | ) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | return nil 28 | } 29 | 30 | func (r *ManufacturerRepository) FindByWMI(wmi string) (*model.Manufacturer, error) { 31 | var manufacturer model.Manufacturer 32 | 33 | err := r.store.db.Get(&manufacturer, 34 | `SELECT wmi, name FROM manufacturers WHERE wmi = $1`, 35 | wmi, 36 | ) 37 | 38 | if errors.Is(err, sql.ErrNoRows) { 39 | return nil, model.ErrManufacturerNotFound 40 | } 41 | 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | return &manufacturer, nil 47 | } 48 | -------------------------------------------------------------------------------- /pkg/store/sqlstore/store.go: -------------------------------------------------------------------------------- 1 | package sqlstore 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/opencars/vin-decoder-api/pkg/domain" 7 | 8 | "github.com/jmoiron/sqlx" 9 | 10 | "github.com/opencars/vin-decoder-api/pkg/config" 11 | ) 12 | 13 | type Store struct { 14 | db *sqlx.DB 15 | 16 | manufacturerRepository *ManufacturerRepository 17 | } 18 | 19 | func (s *Store) Manufacturer() domain.ManufacturerRepository { 20 | if s.manufacturerRepository == nil { 21 | s.manufacturerRepository = &ManufacturerRepository{ 22 | store: s, 23 | } 24 | } 25 | 26 | return s.manufacturerRepository 27 | } 28 | 29 | func New(settings *config.Database) (*Store, error) { 30 | info := fmt.Sprintf("host=%s port=%d user=%s dbname=%s sslmode=%s password=%s", 31 | settings.Host, 32 | settings.Port, 33 | settings.User, 34 | settings.Name, 35 | settings.SSLMode, 36 | settings.Password, 37 | ) 38 | 39 | db, err := sqlx.Connect("postgres", info) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | return &Store{ 45 | db: db, 46 | }, nil 47 | } 48 | -------------------------------------------------------------------------------- /pkg/api/grpc/converting.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "github.com/opencars/grpc/pkg/common" 5 | "github.com/opencars/grpc/pkg/vin_decoding" 6 | 7 | "github.com/opencars/vin-decoder-api/pkg/domain/model" 8 | ) 9 | 10 | func ResultItemFromDomain(r *model.Result) *vin_decoding.DecodeResultItem { 11 | item := vin_decoding.DecodeResultItem{} 12 | 13 | if r.Error != nil { 14 | item.Error = &common.Error{ 15 | Messages: r.Error.Messages, 16 | } 17 | } 18 | 19 | if r.Vehicle != nil { 20 | vehicle := &vin_decoding.Vehicle{ 21 | Check: r.Vehicle.Check, 22 | Country: r.Vehicle.Country, 23 | Manufacturer: r.Vehicle.Manufacturer, 24 | Region: string(r.Vehicle.Region), 25 | } 26 | 27 | if r.Vehicle.Year != nil { 28 | vehicle.Year = uint32(*r.Vehicle.Year) 29 | } 30 | 31 | item.Vehicle = vehicle 32 | } 33 | 34 | if r.VIN != nil { 35 | vin := &vin_decoding.DecodedVIN{ 36 | Vds: r.VIN.VDS, 37 | Vis: r.VIN.VIS, 38 | Wmi: r.VIN.WMI, 39 | } 40 | 41 | item.DecodedVin = vin 42 | } 43 | 44 | return &item 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VIN Decoder 2 | 3 | > :red_car: Decoding vehicle identification number 4 | 5 | ## Development 6 | 7 | Build the binary 8 | 9 | ```sh 10 | make 11 | ``` 12 | 13 | Start postgres 14 | 15 | ```sh 16 | docker-compose up -Vd 17 | ``` 18 | 19 | Run sql migrations 20 | 21 | ```sh 22 | migrate -source file://migrations -database postgres://postgres:password@127.0.0.1:5432/vin-decoder\?sslmode=disable up 23 | ``` 24 | 25 | Run the web server 26 | 27 | ```sh 28 | ./bin/server 29 | ``` 30 | 31 | ## Usage 32 | 33 | For example, you get information about this amazing Tesla Model X 34 | 35 | ```sh 36 | http http://localhost:8080/api/v1/vin-decoder/5YJXCCE40GF010543 37 | ``` 38 | 39 | ```json 40 | { 41 | "vehicle": { 42 | "check_digit": true, 43 | "country": "United States", 44 | "manufacturer": "Tesla, Inc.", 45 | "region": "North America", 46 | "serial": "010543", 47 | "year": 2016 48 | }, 49 | "vin": { 50 | "vds": "XCCE40", 51 | "vis": "GF010543", 52 | "wmi": "5YJ" 53 | } 54 | } 55 | ``` 56 | 57 | ## License 58 | 59 | Project released under the terms of the MIT [license](./LICENSE). 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 opencars 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg/api/grpc/error.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "google.golang.org/genproto/googleapis/rpc/errdetails" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/status" 10 | 11 | "github.com/opencars/seedwork" 12 | ) 13 | 14 | var ( 15 | ErrValidationFailed = status.New(codes.InvalidArgument, "request.validation_failed") 16 | ) 17 | 18 | func handleErr(err error) error { 19 | var vErr seedwork.ValidationError 20 | 21 | if errors.As(err, &vErr) { 22 | br := errdetails.BadRequest{ 23 | FieldViolations: make([]*errdetails.BadRequest_FieldViolation, 0), 24 | } 25 | 26 | for k, vv := range vErr.Messages { 27 | for _, v := range vv { 28 | br.FieldViolations = append(br.FieldViolations, &errdetails.BadRequest_FieldViolation{ 29 | Field: k, 30 | Description: v, 31 | }) 32 | } 33 | } 34 | 35 | st, err := ErrValidationFailed.WithDetails(&br) 36 | if err != nil { 37 | panic(fmt.Sprintf("Unexpected error attaching metadata: %v", err)) 38 | } 39 | 40 | return st.Err() 41 | } 42 | 43 | var e seedwork.Error 44 | if errors.As(err, &e) { 45 | return status.Error(codes.FailedPrecondition, e.Error()) 46 | } 47 | 48 | return err 49 | } 50 | -------------------------------------------------------------------------------- /pkg/api/grpc/api.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/opencars/grpc/pkg/vin_decoding" 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/reflection" 10 | 11 | "github.com/opencars/vin-decoder-api/pkg/domain" 12 | ) 13 | 14 | // API represents gRPC API server. 15 | type API struct { 16 | addr string 17 | s *grpc.Server 18 | svc domain.InternalService 19 | } 20 | 21 | func New(addr string, svc domain.InternalService) *API { 22 | opts := []grpc.ServerOption{ 23 | grpc.ChainUnaryInterceptor( 24 | RequestLoggingInterceptor, 25 | ), 26 | } 27 | 28 | return &API{ 29 | addr: addr, 30 | svc: svc, 31 | s: grpc.NewServer(opts...), 32 | } 33 | } 34 | 35 | // Server returns the underlying gRPC server instance. 36 | func (a *API) Server() *grpc.Server { 37 | return a.s 38 | } 39 | 40 | func (a *API) Run(ctx context.Context) error { 41 | listener, err := net.Listen("tcp", a.addr) 42 | if err != nil { 43 | return err 44 | } 45 | defer listener.Close() 46 | 47 | vin_decoding.RegisterServiceServer(a.s, &vinDecodingHandler{api: a}) 48 | 49 | // Register reflection service 50 | reflection.Register(a.s) 51 | 52 | errors := make(chan error) 53 | go func() { 54 | errors <- a.s.Serve(listener) 55 | }() 56 | 57 | select { 58 | case <-ctx.Done(): 59 | a.s.GracefulStop() 60 | return <-errors 61 | case err := <-errors: 62 | return err 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pkg/domain/command/decode_vin.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "github.com/opencars/schema" 8 | "github.com/opencars/schema/vehicle" 9 | "github.com/opencars/seedwork" 10 | "github.com/opencars/vin-decoder-api/pkg/domain/model" 11 | "google.golang.org/protobuf/types/known/timestamppb" 12 | 13 | validation "github.com/go-ozzo/ozzo-validation/v4" 14 | ) 15 | 16 | type DecodeVIN struct { 17 | UserID string 18 | TokenID string 19 | VIN string 20 | } 21 | 22 | func (c *DecodeVIN) Prepare() { 23 | c.VIN = strings.ReplaceAll(strings.ToUpper(c.VIN), "-", "") 24 | } 25 | 26 | func (c *DecodeVIN) Validate() error { 27 | return validation.ValidateStruct(c, 28 | validation.Field( 29 | &c.UserID, 30 | validation.Required.Error(seedwork.Required), 31 | ), 32 | validation.Field( 33 | &c.TokenID, 34 | validation.Required.Error(seedwork.Required), 35 | ), 36 | validation.Field( 37 | &c.VIN, 38 | validation.Required.Error(seedwork.Required), 39 | validation.Match(model.IsVIN).Error(seedwork.Invalid), 40 | ), 41 | ) 42 | } 43 | 44 | func (c *DecodeVIN) Event() schema.Producable { 45 | msg := vehicle.VINDecoded{ 46 | UserId: c.UserID, 47 | TokenId: c.TokenID, 48 | Vin: c.VIN, 49 | SearchedAt: timestamppb.New(time.Now().UTC()), 50 | } 51 | 52 | return schema.New(&source, &msg).Message( 53 | schema.WithSubject(schema.VinDecodingCustomerActions), 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/domain/service/customer_service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/opencars/schema" 7 | "github.com/opencars/seedwork" 8 | 9 | "github.com/opencars/vin-decoder-api/pkg/domain" 10 | "github.com/opencars/vin-decoder-api/pkg/domain/command" 11 | "github.com/opencars/vin-decoder-api/pkg/domain/model" 12 | ) 13 | 14 | type CustomerService struct { 15 | repo domain.ManufacturerRepository 16 | p schema.Producer 17 | } 18 | 19 | func NewCustomerService(repo domain.ManufacturerRepository, p schema.Producer) *CustomerService { 20 | return &CustomerService{ 21 | repo: repo, 22 | p: p, 23 | } 24 | } 25 | 26 | func (s *CustomerService) DecodeVIN(ctx context.Context, c *command.DecodeVIN) (*model.Result, error) { 27 | if err := seedwork.ProcessCommand(c); err != nil { 28 | return nil, err 29 | } 30 | 31 | vin := Parse(c.VIN) 32 | country := vin.Country() 33 | 34 | result := model.Result{ 35 | VIN: &model.VIN{ 36 | WMI: vin.WMI(), 37 | VDS: vin.VDS(), 38 | VIS: vin.VIS(), 39 | }, 40 | Vehicle: &model.Vehicle{ 41 | Manufacturer: vin.Manufacturer(s.repo), 42 | Year: vin.Year(), 43 | Region: vin.Region(), 44 | Check: vin.Check(), 45 | }, 46 | } 47 | 48 | if result.Vehicle != nil { 49 | result.Vehicle.Country = country.Name 50 | result.Vehicle.CountryUA = country.NameUA 51 | } 52 | 53 | if err := s.p.Produce(ctx, c.Event()); err != nil { 54 | return nil, err 55 | } 56 | 57 | return &result, nil 58 | } 59 | -------------------------------------------------------------------------------- /pkg/api/http/server.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "runtime" 7 | 8 | "github.com/gorilla/mux" 9 | "github.com/opencars/seedwork/httputil" 10 | "github.com/opencars/vin-decoder-api/pkg/domain/command" 11 | 12 | "github.com/opencars/vin-decoder-api/pkg/domain" 13 | "github.com/opencars/vin-decoder-api/pkg/version" 14 | ) 15 | 16 | type server struct { 17 | router *mux.Router 18 | 19 | svc domain.CustomerService 20 | } 21 | 22 | func newServer(svc domain.CustomerService) *server { 23 | srv := server{ 24 | router: mux.NewRouter(), 25 | svc: svc, 26 | } 27 | 28 | srv.configureRouter() 29 | 30 | return &srv 31 | } 32 | 33 | func (*server) Version() httputil.Handler { 34 | return func(w http.ResponseWriter, r *http.Request) error { 35 | v := struct { 36 | Version string `json:"version"` 37 | Go string `json:"go"` 38 | }{ 39 | Version: version.Version, 40 | Go: runtime.Version(), 41 | } 42 | 43 | return json.NewEncoder(w).Encode(v) 44 | } 45 | } 46 | 47 | func (s *server) DecodeVIN() httputil.Handler { 48 | return func(w http.ResponseWriter, r *http.Request) error { 49 | c := command.DecodeVIN{ 50 | UserID: httputil.UserIDFromContext(r.Context()), 51 | TokenID: httputil.TokenIDromContext(r.Context()), 52 | VIN: mux.Vars(r)["vin"], 53 | } 54 | 55 | result, err := s.svc.DecodeVIN(r.Context(), &c) 56 | if err != nil { 57 | return handleErr(err) 58 | } 59 | 60 | return json.NewEncoder(w).Encode(result) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pkg/domain/model/types.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | var IsVIN = regexp.MustCompile(`^[A-HJ-NPR-Z0-9]{17}$`) 8 | 9 | // Result is a union of information about vin-code and decoded vehicle. 10 | type Result struct { 11 | VIN *VIN `json:"vin,omitempty"` 12 | Vehicle *Vehicle `json:"vehicle,omitempty"` 13 | Error *ProcesingError `json:"error,,omitempty"` 14 | } 15 | 16 | type ProcesingError struct { 17 | Messages []string `json:"messages"` 18 | } 19 | 20 | // Result is a union of information about vin-code and decoded vehicle. 21 | type BulkResult struct { 22 | Results []Result `json:"results"` 23 | } 24 | 25 | // VIN represents detailed information about the VIN code. 26 | type VIN struct { 27 | WMI string `json:"wmi"` // World manufacturer identifier. 28 | VDS string `json:"vds"` // Vehicle descriptor section. 29 | VIS string `json:"vis"` // Vehicle identifier section. 30 | } 31 | 32 | // Vehicle represent information about the decoded vehicle. 33 | type Vehicle struct { 34 | Manufacturer string `json:"manufacturer"` 35 | Country string `json:"country"` 36 | CountryUA string `json:"country_ua"` 37 | Year *uint `json:"year,omitempty"` 38 | Region Region `json:"region"` 39 | Check bool `json:"check"` 40 | } 41 | 42 | type Region string 43 | 44 | const ( 45 | Africa Region = "Africa" 46 | Asia Region = "Asia" 47 | Europe Region = "Europe" 48 | NorthAmerica Region = "North America" 49 | Oceania Region = "Oceania" 50 | SouthAmerica Region = "South America" 51 | ) 52 | -------------------------------------------------------------------------------- /pkg/api/http/api.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/gorilla/handlers" 10 | "github.com/opencars/seedwork/logger" 11 | 12 | "github.com/opencars/vin-decoder-api/pkg/config" 13 | "github.com/opencars/vin-decoder-api/pkg/domain" 14 | ) 15 | 16 | // Start starts the server with specified store. 17 | func Start(ctx context.Context, addr string, conf *config.Server, svc domain.CustomerService) error { 18 | s := newServer(svc) 19 | 20 | srv := http.Server{ 21 | Addr: addr, 22 | Handler: handlers.CustomLoggingHandler(os.Stdout, handlers.ProxyHeaders(s.router), logFormatter), 23 | ReadTimeout: conf.ReadTimeout.Duration, 24 | WriteTimeout: conf.WriteTimeout.Duration, 25 | IdleTimeout: conf.IdleTimeout.Duration, 26 | MaxHeaderBytes: 1 << 20, 27 | } 28 | 29 | errs := make(chan error) 30 | go func() { 31 | errs <- srv.ListenAndServe() 32 | }() 33 | 34 | select { 35 | case err := <-errs: 36 | return err 37 | case <-ctx.Done(): 38 | ctxShutDown, cancel := context.WithTimeout(context.Background(), conf.ShutdownTimeout.Duration) 39 | defer cancel() 40 | 41 | err := srv.Shutdown(ctxShutDown) 42 | if err != nil && err != http.ErrServerClosed { 43 | return err 44 | } 45 | 46 | return nil 47 | } 48 | } 49 | 50 | func logFormatter(_ io.Writer, pp handlers.LogFormatterParams) { 51 | logger.WithFields(logger.Fields{ 52 | "method": pp.Request.Method, 53 | "path": pp.URL.Path, 54 | "status": pp.StatusCode, 55 | "size": pp.Size, 56 | "addr": pp.Request.RemoteAddr, 57 | }).Infof("http") 58 | } 59 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/opencars/vin-decoder-api 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.2 6 | 7 | require ( 8 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0 9 | github.com/gorilla/handlers v1.5.1 10 | github.com/gorilla/mux v1.8.0 11 | github.com/jmoiron/sqlx v1.3.4 12 | github.com/lib/pq v1.10.4 13 | github.com/opencars/grpc v0.3.0 14 | github.com/opencars/schema v0.0.10 15 | github.com/opencars/seedwork v0.0.2 16 | github.com/stretchr/testify v1.7.0 17 | golang.org/x/sync v0.12.0 18 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 19 | google.golang.org/grpc v1.43.0 20 | google.golang.org/protobuf v1.27.1 21 | gopkg.in/yaml.v2 v2.4.0 22 | ) 23 | 24 | require ( 25 | github.com/davecgh/go-spew v1.1.0 // indirect 26 | github.com/felixge/httpsnoop v1.0.1 // indirect 27 | github.com/golang/protobuf v1.5.0 // indirect 28 | github.com/kr/pretty v0.1.0 // indirect 29 | github.com/nats-io/nats-server/v2 v2.7.2 // indirect 30 | github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d // indirect 31 | github.com/nats-io/nkeys v0.3.0 // indirect 32 | github.com/nats-io/nuid v1.0.1 // indirect 33 | github.com/pmezard/go-difflib v1.0.0 // indirect 34 | github.com/rs/zerolog v1.26.1 // indirect 35 | github.com/satori/go.uuid v1.2.0 // indirect 36 | golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect 37 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect 38 | golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect 39 | golang.org/x/text v0.3.6 // indirect 40 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 41 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 42 | ) 43 | -------------------------------------------------------------------------------- /pkg/domain/service/internal_service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/opencars/seedwork" 8 | 9 | "github.com/opencars/vin-decoder-api/pkg/domain" 10 | "github.com/opencars/vin-decoder-api/pkg/domain/command" 11 | "github.com/opencars/vin-decoder-api/pkg/domain/model" 12 | ) 13 | 14 | type InternalService struct { 15 | repo domain.ManufacturerRepository 16 | } 17 | 18 | func NewInternalService(repo domain.ManufacturerRepository) *InternalService { 19 | return &InternalService{ 20 | repo: repo, 21 | } 22 | } 23 | 24 | func (s *InternalService) Decode(ctx context.Context, c *command.DecodeVINInternal) (*model.BulkResult, error) { 25 | results := make([]model.Result, 0, len(c.Items)) 26 | 27 | for _, v := range c.Items { 28 | if err := v.Validate(); err != nil { 29 | msgs := make([]string, 0) 30 | for k, vv := range seedwork.ErrorMessages("item", err) { 31 | for _, v := range vv { 32 | msgs = append(msgs, fmt.Sprintf("%s.%s", k, v)) 33 | } 34 | } 35 | 36 | results = append(results, model.Result{ 37 | Error: &model.ProcesingError{ 38 | Messages: msgs, 39 | }, 40 | }) 41 | 42 | continue 43 | } 44 | 45 | vin := Parse(v.VIN) 46 | country := vin.Country() 47 | 48 | result := model.Result{ 49 | VIN: &model.VIN{ 50 | WMI: vin.WMI(), 51 | VDS: vin.VDS(), 52 | VIS: vin.VIS(), 53 | }, 54 | Vehicle: &model.Vehicle{ 55 | Manufacturer: vin.Manufacturer(s.repo), 56 | Year: vin.Year(), 57 | Region: vin.Region(), 58 | Check: vin.Check(), 59 | }, 60 | } 61 | 62 | if result.Vehicle != nil { 63 | result.Vehicle.Country = country.Name 64 | result.Vehicle.CountryUA = country.NameUA 65 | } 66 | 67 | results = append(results, result) 68 | } 69 | 70 | return &model.BulkResult{Results: results}, nil 71 | } 72 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: flow 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | build: 8 | name: build 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Set up Go 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: 1.23 17 | 18 | - name: Build 19 | run: "go build -v ./..." 20 | 21 | test: 22 | name: test 23 | runs-on: ubuntu-latest 24 | services: 25 | postgres: 26 | image: postgres:12-alpine 27 | env: 28 | POSTGRES_USER: postgres 29 | POSTGRES_PASSWORD: password 30 | POSTGRES_DB: vin-decoder 31 | ports: 32 | - 5432:5432 33 | options: >- 34 | --health-cmd pg_isready 35 | --health-interval 10s 36 | --health-timeout 5s 37 | --health-retries 5 38 | steps: 39 | - name: Set up Go 1.x 40 | uses: actions/setup-go@v2 41 | with: 42 | go-version: ^1.23 43 | id: go 44 | 45 | - name: Check out code into the Go module directory 46 | uses: actions/checkout@v2 47 | 48 | - name: Install golang-migrate 49 | run: | 50 | curl -L https://github.com/golang-migrate/migrate/releases/download/v4.14.1/migrate.linux-amd64.tar.gz | tar xvz 51 | sudo mv migrate.linux-amd64 /usr/bin/migrate 52 | which migrate 53 | 54 | - name: Run migrations 55 | run: migrate -source file://migrations -database postgres://postgres:password@localhost:5432/vin-decoder\?sslmode=disable up 56 | 57 | - name: Test 58 | run: go test -v ./... 59 | 60 | lint: 61 | name: lint 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@v2 65 | - name: golangci-lint 66 | uses: golangci/golangci-lint-action@v2 67 | with: 68 | version: latest 69 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "gopkg.in/yaml.v2" 9 | ) 10 | 11 | type Settings struct { 12 | Server Server `yaml:"server"` 13 | Log Log `yaml:"log"` 14 | DB Database `yaml:"database"` 15 | NATS NATS `yaml:"nats"` 16 | } 17 | 18 | // Server represents settings for creating http server. 19 | type Server struct { 20 | ShutdownTimeout Duration `yaml:"shutdown_timeout"` 21 | ReadTimeout Duration `yaml:"read_timeout"` 22 | WriteTimeout Duration `yaml:"write_timeout"` 23 | IdleTimeout Duration `yaml:"idle_timeout"` 24 | } 25 | 26 | // Log represents settings for application logger. 27 | type Log struct { 28 | Level string `yaml:"level"` 29 | Mode string `yaml:"mode"` 30 | } 31 | 32 | type Database struct { 33 | Host string `yaml:"host"` 34 | Port int `yaml:"port"` 35 | User string `yaml:"username"` 36 | Password string `yaml:"password"` 37 | Name string `yaml:"database"` 38 | SSLMode string `yaml:"ssl_mode"` 39 | } 40 | 41 | type NodeNATS struct { 42 | Host string `yaml:"host"` 43 | Port int `yaml:"port"` 44 | } 45 | 46 | func (node *NodeNATS) Address(user, password string) string { 47 | if user != "" && password != "" { 48 | return fmt.Sprintf("nats://%s:%s@%s:%d", user, password, node.Host, node.Port) 49 | } 50 | 51 | return fmt.Sprintf("nats://%s:%d", node.Host, node.Port) 52 | } 53 | 54 | // NATS contains configuration details for application event API. 55 | type NATS struct { 56 | Nodes []NodeNATS `yaml:"nodes"` 57 | User string `yaml:"user"` 58 | Password string `yaml:"password"` 59 | } 60 | 61 | // Address returns calculated address for connecting to NATS. 62 | func (nats *NATS) Address() string { 63 | addrs := make([]string, 0) 64 | 65 | for _, node := range nats.Nodes { 66 | addrs = append(addrs, node.Address(nats.User, nats.Password)) 67 | 68 | } 69 | 70 | return strings.Join(addrs, ",") 71 | } 72 | 73 | // New reads application configuration from specified file path. 74 | func New(path string) (*Settings, error) { 75 | var config Settings 76 | 77 | f, err := os.Open(path) 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | if err := yaml.NewDecoder(f).Decode(&config); err != nil { 83 | return nil, err 84 | } 85 | 86 | return &config, nil 87 | } 88 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "os/signal" 7 | "strconv" 8 | "syscall" 9 | 10 | "github.com/opencars/schema/client" 11 | "github.com/opencars/seedwork/logger" 12 | "golang.org/x/sync/errgroup" 13 | 14 | "github.com/opencars/vin-decoder-api/pkg/api/grpc" 15 | "github.com/opencars/vin-decoder-api/pkg/api/http" 16 | "github.com/opencars/vin-decoder-api/pkg/config" 17 | "github.com/opencars/vin-decoder-api/pkg/domain/service" 18 | "github.com/opencars/vin-decoder-api/pkg/store/sqlstore" 19 | ) 20 | 21 | func main() { 22 | cfg := flag.String("config", "config/config.yaml", "Path to the configuration file") 23 | httpPort := flag.Int("http-port", 8080, "Port for HTTP server") 24 | grpcPort := flag.Int("grpc-port", 3000, "Port for gRPC server") 25 | 26 | flag.Parse() 27 | 28 | conf, err := config.New(*cfg) 29 | if err != nil { 30 | logger.Fatalf("config: %v", err) 31 | } 32 | 33 | logger.NewLogger(logger.LogLevel(conf.Log.Level), conf.Log.Mode == "dev") 34 | 35 | // Initialize store 36 | store, err := sqlstore.New(&conf.DB) 37 | if err != nil { 38 | logger.Fatalf("store: %v", err) 39 | } 40 | 41 | // Initialize NATS client 42 | c, err := client.New(conf.NATS.Address()) 43 | if err != nil { 44 | logger.Fatalf("nats: %v", err) 45 | } 46 | 47 | producer, err := c.Producer() 48 | if err != nil { 49 | logger.Fatalf("producer: %v", err) 50 | } 51 | 52 | // Create services 53 | customerSvc := service.NewCustomerService(store.Manufacturer(), producer) 54 | internalSvc := service.NewInternalService(store.Manufacturer()) 55 | 56 | // Create context with cancellation 57 | ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) 58 | defer stop() 59 | 60 | // Create errgroup with cancellation 61 | g, ctx := errgroup.WithContext(ctx) 62 | 63 | // Start HTTP server 64 | g.Go(func() error { 65 | addr := ":" + strconv.Itoa(*httpPort) 66 | logger.Infof("Starting HTTP server on %s...", addr) 67 | return http.Start(ctx, addr, &conf.Server, customerSvc) 68 | }) 69 | 70 | // Start gRPC server 71 | g.Go(func() error { 72 | addr := ":" + strconv.Itoa(*grpcPort) 73 | logger.Infof("Starting gRPC server on %s...", addr) 74 | api := grpc.New(addr, internalSvc) 75 | 76 | return api.Run(ctx) 77 | }) 78 | 79 | // Wait for interrupt signal or error from servers 80 | logger.Infof("Servers started successfully. Press Ctrl+C to stop...") 81 | if err := g.Wait(); err != nil { 82 | logger.Fatalf("Server error: %v", err) 83 | } 84 | logger.Infof("Servers stopped gracefully") 85 | } 86 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: flow 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | build: 9 | name: build 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Set up Go 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: 1.23 18 | 19 | - name: Build 20 | run: "go build -v ./..." 21 | 22 | test: 23 | name: test 24 | runs-on: ubuntu-latest 25 | services: 26 | postgres: 27 | image: postgres:12-alpine 28 | env: 29 | POSTGRES_USER: postgres 30 | POSTGRES_PASSWORD: password 31 | POSTGRES_DB: vin-decoder 32 | ports: 33 | - 5432:5432 34 | options: >- 35 | --health-cmd pg_isready 36 | --health-interval 10s 37 | --health-timeout 5s 38 | --health-retries 5 39 | steps: 40 | - name: Set up Go 1.x 41 | uses: actions/setup-go@v2 42 | with: 43 | go-version: ^1.23 44 | id: go 45 | 46 | - name: Check out code into the Go module directory 47 | uses: actions/checkout@v2 48 | 49 | - name: Install golang-migrate 50 | run: | 51 | curl -L https://github.com/golang-migrate/migrate/releases/download/v4.14.1/migrate.linux-amd64.tar.gz | tar xvz 52 | sudo mv migrate.linux-amd64 /usr/bin/migrate 53 | which migrate 54 | 55 | - name: Run migrations 56 | run: migrate -source file://migrations -database postgres://postgres:password@localhost:5432/vin-decoder\?sslmode=disable up 57 | 58 | - name: Test 59 | run: go test -v ./... 60 | 61 | lint: 62 | name: lint 63 | runs-on: ubuntu-latest 64 | steps: 65 | - uses: actions/checkout@v2 66 | - name: golangci-lint 67 | uses: golangci/golangci-lint-action@v2 68 | with: 69 | version: latest 70 | 71 | build-and-push-docker-image: 72 | needs: 73 | - build 74 | - test 75 | - lint 76 | name: Build and push docker image 77 | runs-on: ubuntu-latest 78 | 79 | steps: 80 | - name: Checkout code 81 | uses: actions/checkout@v2 82 | 83 | - name: Set up Docker Buildx 84 | id: buildx 85 | uses: docker/setup-buildx-action@v1 86 | 87 | - name: Login to Github Packages 88 | uses: docker/login-action@v1 89 | with: 90 | registry: ghcr.io 91 | username: ${{ github.actor }} 92 | password: ${{ secrets.GHCR_PAT }} 93 | 94 | - name: Set outputs 95 | id: vars 96 | run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" 97 | 98 | - name: Build and push image 99 | uses: docker/build-push-action@v2 100 | with: 101 | push: true 102 | tags: | 103 | ghcr.io/${{ github.repository }} 104 | ghcr.io/${{ github.repository }}:${{ steps.vars.outputs.sha_short }} 105 | 106 | - name: Image digest 107 | run: echo ${{ steps.docker_build.outputs.digest }} 108 | -------------------------------------------------------------------------------- /pkg/domain/service/govin.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | "time" 7 | 8 | "github.com/opencars/seedwork/logger" 9 | "github.com/opencars/vin-decoder-api/pkg/domain" 10 | "github.com/opencars/vin-decoder-api/pkg/domain/model" 11 | ) 12 | 13 | const ( 14 | chars = "ABCDEFGHIJKLMNOPRSTUVWXYZ1234567890" 15 | yearSym = "ABCDEFGHJKLMNPRSTVWXY123456789" 16 | ) 17 | 18 | var weights = []int{8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2} 19 | 20 | func Parse(value string) *VIN { 21 | return &VIN{ 22 | wmi: value[0:3], 23 | vds: value[3:9], 24 | vis: value[9:17], 25 | } 26 | } 27 | 28 | type VIN struct { 29 | wmi string // The World Manufacturer Identifier (WMI) code. 30 | vds string // The Vehicle Descriptor Section (VDS) code. 31 | vis string // The Vehicle Identifier Section (VIS) code. 32 | } 33 | 34 | func IndexOf(lexeme string) int { 35 | return strings.IndexByte(chars, lexeme[0])*len(chars) + strings.IndexByte(chars, lexeme[1]) 36 | } 37 | 38 | func (vin *VIN) WMI() string { 39 | return vin.wmi 40 | } 41 | 42 | func (vin *VIN) VDS() string { 43 | return vin.vds 44 | } 45 | 46 | func (vin *VIN) VIS() string { 47 | return vin.vis 48 | } 49 | 50 | func (vin *VIN) String() string { 51 | return vin.wmi + vin.vds + vin.vis 52 | } 53 | 54 | // Obtain the 2-character region code for the manufacturing region. 55 | func (vin VIN) Region() model.Region { 56 | region := vin.wmi[0] 57 | 58 | switch { 59 | case region >= 'A' && region <= 'H': 60 | return model.Africa 61 | case region >= 'J' && region <= 'R': 62 | return model.Asia 63 | case region >= 'S' && region <= 'Z': 64 | return model.Europe 65 | case region >= '1' && region <= '5': 66 | return model.NorthAmerica 67 | case region >= '6' && region <= '7': 68 | return model.Oceania 69 | case region >= '8' && region <= '9': 70 | return model.SouthAmerica 71 | } 72 | 73 | return "Unknown" 74 | } 75 | 76 | // Extract the single-character model year from the [number]. 77 | func (vin VIN) ModelYear() rune { 78 | return []rune(vin.String())[9] 79 | } 80 | 81 | // Extract the single-character assembly plant designator from the [number]. 82 | func (vin VIN) AssemblyPlant() rune { 83 | return []rune(vin.vis)[0] 84 | } 85 | 86 | // Extract the serial number from the [number]. 87 | func (vin VIN) SerialNumber() string { 88 | return vin.vis[2:] 89 | } 90 | 91 | func value(b byte) int { 92 | if b >= '0' && b <= '9' { 93 | return int(b) - '0' 94 | } 95 | 96 | switch b { 97 | case 'A', 'J': 98 | return 1 99 | case 'B', 'K', 'S': 100 | return 2 101 | case 'C', 'L', 'T': 102 | return 3 103 | case 'D', 'M', 'U': 104 | return 4 105 | case 'E', 'N', 'V': 106 | return 5 107 | case 'F', 'W': 108 | return 6 109 | case 'G', 'P', 'X': 110 | return 7 111 | case 'H', 'Y': 112 | return 8 113 | case 'R', 'Z': 114 | return 9 115 | } 116 | 117 | return -1 118 | } 119 | 120 | func (vin VIN) Check() bool { 121 | if vin.Region() != model.NorthAmerica { 122 | return true 123 | } 124 | 125 | sum := 0 126 | str := vin.String() 127 | for i := range str { 128 | prod := value(str[i]) * weights[i] 129 | sum += prod 130 | } 131 | 132 | res := sum % 11 133 | if res == 10 { 134 | return str[8] == 'X' 135 | } else { 136 | return res+'0' == int(str[8]) 137 | } 138 | } 139 | 140 | func (vin VIN) Year() *uint { 141 | year := time.Now().Year() + 1 142 | yearIndex := (year - 2010) % len(yearSym) 143 | 144 | i := strings.IndexRune(yearSym, vin.ModelYear()) 145 | if i == -1 { 146 | return nil 147 | } 148 | 149 | if i <= yearIndex { 150 | res := uint(year - (yearIndex - i)) 151 | return &res 152 | } 153 | 154 | res := uint(2010 - len(yearSym) + i) 155 | return &res 156 | } 157 | 158 | func (vin VIN) Manufacturer(repo domain.ManufacturerRepository) string { 159 | manufacturer, err := repo.FindByWMI(vin.wmi) 160 | if errors.Is(err, model.ErrManufacturerNotFound) { 161 | return "Unknown" 162 | } 163 | 164 | if err != nil { 165 | logger.Errorf("failed to retrive manufacturer name: %s", err) 166 | return "Unknown" 167 | } 168 | 169 | return manufacturer.Name 170 | } 171 | 172 | func (vin VIN) Country() *Country { 173 | qi := IndexOf(vin.wmi[:2]) 174 | for _, country := range countries { 175 | i := IndexOf(country.From) 176 | j := IndexOf(country.To) 177 | if qi >= i && qi <= j { 178 | return &country 179 | } 180 | } 181 | 182 | return nil 183 | } 184 | -------------------------------------------------------------------------------- /pkg/domain/service/countries.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | type Country struct { 4 | From string 5 | To string 6 | Name string 7 | NameUA string 8 | } 9 | 10 | var countries = []Country{ 11 | { 12 | From: "AA", 13 | To: "AH", 14 | Name: "South Africa", 15 | NameUA: "Південна Африка", 16 | }, 17 | { 18 | From: "AJ", 19 | To: "AN", 20 | Name: "Ivory Coast", 21 | NameUA: "Кот-д'Івуар", 22 | }, 23 | { 24 | From: "BA", 25 | To: "BE", 26 | Name: "Angola", 27 | NameUA: "Ангола", 28 | }, 29 | { 30 | From: "BF", 31 | To: "BK", 32 | Name: "Kenya", 33 | NameUA: "Кенія", 34 | }, 35 | { 36 | From: "BL", 37 | To: "BR", 38 | Name: "Tanzania", 39 | NameUA: "Танзанія", 40 | }, 41 | { 42 | From: "CA", 43 | To: "CE", 44 | Name: "Benin", 45 | NameUA: "Бенін", 46 | }, 47 | { 48 | From: "CF", 49 | To: "CK", 50 | Name: "Madagascar", 51 | NameUA: "Мадагаскар", 52 | }, 53 | { 54 | From: "CL", 55 | To: "CR", 56 | Name: "Tunisia", 57 | NameUA: "Туніс", 58 | }, 59 | { 60 | From: "DA", 61 | To: "DE", 62 | Name: "Egypt", 63 | NameUA: "Єгипет", 64 | }, 65 | { 66 | From: "DF", 67 | To: "DK", 68 | Name: "Morocco", 69 | NameUA: "Марокко", 70 | }, 71 | { 72 | From: "DL", 73 | To: "DR", 74 | Name: "Zambia", 75 | NameUA: "Замбія", 76 | }, 77 | { 78 | From: "EA", 79 | To: "EE", 80 | Name: "Ethiopia", 81 | NameUA: "Ефіопія", 82 | }, 83 | { 84 | From: "EF", 85 | To: "EK", 86 | Name: "Mozambique", 87 | NameUA: "Мозамбік", 88 | }, 89 | { 90 | From: "FA", 91 | To: "FE", 92 | Name: "Ghana", 93 | NameUA: "Гана", 94 | }, 95 | { 96 | From: "FF", 97 | To: "FK", 98 | Name: "Nigeria", 99 | NameUA: "Нігерія", 100 | }, 101 | { 102 | From: "JA", 103 | To: "J0", 104 | Name: "Japan", 105 | NameUA: "Японія", 106 | }, 107 | { 108 | From: "KA", 109 | To: "KE", 110 | Name: "Sri Lanka", 111 | NameUA: "Шрі-Ланка", 112 | }, 113 | { 114 | From: "KF", 115 | To: "KK", 116 | Name: "Israel", 117 | NameUA: "Ізраїль", 118 | }, 119 | { 120 | From: "KL", 121 | To: "KR", 122 | Name: "Korea (South)", 123 | NameUA: "Південна Корея", 124 | }, 125 | { 126 | From: "KS", 127 | To: "K0", 128 | Name: "Kazakhstan", 129 | NameUA: "Казахстан", 130 | }, 131 | { 132 | From: "LA", 133 | To: "L0", 134 | Name: "China", 135 | NameUA: "Китай", 136 | }, 137 | { 138 | From: "MA", 139 | To: "ME", 140 | Name: "India", 141 | NameUA: "Індія", 142 | }, 143 | { 144 | From: "MF", 145 | To: "MK", 146 | Name: "Indonesia", 147 | NameUA: "Індонезія", 148 | }, 149 | { 150 | From: "ML", 151 | To: "MR", 152 | Name: "Thailand", 153 | NameUA: "Таїланд", 154 | }, 155 | { 156 | From: "NA", 157 | To: "NE", 158 | Name: "Iran", 159 | NameUA: "Іран", 160 | }, 161 | { 162 | From: "NF", 163 | To: "NK", 164 | Name: "Pakistan", 165 | NameUA: "Пакистан", 166 | }, 167 | { 168 | From: "NL", 169 | To: "NR", 170 | Name: "Turkey", 171 | NameUA: "Туреччина", 172 | }, 173 | { 174 | From: "PA", 175 | To: "PE", 176 | Name: "Philippines", 177 | NameUA: "Філіппіни", 178 | }, 179 | { 180 | From: "PF", 181 | To: "PK", 182 | Name: "Singapore", 183 | NameUA: "Сінгапур", 184 | }, 185 | { 186 | From: "PL", 187 | To: "PR", 188 | Name: "Malaysia", 189 | NameUA: "Малайзія", 190 | }, 191 | { 192 | From: "RA", 193 | To: "RE", 194 | Name: "United Arab Emirates", 195 | NameUA: "Об'єднані Арабські Емірати", 196 | }, 197 | { 198 | From: "RF", 199 | To: "RK", 200 | Name: "Taiwan", 201 | NameUA: "Тайвань", 202 | }, 203 | { 204 | From: "RL", 205 | To: "RR", 206 | Name: "Vietnam", 207 | NameUA: "В'єтнам", 208 | }, 209 | { 210 | From: "RS", 211 | To: "R0", 212 | Name: "Saudi Arabia", 213 | NameUA: "Саудівська Аравія", 214 | }, 215 | { 216 | From: "SA", 217 | To: "SM", 218 | Name: "United Kingdom", 219 | NameUA: "Велика Британія", 220 | }, 221 | { 222 | From: "SN", 223 | To: "ST", 224 | Name: "Germany", 225 | NameUA: "Німеччина", 226 | }, 227 | { 228 | From: "SU", 229 | To: "SZ", 230 | Name: "Poland", 231 | NameUA: "Польща", 232 | }, 233 | { 234 | From: "S1", 235 | To: "S4", 236 | Name: "Latvia", 237 | NameUA: "Латвія", 238 | }, 239 | { 240 | From: "TA", 241 | To: "TH", 242 | Name: "Switzerland", 243 | NameUA: "Швейцарія", 244 | }, 245 | { 246 | From: "TJ", 247 | To: "TP", 248 | Name: "Czech Republic", 249 | NameUA: "Чехія", 250 | }, 251 | { 252 | From: "TR", 253 | To: "TV", 254 | Name: "Hungary", 255 | NameUA: "Угорщина", 256 | }, 257 | { 258 | From: "TW", 259 | To: "T1", 260 | Name: "Portugal", 261 | NameUA: "Португалія", 262 | }, 263 | { 264 | From: "UH", 265 | To: "UM", 266 | Name: "Denmark", 267 | NameUA: "Данія", 268 | }, 269 | { 270 | From: "UN", 271 | To: "UT", 272 | Name: "Ireland", 273 | NameUA: "Ірландія", 274 | }, 275 | { 276 | From: "UU", 277 | To: "UZ", 278 | Name: "Romania", 279 | NameUA: "Румунія", 280 | }, 281 | { 282 | From: "U5", 283 | To: "U7", 284 | Name: "Slovakia", 285 | NameUA: "Словаччина", 286 | }, 287 | { 288 | From: "VA", 289 | To: "VE", 290 | Name: "Austria", 291 | NameUA: "Австрія", 292 | }, 293 | { 294 | From: "VF", 295 | To: "VR", 296 | Name: "France", 297 | NameUA: "Франція", 298 | }, 299 | { 300 | From: "VS", 301 | To: "VW", 302 | Name: "Spain", 303 | NameUA: "Іспанія", 304 | }, 305 | { 306 | From: "VX", 307 | To: "V2", 308 | Name: "Serbia", 309 | NameUA: "Сербія", 310 | }, 311 | { 312 | From: "V3", 313 | To: "V5", 314 | Name: "Croatia", 315 | NameUA: "Хорватія", 316 | }, 317 | { 318 | From: "V6", 319 | To: "V0", 320 | Name: "Estonia", 321 | NameUA: "Естонія", 322 | }, 323 | { 324 | From: "WA", 325 | To: "W0", 326 | Name: "Germany", 327 | NameUA: "Німеччина", 328 | }, 329 | { 330 | From: "XA", 331 | To: "XE", 332 | Name: "Bulgaria", 333 | NameUA: "Болгарія", 334 | }, 335 | { 336 | From: "XF", 337 | To: "XK", 338 | Name: "Greece", 339 | NameUA: "Греція", 340 | }, 341 | { 342 | From: "XL", 343 | To: "XR", 344 | Name: "Netherlands", 345 | NameUA: "Нідерланди", 346 | }, 347 | { 348 | From: "XS", 349 | To: "XW", 350 | Name: "Russia", 351 | NameUA: "Росія", 352 | }, 353 | { 354 | From: "XX", 355 | To: "X2", 356 | Name: "Luxembourg", 357 | NameUA: "Люксембург", 358 | }, 359 | { 360 | From: "X3", 361 | To: "X0", 362 | Name: "Russia", 363 | NameUA: "Росія", 364 | }, 365 | { 366 | From: "YA", 367 | To: "YE", 368 | Name: "Belgium", 369 | NameUA: "Бельгія", 370 | }, 371 | { 372 | From: "YF", 373 | To: "YK", 374 | Name: "Finland", 375 | NameUA: "Фінляндія", 376 | }, 377 | { 378 | From: "YL", 379 | To: "YR", 380 | Name: "Malta", 381 | NameUA: "Мальта", 382 | }, 383 | { 384 | From: "YS", 385 | To: "YW", 386 | Name: "Sweden", 387 | NameUA: "Швеція", 388 | }, 389 | { 390 | From: "YX", 391 | To: "Y2", 392 | Name: "Norway", 393 | NameUA: "Норвегія", 394 | }, 395 | { 396 | From: "Y3", 397 | To: "Y5", 398 | Name: "Belarus", 399 | NameUA: "Білорусь", 400 | }, 401 | { 402 | From: "Y6", 403 | To: "Y0", 404 | Name: "Ukraine", 405 | NameUA: "Україна", 406 | }, 407 | { 408 | From: "ZA", 409 | To: "ZR", 410 | Name: "Italy", 411 | NameUA: "Італія", 412 | }, 413 | { 414 | From: "ZX", 415 | To: "Z2", 416 | Name: "Slovenia", 417 | NameUA: "Словенія", 418 | }, 419 | { 420 | From: "Z3", 421 | To: "Z5", 422 | Name: "Lithuania", 423 | NameUA: "Литва", 424 | }, 425 | { 426 | From: "1A", 427 | To: "10", 428 | Name: "United States", 429 | NameUA: "Сполучені Штати Америки", 430 | }, 431 | { 432 | From: "2A", 433 | To: "20", 434 | Name: "Canada", 435 | NameUA: "Канада", 436 | }, 437 | { 438 | From: "3A", 439 | To: "37", 440 | Name: "Mexico", 441 | NameUA: "Мексика", 442 | }, 443 | { 444 | From: "38", 445 | To: "30", 446 | Name: "Cayman Islands", 447 | NameUA: "Кайманові острови", 448 | }, 449 | { 450 | From: "4A", 451 | To: "40", 452 | Name: "United States", 453 | NameUA: "Сполучені Штати Америки", 454 | }, 455 | { 456 | From: "5A", 457 | To: "50", 458 | Name: "United States", 459 | NameUA: "Сполучені Штати Америки", 460 | }, 461 | { 462 | From: "6A", 463 | To: "6W", 464 | Name: "Australia", 465 | NameUA: "Австралія", 466 | }, 467 | { 468 | From: "7A", 469 | To: "7E", 470 | Name: "New Zealand", 471 | NameUA: "Нова Зеландія", 472 | }, 473 | { 474 | From: "8A", 475 | To: "8E", 476 | Name: "Argentina", 477 | NameUA: "Аргентина", 478 | }, 479 | { 480 | From: "8F", 481 | To: "8K", 482 | Name: "Chile", 483 | NameUA: "Чилі", 484 | }, 485 | { 486 | From: "8L", 487 | To: "8R", 488 | Name: "Ecuador", 489 | NameUA: "Еквадор", 490 | }, 491 | { 492 | From: "8S", 493 | To: "8W", 494 | Name: "Peru", 495 | NameUA: "Перу", 496 | }, 497 | { 498 | From: "8X", 499 | To: "82", 500 | Name: "Venezuela", 501 | NameUA: "Венесуела", 502 | }, 503 | { 504 | From: "9A", 505 | To: "9E", 506 | Name: "Brazil", 507 | NameUA: "Бразилія", 508 | }, 509 | { 510 | From: "9F", 511 | To: "9K", 512 | Name: "Colombia", 513 | NameUA: "Колумбія", 514 | }, 515 | { 516 | From: "9L", 517 | To: "9R", 518 | Name: "Paraguay", 519 | NameUA: "Парагвай", 520 | }, 521 | { 522 | From: "9S", 523 | To: "9W", 524 | Name: "Uruguay", 525 | NameUA: "Уругвай", 526 | }, 527 | { 528 | From: "9X", 529 | To: "92", 530 | Name: "Trinidad & Tobago", 531 | NameUA: "Тринідад і Тобаго", 532 | }, 533 | { 534 | From: "93", 535 | To: "99", 536 | Name: "Brazil", 537 | NameUA: "Бразилія", 538 | }, 539 | } 540 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 5 | github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= 6 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= 7 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 8 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 9 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 10 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 11 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 12 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 13 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= 14 | github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 15 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 16 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 17 | github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 18 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 19 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 21 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 22 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 23 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 24 | github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= 25 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 26 | github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= 27 | github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 28 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 29 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= 30 | github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= 31 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 32 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 33 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 34 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 35 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 36 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 37 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 38 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 39 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 40 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 41 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 42 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 43 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 44 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 45 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 46 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 47 | github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= 48 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 49 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 50 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 51 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 52 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 53 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 54 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 55 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 56 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 57 | github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= 58 | github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= 59 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 60 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 61 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 62 | github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= 63 | github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= 64 | github.com/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEEeX6s= 65 | github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= 66 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 67 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 68 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 69 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 70 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 71 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 72 | github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= 73 | github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 74 | github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= 75 | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 76 | github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0= 77 | github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= 78 | github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296 h1:vU9tpM3apjYlLLeY23zRWJ9Zktr5jp+mloR942LEOpY= 79 | github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= 80 | github.com/nats-io/nats-server/v2 v2.7.2 h1:+LEN8m0+jdCkiGc884WnDuxR+qj80/5arj+szKuRpRI= 81 | github.com/nats-io/nats-server/v2 v2.7.2/go.mod h1:tckmrt0M6bVaDT3kmh9UrIq/CBOBBse+TpXQi5ldaa8= 82 | github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d h1:GRSmEJutHkdoxKsRypP575IIdoXe7Bm6yHQF6GcDBnA= 83 | github.com/nats-io/nats.go v1.13.1-0.20220121202836-972a071d373d/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= 84 | github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= 85 | github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= 86 | github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= 87 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 88 | github.com/opencars/grpc v0.3.0 h1:r8715lBLoQWrGKhXpW7Fmj/nKFU4W5em+U4RpnJ81oU= 89 | github.com/opencars/grpc v0.3.0/go.mod h1:Uk1ZsAaO/NKJTVXtdj8ML6Bd8PSJkm+VCPPpn1KxvDc= 90 | github.com/opencars/schema v0.0.10 h1:MB6fg1J6v36TmwKvG7PEvNUM8PuH2EyaEQNRlED9BuI= 91 | github.com/opencars/schema v0.0.10/go.mod h1:Rks9JEZsn5gGp/i4JLgZ+RvH81oQaf/EAmdRZmTyFac= 92 | github.com/opencars/seedwork v0.0.2 h1:n1eRYKtY7nPTDCZxaSCAFmDBJ9mtxAnDjKMyMGrqcdM= 93 | github.com/opencars/seedwork v0.0.2/go.mod h1:qoFG3InZdCbwlAAsbbD6p/prDCajVjdD4TGV6yG4HCA= 94 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 95 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 96 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 97 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 98 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 99 | github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 100 | github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= 101 | github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= 102 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 103 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 104 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 105 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 106 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 107 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 108 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 109 | github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 110 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 111 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 112 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 113 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 114 | golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 115 | golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 116 | golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI= 117 | golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 118 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 119 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 120 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 121 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 122 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 123 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 124 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 125 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 126 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 127 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 128 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 129 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 130 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 131 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 132 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 133 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= 134 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 135 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 136 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 137 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 138 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 139 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 140 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 141 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 142 | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= 143 | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 144 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 145 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 146 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 147 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 148 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 149 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 150 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 151 | golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY= 152 | golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 153 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 154 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 155 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 156 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 157 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 158 | golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= 159 | golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 160 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 161 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 162 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 163 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 164 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 165 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 166 | golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= 167 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 168 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 169 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 170 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 171 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 172 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 173 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 174 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 175 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 176 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 177 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 178 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= 179 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 180 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 181 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 182 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 183 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 184 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 185 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 186 | google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= 187 | google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= 188 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 189 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 190 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 191 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 192 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 193 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 194 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 195 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 196 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 197 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 198 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 199 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 200 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 201 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 202 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 203 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 204 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 205 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 206 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 207 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 208 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 209 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 210 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 211 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 212 | -------------------------------------------------------------------------------- /manufacturers.csv: -------------------------------------------------------------------------------- 1 | "wmi";"name";"id" 2 | "LFV";"FAW-VOLKSWAGEN";1215 3 | "1Z3";"MITSUBISHI";122 4 | "1Z5";"MITSUBISHI";123 5 | "1Z7";"MITSUBISHI";124 6 | "LGB";"DONGFENG NISSAN";1216 7 | "10T";"OSHKOSH";1 8 | "11V";"OTTAWA";2 9 | "137";"AM GENERAL, HUMMER";3 10 | "145";"HYUNDAI";4 11 | "15G";"GILLIG";5 12 | "17N";"JOHN DEERE";6 13 | "18X";"WRV";7 14 | "19U";"ACURA";8 15 | "1A4";"CHRYSLER";9 16 | "1A8";"CHRYSLER";10 17 | "1AC";"AMC";11 18 | "1AM";"AMC";12 19 | "ZPB";"LAMBORGHINI";1178 20 | "1B4";"DODGE";14 21 | "1B6";"DODGE";15 22 | "Y6L";"LUAZ, LUTSK, UKRAINE";1179 23 | "1B7";"DODGE";17 24 | "1BA";"BLUE BIRD";18 25 | "1BB";"BLUE BIRD";19 26 | "1BD";"BLUE BIRD";20 27 | "AAV";"VOLKSWAGEN";1180 28 | "1C4";"CHRYSLER";22 29 | "AHT";"TOYOTA";1181 30 | "1C8";"CHRYSLER";24 31 | "1C9";"CHANCE";25 32 | "1CY";"CRANE CARRIER";26 33 | "AFA";"FORD";1182 34 | "1D4";"DODGE";28 35 | "1D5";"DODGE";29 36 | "1D7";"DODGE";30 37 | "1D8";"DODGE";31 38 | "1EC";"FLEETWOOD";32 39 | "1F1";"FORD";33 40 | "1F4";"FORD";34 41 | "1F6";"FORD";35 42 | "BF9";"KIBO MOTORCYCLES";1183 43 | "CL9";"WALLYSCAR";1184 44 | "JA";"ISUZU";1185 45 | "JC1";"FIAT AUTOMOBILES/MAZDA";1186 46 | "JF1-JF3";"SUBARU - FUJI HEAVY INDUSTRIES";1187 47 | "1FE";"FORD";41 48 | "1FF";"FORD";42 49 | "JF4";"SAAB";1188 50 | "JF5";"PONTIAC";1189 51 | "JHL";"HONDA";1190 52 | "JHM";"HONDA";1191 53 | "JM0";"MAZDA FOR OCEANIA EXPORT";1192 54 | "JM1";"MAZDA";1193 55 | "JMB";"MITSUBISHI";1194 56 | "JM6";"MAZDA";1195 57 | "1G5";"GMC, PONTIAC";51 58 | "JN";"NISSAN";1196 59 | "JS";"SUZUKI";1197 60 | "1GA";"CHEVROLET";54 61 | "JT";"TOYOTA";1198 62 | "JY";"YAMAHA";1199 63 | "KL";"DAEWOO/GM KOREA";1200 64 | "1GE";"CADILLAC";60 65 | "1GF";"FLEXIBLE";61 66 | "1GG";"ISUZU";62 67 | "1GH";"GMC, OLDSMOBILE";63 68 | "1GJ";"GMC";64 69 | "1GK";"GMC";65 70 | "KMH";"HYUNDAI";1201 71 | "1GN";"CHEVROLET";67 72 | "KN";"KIA";1202 73 | "KPT";"SSANGYONG";1203 74 | "1HF";"HONDA";70 75 | "L2C";"CHERY JAGUAR LAND ROVER";1204 76 | "1HS";"INTERNATIONAL";73 77 | "1HT";"INTERNATIONAL";74 78 | "1HV";"INTERNATIONAL";75 79 | "L6T";"GEELY";1205 80 | "1J6";"JEEP";77 81 | "1J7";"JEEP";78 82 | "1J8";"JEEP";79 83 | "1JC";"AMC, JEEP";80 84 | "1JD";"AMC";81 85 | "1JE";"JEEP";82 86 | "1JF";"JEEP";83 87 | "1JH";"JEEP";84 88 | "1JM";"JEEP";85 89 | "1JT";"AMC, JEEP";86 90 | "1L1";"LINCOLN";87 91 | "1LN";"LINCOLN";88 92 | "LA6";"KING LONG";1206 93 | "LB3";"GEELY";1207 94 | "LBE";"BEIJING HYUNDAI";1208 95 | "1M8";"MCI";92 96 | "1MB";"MERCEDES-BENZ";93 97 | "LBV";"BMW BRILLIANCE";1209 98 | "1N4";"NISSAN";96 99 | "1N6";"DATSUN, NISSAN";97 100 | "1N9";"NEOPLAN";98 101 | "1NK";"KENWORTH";99 102 | "1NP";"PETERBILT";100 103 | "LC0";"BYD INDUSTRY";1210 104 | "LDC";"DONGFENG PEUGEOT-CITROËN";1211 105 | "1P4";"PLYMOUTH";103 106 | "1P7";"PLYMOUTH";104 107 | "1P9";"PANOZ";105 108 | "1RF";"ROADMASTER";106 109 | "1S9";"SALEEN";107 110 | "177";"THOMAS";108 111 | "1T8";"THOMAS";109 112 | "1TU";"TMC";110 113 | "1V1";"VOLKSWAGEN";111 114 | "LE4";"BEIJING BENZ";1212 115 | "1WA";"AUTOSTAR";113 116 | "1WB";"AUTOSTAR";114 117 | "1WU";"WHITE VOLVO";115 118 | "1WV";"WINNEBAGO";116 119 | "LFM";"FAW TOYOTA";1213 120 | "1XM";"AMC";118 121 | "LFP";"FAW CAR";1214 122 | "1Y1";"CHEVROLET, GEO";120 123 | "1ZW";"MERCURY";126 124 | "2A3";"CHRYSLER";127 125 | "LGJ";"DONGFENG FENGSHEN";1217 126 | "2A8";"CHRYSLER";129 127 | "2B1";"ORION";130 128 | "LGW";"GREAT WALL (HAVEL)";1218 129 | "2B4";"DODGE";132 130 | "2B5";"DODGE";133 131 | "2B6";"DODGE";134 132 | "LGX";"BYD AUTO";1219 133 | "2B8";"DODGE";136 134 | "2BC";"AMC, JEEP";137 135 | "2BD";"JEEP";138 136 | "2BT";"JEEP";139 137 | "2C1";"CHEVROLET, GEO";140 138 | "LH1";"FAW HAIMA";1220 139 | "2C4";"CHRYSLER";142 140 | "2C7";"PONTIAC";143 141 | "2C8";"CHRYSLER";144 142 | "2CC";"AMC, EAGLE";145 143 | "2CK";"GEO, PONTIAC";146 144 | "2CH";"CHEVROLET";147 145 | "2CM";"AMC";148 146 | "LHG";"GUANGZHOU HONDA";1221 147 | "LJ1";"JAC";1222 148 | "2D4";"DODGE";151 149 | "2D6";"DODGE";152 150 | "2D7";"DODGE";153 151 | "2D8";"DODGE";154 152 | "2E3";"EAGLE";155 153 | "LJD";"DONGFENG YUEDA KIA";1223 154 | "LLV";"LIFAN";1224 155 | "LMG";"GAC TRUMPCHI";1225 156 | "2FD";"FORD";159 157 | "2FF";"FORD";160 158 | "LPA";"CHANGAN PSA (DS AUTOMOBILES)";1226 159 | "LS5";"CHANGAN SUZUKI";1227 160 | "LSFA";"SAIC MAXUS";1228 161 | "LSG";"SAIC GENERAL MOTORS";1229 162 | "2FW";"STERLING";165 163 | "2G0";"GMC";167 164 | "2G5";"GMC";172 165 | "2G7";"PONTIAC";173 166 | "2G8";"CHEVROLET";174 167 | "2GA";"CHEVROLET";175 168 | "2GB";"CHEVROLET";176 169 | "2GC";"CHEVROLET";177 170 | "2GD";"GMC";178 171 | "2GJ";"GMC";179 172 | "2GK";"GMC";180 173 | "2GN";"CHEVROLET";181 174 | "2GM";"PONTIAC";182 175 | "2GT";"GMC";183 176 | "2GY";"CADILLAC";184 177 | "LSJ";"SAIC MG";1230 178 | "LSV";"SAIC VOLKSWAGEN";1231 179 | "LTV";"FAW TOYOTA (TIANJIN)";1232 180 | "LVG";"GAC TOYOTA";1233 181 | "2HN";"ACURA";190 182 | "2HS";"INTERNATIONAL";191 183 | "2HT";"INTERNATIONAL";192 184 | "2J4";"JEEP";193 185 | "2J6";"JEEP";194 186 | "2J7";"JEEP";195 187 | "2LM";"LINCOLN";196 188 | "2M2";"MACK";197 189 | "2ME";"MERCURY";198 190 | "2MH";"MERCURY";199 191 | "2MR";"MERCURY";200 192 | "2NK";"KENWORTH";201 193 | "2NP";"PETERBILT";202 194 | "LVH";"DONGFENG HONDA";1234 195 | "LVR";"CHANGAN MAZDA";1235 196 | "2P4";"PLYMOUTH";205 197 | "2P5";"PLYMOUTH";206 198 | "2P9";"PREVOST";207 199 | "2PC";"PREVOST";208 200 | "2S2";"SUZUKI";209 201 | "2S3";"SUZUKI";210 202 | "2S4";"SUZUKI";211 203 | "2T1";"TOYOTA";212 204 | "2T2";"LEXUS";213 205 | "2T3";"TOYOTA";214 206 | "LVS";"CHANGAN FORD";1236 207 | "LVV";"CHERY";1237 208 | "LWV";"GAC FIAT";1238 209 | "LZW";"SAIC GM WULING";1239 210 | "LZY";"YUTONG";1240 211 | "2XK";"KENWORTH";220 212 | "2XM";"EAGLE";221 213 | "2XP";"PETERBILT";222 214 | "3A4";"CHRYSLER";223 215 | "3A8";"CHRYSLER";224 216 | "3AB";"DINA";225 217 | "3AL";"FREIGHTLINER";226 218 | "3B3";"DODGE";227 219 | "3B4";"DODGE";228 220 | "3B6";"DODGE";229 221 | "3B7";"DODGE";230 222 | "3BK";"KENWORTH";231 223 | "3BP";"PETERBILT";232 224 | "3C3";"CHRYSLER";233 225 | "MNT";"NISSAN";1241 226 | "3C8";"CHRYSLER";235 227 | "3CA";"CHRYSLER";236 228 | "3CE";"VOLVO";237 229 | "3CZ";"HONDA";238 230 | "MM0";"MAZDA";1242 231 | "3D5";"DODGE";240 232 | "3D6";"DODGE";241 233 | "3D7";"DODGE";242 234 | "MMB";"MITSUBISHI";1243 235 | "3FB";"FORD";244 236 | "3FC";"FORD";245 237 | "3FD";"FORD";246 238 | "MRH";"HONDA";1244 239 | "3FF";"FORD";248 240 | "3FR";"FORD";249 241 | "3FT";"FORD";250 242 | "3G1";"CHEVROLET";251 243 | "3G2";"PONTIAC";252 244 | "3G4";"BUICK";253 245 | "3G5";"BUICK";254 246 | "3G7";"PONTIAC";255 247 | "3G8";"CHEVROLET";256 248 | "3GA";"CHEVROLET";257 249 | "3GB";"CHEVROLET";258 250 | "3GC";"CHEVROLET";259 251 | "3GD";"GMC";260 252 | "3GE";"CHEVROLET";261 253 | "3GG";"CADILLAC";262 254 | "3GK";"GMC";263 255 | "3GN";"CHEVROLET";264 256 | "3GM";"PONTIAC";265 257 | "3GT";"GMC";266 258 | "3GY";"CADILLAC";267 259 | "3H1";"HONDA";268 260 | "3HA";"INTERNATIONAL";269 261 | "3HS";"INTERNATIONAL";272 262 | "3HT";"INTERNATIONAL";273 263 | "3LN";"LINCOLN";274 264 | "3MA";"MERCURY";275 265 | "3ME";"MERCURY";276 266 | "3N1";"NISSAN";277 267 | "3N6";"NISSAN";278 268 | "3N8";"NISSAN";279 269 | "3NK";"KENWORTH";280 270 | "3NM";"PETERBILT";281 271 | "MS0";"KIA";1245 272 | "3TM";"TOYOTA";283 273 | "NMT";"TOYOTA";1246 274 | "3WK";"KENWORTH";285 275 | "45V";"UTILIMASTER";286 276 | "46G";"GILLIG";287 277 | "478";"HONDA";288 278 | "49H";"STERLING";289 279 | "4A3";"MITSUBISHI";290 280 | "4A4";"MITSUBISHI";291 281 | "4B3";"DODGE";292 282 | "4C3";"CHRYSLER";293 283 | "4CD";"OSHKOSH";294 284 | "4DR";"GENESIS, INTERNATIONAL";295 285 | "4E3";"EAGLE";296 286 | "4F2";"MAZDA";297 287 | "4F4";"MAZDA";298 288 | "4G1";"CHEVROLET";299 289 | "4G2";"PONTIAC";300 290 | "4GB";"CHEVROLET";301 291 | "4GD";"GMC";302 292 | "4GT";"ISUZU, WHITEGMC";303 293 | "4J4";"JEEP";304 294 | "NM0";"FORD OTOSAN";1247 295 | "4KB";"CHEVROLET";306 296 | "4KD";"GMC";307 297 | "4KL";"ISUZU";308 298 | "4M2";"MERCURY";309 299 | "4N1";"NISSAN";310 300 | "4N2";"NISSAN";311 301 | "4NU";"ISUZU";312 302 | "4P3";"PLYMOUTH";313 303 | "PL1";"PROTON";1248 304 | "4S1";"ISUZU";315 305 | "4S2";"ISUZU";316 306 | "4S7";"SPARTAN";320 307 | "4SL";"MAGNUM";321 308 | "4T1";"TOYOTA";322 309 | "4T3";"TOYOTA";323 310 | "4TA";"TOYOTA";324 311 | "PL8";"HYUNDAI INOKOM";1249 312 | "PLP";"SUBARU";1250 313 | "PMH";"HONDA";1251 314 | "PML";"HICOM";1252 315 | "PM1";"BMW";1253 316 | "PM2";"PERODUA";1254 317 | "PM9";"BUFORI";1255 318 | "4VA";"VOLVO";332 319 | "4VG";"VOLVO, WHITEGMC";333 320 | "4VH";"VOLVO";334 321 | "4VJ";"VOLVO";335 322 | "4VK";"VOLVO";336 323 | "PMK";"HONDA BOON SIEW";1256 324 | "PMN";"MODENAS";1257 325 | "PMV";"YAMAHA HONG LEONG";1258 326 | "5AS";"GEM";340 327 | "5B4";"WORKHORSE";341 328 | "5CK";"WESTERN STAR TRUCKS";342 329 | "5FP";"HONDA";344 330 | "5FY";"NEW FLYER";345 331 | "5GA";"BUICK";346 332 | "5GR";"HUMMER";347 333 | "5GT";"HUMMER";348 334 | "5GZ";"SATURN";349 335 | "5J8";"ACURA";351 336 | "5KJ";"WESTERN STAR TRUCKS";352 337 | "5KK";"WESTERN STAR TRUCKS";353 338 | "5LM";"LINCOLN";354 339 | "5LT";"LINCOLN";355 340 | "PNA";"PEUGEOT";1260 341 | "5N3";"INFINITI";357 342 | "PNV";"VOLVO CARS";1261 343 | "5PV";"HINO";360 344 | "5SA";"SUZUKI";361 345 | "5S3";"SAAB";362 346 | "5SX";"AMERCIAN LEFRANCE";363 347 | "5T4";"WORKHORSE";364 348 | "5TB";"TOYOTA";365 349 | "5TD";"TOYOTA";366 350 | "5TE";"TOYOTA";367 351 | "5TF";"TOYOTA";368 352 | "5UM";"BMW";369 353 | "5UX";"BMW";370 354 | "5XY";"KIA";371 355 | "5Y2";"PONTIAC";372 356 | "5YM";"BMW";373 357 | "5Z6";"SUZUKI";374 358 | "PN1";"TOYOTA";1262 359 | "PN8";"NISSAN";1263 360 | "PP1";"MAZDA";1264 361 | "PP3";"HYUNDAI";1265 362 | "PPP";"SUZUKI";1266 363 | "PR8";"FORD";1267 364 | "SAB";"OPTARE";1268 365 | "SAJ";"JAGUAR";1269 366 | "6MP";"MERCURY";383 367 | "SAL";"LAND ROVER";1270 368 | "SAR";"ROVER";1271 369 | "SAT";"TRIUMPH";1272 370 | "SB1";"TOYOTA";1273 371 | "SBM";"MCLAREN AUTOMOTIVE";1274 372 | "SCC";"LOTUS CARS";1275 373 | "SCF";"ASTON MARTIN LAGONDA LIMITED";1276 374 | "SCE";"DELOREAN";1277 375 | "SFD";"ALEXANDER DENNIS";1278 376 | "SFE";"ALEXANDER DENNIS (NORTH AMERICA)";1279 377 | "SHH";"HONDA";1280 378 | "SHS";"HONDA";1281 379 | "SJN";"NISSAN";1282 380 | "SUD";"WIELTON";1283 381 | "TCC";"MICRO COMPACT CAR";1284 382 | "TEB";"JOHNSTON SWEEPER (BUCHER)";1285 383 | "TMA";"HYUNDAI";1286 384 | "TMB";"ŠKODA";1287 385 | "TRU";"AUDI";1288 386 | "TSM";"SUZUKI";1289 387 | "U5Y";"KIA";1290 388 | "UU";"DACIA";1291 389 | "VA0";"ÖAF";1292 390 | "VF1";"RENAULT";1293 391 | "9DB";"MERCEDES-BENZ";409 392 | "9DW";"VOLKSWAGEN";410 393 | "VF2";"RENAULT";1294 394 | "VF3";"PEUGEOT";1295 395 | "VF4";"TALBOT";1296 396 | "VF5";"IVECO UNIC SA";1297 397 | "VF6";"RENAULT TRUCKS/VOLVO";1298 398 | "VF7";"CITROËN";1299 399 | "J81";"CHEVROLET,GEO";418 400 | "J87";"ISUZU";419 401 | "J8B";"CHEVROLET";420 402 | "J8C";"CHEVROLET";421 403 | "J8D";"GMC";422 404 | "J8Y";"CHEVROLET";423 405 | "J8Z";"CHEVROLET";424 406 | "VF8";"MATRA/TALBOT/SIMCA";1300 407 | "VF9";"BUGATTI";1301 408 | "JA7";"MITSUBISHI";427 409 | "JAA";"ISUZU";428 410 | "JAB";"ISUZU";429 411 | "JAC";"ISUZU";430 412 | "JAE";"ACURA";431 413 | "JAL";"ISUZU";432 414 | "JB3";"DODGE";433 415 | "JB4";"DODGE";434 416 | "JB7";"DODGE";435 417 | "JC2";"FORD";436 418 | "JD1";"DAIHATSU";437 419 | "JD2";"DAIHATSU";438 420 | "JE3";"EAGLE";439 421 | "JF1";"SUBARU";440 422 | "JF2";"SUBARU";441 423 | "JF3";"SUBARU";442 424 | "JG1";"CHEVROLET, GEO";444 425 | "JG7";"PONTIAC";445 426 | "JGC";"CHEVROLET, GEO";446 427 | "JH2";"HONDA";447 428 | "JH3";"HONDA";448 429 | "JH4";"ACURA";449 430 | "JHB";"HINO";450 431 | "JHK";"HONDA";451 432 | "JJ3";"CHRYSLER";454 433 | "VFE";"IVECOBUS";1302 434 | "JL6";"MITSUBISHI";456 435 | "JLS";"STERLING";457 436 | "JK8";"SUZUKI";458 437 | "JKS";"SUZUKI";459 438 | "JM2";"MAZDA";461 439 | "JM3";"MAZDA";462 440 | "VNK";"TOYOTA";1303 441 | "VR1";"DS AUTOMOBILES";1304 442 | "JN1";"DATSUN, NISSAN";465 443 | "JN3";"NISSAN";466 444 | "JN4";"NISSAN";467 445 | "JN6";"DATSUN, NISSAN";468 446 | "JN8";"NISSAN";469 447 | "JNA";"NISSAN";470 448 | "JNK";"INFINITI";471 449 | "JNR";"INFINITI";472 450 | "JNX";"INFINITI";473 451 | "JP3";"PLYMOUTH";474 452 | "JP4";"PLYMOUTH";475 453 | "JP7";"PLYMOUTH";476 454 | "JR2X";"ISUZU";477 455 | "JS1";"SUZUKI";478 456 | "JS2";"SUZUKI";479 457 | "JS3";"SUZUKI";480 458 | "JS4";"SUZUKI";481 459 | "JSA";"SUZUKI";482 460 | "JSL";"SUZUKI";483 461 | "JT2";"TOYOTA";484 462 | "JT3";"TOYOTA";485 463 | "JT4";"TOYOTA";486 464 | "JT5";"TOYOTA";487 465 | "JT6";"LEXUS";488 466 | "JT8";"LEXUS";489 467 | "JTD";"TOYOTA";490 468 | "JTE";"TOYOTA";491 469 | "JTH";"LEXUS";492 470 | "JTJ";"LEXUS";493 471 | "JTK";"SCION";494 472 | "JTL";"SCION";495 473 | "JTM";"TOYOTA";496 474 | "JTN";"TOYOTA";497 475 | "JW6";"MITSUBISHI";498 476 | "JW7";"MITSUBISHI";499 477 | "KL1";"CHEVROLET";500 478 | "KL2";"PONTIAC";501 479 | "KL5";"SUZUKI";502 480 | "KL7";"ASUNA";503 481 | "KLA";"DAEWOO";504 482 | "VSS";"SEAT";1305 483 | "KM4";"SUZUKI";506 484 | "KM8";"HYUNDAI";507 485 | "KMF";"HYUNDAI";508 486 | "KNA";"KIA";510 487 | "KNB";"KIA";511 488 | "KNC";"KIA";512 489 | "KND";"HYUNDAI, KIA";513 490 | "KNJ";"FORD";514 491 | "VS7";"CITROËN";1306 492 | "KPH";"MITSUBISHI";517 493 | "L56";"RENAULT SAMSUNG";519 494 | "VV9";"TAURO SPORT AUTO";1307 495 | "WAG";"NEOPLAN";1308 496 | "LES";"ISUZU";522 497 | "WAU";"AUDI";1309 498 | "WAP";"ALPINA";1310 499 | "LM1";"SUZUKI";525 500 | "LM4";"SUZUKI";526 501 | "LM5";"ISUZU";527 502 | "LN1";"SUZUKI";528 503 | "WBA";"BMW";1311 504 | "WBS";"BMW M";1312 505 | "WBX";"BMW";1313 506 | "WDB";"MERCEDES-BENZ";1314 507 | "WDC, WDD, WMX";"DAIMLERCHRYSLER AG/DAIMLER AG";1315 508 | "WEB";"EVOBUS";1316 509 | "WF0";"FORD OF EUROPE";1317 510 | "WJM";"IVECO";1318 511 | "WJR";"IRMSCHER";1319 512 | "WKK";"KARL KÄSSBOHRER FAHRZEUGWERKE";1320 513 | "WMA";"MAN";1321 514 | "WME";"SMART";1322 515 | "WMW";"MINI";1323 516 | "WP0";"PORSCHE CAR";1324 517 | "WP1";"PORSCHE SUV";1325 518 | "WUA";"QUATTRO";1326 519 | "WVG";"VOLKSWAGEN";1327 520 | "WVW";"VOLKSWAGEN";1328 521 | "ML3";"DODGE";547 522 | "WV1";"VOLKSWAGEN COMMERCIAL VEHICLES";1329 523 | "WV2";"VOLKSWAGEN COMMERCIAL VEHICLES";1330 524 | "W09";"RUF AUTOMOBILE";1331 525 | "W0L";"OPEL/VAUXHALL";1332 526 | "W0SV";"OPEL SPECIAL VEHICLES";1333 527 | "XLR";"DAF TRUCKS";1334 528 | "XTA";"AVTOVAZ";1335 529 | "XTB";"AZLK";1336 530 | "YK1";"SAAB";1337 531 | "YS2";"SCANIA, SÖDERTÄLJE";1338 532 | "YS3";"SAAB";1339 533 | "YS4";"SCANIA, KATRINEHOLM";1340 534 | "YTN";"SAAB NEVS";1341 535 | "YV1";"VOLVO CARS";1342 536 | "YV2";"VOLVO TRUCKS";1343 537 | "YV3";"VOLVO BUSES";1344 538 | "YT9";"KOENIGSEGG AUTOMOTIVE AB";1345 539 | "ZA9";"BUGATTI";1346 540 | "ZAM";"MASERATI";1347 541 | "ZAP";"PIAGGIO";1348 542 | "ZAR";"ALFA ROMEO";1349 543 | "ZCF";"IVECO";1350 544 | "SA9";"MORGAN";570 545 | "ZFA";"FIAT";1351 546 | "ZFF";"FERRARI";1352 547 | "ZGA";"IVECOBUS";1353 548 | "SAX";"STERLING";575 549 | "ZHW";"LAMBORGHINI";1354 550 | "ZLA";"LANCIA";1355 551 | "1B";"DODGE";1356 552 | "1C";"CHRYSLER";1357 553 | "1F";"FORD";1358 554 | "1G";"GENERAL MOTORS";1359 555 | "1G1";"CHEVROLET";1360 556 | "1G3";"OLDSMOBILE";1361 557 | "1G4";"BUICK";1362 558 | "1G9";"GOOGLE";1363 559 | "1GB";"CHEVROLET INCOMPLETE VEHICLES";1364 560 | "SU9";"SOLARIS";587 561 | "1GC";"CHEVROLET";1365 562 | "1GD";"GMC INCOMPLETE VEHICLES";1366 563 | "TK9";"SOR";590 564 | "1GM";"PONTIAC";1367 565 | "1HG";"HONDA";1368 566 | "1J";"JEEP";1369 567 | "1L";"LINCOLN";1370 568 | "1M";"MERCURY";1371 569 | "1MR";"CONTINENTAL";1372 570 | "1N";"NISSAN";1373 571 | "1VW";"VOLKSWAGEN";1374 572 | "1YV";"MAZDA";1375 573 | "1ZV";"FORD";1376 574 | "2DG";"ONTARIO DRIVE & GEAR";1377 575 | "2F";"FORD";1378 576 | "2Gx";"GENERAL MOTORS";1379 577 | "2G1";"CHEVROLET";1380 578 | "2G2";"PONTIAC";1381 579 | "2G9";"GNOME HOMES";1382 580 | "2HG";"HONDA";1383 581 | "2HH";"ACURA";1384 582 | "2HJ";"HONDA";1385 583 | "2HK";"HONDA";1386 584 | "2HM";"HYUNDAI";1387 585 | "2L9";"LES CONTENANTS DURABAC";1388 586 | "2LN";"LINCOLN";1389 587 | "2M";"MERCURY";1390 588 | "2T";"TOYOTA";1391 589 | "3F";"FORD";1392 590 | "3G";"GENERAL MOTORS";1393 591 | "VG6";"MACK";620 592 | "3HG";"HONDA";1394 593 | "3HM";"HONDA";1395 594 | "3KP";"KIA";1396 595 | "3N";"NISSAN";1397 596 | "3VW";"VOLKSWAGEN";1398 597 | "4F";"MAZDA";1399 598 | "4J";"MERCEDES-BENZ";1400 599 | "4M";"MERCURY";1401 600 | "4S3";"SUBARU";1402 601 | "4S4";"SUBARU";1403 602 | "4S6";"HONDA";1404 603 | "4T";"TOYOTA";1405 604 | "VTM";"HONDA";633 605 | "4US";"BMW";1406 606 | "5FN";"HONDA";1407 607 | "5J6";"HONDA";1408 608 | "5L";"LINCOLN";1409 609 | "5N1";"NISSAN";1410 610 | "W06";"CADILLAC";639 611 | "5NM";"HYUNDAI";1411 612 | "5NP";"HYUNDAI";1412 613 | "5T";"TOYOTA";1413 614 | "WB1";"BMW";644 615 | "5U";"BMW";1414 616 | "5X";"HYUNDAI/KIA";1415 617 | "WBY";"BMW";648 618 | "WD0";"DODGE";649 619 | "WD1";"DODGE";650 620 | "WD2";"DODGE";651 621 | "WD3";"MERCEDES-BENZ";652 622 | "WD4";"MERCEDES-BENZ";653 623 | "WD5";"DODGE";654 624 | "WD8";"DODGE";655 625 | "5YJ";"TESLA";1416 626 | "55";"MERCEDES-BENZ";1417 627 | "6F";"FORD";1418 628 | "6G";"GENERAL MOTORS";1419 629 | "WDP";"DODGE";660 630 | "WDX";"DODGE";661 631 | "WDY";"DODGE";662 632 | "WDZ";"MERCEDES-BENZ";663 633 | "6G1";"CHEVROLET";1420 634 | "6G2";"PONTIAC";1421 635 | "WF1";"MERKUR";666 636 | "6H";"HOLDEN";1422 637 | "6MM";"MITSUBISHI";1423 638 | "6T1";"TOYOTA";1424 639 | "6U9";"JAPANESE IMPORTS";1425 640 | "7A1";"MITSUBISHI";1426 641 | "7A3";"HONDA";1427 642 | "7A4";"TOYOTA";1428 643 | "7A5";"FORD";1429 644 | "7A8";"NZ TRANSPORT AGENCY (PRE-2009)";1430 645 | "7AT";"NZ TRANSPORT AGENCY (POST-2009)";1431 646 | "8AP";"FIAT";1432 647 | "8AF";"FORD";1433 648 | "8AG";"GENERAL MOTORS";1434 649 | "8AW";"VOLKSWAGEN";1435 650 | "8AJ";"TOYOTA";1436 651 | "XL9";"SPYKER";684 652 | "8A1";"RENAULT";1437 653 | "8AC";"MERCEDES BENZ";1438 654 | "8BC";"CITROËN";1439 655 | "8AD";"PEUGEOT";1440 656 | "8C3";"HONDA";1441 657 | "8AT";"IVECO";1442 658 | "9BD";"FIAT AUTOMÓVEIS";1443 659 | "9BG";"GENERAL MOTORS";1444 660 | "9BW";"VOLKSWAGEN";1445 661 | "YC1";"HONDA";699 662 | "9BF";"FORD";1446 663 | "YB3";"VOLVO";701 664 | "93H";"HONDA";1447 665 | "9BR";"TOYOTA";1448 666 | "936";"PEUGEOT";1449 667 | "935";"CITROËN";1450 668 | "93Y";"RENAULT";1451 669 | "93X";"SOUZA RAMOS - MITSUBISHI / SUZUKI";1452 670 | "9BH";"HYUNDAI";1453 671 | "95P";"CAOA / HYUNDAI";1454 672 | "94D";"NISSAN";1455 673 | "94N";"RWM BRAZIL";1456 674 | "98R";"CHERY";1457 675 | "988";"JEEP";1458 676 | "YV5";"VOLVO";714 677 | "98M";"BMW";1459 678 | "9BM";"MERCEDES-BENZ";1460 679 | "99A";"AUDI";1461 680 | "99J";"JAGUAR LAND ROVER";1462 681 | "9C2";"HONDA MOTORCYCLES";1463 682 | "9C6";"YAMAHA";1464 683 | "ZC2";"CHRYSLER";722 684 | "9CD";"SUZUKI (MOTORCYCLES)";1465 685 | "93W";"FIAT PROFESSIONAL";1466 686 | "ZDC";"HONDA";725 687 | "93Z";"IVECO";1467 688 | "953";"VW TRUCKS / MAN";1468 689 | "9BS";"SCANIA";1469 690 | "9BV";"VOLVO TRUCKS";1470 691 | "9FB";"RENAULT";1471 692 | "9UJ";"CHERY";1472 693 | "9UK";"LIFAN";1473 694 | "9UW";"KIA";1474 695 | "AC5";"HYUNDAI SOUTH AFRICA";743 696 | "ADD";"HYUNDAI SOUTH AFRICA";744 697 | "JA3";"MITSUBISHI";747 698 | "JA4";"MITSUBISHI";748 699 | "JD";"DAIHATSU";750 700 | "JF";"FUJI HEAVY INDUSTRIES (SUBARU)";751 701 | "JH";"HONDA";752 702 | "JK";"KAWASAKI (MOTORCYCLES)";753 703 | "JL5";"MITSUBISHI FUSO";754 704 | "JMY";"MITSUBISHI MOTORS";756 705 | "JMZ";"MAZDA";757 706 | "KM";"HYUNDAI";763 707 | "KMY";"DAELIM (MOTORCYCLES)";764 708 | "KM1";"HYOSUNG (MOTORCYCLES)";765 709 | "KNM";"RENAULT SAMSUNG";767 710 | "KPA";"SSANGYONG";768 711 | "LAN";"CHANGZHOU YAMASAKI MOTORCYCLE";770 712 | "LBB";"ZHEJIANG QIANJIANG MOTORCYCLE (KEEWAY/GENERIC)";771 713 | "LBM";"ZONGSHEN PIAGGIO";773 714 | "LBP";"CHONGQING JAINSHE YAMAHA (MOTORCYCLES)";774 715 | "LB2";"GEELY MOTORCYCLES";775 716 | "LCE";"HANGZHOU CHUNFENG MOTORCYCLES (CFMOTO)";776 717 | "LDD";"DANDONG HUANGHAI AUTOMOBILE";778 718 | "LDN";"SOUEAST MOTOR";779 719 | "LDY";"ZHONGTONG COACH";780 720 | "LET";"JIANGLING-ISUZU MOTORS";781 721 | "LFB";"FAW";783 722 | "LFG";"TAIZHOU CHUANL MOTORCYCLE MANUFACTURING";784 723 | "LFT";"FAW";786 724 | "LFW";"FAW JIEFANG";788 725 | "LFY";"CHANGSHU LIGHT MOTORCYCLE FACTORY";789 726 | "LGH";"QOROS (FORMERLY DONG FENG (DFM))";791 727 | "LHB";"BEIJING AUTOMOTIVE INDUSTRY HOLDING";793 728 | "LJC";"JAC";795 729 | "LKL";"SUZHOU KING LONG";797 730 | "LL6";"HUNAN CHANGFENG MANUFACTURE JOINT-STOCK";798 731 | "LL8";"LINHAI (ATV)";799 732 | "LMC";"SUZUKI HONG KONG (MOTORCYCLES)";800 733 | "LPR";"YAMAHA HONG KONG (MOTORCYCLES)";801 734 | "LSY";"BRILLIANCE ZHONGHUA";805 735 | "LUC";"GUANGQI HONDA";807 736 | "LVZ";"DONG FENG SOKON MOTOR COMPANY (DFSK)";810 737 | "LZM";"MAN CHINA";811 738 | "LZE";"ISUZU GUANGZHOU";812 739 | "LZG";"SHAANXI AUTOMOBILE GROUP";813 740 | "LZP";"ZHONGSHAN GUOCHI MOTORCYCLE (BAOTIAN)";814 741 | "LZZ";"CHONGQING SHUANGZING MECH & ELEC (HOWO)";816 742 | "L4B";"XINGYUE GROUP (MOTORCYCLES)";817 743 | "L5C";"KANGDI (ATV)";818 744 | "L5K";"ZHEJIANG YONGKANG EASY VEHICLE";819 745 | "L5N";"ZHEJIANG TAOTAO";820 746 | "L5Y";"MERATO MOTORCYCLE TAIZHOU ZHONGNENG";821 747 | "L85";"ZHEJIANG YONGKANG HUABAO ELECTRIC APPLIANCE";822 748 | "L8X";"ZHEJIANG SUMMIT HUAWIN MOTORCYCLE";823 749 | "MAB";"MAHINDRA & MAHINDRA";824 750 | "MAC";"MAHINDRA & MAHINDRA";825 751 | "MAJ";"FORD INDIA";826 752 | "MAK";"HONDA SIEL CARS INDIA";827 753 | "MAL";"HYUNDAI";828 754 | "MAT";"TATA MOTORS";829 755 | "MA1";"MAHINDRA & MAHINDRA";830 756 | "MA3";"SUZUKI INDIA (MARUTI)";831 757 | "MA6";"GM INDIA";832 758 | "MA7";"MITSUBISHI INDIA (FORMERLY HONDA)";833 759 | "MBH";"SUZUKI INDIA (MARUTI)";834 760 | "MBJ";"TOYOTA INDIA";835 761 | "MBR";"MERCEDES-BENZ INDIA";836 762 | "MB1";"ASHOK LEYLAND";837 763 | "MCA";"FIAT INDIA";838 764 | "MCB";"GM INDIA";839 765 | "MC2";"VOLVO EICHER COMMERCIAL VEHICLES LIMITED.";840 766 | "MDH";"NISSAN INDIA";841 767 | "MD2";"BAJAJ AUTO";842 768 | "MEE";"RENAULT INDIA";843 769 | "MEX";"VOLKSWAGEN INDIA";844 770 | "MHF";"TOYOTA INDONESIA";845 771 | "MHR";"HONDA INDONESIA";846 772 | "MLC";"SUZUKI THAILAND";847 773 | "MLH";"HONDA THAILAND";848 774 | "MMC";"MITSUBISHI THAILAND";850 775 | "MMM";"CHEVROLET THAILAND";851 776 | "MMT";"MITSUBISHI THAILAND";852 777 | "MM8";"MAZDA THAILAND";853 778 | "MNB";"FORD THAILAND";854 779 | "MPA";"ISUZU THAILAND";856 780 | "MP1";"ISUZU THAILAND";857 781 | "MR0";"TOYOTA THAILAND";859 782 | "NLA";"HONDA TÜRKIYE";860 783 | "NLE";"MERCEDES-BENZ TÜRK TRUCK";861 784 | "NLH";"HYUNDAI ASSAN";862 785 | "NM4";"TOFAŞ TÜRK";864 786 | "PE1";"FORD PHILLIPINES";866 787 | "PE3";"MAZDA PHILLIPINES";867 788 | "RFB";"KYMCO";870 789 | "RFG";"SANYANG SYM";871 790 | "RFL";"ADLY";872 791 | "RFT";"CPI";873 792 | "RF3";"AEON MOTOR";874 793 | "SCA";"ROLLS ROYCE";880 794 | "SCB";"BENTLEY";881 795 | "SDB";"PEUGEOT UK (FORMERLY TALBOT)";885 796 | "SED";"GENERAL MOTORS LUTON PLANT";886 797 | "SEY";"LDV";887 798 | "SFA";"FORD UK";888 799 | "SKF";"VAUXHALL";893 800 | "SLP";"JCB RESEARCH UK";894 801 | "SMT";"TRIUMPH MOTORCYCLES";895 802 | "SUF";"FIAT AUTO POLAND";896 803 | "SUL";"FSC (POLAND)";897 804 | "SUP";"FSO-DAEWOO (POLAND)";898 805 | "SUU";"SOLARIS BUS & COACH (POLAND)";899 806 | "TDM";"QUANTYA SWISS ELECTRIC MOVEMENT (SWITZERLAND)";901 807 | "TMK";"KAROSA (CZECH REPUBLIC)";904 808 | "TMP";"ŠKODA TROLLEYBUSES (CZECH REPUBLIC)";905 809 | "TMT";"TATRA (CZECH REPUBLIC)";906 810 | "TM9";"ŠKODA TROLLEYBUSES (CZECH REPUBLIC)";907 811 | "TNE";"TAZ";908 812 | "TN9";"KAROSA (CZECH REPUBLIC)";909 813 | "TRA";"IKARUS BUS";910 814 | "TSE";"IKARUS EGYEDI AUTOBUSZGYAR";912 815 | "TW1";"TOYOTA CAETANO PORTUGAL";914 816 | "TYA";"MITSUBISHI TRUCKS PORTUGAL";915 817 | "TYB";"MITSUBISHI TRUCKS PORTUGAL";916 818 | "UU1";"RENAULT DACIA";917 819 | "UU3";"ARO";918 820 | "UU6";"DAEWOO ROMANIA";919 821 | "U6Y";"KIA MOTORS SLOVAKIA";921 822 | "VAG";"MAGNA STEYR PUCH";922 823 | "VAN";"MAN AUSTRIA";923 824 | "VBK";"KTM (MOTORCYCLES)";924 825 | "VG5";"MBK (MOTORCYCLES)";932 826 | "VLU";"SCANIA FRANCE";933 827 | "VN1";"SOVAB (FRANCE)";934 828 | "VNE";"IRISBUS (FRANCE)";935 829 | "VNV";"RENAULT-NISSAN";937 830 | "VSA";"MERCEDES-BENZ SPAIN";938 831 | "VSE";"SUZUKI SPAIN (SANTANA MOTORS)";939 832 | "VSK";"NISSAN SPAIN";940 833 | "VSX";"OPEL SPAIN";942 834 | "VS6";"FORD SPAIN";943 835 | "VS9";"CARROCERIAS AYATS (SPAIN)";945 836 | "VTH";"DERBI (MOTORCYCLES)";946 837 | "VTT";"SUZUKI SPAIN (MOTORCYCLES)";947 838 | "VWA";"NISSAN SPAIN";949 839 | "VWV";"VOLKSWAGEN SPAIN";950 840 | "VX1";"ZASTAVA / YUGO SERBIA";951 841 | "WA1";"AUDI SUV";954 842 | "WDA";"DAIMLER";957 843 | "WDC";"DAIMLERCHRYSLER";959 844 | "WDD";"MERCEDES-BENZ";960 845 | "WDF";"MERCEDES-BENZ (COMMERCIAL VEHICLES)";961 846 | "WMX";"MERCEDES-AMG";968 847 | "WV3";"VOLKSWAGEN TRUCKS";977 848 | "XLB";"VOLVO (NEDCAR)";978 849 | "XLE";"SCANIA NETHERLANDS";979 850 | "XMC";"MITSUBISHI (NEDCAR)";981 851 | "XTT";"UAZ/SOLLERS (RUSSIA)";983 852 | "XUF";"GENERAL MOTORS RUSSIA";984 853 | "XUU";"AVTOTOR (RUSSIA";985 854 | "XW8";"VOLKSWAGEN GROUP RUSSIA";986 855 | "XWB";"UZ-DAEWOO (UZBEKISTAN)";987 856 | "XWE";"AVTOTOR (RUSSIA";988 857 | "X4X";"AVTOTOR (RUSSIA";989 858 | "X7L";"RENAULT AVTOFRAMOS (RUSSIA)";990 859 | "X7M";"HYUNDAI TAGAZ (RUSSIA)";991 860 | "YBW";"VOLKSWAGEN BELGIUM";992 861 | "YCM";"MAZDA BELGIUM";993 862 | "YE2";"VAN HOOL (BUSES)";994 863 | "YU7";"HUSABERG (MOTORCYCLES)";1000 864 | "YV4";"VOLVO CARS";1002 865 | "Y6D";"ZAPOROZHETS/AVTOZAZ (UKRAINE)";1005 866 | "ZAA";"AUTOBIANCHI";1006 867 | "ZBN";"BENELLI";1010 868 | "ZCG";"CAGIVA SPA / MV AGUSTA";1011 869 | "ZDM";"DUCATI MOTOR HOLDINGS SPA";1013 870 | "ZDF";"FERRARI DINO";1014 871 | "ZD0";"YAMAHA ITALY";1015 872 | "ZD3";"BETA MOTOR";1016 873 | "ZD4";"APRILIA";1017 874 | "ZFC";"FIAT V.I.";1019 875 | "ZGU";"MOTO GUZZI";1021 876 | "ZJM";"MALAGUTI";1023 877 | "ZJN";"INNOCENTI";1024 878 | "ZKH";"HUSQVARNA MOTORCYCLES ITALY";1025 879 | "ZOM";"OM";1027 880 | "Z8M";"MARUSSIA (RUSSIA)";1028 881 | "1B3";"DODGE";1029 882 | "1C3";"CHRYSLER";1030 883 | "1C6";"CHRYSLER";1031 884 | "1D3";"DODGE";1032 885 | "1FA";"FORD MOTOR COMPANY";1033 886 | "1FB";"FORD MOTOR COMPANY";1034 887 | "1FC";"FORD MOTOR COMPANY";1035 888 | "1FD";"FORD MOTOR COMPANY";1036 889 | "1FM";"FORD MOTOR COMPANY";1037 890 | "1FT";"FORD MOTOR COMPANY";1038 891 | "1FU";"FREIGHTLINER";1039 892 | "1FV";"FREIGHTLINER";1040 893 | "1F9";"FWD CORP.";1041 894 | "1GT";"GMC TRUCK USA";1044 895 | "1G2";"PONTIAC USA";1046 896 | "1G6";"CADILLAC USA";1049 897 | "1G8";"SATURN USA";1050 898 | "1GY";"CADILLAC USA";1052 899 | "1H";" HONDA USA";1053 900 | "1HD";"HARLEY-DAVIDSON";1054 901 | "1J4";"JEEP";1055 902 | "1ME";"MERCURY USA";1057 903 | "1M1";"MACK TRUCK USA";1058 904 | "1M2";"MACK TRUCK USA";1059 905 | "1M3";"MACK TRUCK USA";1060 906 | "1M4";"MACK TRUCK USA";1061 907 | "1M9";"MYNATT TRUCK & EQUIPMENT";1062 908 | "1NX";"NUMMI USA";1064 909 | "1P3";"PLYMOUTH USA";1065 910 | "1R9";"ROADRUNNER HAY SQUEEZE USA";1066 911 | "1XK";"KENWORTH USA";1068 912 | "1XP";"PETERBILT USA";1069 913 | "2A4";"CHRYSLER CANADA";1072 914 | "2B3";"DODGE CANADA";1073 915 | "2B7";"DODGE CANADA";1074 916 | "2C3";"CHRYSLER CANADA";1075 917 | "2CN";"CAMI";1076 918 | "2D3";"DODGE CANADA";1077 919 | "2FA";"FORD MOTOR COMPANY CANADA";1078 920 | "2FB";"FORD MOTOR COMPANY CANADA";1079 921 | "2FC";"FORD MOTOR COMPANY CANADA";1080 922 | "2FM";"FORD MOTOR COMPANY CANADA";1081 923 | "2FT";"FORD MOTOR COMPANY CANADA";1082 924 | "2FU";"FREIGHTLINER";1083 925 | "2FV";"FREIGHTLINER";1084 926 | "2FZ";"STERLING";1085 927 | "2G";"GENERAL MOTORS CANADA";1086 928 | "2G3";"OLDSMOBILE CANADA";1089 929 | "2G4";"BUICK CANADA";1090 930 | "2NV";"NOVA BUS CANADA";1096 931 | "2P3";"PLYMOUTH CANADA";1097 932 | "2V4";"VOLKSWAGEN CANADA";1099 933 | "2V8";"VOLKSWAGEN CANADA";1100 934 | "2WK";"WESTERN STAR";1101 935 | "2WL";"WESTERN STAR";1102 936 | "2WM";"WESTERN STAR";1103 937 | "3C4";"CHRYSLER MEXICO";1104 938 | "3D3";"DODGE MEXICO";1105 939 | "3FA";"FORD MOTOR COMPANY MEXICO";1106 940 | "3FE";"FORD MOTOR COMPANY MEXICO";1107 941 | "3H";"HONDA MEXICO";1109 942 | "3JB";"BRP MEXICO (ALL-TERRAIN VEHICLES)";1110 943 | "3MZ";"MAZDA MEXICO";1111 944 | "3P3";"PLYMOUTH MEXICO";1113 945 | "4JG";"MERCEDES-BENZ USA";1116 946 | "4RK";"NOVA BUS USA";1118 947 | "4S";"SUBARU-ISUZU AUTOMOTIVE";1119 948 | "4T9";"LUMEN MOTORS (ZERO-EMISSION MID-ENGINED CAR)";1121 949 | "4UF";"ARCTIC CAT INC.";1122 950 | "4UZ";"FRT-THOMAS BUS";1124 951 | "4V1";"VOLVO";1125 952 | "4V2";"VOLVO";1126 953 | "4V3";"VOLVO";1127 954 | "4V4";"VOLVO";1128 955 | "4V5";"VOLVO";1129 956 | "4V6";"VOLVO";1130 957 | "4VL";"VOLVO";1131 958 | "4VM";"VOLVO";1132 959 | "4VZ";"VOLVO";1133 960 | "538";"ZERO MOTORCYCLES (USA)";1134 961 | "5F";"HONDA USA-ALABAMA";1135 962 | "6AB";"MAN AUSTRALIA";1141 963 | "6F4";"NISSAN MOTOR COMPANY AUSTRALIA";1142 964 | "6F5";"KENWORTH AUSTRALIA";1143 965 | "6FP";"FORD MOTOR COMPANY AUSTRALIA";1144 966 | "6H8";"GENERAL MOTORS-HOLDEN (PRE NOV 2002)";1147 967 | "8AK";"SUZUKI ARGENTINA";1155 968 | "8GD";"PEUGEOT CHILE";1159 969 | "8GG";"CHEVROLET CHILE";1160 970 | "93R";"TOYOTA BRAZIL";1164 971 | "93U";"AUDI BRAZIL";1165 972 | "93V";"AUDI BRAZIL";1166 973 | --------------------------------------------------------------------------------