From 25ef73fedcb5064d44c55650ee724513ba06eccb Mon Sep 17 00:00:00 2001 From: "Doyin.O" <111298305+0xdev22@users.noreply.github.com> Date: Fri, 19 Apr 2024 14:02:19 -0600 Subject: [PATCH 01/26] Add Telemetry/Subscribe command to Tesla Commands (#305) * Move vehice command to smartcar and tesla client, add telemetry/subscribe to tesla commands * Fix lint issues * Fix issue from PR review * declare commands as constants and use the values --- cmd/devices-api/set_command_compat.go | 13 +++-- internal/constants/consts.go | 9 +++ internal/controllers/nft_controller.go | 28 ++++----- .../user_integrations_controller.go | 58 ++++++++++--------- .../user_integrations_controller_test.go | 31 +++++----- .../services/mocks/smartcar_client_mock.go | 16 +++++ .../mocks/tesla_fleet_api_service_mock.go | 18 +++++- internal/services/mocks/tesla_service_mock.go | 18 +++++- internal/services/smartcar_client.go | 12 +++- internal/services/tesla_fleet_api_service.go | 9 +++ internal/services/tesla_service.go | 8 +++ 11 files changed, 156 insertions(+), 64 deletions(-) diff --git a/cmd/devices-api/set_command_compat.go b/cmd/devices-api/set_command_compat.go index 61107eb6b..c7469fa77 100644 --- a/cmd/devices-api/set_command_compat.go +++ b/cmd/devices-api/set_command_compat.go @@ -13,14 +13,15 @@ import ( "github.com/DIMO-Network/shared/db" - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/internal/constants" - "github.com/DIMO-Network/devices-api/internal/services" - "github.com/DIMO-Network/devices-api/models" "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/volatiletech/sqlboiler/v4/boil" "github.com/volatiletech/sqlboiler/v4/queries/qm" + + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/DIMO-Network/devices-api/internal/services" + "github.com/DIMO-Network/devices-api/models" ) type setCommandCompatibilityCmd struct { @@ -57,7 +58,7 @@ func (p *setCommandCompatibilityCmd) Execute(ctx context.Context, _ *flag.FlagSe return subcommands.ExitSuccess } -var teslaEnabledCommands = []string{"doors/lock", "doors/unlock", "trunk/open", "frunk/open", "charge/limit"} +var teslaEnabledCommands = []string{constants.DoorsLock, constants.DoorsUnlock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit} func setCommandCompatibility(ctx context.Context, settings *config.Settings, pdb db.Store, ddSvc services.DeviceDefinitionService) error { @@ -146,7 +147,7 @@ func setCommandCompatSmartcar(ctx context.Context, settings *config.Settings, pd continue } - md.Commands.Capable = []string{"doors/lock", "doors/unlock"} + md.Commands.Capable = []string{constants.DoorsLock, constants.DoorsUnlock} if err := su.Metadata.Marshal(md); err != nil { return err diff --git a/internal/constants/consts.go b/internal/constants/consts.go index ef7de6ac3..c51467ec3 100644 --- a/internal/constants/consts.go +++ b/internal/constants/consts.go @@ -49,3 +49,12 @@ const ( func (r AutoPiSubStatusEnum) String() string { return string(r) } + +const ( + ChargeLimit string = "charge/limit" + FrunkOpen string = "frunk/open" + TrunkOpen string = "trunk/open" + DoorsLock string = "doors/lock" + DoorsUnlock string = "doors/unlock" + TelemetrySubscribe string = "telemetry/subscribe" +) diff --git a/internal/controllers/nft_controller.go b/internal/controllers/nft_controller.go index 90ceaacd9..0fb15573b 100644 --- a/internal/controllers/nft_controller.go +++ b/internal/controllers/nft_controller.go @@ -9,16 +9,12 @@ import ( "strconv" "strings" - "github.com/DIMO-Network/devices-api/internal/services/registry" - "github.com/DIMO-Network/devices-api/internal/utils" "github.com/DIMO-Network/shared" "github.com/segmentio/ksuid" - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/internal/constants" - "github.com/DIMO-Network/devices-api/internal/controllers/helpers" - "github.com/DIMO-Network/devices-api/internal/services" - "github.com/DIMO-Network/devices-api/models" + "github.com/DIMO-Network/devices-api/internal/services/registry" + "github.com/DIMO-Network/devices-api/internal/utils" + "github.com/DIMO-Network/go-mnemonic" pb "github.com/DIMO-Network/shared/api/users" "github.com/DIMO-Network/shared/db" @@ -36,6 +32,12 @@ import ( "github.com/volatiletech/sqlboiler/v4/queries/qm" "github.com/volatiletech/sqlboiler/v4/types" "golang.org/x/exp/slices" + + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/DIMO-Network/devices-api/internal/controllers/helpers" + "github.com/DIMO-Network/devices-api/internal/services" + "github.com/DIMO-Network/devices-api/models" ) type NFTController struct { @@ -473,7 +475,7 @@ func (nc *NFTController) GetVehicleStatus(c *fiber.Ctx) error { // @Param tokenID path string true "Token ID" // @Router /vehicle/{tokenID}/commands/doors/unlock [post] func (nc *NFTController) UnlockDoors(c *fiber.Ctx) error { - return nc.handleEnqueueCommand(c, "doors/unlock") + return nc.handleEnqueueCommand(c, constants.DoorsUnlock) } // LockDoors godoc @@ -485,7 +487,7 @@ func (nc *NFTController) UnlockDoors(c *fiber.Ctx) error { // @Param tokenID path string true "Token ID" // @Router /vehicle/{tokenID}/commands/doors/lock [post] func (nc *NFTController) LockDoors(c *fiber.Ctx) error { - return nc.handleEnqueueCommand(c, "doors/lock") + return nc.handleEnqueueCommand(c, constants.DoorsLock) } // OpenTrunk godoc @@ -497,7 +499,7 @@ func (nc *NFTController) LockDoors(c *fiber.Ctx) error { // @Param tokenID path string true "Token ID" // @Router /vehicle/{tokenID}/commands/trunk/open [post] func (nc *NFTController) OpenTrunk(c *fiber.Ctx) error { - return nc.handleEnqueueCommand(c, "trunk/open") + return nc.handleEnqueueCommand(c, constants.TrunkOpen) } // OpenFrunk godoc @@ -509,7 +511,7 @@ func (nc *NFTController) OpenTrunk(c *fiber.Ctx) error { // @Param tokenID path string true "Token ID" // @Router /vehicle/{tokenID}/commands/frunk/open [post] func (nc *NFTController) OpenFrunk(c *fiber.Ctx) error { - return nc.handleEnqueueCommand(c, "frunk/open") + return nc.handleEnqueueCommand(c, constants.FrunkOpen) } // handleEnqueueCommand enqueues the command specified by commandPath with the @@ -697,7 +699,7 @@ func (udc *UserDevicesController) GetBurnDevice(c *fiber.Ctx) error { if err != nil { return err } - defer tx.Rollback() //nolint + defer tx.Rollback() // nolint vehicleNFT, err := models.VehicleNFTS( models.VehicleNFTWhere.TokenID.EQ(tid), @@ -757,7 +759,7 @@ func (udc *UserDevicesController) PostBurnDevice(c *fiber.Ctx) error { if err != nil { return err } - defer tx.Rollback() //nolint + defer tx.Rollback() // nolint vehicleNFT, err := models.VehicleNFTS( models.VehicleNFTWhere.TokenID.EQ(tid), diff --git a/internal/controllers/user_integrations_controller.go b/internal/controllers/user_integrations_controller.go index 453944ef0..e16f93842 100644 --- a/internal/controllers/user_integrations_controller.go +++ b/internal/controllers/user_integrations_controller.go @@ -14,11 +14,6 @@ import ( smartcar "github.com/smartcar/go-sdk" ddgrpc "github.com/DIMO-Network/device-definitions-api/pkg/grpc" - "github.com/DIMO-Network/devices-api/internal/constants" - "github.com/DIMO-Network/devices-api/internal/controllers/helpers" - "github.com/DIMO-Network/devices-api/internal/services" - "github.com/DIMO-Network/devices-api/internal/services/registry" - "github.com/DIMO-Network/devices-api/models" "github.com/DIMO-Network/shared" pb "github.com/DIMO-Network/shared/api/users" "github.com/ethereum/go-ethereum/common" @@ -33,6 +28,12 @@ import ( "github.com/volatiletech/sqlboiler/v4/queries/qm" "golang.org/x/exp/slices" "golang.org/x/mod/semver" + + "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/DIMO-Network/devices-api/internal/controllers/helpers" + "github.com/DIMO-Network/devices-api/internal/services" + "github.com/DIMO-Network/devices-api/internal/services/registry" + "github.com/DIMO-Network/devices-api/models" ) // GetUserDeviceIntegration godoc @@ -72,7 +73,7 @@ func (udc *UserDevicesController) deleteDeviceIntegration(ctx context.Context, u if err != nil { return err } - defer tx.Rollback() //nolint + defer tx.Rollback() // nolint apiInt, err := models.UserDeviceAPIIntegrations( models.UserDeviceAPIIntegrationWhere.UserDeviceID.EQ(userDeviceID), @@ -168,7 +169,7 @@ func (udc *UserDevicesController) DeleteUserDeviceIntegration(c *fiber.Ctx) erro return err } - defer tx.Rollback() //nolint + defer tx.Rollback() // nolint device, err := models.UserDevices( models.UserDeviceWhere.ID.EQ(userDeviceID), @@ -314,14 +315,14 @@ func (udc *UserDevicesController) handleEnqueueCommand(c *fiber.Ctx, commandPath // TODO(elffjs): This map is ugly. Surely we interface our way out of this? commandMap := map[string]map[string]func(udai *models.UserDeviceAPIIntegration) (string, error){ constants.SmartCarVendor: { - "doors/unlock": udc.smartcarTaskSvc.UnlockDoors, - "doors/lock": udc.smartcarTaskSvc.LockDoors, + constants.DoorsUnlock: udc.smartcarTaskSvc.UnlockDoors, + constants.DoorsLock: udc.smartcarTaskSvc.LockDoors, }, constants.TeslaVendor: { - "doors/unlock": udc.teslaTaskService.UnlockDoors, - "doors/lock": udc.teslaTaskService.LockDoors, - "trunk/open": udc.teslaTaskService.OpenTrunk, - "frunk/open": udc.teslaTaskService.OpenFrunk, + constants.DoorsUnlock: udc.teslaTaskService.UnlockDoors, + constants.DoorsLock: udc.teslaTaskService.LockDoors, + constants.TrunkOpen: udc.teslaTaskService.OpenTrunk, + constants.FrunkOpen: udc.teslaTaskService.OpenFrunk, }, } @@ -387,7 +388,7 @@ type CommandResponse struct { // @Param integrationID path string true "Integration ID" // @Router /user/devices/{userDeviceID}/integrations/{integrationID}/commands/doors/unlock [post] func (udc *UserDevicesController) UnlockDoors(c *fiber.Ctx) error { - return udc.handleEnqueueCommand(c, "doors/unlock") + return udc.handleEnqueueCommand(c, constants.DoorsUnlock) } // LockDoors godoc @@ -401,7 +402,7 @@ func (udc *UserDevicesController) UnlockDoors(c *fiber.Ctx) error { // @Param integrationID path string true "Integration ID" // @Router /user/devices/{userDeviceID}/integrations/{integrationID}/commands/doors/lock [post] func (udc *UserDevicesController) LockDoors(c *fiber.Ctx) error { - return udc.handleEnqueueCommand(c, "doors/lock") + return udc.handleEnqueueCommand(c, constants.DoorsLock) } // OpenTrunk godoc @@ -415,7 +416,7 @@ func (udc *UserDevicesController) LockDoors(c *fiber.Ctx) error { // @Param integrationID path string true "Integration ID" // @Router /user/devices/{userDeviceID}/integrations/{integrationID}/commands/trunk/open [post] func (udc *UserDevicesController) OpenTrunk(c *fiber.Ctx) error { - return udc.handleEnqueueCommand(c, "trunk/open") + return udc.handleEnqueueCommand(c, constants.TrunkOpen) } // OpenFrunk godoc @@ -429,7 +430,7 @@ func (udc *UserDevicesController) OpenTrunk(c *fiber.Ctx) error { // @Param integrationID path string true "Integration ID" // @Router /user/devices/{userDeviceID}/integrations/{integrationID}/commands/frunk/open [post] func (udc *UserDevicesController) OpenFrunk(c *fiber.Ctx) error { - return udc.handleEnqueueCommand(c, "frunk/open") + return udc.handleEnqueueCommand(c, constants.FrunkOpen) } // GetAutoPiUnitInfo godoc @@ -794,7 +795,7 @@ func (udc *UserDevicesController) PostPairAutoPi(c *fiber.Ctx) error { if err != nil { return err } - defer tx.Rollback() //nolint + defer tx.Rollback() // nolint vnft, ad, err := udc.checkPairable(c.Context(), tx, userDeviceID, pairReq.ExternalID) if err != nil { @@ -1077,7 +1078,7 @@ func (udc *UserDevicesController) UnpairAutoPi(c *fiber.Ctx) error { if err != nil { return err } - defer tx.Rollback() //nolint + defer tx.Rollback() // nolint vnft, apnft, err := udc.checkUnpairable(c.Context(), tx, userDeviceID) if err != nil { @@ -1298,7 +1299,7 @@ func (udc *UserDevicesController) PostClaimAutoPi(c *fiber.Ctx) error { if err != nil { return err } - defer tx.Rollback() //nolint + defer tx.Rollback() // nolint udc.log.Info().Interface("payload", reqBody).Msg("Got claim request.") @@ -1429,7 +1430,7 @@ func (udc *UserDevicesController) registerDeviceIntegrationInner(c *fiber.Ctx, u if err != nil { return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("failed to create transaction: %s", err)) } - defer tx.Rollback() //nolint + defer tx.Rollback() // nolint ud, err := models.UserDevices( models.UserDeviceWhere.ID.EQ(userDeviceID), ).One(c.Context(), tx) @@ -1715,9 +1716,7 @@ func (udc *UserDevicesController) registerSmartcarIntegration(c *fiber.Ctx, logg } if doorControl { - commands = &services.UserDeviceAPIIntegrationsMetadataCommands{ - Enabled: []string{"doors/unlock", "doors/lock"}, - } + commands = udc.smartcarClient.GetAvailableCommands() } meta := services.UserDeviceAPIIntegrationsMetadata{ @@ -1877,11 +1876,16 @@ func (udc *UserDevicesController) registerDeviceTesla(c *fiber.Ctx, logger *zero return err } + var commands *services.UserDeviceAPIIntegrationsMetadataCommands + if apiVersion == constants.TeslaAPIV2 { + commands = udc.teslaFleetAPISvc.GetAvailableCommands() + } else { + commands = udc.teslaService.GetAvailableCommands() + } + // TODO(elffjs): Stupid to marshal this again and again. meta := services.UserDeviceAPIIntegrationsMetadata{ - Commands: &services.UserDeviceAPIIntegrationsMetadataCommands{ - Enabled: []string{"doors/unlock", "doors/lock", "trunk/open", "frunk/open", "charge/limit"}, - }, + Commands: commands, TeslaAPIVersion: apiVersion, } diff --git a/internal/controllers/user_integrations_controller_test.go b/internal/controllers/user_integrations_controller_test.go index 76a05159a..481fc93c0 100644 --- a/internal/controllers/user_integrations_controller_test.go +++ b/internal/controllers/user_integrations_controller_test.go @@ -26,15 +26,6 @@ import ( "github.com/DIMO-Network/shared/db" ddgrpc "github.com/DIMO-Network/device-definitions-api/pkg/grpc" - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/internal/constants" - "github.com/DIMO-Network/devices-api/internal/contracts" - "github.com/DIMO-Network/devices-api/internal/middleware/owner" - "github.com/DIMO-Network/devices-api/internal/services" - mock_services "github.com/DIMO-Network/devices-api/internal/services/mocks" - "github.com/DIMO-Network/devices-api/internal/services/registry" - "github.com/DIMO-Network/devices-api/internal/test" - "github.com/DIMO-Network/devices-api/models" "github.com/DIMO-Network/shared" smock "github.com/Shopify/sarama/mocks" "github.com/gofiber/fiber/v2" @@ -50,6 +41,16 @@ import ( "github.com/volatiletech/sqlboiler/v4/boil" "github.com/volatiletech/sqlboiler/v4/types" "go.uber.org/mock/gomock" + + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/DIMO-Network/devices-api/internal/contracts" + "github.com/DIMO-Network/devices-api/internal/middleware/owner" + "github.com/DIMO-Network/devices-api/internal/services" + mock_services "github.com/DIMO-Network/devices-api/internal/services/mocks" + "github.com/DIMO-Network/devices-api/internal/services/registry" + "github.com/DIMO-Network/devices-api/internal/test" + "github.com/DIMO-Network/devices-api/models" ) type UserIntegrationsControllerTestSuite struct { @@ -485,6 +486,7 @@ func (s *UserIntegrationsControllerTestSuite) TestPostTesla() { VIN: "5YJYGDEF9NF010423", }, nil) s.teslaSvc.EXPECT().WakeUpVehicle("abc", 1145).Return(nil) + s.teslaSvc.EXPECT().GetAvailableCommands().Return(&services.UserDeviceAPIIntegrationsMetadataCommands{Enabled: []string{constants.DoorsUnlock, constants.DoorsLock}}) s.deviceDefSvc.EXPECT().GetDeviceDefinitionByID(gomock.Any(), ud.DeviceDefinitionID).Times(2).Return(dd[0], nil) s.deviceDefSvc.EXPECT().FindDeviceDefinitionByMMY(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(dd[0], nil) @@ -648,7 +650,7 @@ func (s *UserIntegrationsControllerTestSuite) TestGetAutoPiInfoNoUDAI_ShouldUpda // assert assert.Equal(s.T(), fiber.StatusOK, response.StatusCode) body, _ := io.ReadAll(response.Body) - //assert + // assert assert.Equal(s.T(), false, gjson.GetBytes(body, "isUpdated").Bool()) assert.Equal(s.T(), unitID, gjson.GetBytes(body, "unitId").String()) assert.Equal(s.T(), "4321", gjson.GetBytes(body, "deviceId").String()) @@ -688,7 +690,7 @@ func (s *UserIntegrationsControllerTestSuite) TestGetAutoPiInfoNoUDAI_UpToDate() // assert assert.Equal(s.T(), fiber.StatusOK, response.StatusCode) body, _ := io.ReadAll(response.Body) - //assert + // assert assert.Equal(s.T(), true, gjson.GetBytes(body, "isUpdated").Bool()) assert.Equal(s.T(), "1.22.8", gjson.GetBytes(body, "releaseVersion").String()) assert.Equal(s.T(), false, gjson.GetBytes(body, "shouldUpdate").Bool()) // returned version is 1.21.9 which is our cutoff @@ -725,7 +727,7 @@ func (s *UserIntegrationsControllerTestSuite) TestGetAutoPiInfoNoUDAI_FutureUpda // assert assert.Equal(s.T(), fiber.StatusOK, response.StatusCode) body, _ := io.ReadAll(response.Body) - //assert + // assert assert.Equal(s.T(), false, gjson.GetBytes(body, "isUpdated").Bool()) assert.Equal(s.T(), "1.23.1", gjson.GetBytes(body, "releaseVersion").String()) assert.Equal(s.T(), false, gjson.GetBytes(body, "shouldUpdate").Bool()) @@ -764,7 +766,7 @@ func (s *UserIntegrationsControllerTestSuite) TestGetAutoPiInfoNoUDAI_ShouldUpda // assert assert.Equal(s.T(), fiber.StatusOK, response.StatusCode) body, _ := io.ReadAll(response.Body) - //assert + // assert assert.Equal(s.T(), unitID, gjson.GetBytes(body, "unitId").String()) assert.Equal(s.T(), "v1.22.8", gjson.GetBytes(body, "releaseVersion").String()) assert.Equal(s.T(), false, gjson.GetBytes(body, "shouldUpdate").Bool()) // this because releaseVersion below 1.21.9 @@ -935,6 +937,9 @@ func (s *UserIntegrationsControllerTestSuite) TestPostTesla_V2() { VIN: "5YJYGDEF9NF010423", }, nil) s.teslaFleetAPISvc.EXPECT().WakeUpVehicle(gomock.Any(), "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", "na", 1145).Return(nil) + s.teslaFleetAPISvc.EXPECT().GetAvailableCommands().Return(&services.UserDeviceAPIIntegrationsMetadataCommands{ + Enabled: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, + }) s.deviceDefSvc.EXPECT().GetDeviceDefinitionByID(gomock.Any(), ud.DeviceDefinitionID).Times(2).Return(dd[0], nil) s.deviceDefSvc.EXPECT().FindDeviceDefinitionByMMY(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(dd[0], nil) diff --git a/internal/services/mocks/smartcar_client_mock.go b/internal/services/mocks/smartcar_client_mock.go index 090ea329f..177de274f 100644 --- a/internal/services/mocks/smartcar_client_mock.go +++ b/internal/services/mocks/smartcar_client_mock.go @@ -5,6 +5,7 @@ // // mockgen -source smartcar_client.go -destination mocks/smartcar_client_mock.go // + // Package mock_services is a generated GoMock package. package mock_services @@ -12,6 +13,7 @@ import ( context "context" reflect "reflect" + services "github.com/DIMO-Network/devices-api/internal/services" smartcar "github.com/smartcar/go-sdk" gomock "go.uber.org/mock/gomock" ) @@ -54,6 +56,20 @@ func (mr *MockSmartcarClientMockRecorder) ExchangeCode(ctx, code, redirectURI an return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExchangeCode", reflect.TypeOf((*MockSmartcarClient)(nil).ExchangeCode), ctx, code, redirectURI) } +// GetAvailableCommands mocks base method. +func (m *MockSmartcarClient) GetAvailableCommands() *services.UserDeviceAPIIntegrationsMetadataCommands { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAvailableCommands") + ret0, _ := ret[0].(*services.UserDeviceAPIIntegrationsMetadataCommands) + return ret0 +} + +// GetAvailableCommands indicates an expected call of GetAvailableCommands. +func (mr *MockSmartcarClientMockRecorder) GetAvailableCommands() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAvailableCommands", reflect.TypeOf((*MockSmartcarClient)(nil).GetAvailableCommands)) +} + // GetEndpoints mocks base method. func (m *MockSmartcarClient) GetEndpoints(ctx context.Context, accessToken, id string) ([]string, error) { m.ctrl.T.Helper() diff --git a/internal/services/mocks/tesla_fleet_api_service_mock.go b/internal/services/mocks/tesla_fleet_api_service_mock.go index 968f31a74..335466789 100644 --- a/internal/services/mocks/tesla_fleet_api_service_mock.go +++ b/internal/services/mocks/tesla_fleet_api_service_mock.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: internal/services/tesla_fleet_api_service.go +// Source: tesla_fleet_api_service.go // // Generated by this command: // -// mockgen -source=internal/services/tesla_fleet_api_service.go -destination=internal/services/mocks/tesla_fleet_api_service_mock.go +// mockgen -source tesla_fleet_api_service.go -destination mocks/tesla_fleet_api_service_mock.go // // Package mock_services is a generated GoMock package. @@ -55,6 +55,20 @@ func (mr *MockTeslaFleetAPIServiceMockRecorder) CompleteTeslaAuthCodeExchange(ct return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompleteTeslaAuthCodeExchange", reflect.TypeOf((*MockTeslaFleetAPIService)(nil).CompleteTeslaAuthCodeExchange), ctx, authCode, redirectURI, region) } +// GetAvailableCommands mocks base method. +func (m *MockTeslaFleetAPIService) GetAvailableCommands() *services.UserDeviceAPIIntegrationsMetadataCommands { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAvailableCommands") + ret0, _ := ret[0].(*services.UserDeviceAPIIntegrationsMetadataCommands) + return ret0 +} + +// GetAvailableCommands indicates an expected call of GetAvailableCommands. +func (mr *MockTeslaFleetAPIServiceMockRecorder) GetAvailableCommands() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAvailableCommands", reflect.TypeOf((*MockTeslaFleetAPIService)(nil).GetAvailableCommands)) +} + // GetVehicle mocks base method. func (m *MockTeslaFleetAPIService) GetVehicle(ctx context.Context, token, region string, vehicleID int) (*services.TeslaVehicle, error) { m.ctrl.T.Helper() diff --git a/internal/services/mocks/tesla_service_mock.go b/internal/services/mocks/tesla_service_mock.go index e1ac8edbe..b3e89465a 100644 --- a/internal/services/mocks/tesla_service_mock.go +++ b/internal/services/mocks/tesla_service_mock.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: internal/services/tesla_service.go +// Source: tesla_service.go // // Generated by this command: // -// mockgen -source=internal/services/tesla_service.go -destination=internal/services/mocks/tesla_service_mock.go +// mockgen -source tesla_service.go -destination mocks/tesla_service_mock.go // // Package mock_services is a generated GoMock package. @@ -39,6 +39,20 @@ func (m *MockTeslaService) EXPECT() *MockTeslaServiceMockRecorder { return m.recorder } +// GetAvailableCommands mocks base method. +func (m *MockTeslaService) GetAvailableCommands() *services.UserDeviceAPIIntegrationsMetadataCommands { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAvailableCommands") + ret0, _ := ret[0].(*services.UserDeviceAPIIntegrationsMetadataCommands) + return ret0 +} + +// GetAvailableCommands indicates an expected call of GetAvailableCommands. +func (mr *MockTeslaServiceMockRecorder) GetAvailableCommands() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAvailableCommands", reflect.TypeOf((*MockTeslaService)(nil).GetAvailableCommands)) +} + // GetVehicle mocks base method. func (m *MockTeslaService) GetVehicle(ownerAccessToken string, id int) (*services.TeslaVehicle, error) { m.ctrl.T.Helper() diff --git a/internal/services/smartcar_client.go b/internal/services/smartcar_client.go index 4aa8683c3..aa02482a6 100644 --- a/internal/services/smartcar_client.go +++ b/internal/services/smartcar_client.go @@ -11,9 +11,11 @@ import ( "strings" "time" - "github.com/DIMO-Network/devices-api/internal/config" "github.com/pkg/errors" smartcar "github.com/smartcar/go-sdk" + + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" ) //go:generate mockgen -source smartcar_client.go -destination mocks/smartcar_client_mock.go @@ -28,6 +30,7 @@ type SmartcarClient interface { HasDoorControl(ctx context.Context, accessToken string, id string) (bool, error) GetVIN(ctx context.Context, accessToken string, id string) (string, error) GetInfo(ctx context.Context, accessToken string, id string) (*smartcar.Info, error) + GetAvailableCommands() *UserDeviceAPIIntegrationsMetadataCommands } type smartcarClient struct { @@ -238,3 +241,10 @@ func (s *smartcarClient) GetInfo(ctx context.Context, accessToken string, id str } return info, nil } + +func (s *smartcarClient) GetAvailableCommands() *UserDeviceAPIIntegrationsMetadataCommands { + return &UserDeviceAPIIntegrationsMetadataCommands{ + Enabled: []string{constants.DoorsUnlock, constants.DoorsLock}, + Capable: []string{constants.DoorsUnlock, constants.DoorsLock}, + } +} diff --git a/internal/services/tesla_fleet_api_service.go b/internal/services/tesla_fleet_api_service.go index 0a141a450..e85cc9ad0 100644 --- a/internal/services/tesla_fleet_api_service.go +++ b/internal/services/tesla_fleet_api_service.go @@ -12,6 +12,7 @@ import ( "golang.org/x/oauth2" "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" ) //go:generate mockgen -source tesla_fleet_api_service.go -destination mocks/tesla_fleet_api_service_mock.go @@ -20,6 +21,7 @@ type TeslaFleetAPIService interface { GetVehicles(ctx context.Context, token, region string) ([]TeslaVehicle, error) GetVehicle(ctx context.Context, token, region string, vehicleID int) (*TeslaVehicle, error) WakeUpVehicle(ctx context.Context, token, region string, vehicleID int) error + GetAvailableCommands() *UserDeviceAPIIntegrationsMetadataCommands } var teslaScopes = []string{"openid", "offline_access", "user_data", "vehicle_device_data", "vehicle_cmds", "vehicle_charging_cmds", "energy_device_data", "energy_device_data", "energy_cmds"} @@ -155,6 +157,13 @@ func (t *teslaFleetAPIService) WakeUpVehicle(ctx context.Context, token, region return nil } +func (t *teslaFleetAPIService) GetAvailableCommands() *UserDeviceAPIIntegrationsMetadataCommands { + return &UserDeviceAPIIntegrationsMetadataCommands{ + Enabled: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, + Capable: []string{constants.TelemetrySubscribe, constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, + } +} + // performTeslaGetRequest a helper function for making http requests, it adds a timeout context and parses error response func (t *teslaFleetAPIService) performTeslaGetRequest(ctx context.Context, url, token string) (*http.Response, error) { ctxTimeout, cancel := context.WithTimeout(ctx, time.Second*10) diff --git a/internal/services/tesla_service.go b/internal/services/tesla_service.go index 6a82ce58e..491e57ba9 100644 --- a/internal/services/tesla_service.go +++ b/internal/services/tesla_service.go @@ -7,12 +7,14 @@ import ( "time" "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" ) //go:generate mockgen -source tesla_service.go -destination mocks/tesla_service_mock.go type TeslaService interface { GetVehicle(ownerAccessToken string, id int) (*TeslaVehicle, error) WakeUpVehicle(ownerAccessToken string, id int) error + GetAvailableCommands() *UserDeviceAPIIntegrationsMetadataCommands } type teslaService struct { @@ -77,6 +79,12 @@ func (t *teslaService) WakeUpVehicle(ownerAccessToken string, id int) error { return nil } +func (t *teslaService) GetAvailableCommands() *UserDeviceAPIIntegrationsMetadataCommands { + return &UserDeviceAPIIntegrationsMetadataCommands{ + Enabled: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, + } +} + type TeslaVehicle struct { ID int `json:"id"` VehicleID int `json:"vehicle_id"` From b02fefd46bf051826e6ee3382ab3be17a2111e20 Mon Sep 17 00:00:00 2001 From: "Doyin.O" <111298305+0xdev22@users.noreply.github.com> Date: Mon, 22 Apr 2024 13:33:05 -0600 Subject: [PATCH 02/26] Feature/si 2557 create endpoint to fetch available commands (#308) * Move vehice command to smartcar and tesla client, add telemetry/subscribe to tesla commands * Fix lint issues * Create endpoint for fetching all commands available to an integration * Compute disabled from capable and enabled commands * Change method, generate swagger and remove omitempty --- .golangci.yml | 2 +- cmd/devices-api/api.go | 31 +- docs/docs.go | 1652 +++------------- docs/swagger.json | 1670 +++-------------- docs/swagger.yaml | 1384 +++----------- .../user_integrations_auth_controller.go | 82 +- .../user_integrations_auth_controller_test.go | 100 +- internal/services/models.go | 5 +- internal/services/tesla_fleet_api_service.go | 2 +- internal/services/tesla_service.go | 1 + internal/utils/utils.go | 19 + internal/utils/utils_test.go | 16 + 12 files changed, 1091 insertions(+), 3873 deletions(-) create mode 100644 internal/utils/utils_test.go diff --git a/.golangci.yml b/.golangci.yml index 7bfc5f1c9..ff7c83e80 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -20,7 +20,7 @@ issues: linters-settings: gci: # DEPRECATED: use `sections` and `prefix(github.com/org/project)` instead. - local-prefixes: github.com/org/project + # local-prefixes: github.com/org/project # Section configuration to compare against. # Section names are case-insensitive and may contain parameters in (). # The default order of sections is `standard > default > custom > blank > dot`, diff --git a/cmd/devices-api/api.go b/cmd/devices-api/api.go index 49600f1ad..2234f97ec 100644 --- a/cmd/devices-api/api.go +++ b/cmd/devices-api/api.go @@ -19,11 +19,12 @@ import ( "github.com/DIMO-Network/devices-api/internal/rpc" - "github.com/DIMO-Network/devices-api/internal/middleware/metrics" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" + "github.com/DIMO-Network/devices-api/internal/middleware/metrics" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery" "github.com/DIMO-Network/shared/redis" @@ -34,15 +35,6 @@ import ( "github.com/DIMO-Network/devices-api/internal/controllers/helpers" "github.com/DIMO-Network/devices-api/internal/middleware/owner" - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/internal/constants" - "github.com/DIMO-Network/devices-api/internal/controllers" - "github.com/DIMO-Network/devices-api/internal/services" - "github.com/DIMO-Network/devices-api/internal/services/autopi" - "github.com/DIMO-Network/devices-api/internal/services/issuer" - "github.com/DIMO-Network/devices-api/internal/services/macaron" - "github.com/DIMO-Network/devices-api/internal/services/registry" - pb "github.com/DIMO-Network/devices-api/pkg/grpc" "github.com/DIMO-Network/shared" pbuser "github.com/DIMO-Network/shared/api/users" "github.com/DIMO-Network/shared/middleware/privilegetoken" @@ -58,6 +50,16 @@ import ( "github.com/rs/zerolog" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/DIMO-Network/devices-api/internal/controllers" + "github.com/DIMO-Network/devices-api/internal/services" + "github.com/DIMO-Network/devices-api/internal/services/autopi" + "github.com/DIMO-Network/devices-api/internal/services/issuer" + "github.com/DIMO-Network/devices-api/internal/services/macaron" + "github.com/DIMO-Network/devices-api/internal/services/registry" + pb "github.com/DIMO-Network/devices-api/pkg/grpc" ) func startWebAPI(logger zerolog.Logger, settings *config.Settings, pdb db.Store, eventService services.EventService, producer sarama.SyncProducer, s3ServiceClient *s3.Client, s3NFTServiceClient *s3.Client) { @@ -148,8 +150,8 @@ func startWebAPI(logger zerolog.Logger, settings *config.Settings, pdb db.Store, userIntegrationAuthController := controllers.NewUserIntegrationAuthController(settings, pdb.DBS, &logger, ddSvc, teslaFleetAPISvc, redisCache, cipher, usersClient) // commenting this out b/c the library includes the path in the metrics which saturates prometheus queries - need to fork / make our own - //prometheus := fiberprometheus.New("devices-api") - //app.Use(prometheus.Middleware) + // prometheus := fiberprometheus.New("devices-api") + // app.Use(prometheus.Middleware) app.Use(metrics.HTTPMetricsMiddleware) app.Use(fiberrecover.New(fiberrecover.Config{ @@ -157,11 +159,11 @@ func startWebAPI(logger zerolog.Logger, settings *config.Settings, pdb db.Store, EnableStackTrace: true, StackTraceHandler: nil, })) - //cors + // cors app.Use(cors.New()) // request logging app.Use(zflogger.New(logger, nil)) - //cache + // cache cacheHandler := cache.New(cache.Config{ Next: func(c *fiber.Ctx) bool { return c.Query("refresh") == "true" @@ -231,6 +233,7 @@ func startWebAPI(logger zerolog.Logger, settings *config.Settings, pdb db.Store, v1Auth.Post("/user/devices/fromsmartcar", userDeviceController.RegisterDeviceForUserFromSmartcar) v1Auth.Post("/user/devices", userDeviceController.RegisterDeviceForUser) v1Auth.Post("/integration/:tokenID/credentials", userIntegrationAuthController.CompleteOAuthExchange) + v1Auth.Get("/integration/:tokenID/commands", userIntegrationAuthController.GetCommandsByIntegration) // Autopi specific routes. amdOwnerMw := owner.AftermarketDevice(pdb, usersClient, &logger) diff --git a/docs/docs.go b/docs/docs.go index ddd0823a4..e9dd7beea 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,6 +1,4 @@ -// Package docs GENERATED BY SWAG; DO NOT EDIT -// This file was generated by swaggo/swag at -// 2024-03-26 15:50:39.486471 -0600 MDT m=+2.385957543 +// Package docs Code generated by swaggo/swag at 2024-04-22 12:50:08.40461 -0400 EDT m=+2.278998709. DO NOT EDIT package docs import "github.com/swaggo/swag" @@ -75,7 +73,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiDeviceInfo" + "$ref": "#/definitions/internal_controllers.AutoPiDeviceInfo" } } } @@ -134,7 +132,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiClaimRequest" + "$ref": "#/definitions/internal_controllers.AutoPiClaimRequest" } } ], @@ -199,7 +197,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/services.AutoPiTask" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.AutoPiTask" } } } @@ -277,7 +275,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/constants.CountryInfo" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_constants.CountryInfo" } } } @@ -305,7 +303,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/constants.CountryInfo" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_constants.CountryInfo" } }, "400": { @@ -317,74 +315,6 @@ const docTemplate = `{ } } }, - "/dcn/{tokenID}": { - "get": { - "description": "retrieves the DCN NFT metadata for a given token ID address", - "produces": [ - "application/json" - ], - "tags": [ - "dcn" - ], - "parameters": [ - { - "type": "string", - "description": "DCN node id decimal representation", - "name": "tokenID", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/internal_controllers.NFTMetadataResp" - } - }, - "400": { - "description": "Bad Request" - }, - "404": { - "description": "Not Found" - } - } - } - }, - "/dcn/{tokenID}/image": { - "get": { - "description": "retrieves the DCN NFT metadata for a given token address", - "produces": [ - "image/svg+xml" - ], - "tags": [ - "dcn" - ], - "parameters": [ - { - "type": "string", - "description": "DCN node id decimal representation", - "name": "tokenID", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/internal_controllers.NFTMetadataResp" - } - }, - "400": { - "description": "Bad Request" - }, - "404": { - "description": "Not Found" - } - } - } - }, "/documents": { "get": { "security": [ @@ -408,7 +338,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.DocumentResponse" + "$ref": "#/definitions/internal_controllers.DocumentResponse" } } } @@ -463,7 +393,7 @@ const docTemplate = `{ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.DocumentResponse" + "$ref": "#/definitions/internal_controllers.DocumentResponse" } } } @@ -499,7 +429,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.DocumentResponse" + "$ref": "#/definitions/internal_controllers.DocumentResponse" } } } @@ -590,8 +520,8 @@ const docTemplate = `{ } } }, - "/integration/:tokenID/credentials": { - "post": { + "/integration/:tokenID/commands": { + "get": { "security": [ { "ApiKeyAuth": [] @@ -600,7 +530,7 @@ const docTemplate = `{ "BearerAuth": [] } ], - "description": "Complete Tesla auth and get devices for authenticated user", + "description": "Get a list of available commands by integration", "consumes": [ "application/json" ], @@ -608,7 +538,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "user-devices" + "integrations" ], "parameters": [ { @@ -617,112 +547,62 @@ const docTemplate = `{ "name": "tokenID", "in": "path", "required": true - }, - { - "description": "all fields are required", - "name": "user_device", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeRequest" - } } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeResponseWrapper" + "$ref": "#/definitions/internal_controllers.GetCommandsByIntegrationResponse" } } } } }, - "/integrations": { - "get": { + "/integration/:tokenID/credentials": { + "post": { "security": [ + { + "ApiKeyAuth": [] + }, { "BearerAuth": [] } ], - "description": "gets list of integrations we have defined", - "produces": [ + "description": "Complete Tesla auth and get devices for authenticated user", + "consumes": [ "application/json" ], - "tags": [ - "integrations" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/grpc.Integration" - } - } - } - } - } - }, - "/manufacturer/{tokenId}": { - "get": { - "description": "Retrieves NFT metadata for a given manufacturer.", "produces": [ "application/json" ], "tags": [ - "nfts" + "user-devices" ], "parameters": [ { - "type": "integer", - "description": "token id", - "name": "tokenId", + "type": "string", + "description": "token id for integration", + "name": "tokenID", "in": "path", "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/internal_controllers.NFTMetadataResp" - } }, - "404": { - "description": "Not Found" - } - } - } - }, - "/synthetic/device/{tokenId}": { - "get": { - "description": "Retrieves NFT metadata for a given synthetic device.", - "produces": [ - "application/json" - ], - "tags": [ - "nfts" - ], - "parameters": [ { - "type": "integer", - "description": "token id", - "name": "tokenId", - "in": "path", - "required": true + "description": "all fields are required", + "name": "user_device", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_controllers.CompleteOAuthExchangeRequest" + } } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/internal_controllers.NFTMetadataResp" + "$ref": "#/definitions/internal_controllers.CompleteOAuthExchangeResponseWrapper" } - }, - "404": { - "description": "Not Found" } } } @@ -754,7 +634,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDevice" + "$ref": "#/definitions/internal_controllers.RegisterUserDevice" } } ], @@ -762,7 +642,7 @@ const docTemplate = `{ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceResponse" + "$ref": "#/definitions/internal_controllers.RegisterUserDeviceResponse" } } } @@ -854,7 +734,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiPairRequest" + "$ref": "#/definitions/internal_controllers.AutoPiPairRequest" } } ], @@ -914,7 +794,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiPairRequest" + "$ref": "#/definitions/internal_controllers.AutoPiPairRequest" } } ], @@ -942,7 +822,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterDeviceIntegrationRequest" + "$ref": "#/definitions/internal_controllers.RegisterDeviceIntegrationRequest" } } ], @@ -980,7 +860,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceSmartcar" + "$ref": "#/definitions/internal_controllers.RegisterUserDeviceSmartcar" } } ], @@ -988,13 +868,13 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull" + "$ref": "#/definitions/internal_controllers.UserDeviceFull" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull" + "$ref": "#/definitions/internal_controllers.UserDeviceFull" } }, "400": { @@ -1039,7 +919,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceVIN" + "$ref": "#/definitions/internal_controllers.RegisterUserDeviceVIN" } } ], @@ -1047,7 +927,7 @@ const docTemplate = `{ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull" + "$ref": "#/definitions/internal_controllers.UserDeviceFull" } }, "400": { @@ -1080,7 +960,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.MyDevicesResp" + "$ref": "#/definitions/internal_controllers.MyDevicesResp" } } } @@ -1104,7 +984,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.MyDevicesResp" + "$ref": "#/definitions/internal_controllers.MyDevicesResp" } } } @@ -1190,7 +1070,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.MintRequest" + "$ref": "#/definitions/internal_controllers.MintRequest" } } ], @@ -1285,7 +1165,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UpdateCountryCodeReq" + "$ref": "#/definitions/internal_controllers.UpdateCountryCodeReq" } } ], @@ -1326,7 +1206,7 @@ const docTemplate = `{ "404": { "description": "Vehicle not found", "schema": { - "$ref": "#/definitions/helpers.ErrorRes" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes" } } } @@ -1369,7 +1249,7 @@ const docTemplate = `{ "404": { "description": "Vehicle not found", "schema": { - "$ref": "#/definitions/helpers.ErrorRes" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes" } } } @@ -1396,13 +1276,13 @@ const docTemplate = `{ "404": { "description": "Vehicle not found", "schema": { - "$ref": "#/definitions/helpers.ErrorRes" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes" } }, "429": { "description": "Last query already cleared", "schema": { - "$ref": "#/definitions/helpers.ErrorRes" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes" } } } @@ -1423,7 +1303,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceIntegrationResponse" + "$ref": "#/definitions/internal_controllers.GetUserDeviceIntegrationResponse" } } } @@ -1555,7 +1435,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse" + "$ref": "#/definitions/internal_controllers.CommandResponse" } } } @@ -1594,7 +1474,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse" + "$ref": "#/definitions/internal_controllers.CommandResponse" } } } @@ -1633,7 +1513,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse" + "$ref": "#/definitions/internal_controllers.CommandResponse" } } } @@ -1749,7 +1629,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse" + "$ref": "#/definitions/internal_controllers.CommandResponse" } } } @@ -1795,7 +1675,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandRequestStatusResp" + "$ref": "#/definitions/internal_controllers.CommandRequestStatusResp" } } } @@ -1825,7 +1705,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UpdateNameReq" + "$ref": "#/definitions/internal_controllers.UpdateNameReq" } }, { @@ -1900,7 +1780,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UpdateVINReq" + "$ref": "#/definitions/internal_controllers.UpdateVINReq" } }, { @@ -1943,7 +1823,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.NFTImageData" + "$ref": "#/definitions/internal_controllers.NFTImageData" } } ], @@ -1977,7 +1857,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.GetGeofence" + "$ref": "#/definitions/internal_controllers.GetGeofence" } } } @@ -2009,7 +1889,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CreateGeofence" + "$ref": "#/definitions/internal_controllers.CreateGeofence" } } ], @@ -2017,7 +1897,7 @@ const docTemplate = `{ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/helpers.CreateResponse" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.CreateResponse" } } } @@ -2057,7 +1937,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CreateGeofence" + "$ref": "#/definitions/internal_controllers.CreateGeofence" } } ], @@ -2472,7 +2352,7 @@ const docTemplate = `{ "big.Int": { "type": "object" }, - "constants.CountryInfo": { + "github_com_DIMO-Network_devices-api_internal_constants.CountryInfo": { "type": "object", "properties": { "alpha_2": { @@ -2501,1020 +2381,269 @@ const docTemplate = `{ } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.AutoPiClaimRequest": { + "github_com_DIMO-Network_devices-api_internal_controllers_helpers.CreateResponse": { "type": "object", "properties": { - "aftermarketDeviceSignature": { - "description": "AftermarketDeviceSignature is the signature from the aftermarket device.", + "id": { "type": "string" + } + } + }, + "github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes": { + "type": "object", + "properties": { + "code": { + "type": "integer" }, - "userSignature": { - "description": "UserSignature is the signature from the user, using their private key.", + "message": { "type": "string" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.AutoPiDeviceInfo": { + "github_com_DIMO-Network_devices-api_internal_services.AutoPiTask": { "type": "object", "properties": { - "beneficiaryAddress": { - "description": "OwnerAddress is the Ethereum address of the NFT owner.", - "type": "array", - "items": { - "type": "integer" - } - }, - "claim": { - "description": "Claim contains the status of the on-chain claiming meta-transaction.", - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiTransactionStatus" + "code": { + "type": "integer" }, - "deviceId": { + "description": { "type": "string" }, - "dockerReleases": { - "type": "array", - "items": { - "type": "integer" - } - }, - "ethereumAddress": { - "description": "OwnerAddress is the Ethereum address of the NFT owner.", - "type": "array", - "items": { - "type": "integer" - } - }, - "hwRevision": { + "error": { "type": "string" }, - "isUpdated": { - "type": "boolean" - }, - "lastCommunication": { + "status": { "type": "string" }, - "manufacturer": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.ManufacturerInfo" - }, - "ownerAddress": { + "taskId": { "type": "string" }, - "pair": { - "description": "Pair contains the status of the on-chain pairing meta-transaction.", - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiTransactionStatus" - }, - "releaseVersion": { + "updatedAt": { "type": "string" }, - "shouldUpdate": { - "type": "boolean" - }, - "template": { + "updates": { + "description": "Updates increments every time the job was updated.", "type": "integer" - }, - "tokenId": { - "$ref": "#/definitions/big.Int" - }, - "unitId": { + } + } + }, + "github_com_DIMO-Network_devices-api_internal_services.DeviceAttribute": { + "type": "object", + "properties": { + "name": { "type": "string" }, - "unpair": { - "description": "Unpair contains the status of the on-chain unpairing meta-transaction.", - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiTransactionStatus" + "value": { + "type": "string" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.AutoPiPairRequest": { + "github_com_DIMO-Network_devices-api_internal_services.DeviceCompatibility": { "type": "object", "properties": { - "aftermarketDeviceSignature": { - "description": "AftermarketDeviceSignature is the 65-byte, hex-encoded Ethereum signature of\nthe pairing payload by the device. Only needed if the vehicle owner and aftermarket\ndevice owner are not the same.", + "capabilities": { "type": "array", "items": { "type": "integer" } }, - "externalId": { + "country": { "type": "string" }, - "signature": { - "description": "AftermarketDeviceSignature is the 65-byte, hex-encoded Ethereum signature of\nthe pairing payload by the device. Only needed if the vehicle owner and aftermarket\ndevice owner are not the same.", - "type": "array", - "items": { - "type": "integer" - } + "id": { + "type": "string" + }, + "region": { + "type": "string" + }, + "style": { + "type": "string" + }, + "type": { + "type": "string" + }, + "vendor": { + "type": "string" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.AutoPiTransactionStatus": { + "github_com_DIMO-Network_devices-api_internal_services.DeviceDefinition": { "type": "object", "properties": { - "createdAt": { - "description": "CreatedAt is the timestamp of the creation of the meta-transaction.", - "type": "string", - "example": "2022-10-01T09:22:21.002Z" + "compatibleIntegrations": { + "description": "CompatibleIntegrations has systems this vehicle can integrate with", + "type": "array", + "items": { + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceCompatibility" + } }, - "hash": { - "description": "Hash is the hexidecimal transaction hash, available for any transaction at the Submitted stage or greater.", - "type": "string", - "example": "0x28b4662f1e1b15083261a4a5077664f4003d58cb528826b7aab7fad466c28e70" - }, - "status": { - "description": "Status is the state of the transaction performing this operation. There are only four options.", - "type": "string", - "enum": [ - "Unsubmitted", - "Submitted", - "Mined", - "Confirmed" - ], - "example": "Mined" - }, - "updatedAt": { - "description": "UpdatedAt is the last time we updated the status of the transaction.", - "type": "string", - "example": "2022-10-01T09:22:26.337Z" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.BurnRequest": { - "type": "object", - "required": [ - "signature" - ], - "properties": { - "signature": { - "description": "Signature is the hex encoding of the EIP-712 signature result.", - "type": "string" - }, - "tokenId": { - "$ref": "#/definitions/big.Int" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.BurnSyntheticDeviceRequest": { - "type": "object", - "properties": { - "signature": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.CommandRequestStatusResp": { - "type": "object", - "properties": { - "command": { - "type": "string", - "example": "doors/unlock" - }, - "createdAt": { - "type": "string", - "example": "2022-08-09T19:38:39Z" - }, - "id": { - "type": "string", - "example": "2D8LqUHQtaMHH6LYPqznmJMBeZm" - }, - "status": { - "type": "string", - "enum": [ - "Pending", - "Complete", - "Failed" - ], - "example": "Complete" - }, - "updatedAt": { - "type": "string", - "example": "2022-08-09T19:39:22Z" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse": { - "type": "object", - "properties": { - "requestId": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeRequest": { - "type": "object", - "properties": { - "authorizationCode": { - "type": "string" - }, - "redirectUri": { - "type": "string" - }, - "region": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeResponse": { - "type": "object", - "properties": { - "definition": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.DeviceDefinition" - }, - "externalId": { - "type": "string" - }, - "vin": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeResponseWrapper": { - "type": "object", - "properties": { - "vehicles": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeResponse" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.CreateGeofence": { - "type": "object", - "properties": { - "h3Indexes": { - "description": "required: false", - "type": "array", - "items": { - "type": "string" - } - }, - "name": { - "description": "required: true", - "type": "string" - }, - "type": { - "description": "one of following: \"PrivacyFence\", \"TriggerEntry\", \"TriggerExit\"\nrequired: true", - "type": "string" - }, - "userDeviceIds": { - "description": "Optionally link the geofence with a list of user device ID", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.DeviceDefinition": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "make": { - "type": "string" - }, - "model": { - "type": "string" - }, - "year": { - "type": "integer" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.DeviceSnapshot": { - "type": "object", - "properties": { - "ambientTemp": { - "type": "number" - }, - "batteryCapacity": { - "type": "integer" - }, - "batteryVoltage": { - "type": "number" - }, - "chargeLimit": { - "type": "number" - }, - "charging": { - "type": "boolean" - }, - "fuelPercentRemaining": { - "type": "number" - }, - "latitude": { - "type": "number" - }, - "longitude": { - "type": "number" - }, - "odometer": { - "type": "number" - }, - "oil": { - "type": "number" - }, - "range": { - "type": "number" - }, - "recordCreatedAt": { - "type": "string" - }, - "recordUpdatedAt": { - "type": "string" - }, - "soc": { - "type": "number" - }, - "tirePressure": { - "$ref": "#/definitions/smartcar.TirePressure" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.DocumentResponse": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "ext": { - "type": "string" - }, - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "url": { - "type": "string" - }, - "userDeviceId": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.GeoFenceUserDevice": { - "type": "object", - "properties": { - "mmy": { - "type": "string" - }, - "name": { - "type": "string" - }, - "userDeviceId": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.GetGeofence": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "h3Indexes": { - "type": "array", - "items": { - "type": "string" - } - }, - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "updatedAt": { - "type": "string" - }, - "userDevices": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.GeoFenceUserDevice" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceErrorCodeQueriesResponse": { - "type": "object", - "properties": { - "queries": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceErrorCodeQueriesResponseItem" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceErrorCodeQueriesResponseItem": { - "type": "object", - "properties": { - "clearedAt": { - "description": "ClearedAt is the time at which the user cleared the codes from this query.\nMay be null.", - "type": "string", - "example": "2023-05-23T12:57:05Z" - }, - "errorCodes": { - "type": "array", - "items": { - "$ref": "#/definitions/services.ErrorCodesResponse" - } - }, - "requestedAt": { - "type": "string", - "example": "2023-05-23T12:56:36Z" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceIntegrationResponse": { - "type": "object", - "properties": { - "createdAt": { - "description": "CreatedAt is the creation time of this integration for this device.", - "type": "string" - }, - "externalId": { - "description": "ExternalID is the identifier used by the third party for the device. It may be absent if we\nhaven't authorized yet.", - "type": "string" - }, - "status": { - "description": "Status is one of \"Pending\", \"PendingFirstData\", \"Active\", \"Failed\", \"DuplicateIntegration\".", - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.ManufacturerInfo": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "tokenId": { - "$ref": "#/definitions/big.Int" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.MintRequest": { - "type": "object", - "required": [ - "imageData", - "signature" - ], - "properties": { - "imageData": { - "description": "ImageData contains the base64-encoded NFT PNG image.", - "type": "string" - }, - "imageDataTransparent": { - "description": "ImageDataTransparent contains the base64-encoded NFT PNG image\nwith a transparent background, for use in the app. For compatibility\nwith older versions it is not required.", - "type": "string" - }, - "signature": { - "description": "Signature is the hex encoding of the EIP-712 signature result.", - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.MintSyntheticDeviceRequest": { - "type": "object", - "properties": { - "signature": { - "type": "string", - "example": "0xc565d38982e1a5004efb5ee390fba0a08bb5e72b3f3e91094c66bc395c324f785425d58d5c1a601372d9c16164e380c63e89f1e0ea95fdefdf7b2854c4f938e81b" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.MyDevicesResp": { - "type": "object", - "properties": { - "sharedDevices": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull" - } - }, - "userDevices": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.NFTAttribute": { - "type": "object", - "properties": { - "trait_type": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.NFTData": { - "type": "object", - "properties": { - "ownerAddress": { - "description": "OwnerAddress is the Ethereum address of the NFT owner.", - "type": "array", - "items": { - "type": "integer" - } - }, - "status": { - "description": "Status is the minting status of the NFT.", - "type": "string", - "enum": [ - "Unstarted", - "Submitted", - "Mined", - "Confirmed" - ], - "example": "Confirmed" - }, - "tokenId": { - "type": "number", - "example": 37 - }, - "tokenUri": { - "type": "string", - "example": "https://nft.dimo.zone/37" - }, - "txHash": { - "description": "TxHash is the hash of the minting transaction.", - "type": "string", - "example": "0x30bce3da6985897224b29a0fe064fd2b426bb85a394cc09efe823b5c83326a8e" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.NFTImageData": { - "type": "object", - "required": [ - "imageData" - ], - "properties": { - "imageData": { - "description": "ImageData contains the base64-encoded NFT PNG image.", - "type": "string" - }, - "imageDataTransparent": { - "description": "ImageDataTransparent contains the base64-encoded NFT PNG image\nwith a transparent background, for use in the app. For compatibility\nwith older versions it is not required.", - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.NFTMetadataResp": { - "type": "object", - "properties": { - "attributes": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.NFTAttribute" - } - }, - "description": { - "type": "string" - }, - "image": { - "type": "string" - }, - "name": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.Offer": { - "type": "object", - "properties": { - "declineReason": { - "description": "The reason the offer was declined from the vendor", - "type": "string" - }, - "error": { - "description": "An error from the vendor (eg. when the VIN is invalid)", - "type": "string" - }, - "grade": { - "description": "The grade of the offer from the vendor (eg. \"RETAIL\")", - "type": "string" - }, - "price": { - "description": "The offer price from the vendor", - "type": "integer" - }, - "url": { - "description": "The offer URL from the vendor", - "type": "string" - }, - "vendor": { - "description": "The vendor of the offer (eg. \"carmax\", \"carvana\", etc.)", - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.Privilege": { - "type": "object", - "properties": { - "expiry": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "updatedAt": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.PrivilegeUser": { - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "privileges": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.Privilege" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.QueryDeviceErrorCodesReq": { - "type": "object", - "properties": { - "errorCodes": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "P0106", - "P0279" - ] - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.QueryDeviceErrorCodesResponse": { - "type": "object", - "properties": { - "clearedAt": { - "type": "string" - }, - "errorCodes": { - "type": "array", - "items": { - "$ref": "#/definitions/services.ErrorCodesResponse" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.RangeSet": { - "type": "object", - "properties": { - "rangeBasis": { - "description": "The basis for the range calculation (eg. \"MPG\" or \"MPG Highway\")", - "type": "string" - }, - "rangeDistance": { - "description": "The estimated range distance", - "type": "integer" - }, - "rangeUnit": { - "description": "The unit used for the rangeDistance (eg. \"miles\" or \"kilometers\")", - "type": "string" - }, - "updated": { - "description": "The time the data was collected", - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.RegisterDeviceIntegrationRequest": { - "type": "object", - "properties": { - "accessToken": { - "type": "string" - }, - "code": { - "description": "Code is an OAuth authorization code. Not used in all integrations.", - "type": "string" - }, - "expiresIn": { - "type": "integer" - }, - "externalId": { - "description": "ExternalID is the only field needed for AutoPi registrations. It is the UnitID.", - "type": "string" - }, - "redirectURI": { - "description": "RedirectURI is the OAuth redirect URI used by the frontend. Not used in all integrations.", - "type": "string" - }, - "refreshToken": { - "type": "string" - }, - "version": { - "type": "integer" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDevice": { - "type": "object", - "properties": { - "countryCode": { - "type": "string" - }, - "deviceDefinitionId": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceResponse": { - "type": "object", - "properties": { - "deviceDefinitionId": { - "type": "string" - }, - "integrationCapabilities": { + "deviceAttributes": { + "description": "DeviceAttributes is a list of attributes for the device type as defined in device_types.properties", "type": "array", "items": { - "$ref": "#/definitions/services.DeviceCompatibility" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceAttribute" } }, - "userDeviceId": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceSmartcar": { - "type": "object", - "properties": { - "code": { - "description": "Code refers to the auth code provided by smartcar when user logs in", - "type": "string" - }, - "countryCode": { + "deviceDefinitionId": { "type": "string" }, - "redirectURI": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceVIN": { - "type": "object", - "properties": { - "canProtocol": { - "description": "CANProtocol is the protocol that was detected by edge-network from the autopi.", + "imageUrl": { "type": "string" }, - "countryCode": { - "type": "string" + "make": { + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceMake" }, - "vin": { + "metadata": {}, + "name": { "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.SyntheticDeviceStatus": { - "type": "object", - "properties": { - "address": { - "type": "string", - "example": "0xAED7EA8035eEc47E657B34eF5D020c7005487443" }, - "status": { - "type": "string", - "enum": [ - "Unstarted", - "Submitted", - "Mined", - "Confirmed" - ], - "example": "Confirmed" + "type": { + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceType" }, - "tokenId": { - "type": "number", - "example": 15 + "vehicleData": { + "description": "VehicleInfo will be empty if not a vehicle type", + "allOf": [ + { + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceVehicleInfo" + } + ] }, - "txHash": { - "type": "string", - "example": "0x30bce3da6985897224b29a0fe064fd2b426bb85a394cc09efe823b5c83326a8e" + "verified": { + "type": "boolean" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.UpdateCountryCodeReq": { + "github_com_DIMO-Network_devices-api_internal_services.DeviceMake": { "type": "object", "properties": { - "countryCode": { + "id": { "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.UpdateNameReq": { - "type": "object", - "properties": { + }, + "logo_url": { + "type": "string" + }, "name": { "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.UpdateVINReq": { - "type": "object", - "required": [ - "vin" - ], - "properties": { - "signature": { - "description": "Signature is the hex-encoded result of the AutoPi signing the VIN. It must\nbe present to verify the VIN.", - "type": "string", - "example": "16b15f88bbd2e0a22d1d0084b8b7080f2003ea83eab1a00f80d8c18446c9c1b6224f17aa09eaf167717ca4f355bb6dc94356e037edf3adf6735a86fc3741f5231b" }, - "vin": { - "description": "VIN is a vehicle identification number. At the very least, it must be\n17 characters in length and contain only letters and numbers.", - "type": "string", - "example": "4Y1SL65848Z411439" + "oem_platform_name": { + "type": "string" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull": { + "github_com_DIMO-Network_devices-api_internal_services.DeviceType": { "type": "object", "properties": { - "countryCode": { - "type": "string" - }, - "customImageUrl": { - "type": "string" - }, - "deviceDefinition": { - "$ref": "#/definitions/services.DeviceDefinition" - }, - "id": { - "type": "string" - }, - "integrations": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceIntegrationStatus" - } - }, - "metadata": { - "$ref": "#/definitions/services.UserDeviceMetadata" - }, - "name": { + "make": { "type": "string" }, - "nft": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.NFTData" - }, - "optedInAt": { + "model": { "type": "string" }, - "privilegedUsers": { + "subModels": { "type": "array", "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.PrivilegeUser" + "type": "string" } }, - "vin": { + "type": { + "description": "Type is eg. Vehicle, E-bike, roomba", "type": "string" }, - "vinConfirmed": { - "type": "boolean" - }, - "vinCredential": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.VINCredentialData" + "year": { + "type": "integer" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceIntegrationStatus": { + "github_com_DIMO-Network_devices-api_internal_services.DeviceVehicleInfo": { "type": "object", "properties": { - "createdAt": { + "base_msrp": { + "type": "integer" + }, + "driven_wheels": { "type": "string" }, - "externalId": { + "epa_class": { "type": "string" }, - "integrationId": { + "fuel_tank_capacity_gal": { "type": "string" }, - "integrationVendor": { + "fuel_type": { "type": "string" }, - "metadata": { + "mpg": { "type": "string" }, - "status": { + "mpg_city": { "type": "string" }, - "syntheticDevice": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.SyntheticDeviceStatus" + "mpg_highway": { + "type": "string" }, - "tokenId": { - "$ref": "#/definitions/big.Int" + "number_of_doors": { + "type": "string" }, - "updatedAt": { + "vehicle_type": { + "description": "VehicleType PASSENGER CAR, from NHTSA", "type": "string" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.VINCredentialData": { + "github_com_DIMO-Network_devices-api_internal_services.ErrorCodesResponse": { "type": "object", "properties": { - "expiresAt": { - "type": "string" - }, - "issuedAt": { - "type": "string" - }, - "valid": { - "type": "boolean" + "code": { + "type": "string", + "example": "P0148" }, - "vin": { - "type": "string" + "description": { + "type": "string", + "example": "Fuel delivery error" } } }, - "grpc.Integration": { + "github_com_DIMO-Network_devices-api_internal_services.PowertrainType": { + "type": "string", + "enum": [ + "ICE", + "HEV", + "PHEV", + "BEV", + "FCEV" + ], + "x-enum-varnames": [ + "ICE", + "HEV", + "PHEV", + "BEV", + "FCEV" + ] + }, + "github_com_DIMO-Network_devices-api_internal_services.UserDeviceMetadata": { "type": "object", "properties": { - "auto_pi_default_template_id": { - "type": "integer" - }, - "auto_pi_powertrain_template": { - "$ref": "#/definitions/grpc.Integration_AutoPiPowertrainTemplate" - }, - "id": { + "canProtocol": { + "description": "CANProtocol is the protocol that was detected by edge-network from the autopi.", "type": "string" }, - "manufacturer_token_id": { - "type": "integer" - }, - "points": { - "type": "integer" - }, - "refresh_limit_secs": { - "type": "integer" - }, - "style": { - "type": "string" + "elasticDefinitionSynced": { + "type": "boolean" }, - "token_id": { - "description": "token_id can have a 0 value, which means it has not yet been minted and no token id has been assigned. This case should be checked for and handled.", - "type": "integer" + "elasticRegionSynced": { + "type": "boolean" }, - "type": { + "geoDecodedCountry": { "type": "string" }, - "vendor": { + "geoDecodedStateProv": { "type": "string" - } - } - }, - "grpc.Integration_AutoPiPowertrainTemplate": { - "type": "object", - "properties": { - "BEV": { - "type": "integer" - }, - "HEV": { - "type": "integer" - }, - "ICE": { - "type": "integer" }, - "PHEV": { - "type": "integer" - } - } - }, - "helpers.CreateResponse": { - "type": "object", - "properties": { - "id": { + "postal_code": { "type": "string" - } - } - }, - "helpers.ErrorRes": { - "type": "object", - "properties": { - "code": { - "type": "integer" }, - "message": { - "type": "string" + "powertrainType": { + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.PowertrainType" } } }, @@ -3535,7 +2664,6 @@ const docTemplate = `{ "type": "object", "properties": { "beneficiaryAddress": { - "description": "OwnerAddress is the Ethereum address of the NFT owner.", "type": "array", "items": { "type": "integer" @@ -3543,7 +2671,11 @@ const docTemplate = `{ }, "claim": { "description": "Claim contains the status of the on-chain claiming meta-transaction.", - "$ref": "#/definitions/internal_controllers.AutoPiTransactionStatus" + "allOf": [ + { + "$ref": "#/definitions/internal_controllers.AutoPiTransactionStatus" + } + ] }, "deviceId": { "type": "string" @@ -3555,7 +2687,6 @@ const docTemplate = `{ } }, "ethereumAddress": { - "description": "OwnerAddress is the Ethereum address of the NFT owner.", "type": "array", "items": { "type": "integer" @@ -3578,7 +2709,11 @@ const docTemplate = `{ }, "pair": { "description": "Pair contains the status of the on-chain pairing meta-transaction.", - "$ref": "#/definitions/internal_controllers.AutoPiTransactionStatus" + "allOf": [ + { + "$ref": "#/definitions/internal_controllers.AutoPiTransactionStatus" + } + ] }, "releaseVersion": { "type": "string" @@ -3597,7 +2732,11 @@ const docTemplate = `{ }, "unpair": { "description": "Unpair contains the status of the on-chain unpairing meta-transaction.", - "$ref": "#/definitions/internal_controllers.AutoPiTransactionStatus" + "allOf": [ + { + "$ref": "#/definitions/internal_controllers.AutoPiTransactionStatus" + } + ] } } }, @@ -3863,7 +3002,7 @@ const docTemplate = `{ "type": "string" }, "type": { - "type": "string" + "$ref": "#/definitions/internal_controllers.DocumentTypeEnum" }, "url": { "type": "string" @@ -3873,6 +3012,27 @@ const docTemplate = `{ } } }, + "internal_controllers.DocumentTypeEnum": { + "type": "string", + "enum": [ + "DriversLicense", + "Other", + "VehicleTitle", + "VehicleRegistration", + "VehicleInsurance", + "VehicleMaintenance", + "VehicleCustomImage" + ], + "x-enum-varnames": [ + "DriversLicense", + "Other", + "VehicleTitle", + "VehicleRegistration", + "VehicleInsurance", + "VehicleMaintenance", + "VehicleCustomImage" + ] + }, "internal_controllers.GeoFenceUserDevice": { "type": "object", "properties": { @@ -3882,8 +3042,25 @@ const docTemplate = `{ "name": { "type": "string" }, - "userDeviceId": { - "type": "string" + "userDeviceId": { + "type": "string" + } + } + }, + "internal_controllers.GetCommandsByIntegrationResponse": { + "type": "object", + "properties": { + "disabled": { + "type": "array", + "items": { + "type": "string" + } + }, + "enabled": { + "type": "array", + "items": { + "type": "string" + } } } }, @@ -3941,7 +3118,7 @@ const docTemplate = `{ "errorCodes": { "type": "array", "items": { - "$ref": "#/definitions/services.ErrorCodesResponse" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.ErrorCodesResponse" } }, "requestedAt": { @@ -4108,35 +3285,6 @@ const docTemplate = `{ } } }, - "internal_controllers.Offer": { - "type": "object", - "properties": { - "declineReason": { - "description": "The reason the offer was declined from the vendor", - "type": "string" - }, - "error": { - "description": "An error from the vendor (eg. when the VIN is invalid)", - "type": "string" - }, - "grade": { - "description": "The grade of the offer from the vendor (eg. \"RETAIL\")", - "type": "string" - }, - "price": { - "description": "The offer price from the vendor", - "type": "integer" - }, - "url": { - "description": "The offer URL from the vendor", - "type": "string" - }, - "vendor": { - "description": "The vendor of the offer (eg. \"carmax\", \"carvana\", etc.)", - "type": "string" - } - } - }, "internal_controllers.Privilege": { "type": "object", "properties": { @@ -4189,32 +3337,11 @@ const docTemplate = `{ "errorCodes": { "type": "array", "items": { - "$ref": "#/definitions/services.ErrorCodesResponse" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.ErrorCodesResponse" } } } }, - "internal_controllers.RangeSet": { - "type": "object", - "properties": { - "rangeBasis": { - "description": "The basis for the range calculation (eg. \"MPG\" or \"MPG Highway\")", - "type": "string" - }, - "rangeDistance": { - "description": "The estimated range distance", - "type": "integer" - }, - "rangeUnit": { - "description": "The unit used for the rangeDistance (eg. \"miles\" or \"kilometers\")", - "type": "string" - }, - "updated": { - "description": "The time the data was collected", - "type": "string" - } - } - }, "internal_controllers.RegisterDeviceIntegrationRequest": { "type": "object", "properties": { @@ -4264,7 +3391,7 @@ const docTemplate = `{ "integrationCapabilities": { "type": "array", "items": { - "$ref": "#/definitions/services.DeviceCompatibility" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceCompatibility" } }, "userDeviceId": { @@ -4373,7 +3500,7 @@ const docTemplate = `{ "type": "string" }, "deviceDefinition": { - "$ref": "#/definitions/services.DeviceDefinition" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceDefinition" }, "id": { "type": "string" @@ -4385,7 +3512,7 @@ const docTemplate = `{ } }, "metadata": { - "$ref": "#/definitions/services.UserDeviceMetadata" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.UserDeviceMetadata" }, "name": { "type": "string" @@ -4465,232 +3592,6 @@ const docTemplate = `{ "math.HexOrDecimal256": { "type": "object" }, - "services.AutoPiTask": { - "type": "object", - "properties": { - "code": { - "type": "integer" - }, - "description": { - "type": "string" - }, - "error": { - "type": "string" - }, - "status": { - "type": "string" - }, - "taskId": { - "type": "string" - }, - "updatedAt": { - "type": "string" - }, - "updates": { - "description": "Updates increments every time the job was updated.", - "type": "integer" - } - } - }, - "services.DeviceAttribute": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "services.DeviceCompatibility": { - "type": "object", - "properties": { - "capabilities": { - "type": "array", - "items": { - "type": "integer" - } - }, - "country": { - "type": "string" - }, - "id": { - "type": "string" - }, - "region": { - "type": "string" - }, - "style": { - "type": "string" - }, - "type": { - "type": "string" - }, - "vendor": { - "type": "string" - } - } - }, - "services.DeviceDefinition": { - "type": "object", - "properties": { - "compatibleIntegrations": { - "description": "CompatibleIntegrations has systems this vehicle can integrate with", - "type": "array", - "items": { - "$ref": "#/definitions/services.DeviceCompatibility" - } - }, - "deviceAttributes": { - "description": "DeviceAttributes is a list of attributes for the device type as defined in device_types.properties", - "type": "array", - "items": { - "$ref": "#/definitions/services.DeviceAttribute" - } - }, - "deviceDefinitionId": { - "type": "string" - }, - "imageUrl": { - "type": "string" - }, - "make": { - "$ref": "#/definitions/services.DeviceMake" - }, - "metadata": {}, - "name": { - "type": "string" - }, - "type": { - "$ref": "#/definitions/services.DeviceType" - }, - "vehicleData": { - "description": "VehicleInfo will be empty if not a vehicle type", - "$ref": "#/definitions/services.DeviceVehicleInfo" - }, - "verified": { - "type": "boolean" - } - } - }, - "services.DeviceMake": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "logo_url": { - "type": "string" - }, - "name": { - "type": "string" - }, - "oem_platform_name": { - "type": "string" - } - } - }, - "services.DeviceType": { - "type": "object", - "properties": { - "make": { - "type": "string" - }, - "model": { - "type": "string" - }, - "subModels": { - "type": "array", - "items": { - "type": "string" - } - }, - "type": { - "description": "Type is eg. Vehicle, E-bike, roomba", - "type": "string" - }, - "year": { - "type": "integer" - } - } - }, - "services.DeviceVehicleInfo": { - "type": "object", - "properties": { - "base_msrp": { - "type": "integer" - }, - "driven_wheels": { - "type": "string" - }, - "epa_class": { - "type": "string" - }, - "fuel_tank_capacity_gal": { - "type": "string" - }, - "fuel_type": { - "type": "string" - }, - "mpg": { - "type": "string" - }, - "mpg_city": { - "type": "string" - }, - "mpg_highway": { - "type": "string" - }, - "number_of_doors": { - "type": "string" - }, - "vehicle_type": { - "description": "VehicleType PASSENGER CAR, from NHTSA", - "type": "string" - } - } - }, - "services.ErrorCodesResponse": { - "type": "object", - "properties": { - "code": { - "type": "string", - "example": "P0148" - }, - "description": { - "type": "string", - "example": "Fuel delivery error" - } - } - }, - "services.UserDeviceMetadata": { - "type": "object", - "properties": { - "canProtocol": { - "description": "CANProtocol is the protocol that was detected by edge-network from the autopi.", - "type": "string" - }, - "elasticDefinitionSynced": { - "type": "boolean" - }, - "elasticRegionSynced": { - "type": "boolean" - }, - "geoDecodedCountry": { - "type": "string" - }, - "geoDecodedStateProv": { - "type": "string" - }, - "postal_code": { - "type": "string" - }, - "powertrainType": { - "type": "string" - } - } - }, "smartcar.TirePressure": { "type": "object", "properties": { @@ -4717,9 +3618,20 @@ const docTemplate = `{ "type": "string" }, "unitSystem": { - "type": "string" + "$ref": "#/definitions/smartcar.UnitSystem" } } + }, + "smartcar.UnitSystem": { + "type": "string", + "enum": [ + "metric", + "imperial" + ], + "x-enum-varnames": [ + "Metric", + "Imperial" + ] } }, "securityDefinitions": { @@ -4741,6 +3653,8 @@ var SwaggerInfo = &swag.Spec{ Description: "", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", } func init() { diff --git a/docs/swagger.json b/docs/swagger.json index b055d7110..6ea8725ad 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -65,7 +65,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiDeviceInfo" + "$ref": "#/definitions/internal_controllers.AutoPiDeviceInfo" } } } @@ -124,7 +124,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiClaimRequest" + "$ref": "#/definitions/internal_controllers.AutoPiClaimRequest" } } ], @@ -189,7 +189,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/services.AutoPiTask" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.AutoPiTask" } } } @@ -267,7 +267,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/constants.CountryInfo" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_constants.CountryInfo" } } } @@ -295,7 +295,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/constants.CountryInfo" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_constants.CountryInfo" } }, "400": { @@ -307,74 +307,6 @@ } } }, - "/dcn/{tokenID}": { - "get": { - "description": "retrieves the DCN NFT metadata for a given token ID address", - "produces": [ - "application/json" - ], - "tags": [ - "dcn" - ], - "parameters": [ - { - "type": "string", - "description": "DCN node id decimal representation", - "name": "tokenID", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/internal_controllers.NFTMetadataResp" - } - }, - "400": { - "description": "Bad Request" - }, - "404": { - "description": "Not Found" - } - } - } - }, - "/dcn/{tokenID}/image": { - "get": { - "description": "retrieves the DCN NFT metadata for a given token address", - "produces": [ - "image/svg+xml" - ], - "tags": [ - "dcn" - ], - "parameters": [ - { - "type": "string", - "description": "DCN node id decimal representation", - "name": "tokenID", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/internal_controllers.NFTMetadataResp" - } - }, - "400": { - "description": "Bad Request" - }, - "404": { - "description": "Not Found" - } - } - } - }, "/documents": { "get": { "security": [ @@ -398,7 +330,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.DocumentResponse" + "$ref": "#/definitions/internal_controllers.DocumentResponse" } } } @@ -453,7 +385,7 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.DocumentResponse" + "$ref": "#/definitions/internal_controllers.DocumentResponse" } } } @@ -489,7 +421,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.DocumentResponse" + "$ref": "#/definitions/internal_controllers.DocumentResponse" } } } @@ -580,8 +512,8 @@ } } }, - "/integration/:tokenID/credentials": { - "post": { + "/integration/:tokenID/commands": { + "get": { "security": [ { "ApiKeyAuth": [] @@ -590,7 +522,7 @@ "BearerAuth": [] } ], - "description": "Complete Tesla auth and get devices for authenticated user", + "description": "Get a list of available commands by integration", "consumes": [ "application/json" ], @@ -598,7 +530,7 @@ "application/json" ], "tags": [ - "user-devices" + "integrations" ], "parameters": [ { @@ -607,112 +539,62 @@ "name": "tokenID", "in": "path", "required": true - }, - { - "description": "all fields are required", - "name": "user_device", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeRequest" - } } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeResponseWrapper" + "$ref": "#/definitions/internal_controllers.GetCommandsByIntegrationResponse" } } } } }, - "/integrations": { - "get": { + "/integration/:tokenID/credentials": { + "post": { "security": [ + { + "ApiKeyAuth": [] + }, { "BearerAuth": [] } ], - "description": "gets list of integrations we have defined", - "produces": [ + "description": "Complete Tesla auth and get devices for authenticated user", + "consumes": [ "application/json" ], - "tags": [ - "integrations" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/grpc.Integration" - } - } - } - } - } - }, - "/manufacturer/{tokenId}": { - "get": { - "description": "Retrieves NFT metadata for a given manufacturer.", "produces": [ "application/json" ], "tags": [ - "nfts" + "user-devices" ], "parameters": [ { - "type": "integer", - "description": "token id", - "name": "tokenId", + "type": "string", + "description": "token id for integration", + "name": "tokenID", "in": "path", "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/internal_controllers.NFTMetadataResp" - } }, - "404": { - "description": "Not Found" - } - } - } - }, - "/synthetic/device/{tokenId}": { - "get": { - "description": "Retrieves NFT metadata for a given synthetic device.", - "produces": [ - "application/json" - ], - "tags": [ - "nfts" - ], - "parameters": [ { - "type": "integer", - "description": "token id", - "name": "tokenId", - "in": "path", - "required": true + "description": "all fields are required", + "name": "user_device", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_controllers.CompleteOAuthExchangeRequest" + } } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/internal_controllers.NFTMetadataResp" + "$ref": "#/definitions/internal_controllers.CompleteOAuthExchangeResponseWrapper" } - }, - "404": { - "description": "Not Found" } } } @@ -744,7 +626,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDevice" + "$ref": "#/definitions/internal_controllers.RegisterUserDevice" } } ], @@ -752,7 +634,7 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceResponse" + "$ref": "#/definitions/internal_controllers.RegisterUserDeviceResponse" } } } @@ -844,7 +726,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiPairRequest" + "$ref": "#/definitions/internal_controllers.AutoPiPairRequest" } } ], @@ -904,7 +786,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiPairRequest" + "$ref": "#/definitions/internal_controllers.AutoPiPairRequest" } } ], @@ -932,7 +814,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterDeviceIntegrationRequest" + "$ref": "#/definitions/internal_controllers.RegisterDeviceIntegrationRequest" } } ], @@ -970,7 +852,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceSmartcar" + "$ref": "#/definitions/internal_controllers.RegisterUserDeviceSmartcar" } } ], @@ -978,13 +860,13 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull" + "$ref": "#/definitions/internal_controllers.UserDeviceFull" } }, "201": { "description": "Created", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull" + "$ref": "#/definitions/internal_controllers.UserDeviceFull" } }, "400": { @@ -1029,7 +911,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceVIN" + "$ref": "#/definitions/internal_controllers.RegisterUserDeviceVIN" } } ], @@ -1037,7 +919,7 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull" + "$ref": "#/definitions/internal_controllers.UserDeviceFull" } }, "400": { @@ -1070,7 +952,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.MyDevicesResp" + "$ref": "#/definitions/internal_controllers.MyDevicesResp" } } } @@ -1094,7 +976,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.MyDevicesResp" + "$ref": "#/definitions/internal_controllers.MyDevicesResp" } } } @@ -1180,7 +1062,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.MintRequest" + "$ref": "#/definitions/internal_controllers.MintRequest" } } ], @@ -1275,7 +1157,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UpdateCountryCodeReq" + "$ref": "#/definitions/internal_controllers.UpdateCountryCodeReq" } } ], @@ -1316,7 +1198,7 @@ "404": { "description": "Vehicle not found", "schema": { - "$ref": "#/definitions/helpers.ErrorRes" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes" } } } @@ -1359,7 +1241,7 @@ "404": { "description": "Vehicle not found", "schema": { - "$ref": "#/definitions/helpers.ErrorRes" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes" } } } @@ -1386,13 +1268,13 @@ "404": { "description": "Vehicle not found", "schema": { - "$ref": "#/definitions/helpers.ErrorRes" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes" } }, "429": { "description": "Last query already cleared", "schema": { - "$ref": "#/definitions/helpers.ErrorRes" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes" } } } @@ -1413,7 +1295,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceIntegrationResponse" + "$ref": "#/definitions/internal_controllers.GetUserDeviceIntegrationResponse" } } } @@ -1545,7 +1427,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse" + "$ref": "#/definitions/internal_controllers.CommandResponse" } } } @@ -1584,7 +1466,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse" + "$ref": "#/definitions/internal_controllers.CommandResponse" } } } @@ -1623,7 +1505,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse" + "$ref": "#/definitions/internal_controllers.CommandResponse" } } } @@ -1739,7 +1621,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse" + "$ref": "#/definitions/internal_controllers.CommandResponse" } } } @@ -1785,7 +1667,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandRequestStatusResp" + "$ref": "#/definitions/internal_controllers.CommandRequestStatusResp" } } } @@ -1815,7 +1697,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UpdateNameReq" + "$ref": "#/definitions/internal_controllers.UpdateNameReq" } }, { @@ -1890,7 +1772,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UpdateVINReq" + "$ref": "#/definitions/internal_controllers.UpdateVINReq" } }, { @@ -1933,7 +1815,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.NFTImageData" + "$ref": "#/definitions/internal_controllers.NFTImageData" } } ], @@ -1967,7 +1849,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.GetGeofence" + "$ref": "#/definitions/internal_controllers.GetGeofence" } } } @@ -1999,7 +1881,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CreateGeofence" + "$ref": "#/definitions/internal_controllers.CreateGeofence" } } ], @@ -2007,7 +1889,7 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/helpers.CreateResponse" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.CreateResponse" } } } @@ -2047,7 +1929,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CreateGeofence" + "$ref": "#/definitions/internal_controllers.CreateGeofence" } } ], @@ -2462,7 +2344,7 @@ "big.Int": { "type": "object" }, - "constants.CountryInfo": { + "github_com_DIMO-Network_devices-api_internal_constants.CountryInfo": { "type": "object", "properties": { "alpha_2": { @@ -2491,1032 +2373,269 @@ } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.AutoPiClaimRequest": { + "github_com_DIMO-Network_devices-api_internal_controllers_helpers.CreateResponse": { "type": "object", "properties": { - "aftermarketDeviceSignature": { - "description": "AftermarketDeviceSignature is the signature from the aftermarket device.", + "id": { "type": "string" + } + } + }, + "github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes": { + "type": "object", + "properties": { + "code": { + "type": "integer" }, - "userSignature": { - "description": "UserSignature is the signature from the user, using their private key.", + "message": { "type": "string" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.AutoPiDeviceInfo": { + "github_com_DIMO-Network_devices-api_internal_services.AutoPiTask": { "type": "object", "properties": { - "beneficiaryAddress": { - "description": "OwnerAddress is the Ethereum address of the NFT owner.", - "type": "array", - "items": { - "type": "integer" - } - }, - "claim": { - "description": "Claim contains the status of the on-chain claiming meta-transaction.", - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiTransactionStatus" + "code": { + "type": "integer" }, - "deviceId": { + "description": { "type": "string" }, - "dockerReleases": { - "type": "array", - "items": { - "type": "integer" - } - }, - "ethereumAddress": { - "description": "OwnerAddress is the Ethereum address of the NFT owner.", - "type": "array", - "items": { - "type": "integer" - } - }, - "hwRevision": { + "error": { "type": "string" }, - "isUpdated": { - "type": "boolean" - }, - "lastCommunication": { + "status": { "type": "string" }, - "manufacturer": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.ManufacturerInfo" - }, - "ownerAddress": { + "taskId": { "type": "string" }, - "pair": { - "description": "Pair contains the status of the on-chain pairing meta-transaction.", - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiTransactionStatus" - }, - "releaseVersion": { + "updatedAt": { "type": "string" }, - "shouldUpdate": { - "type": "boolean" - }, - "template": { + "updates": { + "description": "Updates increments every time the job was updated.", "type": "integer" - }, - "tokenId": { - "$ref": "#/definitions/big.Int" - }, - "unitId": { + } + } + }, + "github_com_DIMO-Network_devices-api_internal_services.DeviceAttribute": { + "type": "object", + "properties": { + "name": { "type": "string" }, - "unpair": { - "description": "Unpair contains the status of the on-chain unpairing meta-transaction.", - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiTransactionStatus" + "value": { + "type": "string" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.AutoPiPairRequest": { + "github_com_DIMO-Network_devices-api_internal_services.DeviceCompatibility": { "type": "object", "properties": { - "aftermarketDeviceSignature": { - "description": "AftermarketDeviceSignature is the 65-byte, hex-encoded Ethereum signature of\nthe pairing payload by the device. Only needed if the vehicle owner and aftermarket\ndevice owner are not the same.", + "capabilities": { "type": "array", "items": { "type": "integer" } }, - "externalId": { + "country": { "type": "string" }, - "signature": { - "description": "AftermarketDeviceSignature is the 65-byte, hex-encoded Ethereum signature of\nthe pairing payload by the device. Only needed if the vehicle owner and aftermarket\ndevice owner are not the same.", - "type": "array", - "items": { - "type": "integer" - } + "id": { + "type": "string" + }, + "region": { + "type": "string" + }, + "style": { + "type": "string" + }, + "type": { + "type": "string" + }, + "vendor": { + "type": "string" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.AutoPiTransactionStatus": { + "github_com_DIMO-Network_devices-api_internal_services.DeviceDefinition": { "type": "object", "properties": { - "createdAt": { - "description": "CreatedAt is the timestamp of the creation of the meta-transaction.", - "type": "string", - "example": "2022-10-01T09:22:21.002Z" + "compatibleIntegrations": { + "description": "CompatibleIntegrations has systems this vehicle can integrate with", + "type": "array", + "items": { + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceCompatibility" + } }, - "hash": { - "description": "Hash is the hexidecimal transaction hash, available for any transaction at the Submitted stage or greater.", - "type": "string", - "example": "0x28b4662f1e1b15083261a4a5077664f4003d58cb528826b7aab7fad466c28e70" - }, - "status": { - "description": "Status is the state of the transaction performing this operation. There are only four options.", - "type": "string", - "enum": [ - "Unsubmitted", - "Submitted", - "Mined", - "Confirmed" - ], - "example": "Mined" - }, - "updatedAt": { - "description": "UpdatedAt is the last time we updated the status of the transaction.", - "type": "string", - "example": "2022-10-01T09:22:26.337Z" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.BurnRequest": { - "type": "object", - "required": [ - "signature" - ], - "properties": { - "signature": { - "description": "Signature is the hex encoding of the EIP-712 signature result.", - "type": "string" - }, - "tokenId": { - "$ref": "#/definitions/big.Int" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.BurnSyntheticDeviceRequest": { - "type": "object", - "properties": { - "signature": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.CommandRequestStatusResp": { - "type": "object", - "properties": { - "command": { - "type": "string", - "example": "doors/unlock" - }, - "createdAt": { - "type": "string", - "example": "2022-08-09T19:38:39Z" - }, - "id": { - "type": "string", - "example": "2D8LqUHQtaMHH6LYPqznmJMBeZm" - }, - "status": { - "type": "string", - "enum": [ - "Pending", - "Complete", - "Failed" - ], - "example": "Complete" - }, - "updatedAt": { - "type": "string", - "example": "2022-08-09T19:39:22Z" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse": { - "type": "object", - "properties": { - "requestId": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeRequest": { - "type": "object", - "properties": { - "authorizationCode": { - "type": "string" - }, - "redirectUri": { - "type": "string" - }, - "region": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeResponse": { - "type": "object", - "properties": { - "definition": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.DeviceDefinition" - }, - "externalId": { - "type": "string" - }, - "vin": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeResponseWrapper": { - "type": "object", - "properties": { - "vehicles": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeResponse" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.CreateGeofence": { - "type": "object", - "properties": { - "h3Indexes": { - "description": "required: false", - "type": "array", - "items": { - "type": "string" - } - }, - "name": { - "description": "required: true", - "type": "string" - }, - "type": { - "description": "one of following: \"PrivacyFence\", \"TriggerEntry\", \"TriggerExit\"\nrequired: true", - "type": "string" - }, - "userDeviceIds": { - "description": "Optionally link the geofence with a list of user device ID", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.DeviceDefinition": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "make": { - "type": "string" - }, - "model": { - "type": "string" - }, - "year": { - "type": "integer" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.DeviceRange": { - "type": "object", - "properties": { - "rangeSets": { - "description": "Contains a list of range sets, one for each range basis (may be empty)", - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RangeSet" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.DeviceSnapshot": { - "type": "object", - "properties": { - "ambientTemp": { - "type": "number" - }, - "batteryCapacity": { - "type": "integer" - }, - "batteryVoltage": { - "type": "number" - }, - "chargeLimit": { - "type": "number" - }, - "charging": { - "type": "boolean" - }, - "fuelPercentRemaining": { - "type": "number" - }, - "latitude": { - "type": "number" - }, - "longitude": { - "type": "number" - }, - "odometer": { - "type": "number" - }, - "oil": { - "type": "number" - }, - "range": { - "type": "number" - }, - "recordCreatedAt": { - "type": "string" - }, - "recordUpdatedAt": { - "type": "string" - }, - "soc": { - "type": "number" - }, - "tirePressure": { - "$ref": "#/definitions/smartcar.TirePressure" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.DocumentResponse": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "ext": { - "type": "string" - }, - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "url": { - "type": "string" - }, - "userDeviceId": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.GeoFenceUserDevice": { - "type": "object", - "properties": { - "mmy": { - "type": "string" - }, - "name": { - "type": "string" - }, - "userDeviceId": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.GetGeofence": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "h3Indexes": { - "type": "array", - "items": { - "type": "string" - } - }, - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "updatedAt": { - "type": "string" - }, - "userDevices": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.GeoFenceUserDevice" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceErrorCodeQueriesResponse": { - "type": "object", - "properties": { - "queries": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceErrorCodeQueriesResponseItem" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceErrorCodeQueriesResponseItem": { - "type": "object", - "properties": { - "clearedAt": { - "description": "ClearedAt is the time at which the user cleared the codes from this query.\nMay be null.", - "type": "string", - "example": "2023-05-23T12:57:05Z" - }, - "errorCodes": { - "type": "array", - "items": { - "$ref": "#/definitions/services.ErrorCodesResponse" - } - }, - "requestedAt": { - "type": "string", - "example": "2023-05-23T12:56:36Z" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceIntegrationResponse": { - "type": "object", - "properties": { - "createdAt": { - "description": "CreatedAt is the creation time of this integration for this device.", - "type": "string" - }, - "externalId": { - "description": "ExternalID is the identifier used by the third party for the device. It may be absent if we\nhaven't authorized yet.", - "type": "string" - }, - "status": { - "description": "Status is one of \"Pending\", \"PendingFirstData\", \"Active\", \"Failed\", \"DuplicateIntegration\".", - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.ManufacturerInfo": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "tokenId": { - "$ref": "#/definitions/big.Int" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.MintRequest": { - "type": "object", - "required": [ - "imageData", - "signature" - ], - "properties": { - "imageData": { - "description": "ImageData contains the base64-encoded NFT PNG image.", - "type": "string" - }, - "imageDataTransparent": { - "description": "ImageDataTransparent contains the base64-encoded NFT PNG image\nwith a transparent background, for use in the app. For compatibility\nwith older versions it is not required.", - "type": "string" - }, - "signature": { - "description": "Signature is the hex encoding of the EIP-712 signature result.", - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.MintSyntheticDeviceRequest": { - "type": "object", - "properties": { - "signature": { - "type": "string", - "example": "0xc565d38982e1a5004efb5ee390fba0a08bb5e72b3f3e91094c66bc395c324f785425d58d5c1a601372d9c16164e380c63e89f1e0ea95fdefdf7b2854c4f938e81b" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.MyDevicesResp": { - "type": "object", - "properties": { - "sharedDevices": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull" - } - }, - "userDevices": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.NFTAttribute": { - "type": "object", - "properties": { - "trait_type": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.NFTData": { - "type": "object", - "properties": { - "ownerAddress": { - "description": "OwnerAddress is the Ethereum address of the NFT owner.", - "type": "array", - "items": { - "type": "integer" - } - }, - "status": { - "description": "Status is the minting status of the NFT.", - "type": "string", - "enum": [ - "Unstarted", - "Submitted", - "Mined", - "Confirmed" - ], - "example": "Confirmed" - }, - "tokenId": { - "type": "number", - "example": 37 - }, - "tokenUri": { - "type": "string", - "example": "https://nft.dimo.zone/37" - }, - "txHash": { - "description": "TxHash is the hash of the minting transaction.", - "type": "string", - "example": "0x30bce3da6985897224b29a0fe064fd2b426bb85a394cc09efe823b5c83326a8e" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.NFTImageData": { - "type": "object", - "required": [ - "imageData" - ], - "properties": { - "imageData": { - "description": "ImageData contains the base64-encoded NFT PNG image.", - "type": "string" - }, - "imageDataTransparent": { - "description": "ImageDataTransparent contains the base64-encoded NFT PNG image\nwith a transparent background, for use in the app. For compatibility\nwith older versions it is not required.", - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.NFTMetadataResp": { - "type": "object", - "properties": { - "attributes": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.NFTAttribute" - } - }, - "description": { - "type": "string" - }, - "image": { - "type": "string" - }, - "name": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.Offer": { - "type": "object", - "properties": { - "declineReason": { - "description": "The reason the offer was declined from the vendor", - "type": "string" - }, - "error": { - "description": "An error from the vendor (eg. when the VIN is invalid)", - "type": "string" - }, - "grade": { - "description": "The grade of the offer from the vendor (eg. \"RETAIL\")", - "type": "string" - }, - "price": { - "description": "The offer price from the vendor", - "type": "integer" - }, - "url": { - "description": "The offer URL from the vendor", - "type": "string" - }, - "vendor": { - "description": "The vendor of the offer (eg. \"carmax\", \"carvana\", etc.)", - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.Privilege": { - "type": "object", - "properties": { - "expiry": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "updatedAt": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.PrivilegeUser": { - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "privileges": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.Privilege" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.QueryDeviceErrorCodesReq": { - "type": "object", - "properties": { - "errorCodes": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "P0106", - "P0279" - ] - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.QueryDeviceErrorCodesResponse": { - "type": "object", - "properties": { - "clearedAt": { - "type": "string" - }, - "errorCodes": { - "type": "array", - "items": { - "$ref": "#/definitions/services.ErrorCodesResponse" - } - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.RangeSet": { - "type": "object", - "properties": { - "rangeBasis": { - "description": "The basis for the range calculation (eg. \"MPG\" or \"MPG Highway\")", - "type": "string" - }, - "rangeDistance": { - "description": "The estimated range distance", - "type": "integer" - }, - "rangeUnit": { - "description": "The unit used for the rangeDistance (eg. \"miles\" or \"kilometers\")", - "type": "string" - }, - "updated": { - "description": "The time the data was collected", - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.RegisterDeviceIntegrationRequest": { - "type": "object", - "properties": { - "accessToken": { - "type": "string" - }, - "code": { - "description": "Code is an OAuth authorization code. Not used in all integrations.", - "type": "string" - }, - "expiresIn": { - "type": "integer" - }, - "externalId": { - "description": "ExternalID is the only field needed for AutoPi registrations. It is the UnitID.", - "type": "string" - }, - "redirectURI": { - "description": "RedirectURI is the OAuth redirect URI used by the frontend. Not used in all integrations.", - "type": "string" - }, - "refreshToken": { - "type": "string" - }, - "version": { - "type": "integer" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDevice": { - "type": "object", - "properties": { - "countryCode": { - "type": "string" - }, - "deviceDefinitionId": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceResponse": { - "type": "object", - "properties": { - "deviceDefinitionId": { - "type": "string" - }, - "integrationCapabilities": { + "deviceAttributes": { + "description": "DeviceAttributes is a list of attributes for the device type as defined in device_types.properties", "type": "array", "items": { - "$ref": "#/definitions/services.DeviceCompatibility" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceAttribute" } }, - "userDeviceId": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceSmartcar": { - "type": "object", - "properties": { - "code": { - "description": "Code refers to the auth code provided by smartcar when user logs in", - "type": "string" - }, - "countryCode": { + "deviceDefinitionId": { "type": "string" }, - "redirectURI": { - "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceVIN": { - "type": "object", - "properties": { - "canProtocol": { - "description": "CANProtocol is the protocol that was detected by edge-network from the autopi.", + "imageUrl": { "type": "string" }, - "countryCode": { - "type": "string" + "make": { + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceMake" }, - "vin": { + "metadata": {}, + "name": { "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.SyntheticDeviceStatus": { - "type": "object", - "properties": { - "address": { - "type": "string", - "example": "0xAED7EA8035eEc47E657B34eF5D020c7005487443" }, - "status": { - "type": "string", - "enum": [ - "Unstarted", - "Submitted", - "Mined", - "Confirmed" - ], - "example": "Confirmed" + "type": { + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceType" }, - "tokenId": { - "type": "number", - "example": 15 + "vehicleData": { + "description": "VehicleInfo will be empty if not a vehicle type", + "allOf": [ + { + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceVehicleInfo" + } + ] }, - "txHash": { - "type": "string", - "example": "0x30bce3da6985897224b29a0fe064fd2b426bb85a394cc09efe823b5c83326a8e" + "verified": { + "type": "boolean" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.UpdateCountryCodeReq": { + "github_com_DIMO-Network_devices-api_internal_services.DeviceMake": { "type": "object", "properties": { - "countryCode": { + "id": { "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.UpdateNameReq": { - "type": "object", - "properties": { + }, + "logo_url": { + "type": "string" + }, "name": { "type": "string" - } - } - }, - "github.com_DIMO-Network_devices-api_internal_controllers.UpdateVINReq": { - "type": "object", - "required": [ - "vin" - ], - "properties": { - "signature": { - "description": "Signature is the hex-encoded result of the AutoPi signing the VIN. It must\nbe present to verify the VIN.", - "type": "string", - "example": "16b15f88bbd2e0a22d1d0084b8b7080f2003ea83eab1a00f80d8c18446c9c1b6224f17aa09eaf167717ca4f355bb6dc94356e037edf3adf6735a86fc3741f5231b" }, - "vin": { - "description": "VIN is a vehicle identification number. At the very least, it must be\n17 characters in length and contain only letters and numbers.", - "type": "string", - "example": "4Y1SL65848Z411439" + "oem_platform_name": { + "type": "string" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull": { + "github_com_DIMO-Network_devices-api_internal_services.DeviceType": { "type": "object", "properties": { - "countryCode": { - "type": "string" - }, - "customImageUrl": { - "type": "string" - }, - "deviceDefinition": { - "$ref": "#/definitions/services.DeviceDefinition" - }, - "id": { - "type": "string" - }, - "integrations": { - "type": "array", - "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceIntegrationStatus" - } - }, - "metadata": { - "$ref": "#/definitions/services.UserDeviceMetadata" - }, - "name": { + "make": { "type": "string" }, - "nft": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.NFTData" - }, - "optedInAt": { + "model": { "type": "string" }, - "privilegedUsers": { + "subModels": { "type": "array", "items": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.PrivilegeUser" + "type": "string" } }, - "vin": { + "type": { + "description": "Type is eg. Vehicle, E-bike, roomba", "type": "string" }, - "vinConfirmed": { - "type": "boolean" - }, - "vinCredential": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.VINCredentialData" + "year": { + "type": "integer" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceIntegrationStatus": { + "github_com_DIMO-Network_devices-api_internal_services.DeviceVehicleInfo": { "type": "object", "properties": { - "createdAt": { + "base_msrp": { + "type": "integer" + }, + "driven_wheels": { "type": "string" }, - "externalId": { + "epa_class": { "type": "string" }, - "integrationId": { + "fuel_tank_capacity_gal": { "type": "string" }, - "integrationVendor": { + "fuel_type": { "type": "string" }, - "metadata": { + "mpg": { "type": "string" }, - "status": { + "mpg_city": { "type": "string" }, - "syntheticDevice": { - "$ref": "#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.SyntheticDeviceStatus" + "mpg_highway": { + "type": "string" }, - "tokenId": { - "$ref": "#/definitions/big.Int" + "number_of_doors": { + "type": "string" }, - "updatedAt": { + "vehicle_type": { + "description": "VehicleType PASSENGER CAR, from NHTSA", "type": "string" } } }, - "github.com_DIMO-Network_devices-api_internal_controllers.VINCredentialData": { + "github_com_DIMO-Network_devices-api_internal_services.ErrorCodesResponse": { "type": "object", "properties": { - "expiresAt": { - "type": "string" - }, - "issuedAt": { - "type": "string" - }, - "valid": { - "type": "boolean" + "code": { + "type": "string", + "example": "P0148" }, - "vin": { - "type": "string" + "description": { + "type": "string", + "example": "Fuel delivery error" } } }, - "grpc.Integration": { + "github_com_DIMO-Network_devices-api_internal_services.PowertrainType": { + "type": "string", + "enum": [ + "ICE", + "HEV", + "PHEV", + "BEV", + "FCEV" + ], + "x-enum-varnames": [ + "ICE", + "HEV", + "PHEV", + "BEV", + "FCEV" + ] + }, + "github_com_DIMO-Network_devices-api_internal_services.UserDeviceMetadata": { "type": "object", "properties": { - "auto_pi_default_template_id": { - "type": "integer" - }, - "auto_pi_powertrain_template": { - "$ref": "#/definitions/grpc.Integration_AutoPiPowertrainTemplate" - }, - "id": { + "canProtocol": { + "description": "CANProtocol is the protocol that was detected by edge-network from the autopi.", "type": "string" }, - "manufacturer_token_id": { - "type": "integer" - }, - "points": { - "type": "integer" - }, - "refresh_limit_secs": { - "type": "integer" - }, - "style": { - "type": "string" + "elasticDefinitionSynced": { + "type": "boolean" }, - "token_id": { - "description": "token_id can have a 0 value, which means it has not yet been minted and no token id has been assigned. This case should be checked for and handled.", - "type": "integer" + "elasticRegionSynced": { + "type": "boolean" }, - "type": { + "geoDecodedCountry": { "type": "string" }, - "vendor": { + "geoDecodedStateProv": { "type": "string" - } - } - }, - "grpc.Integration_AutoPiPowertrainTemplate": { - "type": "object", - "properties": { - "BEV": { - "type": "integer" - }, - "HEV": { - "type": "integer" - }, - "ICE": { - "type": "integer" }, - "PHEV": { - "type": "integer" - } - } - }, - "helpers.CreateResponse": { - "type": "object", - "properties": { - "id": { + "postal_code": { "type": "string" - } - } - }, - "helpers.ErrorRes": { - "type": "object", - "properties": { - "code": { - "type": "integer" }, - "message": { - "type": "string" + "powertrainType": { + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.PowertrainType" } } }, @@ -3537,7 +2656,6 @@ "type": "object", "properties": { "beneficiaryAddress": { - "description": "OwnerAddress is the Ethereum address of the NFT owner.", "type": "array", "items": { "type": "integer" @@ -3545,7 +2663,11 @@ }, "claim": { "description": "Claim contains the status of the on-chain claiming meta-transaction.", - "$ref": "#/definitions/internal_controllers.AutoPiTransactionStatus" + "allOf": [ + { + "$ref": "#/definitions/internal_controllers.AutoPiTransactionStatus" + } + ] }, "deviceId": { "type": "string" @@ -3557,7 +2679,6 @@ } }, "ethereumAddress": { - "description": "OwnerAddress is the Ethereum address of the NFT owner.", "type": "array", "items": { "type": "integer" @@ -3580,7 +2701,11 @@ }, "pair": { "description": "Pair contains the status of the on-chain pairing meta-transaction.", - "$ref": "#/definitions/internal_controllers.AutoPiTransactionStatus" + "allOf": [ + { + "$ref": "#/definitions/internal_controllers.AutoPiTransactionStatus" + } + ] }, "releaseVersion": { "type": "string" @@ -3599,7 +2724,11 @@ }, "unpair": { "description": "Unpair contains the status of the on-chain unpairing meta-transaction.", - "$ref": "#/definitions/internal_controllers.AutoPiTransactionStatus" + "allOf": [ + { + "$ref": "#/definitions/internal_controllers.AutoPiTransactionStatus" + } + ] } } }, @@ -3799,18 +2928,6 @@ } } }, - "internal_controllers.DeviceRange": { - "type": "object", - "properties": { - "rangeSets": { - "description": "Contains a list of range sets, one for each range basis (may be empty)", - "type": "array", - "items": { - "$ref": "#/definitions/internal_controllers.RangeSet" - } - } - } - }, "internal_controllers.DeviceSnapshot": { "type": "object", "properties": { @@ -3877,7 +2994,7 @@ "type": "string" }, "type": { - "type": "string" + "$ref": "#/definitions/internal_controllers.DocumentTypeEnum" }, "url": { "type": "string" @@ -3887,6 +3004,27 @@ } } }, + "internal_controllers.DocumentTypeEnum": { + "type": "string", + "enum": [ + "DriversLicense", + "Other", + "VehicleTitle", + "VehicleRegistration", + "VehicleInsurance", + "VehicleMaintenance", + "VehicleCustomImage" + ], + "x-enum-varnames": [ + "DriversLicense", + "Other", + "VehicleTitle", + "VehicleRegistration", + "VehicleInsurance", + "VehicleMaintenance", + "VehicleCustomImage" + ] + }, "internal_controllers.GeoFenceUserDevice": { "type": "object", "properties": { @@ -3896,8 +3034,25 @@ "name": { "type": "string" }, - "userDeviceId": { - "type": "string" + "userDeviceId": { + "type": "string" + } + } + }, + "internal_controllers.GetCommandsByIntegrationResponse": { + "type": "object", + "properties": { + "disabled": { + "type": "array", + "items": { + "type": "string" + } + }, + "enabled": { + "type": "array", + "items": { + "type": "string" + } } } }, @@ -3955,7 +3110,7 @@ "errorCodes": { "type": "array", "items": { - "$ref": "#/definitions/services.ErrorCodesResponse" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.ErrorCodesResponse" } }, "requestedAt": { @@ -4122,35 +3277,6 @@ } } }, - "internal_controllers.Offer": { - "type": "object", - "properties": { - "declineReason": { - "description": "The reason the offer was declined from the vendor", - "type": "string" - }, - "error": { - "description": "An error from the vendor (eg. when the VIN is invalid)", - "type": "string" - }, - "grade": { - "description": "The grade of the offer from the vendor (eg. \"RETAIL\")", - "type": "string" - }, - "price": { - "description": "The offer price from the vendor", - "type": "integer" - }, - "url": { - "description": "The offer URL from the vendor", - "type": "string" - }, - "vendor": { - "description": "The vendor of the offer (eg. \"carmax\", \"carvana\", etc.)", - "type": "string" - } - } - }, "internal_controllers.Privilege": { "type": "object", "properties": { @@ -4203,32 +3329,11 @@ "errorCodes": { "type": "array", "items": { - "$ref": "#/definitions/services.ErrorCodesResponse" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.ErrorCodesResponse" } } } }, - "internal_controllers.RangeSet": { - "type": "object", - "properties": { - "rangeBasis": { - "description": "The basis for the range calculation (eg. \"MPG\" or \"MPG Highway\")", - "type": "string" - }, - "rangeDistance": { - "description": "The estimated range distance", - "type": "integer" - }, - "rangeUnit": { - "description": "The unit used for the rangeDistance (eg. \"miles\" or \"kilometers\")", - "type": "string" - }, - "updated": { - "description": "The time the data was collected", - "type": "string" - } - } - }, "internal_controllers.RegisterDeviceIntegrationRequest": { "type": "object", "properties": { @@ -4278,7 +3383,7 @@ "integrationCapabilities": { "type": "array", "items": { - "$ref": "#/definitions/services.DeviceCompatibility" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceCompatibility" } }, "userDeviceId": { @@ -4387,7 +3492,7 @@ "type": "string" }, "deviceDefinition": { - "$ref": "#/definitions/services.DeviceDefinition" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceDefinition" }, "id": { "type": "string" @@ -4399,7 +3504,7 @@ } }, "metadata": { - "$ref": "#/definitions/services.UserDeviceMetadata" + "$ref": "#/definitions/github_com_DIMO-Network_devices-api_internal_services.UserDeviceMetadata" }, "name": { "type": "string" @@ -4479,232 +3584,6 @@ "math.HexOrDecimal256": { "type": "object" }, - "services.AutoPiTask": { - "type": "object", - "properties": { - "code": { - "type": "integer" - }, - "description": { - "type": "string" - }, - "error": { - "type": "string" - }, - "status": { - "type": "string" - }, - "taskId": { - "type": "string" - }, - "updatedAt": { - "type": "string" - }, - "updates": { - "description": "Updates increments every time the job was updated.", - "type": "integer" - } - } - }, - "services.DeviceAttribute": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "services.DeviceCompatibility": { - "type": "object", - "properties": { - "capabilities": { - "type": "array", - "items": { - "type": "integer" - } - }, - "country": { - "type": "string" - }, - "id": { - "type": "string" - }, - "region": { - "type": "string" - }, - "style": { - "type": "string" - }, - "type": { - "type": "string" - }, - "vendor": { - "type": "string" - } - } - }, - "services.DeviceDefinition": { - "type": "object", - "properties": { - "compatibleIntegrations": { - "description": "CompatibleIntegrations has systems this vehicle can integrate with", - "type": "array", - "items": { - "$ref": "#/definitions/services.DeviceCompatibility" - } - }, - "deviceAttributes": { - "description": "DeviceAttributes is a list of attributes for the device type as defined in device_types.properties", - "type": "array", - "items": { - "$ref": "#/definitions/services.DeviceAttribute" - } - }, - "deviceDefinitionId": { - "type": "string" - }, - "imageUrl": { - "type": "string" - }, - "make": { - "$ref": "#/definitions/services.DeviceMake" - }, - "metadata": {}, - "name": { - "type": "string" - }, - "type": { - "$ref": "#/definitions/services.DeviceType" - }, - "vehicleData": { - "description": "VehicleInfo will be empty if not a vehicle type", - "$ref": "#/definitions/services.DeviceVehicleInfo" - }, - "verified": { - "type": "boolean" - } - } - }, - "services.DeviceMake": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "logo_url": { - "type": "string" - }, - "name": { - "type": "string" - }, - "oem_platform_name": { - "type": "string" - } - } - }, - "services.DeviceType": { - "type": "object", - "properties": { - "make": { - "type": "string" - }, - "model": { - "type": "string" - }, - "subModels": { - "type": "array", - "items": { - "type": "string" - } - }, - "type": { - "description": "Type is eg. Vehicle, E-bike, roomba", - "type": "string" - }, - "year": { - "type": "integer" - } - } - }, - "services.DeviceVehicleInfo": { - "type": "object", - "properties": { - "base_msrp": { - "type": "integer" - }, - "driven_wheels": { - "type": "string" - }, - "epa_class": { - "type": "string" - }, - "fuel_tank_capacity_gal": { - "type": "string" - }, - "fuel_type": { - "type": "string" - }, - "mpg": { - "type": "string" - }, - "mpg_city": { - "type": "string" - }, - "mpg_highway": { - "type": "string" - }, - "number_of_doors": { - "type": "string" - }, - "vehicle_type": { - "description": "VehicleType PASSENGER CAR, from NHTSA", - "type": "string" - } - } - }, - "services.ErrorCodesResponse": { - "type": "object", - "properties": { - "code": { - "type": "string", - "example": "P0148" - }, - "description": { - "type": "string", - "example": "Fuel delivery error" - } - } - }, - "services.UserDeviceMetadata": { - "type": "object", - "properties": { - "canProtocol": { - "description": "CANProtocol is the protocol that was detected by edge-network from the autopi.", - "type": "string" - }, - "elasticDefinitionSynced": { - "type": "boolean" - }, - "elasticRegionSynced": { - "type": "boolean" - }, - "geoDecodedCountry": { - "type": "string" - }, - "geoDecodedStateProv": { - "type": "string" - }, - "postal_code": { - "type": "string" - }, - "powertrainType": { - "type": "string" - } - } - }, "smartcar.TirePressure": { "type": "object", "properties": { @@ -4731,9 +3610,20 @@ "type": "string" }, "unitSystem": { - "type": "string" + "$ref": "#/definitions/smartcar.UnitSystem" } } + }, + "smartcar.UnitSystem": { + "type": "string", + "enum": [ + "metric", + "imperial" + ], + "x-enum-varnames": [ + "Metric", + "Imperial" + ] } }, "securityDefinitions": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 20c4b22ac..5ad162337 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -42,7 +42,7 @@ definitions: type: object big.Int: type: object - constants.CountryInfo: + github_com_DIMO-Network_devices-api_internal_constants.CountryInfo: properties: alpha_2: type: string @@ -61,727 +61,186 @@ definitions: sub_region_code: type: integer type: object - github.com_DIMO-Network_devices-api_internal_controllers.AutoPiClaimRequest: + github_com_DIMO-Network_devices-api_internal_controllers_helpers.CreateResponse: properties: - aftermarketDeviceSignature: - description: AftermarketDeviceSignature is the signature from the aftermarket - device. - type: string - userSignature: - description: UserSignature is the signature from the user, using their private - key. - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.AutoPiDeviceInfo: - properties: - beneficiaryAddress: - description: OwnerAddress is the Ethereum address of the NFT owner. - items: - type: integer - type: array - claim: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiTransactionStatus' - description: Claim contains the status of the on-chain claiming meta-transaction. - deviceId: - type: string - dockerReleases: - items: - type: integer - type: array - ethereumAddress: - description: OwnerAddress is the Ethereum address of the NFT owner. - items: - type: integer - type: array - hwRevision: - type: string - isUpdated: - type: boolean - lastCommunication: - type: string - manufacturer: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.ManufacturerInfo' - ownerAddress: - type: string - pair: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiTransactionStatus' - description: Pair contains the status of the on-chain pairing meta-transaction. - releaseVersion: - type: string - shouldUpdate: - type: boolean - template: - type: integer - tokenId: - $ref: '#/definitions/big.Int' - unitId: - type: string - unpair: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiTransactionStatus' - description: Unpair contains the status of the on-chain unpairing meta-transaction. - type: object - github.com_DIMO-Network_devices-api_internal_controllers.AutoPiPairRequest: - properties: - aftermarketDeviceSignature: - description: |- - AftermarketDeviceSignature is the 65-byte, hex-encoded Ethereum signature of - the pairing payload by the device. Only needed if the vehicle owner and aftermarket - device owner are not the same. - items: - type: integer - type: array - externalId: - type: string - signature: - description: |- - AftermarketDeviceSignature is the 65-byte, hex-encoded Ethereum signature of - the pairing payload by the device. Only needed if the vehicle owner and aftermarket - device owner are not the same. - items: - type: integer - type: array - type: object - github.com_DIMO-Network_devices-api_internal_controllers.AutoPiTransactionStatus: - properties: - createdAt: - description: CreatedAt is the timestamp of the creation of the meta-transaction. - example: "2022-10-01T09:22:21.002Z" - type: string - hash: - description: Hash is the hexidecimal transaction hash, available for any transaction - at the Submitted stage or greater. - example: 0x28b4662f1e1b15083261a4a5077664f4003d58cb528826b7aab7fad466c28e70 - type: string - status: - description: Status is the state of the transaction performing this operation. - There are only four options. - enum: - - Unsubmitted - - Submitted - - Mined - - Confirmed - example: Mined - type: string - updatedAt: - description: UpdatedAt is the last time we updated the status of the transaction. - example: "2022-10-01T09:22:26.337Z" - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.BurnRequest: - properties: - signature: - description: Signature is the hex encoding of the EIP-712 signature result. - type: string - tokenId: - $ref: '#/definitions/big.Int' - required: - - signature - type: object - github.com_DIMO-Network_devices-api_internal_controllers.BurnSyntheticDeviceRequest: - properties: - signature: - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.CommandRequestStatusResp: - properties: - command: - example: doors/unlock - type: string - createdAt: - example: "2022-08-09T19:38:39Z" - type: string - id: - example: 2D8LqUHQtaMHH6LYPqznmJMBeZm - type: string - status: - enum: - - Pending - - Complete - - Failed - example: Complete - type: string - updatedAt: - example: "2022-08-09T19:39:22Z" - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse: - properties: - requestId: - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeRequest: - properties: - authorizationCode: - type: string - redirectUri: - type: string - region: - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeResponse: - properties: - definition: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.DeviceDefinition' - externalId: - type: string - vin: - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeResponseWrapper: - properties: - vehicles: - items: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeResponse' - type: array - type: object - github.com_DIMO-Network_devices-api_internal_controllers.CreateGeofence: - properties: - h3Indexes: - description: 'required: false' - items: - type: string - type: array - name: - description: 'required: true' - type: string - type: - description: |- - one of following: "PrivacyFence", "TriggerEntry", "TriggerExit" - required: true - type: string - userDeviceIds: - description: Optionally link the geofence with a list of user device ID - items: - type: string - type: array - type: object - github.com_DIMO-Network_devices-api_internal_controllers.DeviceDefinition: - properties: - id: - type: string - make: - type: string - model: - type: string - year: - type: integer - type: object - github.com_DIMO-Network_devices-api_internal_controllers.DeviceSnapshot: - properties: - ambientTemp: - type: number - batteryCapacity: - type: integer - batteryVoltage: - type: number - chargeLimit: - type: number - charging: - type: boolean - fuelPercentRemaining: - type: number - latitude: - type: number - longitude: - type: number - odometer: - type: number - oil: - type: number - range: - type: number - recordCreatedAt: - type: string - recordUpdatedAt: - type: string - soc: - type: number - tirePressure: - $ref: '#/definitions/smartcar.TirePressure' - type: object - github.com_DIMO-Network_devices-api_internal_controllers.DocumentResponse: - properties: - createdAt: - type: string - ext: - type: string - id: - type: string - name: - type: string - type: - type: string - url: - type: string - userDeviceId: - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.GeoFenceUserDevice: - properties: - mmy: - type: string - name: - type: string - userDeviceId: - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.GetGeofence: - properties: - createdAt: - type: string - h3Indexes: - items: - type: string - type: array - id: - type: string - name: - type: string - type: - type: string - updatedAt: - type: string - userDevices: - items: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.GeoFenceUserDevice' - type: array - type: object - github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceErrorCodeQueriesResponse: - properties: - queries: - items: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceErrorCodeQueriesResponseItem' - type: array - type: object - github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceErrorCodeQueriesResponseItem: - properties: - clearedAt: - description: |- - ClearedAt is the time at which the user cleared the codes from this query. - May be null. - example: "2023-05-23T12:57:05Z" - type: string - errorCodes: - items: - $ref: '#/definitions/services.ErrorCodesResponse' - type: array - requestedAt: - example: "2023-05-23T12:56:36Z" - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceIntegrationResponse: - properties: - createdAt: - description: CreatedAt is the creation time of this integration for this device. - type: string - externalId: - description: |- - ExternalID is the identifier used by the third party for the device. It may be absent if we - haven't authorized yet. - type: string - status: - description: Status is one of "Pending", "PendingFirstData", "Active", "Failed", - "DuplicateIntegration". - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.ManufacturerInfo: - properties: - name: - type: string - tokenId: - $ref: '#/definitions/big.Int' - type: object - github.com_DIMO-Network_devices-api_internal_controllers.MintRequest: - properties: - imageData: - description: ImageData contains the base64-encoded NFT PNG image. - type: string - imageDataTransparent: - description: |- - ImageDataTransparent contains the base64-encoded NFT PNG image - with a transparent background, for use in the app. For compatibility - with older versions it is not required. - type: string - signature: - description: Signature is the hex encoding of the EIP-712 signature result. + id: type: string - required: - - imageData - - signature type: object - github.com_DIMO-Network_devices-api_internal_controllers.MintSyntheticDeviceRequest: + github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes: properties: - signature: - example: 0xc565d38982e1a5004efb5ee390fba0a08bb5e72b3f3e91094c66bc395c324f785425d58d5c1a601372d9c16164e380c63e89f1e0ea95fdefdf7b2854c4f938e81b + code: + type: integer + message: type: string type: object - github.com_DIMO-Network_devices-api_internal_controllers.MyDevicesResp: - properties: - sharedDevices: - items: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull' - type: array - userDevices: - items: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull' - type: array - type: object - github.com_DIMO-Network_devices-api_internal_controllers.NFTAttribute: + github_com_DIMO-Network_devices-api_internal_services.AutoPiTask: properties: - trait_type: + code: + type: integer + description: type: string - value: + error: type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.NFTData: - properties: - ownerAddress: - description: OwnerAddress is the Ethereum address of the NFT owner. - items: - type: integer - type: array status: - description: Status is the minting status of the NFT. - enum: - - Unstarted - - Submitted - - Mined - - Confirmed - example: Confirmed type: string - tokenId: - example: 37 - type: number - tokenUri: - example: https://nft.dimo.zone/37 + taskId: type: string - txHash: - description: TxHash is the hash of the minting transaction. - example: 0x30bce3da6985897224b29a0fe064fd2b426bb85a394cc09efe823b5c83326a8e + updatedAt: type: string + updates: + description: Updates increments every time the job was updated. + type: integer type: object - github.com_DIMO-Network_devices-api_internal_controllers.NFTImageData: + github_com_DIMO-Network_devices-api_internal_services.DeviceAttribute: properties: - imageData: - description: ImageData contains the base64-encoded NFT PNG image. + name: type: string - imageDataTransparent: - description: |- - ImageDataTransparent contains the base64-encoded NFT PNG image - with a transparent background, for use in the app. For compatibility - with older versions it is not required. + value: type: string - required: - - imageData type: object - github.com_DIMO-Network_devices-api_internal_controllers.NFTMetadataResp: + github_com_DIMO-Network_devices-api_internal_services.DeviceCompatibility: properties: - attributes: + capabilities: items: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.NFTAttribute' + type: integer type: array - description: - type: string - image: - type: string - name: + country: type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.Offer: - properties: - declineReason: - description: The reason the offer was declined from the vendor + id: type: string - error: - description: An error from the vendor (eg. when the VIN is invalid) + region: type: string - grade: - description: The grade of the offer from the vendor (eg. "RETAIL") + style: type: string - price: - description: The offer price from the vendor - type: integer - url: - description: The offer URL from the vendor + type: type: string vendor: - description: The vendor of the offer (eg. "carmax", "carvana", etc.) - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.Privilege: - properties: - expiry: - type: string - id: - type: integer - updatedAt: - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.PrivilegeUser: - properties: - address: type: string - privileges: - items: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.Privilege' - type: array type: object - github.com_DIMO-Network_devices-api_internal_controllers.QueryDeviceErrorCodesReq: + github_com_DIMO-Network_devices-api_internal_services.DeviceDefinition: properties: - errorCodes: - example: - - P0106 - - P0279 + compatibleIntegrations: + description: CompatibleIntegrations has systems this vehicle can integrate + with items: - type: string + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceCompatibility' type: array - type: object - github.com_DIMO-Network_devices-api_internal_controllers.QueryDeviceErrorCodesResponse: - properties: - clearedAt: - type: string - errorCodes: + deviceAttributes: + description: DeviceAttributes is a list of attributes for the device type + as defined in device_types.properties items: - $ref: '#/definitions/services.ErrorCodesResponse' + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceAttribute' type: array - type: object - github.com_DIMO-Network_devices-api_internal_controllers.RangeSet: - properties: - rangeBasis: - description: The basis for the range calculation (eg. "MPG" or "MPG Highway") - type: string - rangeDistance: - description: The estimated range distance - type: integer - rangeUnit: - description: The unit used for the rangeDistance (eg. "miles" or "kilometers") - type: string - updated: - description: The time the data was collected - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.RegisterDeviceIntegrationRequest: - properties: - accessToken: - type: string - code: - description: Code is an OAuth authorization code. Not used in all integrations. - type: string - expiresIn: - type: integer - externalId: - description: ExternalID is the only field needed for AutoPi registrations. - It is the UnitID. - type: string - redirectURI: - description: RedirectURI is the OAuth redirect URI used by the frontend. Not - used in all integrations. - type: string - refreshToken: - type: string - version: - type: integer - type: object - github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDevice: - properties: - countryCode: - type: string - deviceDefinitionId: - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceResponse: - properties: deviceDefinitionId: type: string - integrationCapabilities: - items: - $ref: '#/definitions/services.DeviceCompatibility' - type: array - userDeviceId: - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceSmartcar: - properties: - code: - description: Code refers to the auth code provided by smartcar when user logs - in - type: string - countryCode: - type: string - redirectURI: - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceVIN: - properties: - canProtocol: - description: CANProtocol is the protocol that was detected by edge-network - from the autopi. - type: string - countryCode: - type: string - vin: - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.SyntheticDeviceStatus: - properties: - address: - example: 0xAED7EA8035eEc47E657B34eF5D020c7005487443 - type: string - status: - enum: - - Unstarted - - Submitted - - Mined - - Confirmed - example: Confirmed - type: string - tokenId: - example: 15 - type: number - txHash: - example: 0x30bce3da6985897224b29a0fe064fd2b426bb85a394cc09efe823b5c83326a8e - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.UpdateCountryCodeReq: - properties: - countryCode: - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.UpdateNameReq: - properties: - name: - type: string - type: object - github.com_DIMO-Network_devices-api_internal_controllers.UpdateVINReq: - properties: - signature: - description: |- - Signature is the hex-encoded result of the AutoPi signing the VIN. It must - be present to verify the VIN. - example: 16b15f88bbd2e0a22d1d0084b8b7080f2003ea83eab1a00f80d8c18446c9c1b6224f17aa09eaf167717ca4f355bb6dc94356e037edf3adf6735a86fc3741f5231b - type: string - vin: - description: |- - VIN is a vehicle identification number. At the very least, it must be - 17 characters in length and contain only letters and numbers. - example: 4Y1SL65848Z411439 - type: string - required: - - vin - type: object - github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull: - properties: - countryCode: - type: string - customImageUrl: - type: string - deviceDefinition: - $ref: '#/definitions/services.DeviceDefinition' - id: + imageUrl: type: string - integrations: - items: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceIntegrationStatus' - type: array - metadata: - $ref: '#/definitions/services.UserDeviceMetadata' + make: + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceMake' + metadata: {} name: type: string - nft: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.NFTData' - optedInAt: - type: string - privilegedUsers: - items: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.PrivilegeUser' - type: array - vin: - type: string - vinConfirmed: + type: + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceType' + vehicleData: + allOf: + - $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceVehicleInfo' + description: VehicleInfo will be empty if not a vehicle type + verified: type: boolean - vinCredential: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.VINCredentialData' type: object - github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceIntegrationStatus: + github_com_DIMO-Network_devices-api_internal_services.DeviceMake: properties: - createdAt: - type: string - externalId: - type: string - integrationId: - type: string - integrationVendor: + id: type: string - metadata: + logo_url: type: string - status: + name: type: string - syntheticDevice: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.SyntheticDeviceStatus' - tokenId: - $ref: '#/definitions/big.Int' - updatedAt: + oem_platform_name: type: string type: object - github.com_DIMO-Network_devices-api_internal_controllers.VINCredentialData: + github_com_DIMO-Network_devices-api_internal_services.DeviceType: properties: - expiresAt: + make: type: string - issuedAt: + model: type: string - valid: - type: boolean - vin: + subModels: + items: + type: string + type: array + type: + description: Type is eg. Vehicle, E-bike, roomba type: string + year: + type: integer type: object - grpc.Integration: + github_com_DIMO-Network_devices-api_internal_services.DeviceVehicleInfo: properties: - auto_pi_default_template_id: + base_msrp: type: integer - auto_pi_powertrain_template: - $ref: '#/definitions/grpc.Integration_AutoPiPowertrainTemplate' - id: + driven_wheels: type: string - manufacturer_token_id: - type: integer - points: - type: integer - refresh_limit_secs: - type: integer - style: + epa_class: type: string - token_id: - description: token_id can have a 0 value, which means it has not yet been - minted and no token id has been assigned. This case should be checked for - and handled. - type: integer - type: + fuel_tank_capacity_gal: type: string - vendor: + fuel_type: + type: string + mpg: + type: string + mpg_city: + type: string + mpg_highway: + type: string + number_of_doors: + type: string + vehicle_type: + description: VehicleType PASSENGER CAR, from NHTSA type: string type: object - grpc.Integration_AutoPiPowertrainTemplate: - properties: - BEV: - type: integer - HEV: - type: integer - ICE: - type: integer - PHEV: - type: integer - type: object - helpers.CreateResponse: + github_com_DIMO-Network_devices-api_internal_services.ErrorCodesResponse: properties: - id: + code: + example: P0148 + type: string + description: + example: Fuel delivery error type: string type: object - helpers.ErrorRes: + github_com_DIMO-Network_devices-api_internal_services.PowertrainType: + enum: + - ICE + - HEV + - PHEV + - BEV + - FCEV + type: string + x-enum-varnames: + - ICE + - HEV + - PHEV + - BEV + - FCEV + github_com_DIMO-Network_devices-api_internal_services.UserDeviceMetadata: properties: - code: - type: integer - message: + canProtocol: + description: CANProtocol is the protocol that was detected by edge-network + from the autopi. + type: string + elasticDefinitionSynced: + type: boolean + elasticRegionSynced: + type: boolean + geoDecodedCountry: + type: string + geoDecodedStateProv: + type: string + postal_code: type: string + powertrainType: + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_services.PowertrainType' type: object internal_controllers.AutoPiClaimRequest: properties: @@ -797,12 +256,12 @@ definitions: internal_controllers.AutoPiDeviceInfo: properties: beneficiaryAddress: - description: OwnerAddress is the Ethereum address of the NFT owner. items: type: integer type: array claim: - $ref: '#/definitions/internal_controllers.AutoPiTransactionStatus' + allOf: + - $ref: '#/definitions/internal_controllers.AutoPiTransactionStatus' description: Claim contains the status of the on-chain claiming meta-transaction. deviceId: type: string @@ -811,7 +270,6 @@ definitions: type: integer type: array ethereumAddress: - description: OwnerAddress is the Ethereum address of the NFT owner. items: type: integer type: array @@ -826,7 +284,8 @@ definitions: ownerAddress: type: string pair: - $ref: '#/definitions/internal_controllers.AutoPiTransactionStatus' + allOf: + - $ref: '#/definitions/internal_controllers.AutoPiTransactionStatus' description: Pair contains the status of the on-chain pairing meta-transaction. releaseVersion: type: string @@ -839,7 +298,8 @@ definitions: unitId: type: string unpair: - $ref: '#/definitions/internal_controllers.AutoPiTransactionStatus' + allOf: + - $ref: '#/definitions/internal_controllers.AutoPiTransactionStatus' description: Unpair contains the status of the on-chain unpairing meta-transaction. type: object internal_controllers.AutoPiPairRequest: @@ -1028,12 +488,30 @@ definitions: name: type: string type: - type: string + $ref: '#/definitions/internal_controllers.DocumentTypeEnum' url: type: string userDeviceId: type: string type: object + internal_controllers.DocumentTypeEnum: + enum: + - DriversLicense + - Other + - VehicleTitle + - VehicleRegistration + - VehicleInsurance + - VehicleMaintenance + - VehicleCustomImage + type: string + x-enum-varnames: + - DriversLicense + - Other + - VehicleTitle + - VehicleRegistration + - VehicleInsurance + - VehicleMaintenance + - VehicleCustomImage internal_controllers.GeoFenceUserDevice: properties: mmy: @@ -1043,6 +521,17 @@ definitions: userDeviceId: type: string type: object + internal_controllers.GetCommandsByIntegrationResponse: + properties: + disabled: + items: + type: string + type: array + enabled: + items: + type: string + type: array + type: object internal_controllers.GetGeofence: properties: createdAt: @@ -1081,7 +570,7 @@ definitions: type: string errorCodes: items: - $ref: '#/definitions/services.ErrorCodesResponse' + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_services.ErrorCodesResponse' type: array requestedAt: example: "2023-05-23T12:56:36Z" @@ -1205,27 +694,6 @@ definitions: name: type: string type: object - internal_controllers.Offer: - properties: - declineReason: - description: The reason the offer was declined from the vendor - type: string - error: - description: An error from the vendor (eg. when the VIN is invalid) - type: string - grade: - description: The grade of the offer from the vendor (eg. "RETAIL") - type: string - price: - description: The offer price from the vendor - type: integer - url: - description: The offer URL from the vendor - type: string - vendor: - description: The vendor of the offer (eg. "carmax", "carvana", etc.) - type: string - type: object internal_controllers.Privilege: properties: expiry: @@ -1260,24 +728,9 @@ definitions: type: string errorCodes: items: - $ref: '#/definitions/services.ErrorCodesResponse' + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_services.ErrorCodesResponse' type: array type: object - internal_controllers.RangeSet: - properties: - rangeBasis: - description: The basis for the range calculation (eg. "MPG" or "MPG Highway") - type: string - rangeDistance: - description: The estimated range distance - type: integer - rangeUnit: - description: The unit used for the rangeDistance (eg. "miles" or "kilometers") - type: string - updated: - description: The time the data was collected - type: string - type: object internal_controllers.RegisterDeviceIntegrationRequest: properties: accessToken: @@ -1313,7 +766,7 @@ definitions: type: string integrationCapabilities: items: - $ref: '#/definitions/services.DeviceCompatibility' + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceCompatibility' type: array userDeviceId: type: string @@ -1350,264 +803,110 @@ definitions: - Unstarted - Submitted - Mined - - Confirmed - example: Confirmed - type: string - tokenId: - example: 15 - type: number - txHash: - example: 0x30bce3da6985897224b29a0fe064fd2b426bb85a394cc09efe823b5c83326a8e - type: string - type: object - internal_controllers.UpdateCountryCodeReq: - properties: - countryCode: - type: string - type: object - internal_controllers.UpdateNameReq: - properties: - name: - type: string - type: object - internal_controllers.UpdateVINReq: - properties: - signature: - description: |- - Signature is the hex-encoded result of the AutoPi signing the VIN. It must - be present to verify the VIN. - example: 16b15f88bbd2e0a22d1d0084b8b7080f2003ea83eab1a00f80d8c18446c9c1b6224f17aa09eaf167717ca4f355bb6dc94356e037edf3adf6735a86fc3741f5231b - type: string - vin: - description: |- - VIN is a vehicle identification number. At the very least, it must be - 17 characters in length and contain only letters and numbers. - example: 4Y1SL65848Z411439 - type: string - required: - - vin - type: object - internal_controllers.UserDeviceFull: - properties: - countryCode: - type: string - customImageUrl: - type: string - deviceDefinition: - $ref: '#/definitions/services.DeviceDefinition' - id: - type: string - integrations: - items: - $ref: '#/definitions/internal_controllers.UserDeviceIntegrationStatus' - type: array - metadata: - $ref: '#/definitions/services.UserDeviceMetadata' - name: - type: string - nft: - $ref: '#/definitions/internal_controllers.NFTData' - optedInAt: - type: string - privilegedUsers: - items: - $ref: '#/definitions/internal_controllers.PrivilegeUser' - type: array - vin: - type: string - vinConfirmed: - type: boolean - vinCredential: - $ref: '#/definitions/internal_controllers.VINCredentialData' - type: object - internal_controllers.UserDeviceIntegrationStatus: - properties: - createdAt: - type: string - externalId: - type: string - integrationId: - type: string - integrationVendor: - type: string - metadata: - type: string - status: - type: string - syntheticDevice: - $ref: '#/definitions/internal_controllers.SyntheticDeviceStatus' - tokenId: - $ref: '#/definitions/big.Int' - updatedAt: - type: string - type: object - internal_controllers.VINCredentialData: - properties: - expiresAt: - type: string - issuedAt: - type: string - valid: - type: boolean - vin: - type: string - type: object - math.HexOrDecimal256: - type: object - services.AutoPiTask: - properties: - code: - type: integer - description: - type: string - error: - type: string - status: - type: string - taskId: - type: string - updatedAt: - type: string - updates: - description: Updates increments every time the job was updated. - type: integer - type: object - services.DeviceAttribute: - properties: - name: - type: string - value: - type: string - type: object - services.DeviceCompatibility: - properties: - capabilities: - items: - type: integer - type: array - country: - type: string - id: - type: string - region: - type: string - style: - type: string - type: + - Confirmed + example: Confirmed type: string - vendor: + tokenId: + example: 15 + type: number + txHash: + example: 0x30bce3da6985897224b29a0fe064fd2b426bb85a394cc09efe823b5c83326a8e type: string type: object - services.DeviceDefinition: + internal_controllers.UpdateCountryCodeReq: properties: - compatibleIntegrations: - description: CompatibleIntegrations has systems this vehicle can integrate - with - items: - $ref: '#/definitions/services.DeviceCompatibility' - type: array - deviceAttributes: - description: DeviceAttributes is a list of attributes for the device type - as defined in device_types.properties - items: - $ref: '#/definitions/services.DeviceAttribute' - type: array - deviceDefinitionId: - type: string - imageUrl: + countryCode: type: string - make: - $ref: '#/definitions/services.DeviceMake' - metadata: {} + type: object + internal_controllers.UpdateNameReq: + properties: name: type: string - type: - $ref: '#/definitions/services.DeviceType' - vehicleData: - $ref: '#/definitions/services.DeviceVehicleInfo' - description: VehicleInfo will be empty if not a vehicle type - verified: - type: boolean type: object - services.DeviceMake: + internal_controllers.UpdateVINReq: properties: - id: - type: string - logo_url: - type: string - name: + signature: + description: |- + Signature is the hex-encoded result of the AutoPi signing the VIN. It must + be present to verify the VIN. + example: 16b15f88bbd2e0a22d1d0084b8b7080f2003ea83eab1a00f80d8c18446c9c1b6224f17aa09eaf167717ca4f355bb6dc94356e037edf3adf6735a86fc3741f5231b type: string - oem_platform_name: + vin: + description: |- + VIN is a vehicle identification number. At the very least, it must be + 17 characters in length and contain only letters and numbers. + example: 4Y1SL65848Z411439 type: string + required: + - vin type: object - services.DeviceType: + internal_controllers.UserDeviceFull: properties: - make: + countryCode: type: string - model: + customImageUrl: type: string - subModels: + deviceDefinition: + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_services.DeviceDefinition' + id: + type: string + integrations: items: - type: string + $ref: '#/definitions/internal_controllers.UserDeviceIntegrationStatus' type: array - type: - description: Type is eg. Vehicle, E-bike, roomba + metadata: + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_services.UserDeviceMetadata' + name: type: string - year: - type: integer - type: object - services.DeviceVehicleInfo: - properties: - base_msrp: - type: integer - driven_wheels: + nft: + $ref: '#/definitions/internal_controllers.NFTData' + optedInAt: type: string - epa_class: + privilegedUsers: + items: + $ref: '#/definitions/internal_controllers.PrivilegeUser' + type: array + vin: type: string - fuel_tank_capacity_gal: + vinConfirmed: + type: boolean + vinCredential: + $ref: '#/definitions/internal_controllers.VINCredentialData' + type: object + internal_controllers.UserDeviceIntegrationStatus: + properties: + createdAt: type: string - fuel_type: + externalId: type: string - mpg: + integrationId: type: string - mpg_city: + integrationVendor: type: string - mpg_highway: + metadata: type: string - number_of_doors: + status: type: string - vehicle_type: - description: VehicleType PASSENGER CAR, from NHTSA + syntheticDevice: + $ref: '#/definitions/internal_controllers.SyntheticDeviceStatus' + tokenId: + $ref: '#/definitions/big.Int' + updatedAt: type: string type: object - services.ErrorCodesResponse: + internal_controllers.VINCredentialData: properties: - code: - example: P0148 - type: string - description: - example: Fuel delivery error + expiresAt: type: string - type: object - services.UserDeviceMetadata: - properties: - canProtocol: - description: CANProtocol is the protocol that was detected by edge-network - from the autopi. + issuedAt: type: string - elasticDefinitionSynced: - type: boolean - elasticRegionSynced: + valid: type: boolean - geoDecodedCountry: - type: string - geoDecodedStateProv: - type: string - postal_code: - type: string - powertrainType: + vin: type: string type: object + math.HexOrDecimal256: + type: object smartcar.TirePressure: properties: age: @@ -1626,8 +925,16 @@ definitions: requestId: type: string unitSystem: - type: string - type: object + $ref: '#/definitions/smartcar.UnitSystem' + type: object + smartcar.UnitSystem: + enum: + - metric + - imperial + type: string + x-enum-varnames: + - Metric + - Imperial info: contact: {} title: DIMO Devices API @@ -1708,7 +1015,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiDeviceInfo' + $ref: '#/definitions/internal_controllers.AutoPiDeviceInfo' security: - BearerAuth: [] tags: @@ -1746,7 +1053,7 @@ paths: name: claimRequest required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiClaimRequest' + $ref: '#/definitions/internal_controllers.AutoPiClaimRequest' produces: - application/json responses: @@ -1788,7 +1095,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/services.AutoPiTask' + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_services.AutoPiTask' security: - BearerAuth: [] tags: @@ -1802,7 +1109,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/constants.CountryInfo' + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_constants.CountryInfo' tags: - countries /countries/{countryCode}: @@ -1820,57 +1127,13 @@ paths: "200": description: OK schema: - $ref: '#/definitions/constants.CountryInfo' + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_constants.CountryInfo' "400": description: invalid country code "404": description: country not found with that country code tags: - countries - /dcn/{tokenID}: - get: - description: retrieves the DCN NFT metadata for a given token ID address - parameters: - - description: DCN node id decimal representation - in: path - name: tokenID - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/internal_controllers.NFTMetadataResp' - "400": - description: Bad Request - "404": - description: Not Found - tags: - - dcn - /dcn/{tokenID}/image: - get: - description: retrieves the DCN NFT metadata for a given token address - parameters: - - description: DCN node id decimal representation - in: path - name: tokenID - required: true - type: string - produces: - - image/svg+xml - responses: - "200": - description: OK - schema: - $ref: '#/definitions/internal_controllers.NFTMetadataResp' - "400": - description: Bad Request - "404": - description: Not Found - tags: - - dcn /documents: get: consumes: @@ -1883,7 +1146,7 @@ paths: description: OK schema: items: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.DocumentResponse' + $ref: '#/definitions/internal_controllers.DocumentResponse' type: array security: - BearerAuth: [] @@ -1920,7 +1183,7 @@ paths: "201": description: Created schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.DocumentResponse' + $ref: '#/definitions/internal_controllers.DocumentResponse' security: - BearerAuth: [] tags: @@ -1961,7 +1224,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.DocumentResponse' + $ref: '#/definitions/internal_controllers.DocumentResponse' security: - BearerAuth: [] tags: @@ -2000,91 +1263,58 @@ paths: type: array tags: - integrations - /integration/:tokenID/credentials: - post: + /integration/:tokenID/commands: + get: consumes: - application/json - description: Complete Tesla auth and get devices for authenticated user + description: Get a list of available commands by integration parameters: - description: token id for integration in: path name: tokenID required: true type: string - - description: all fields are required - in: body - name: user_device - required: true - schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeRequest' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CompleteOAuthExchangeResponseWrapper' + $ref: '#/definitions/internal_controllers.GetCommandsByIntegrationResponse' security: - ApiKeyAuth: [] - BearerAuth: [] tags: - - user-devices - /integrations: - get: - description: gets list of integrations we have defined - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/grpc.Integration' - type: array - security: - - BearerAuth: [] - tags: - integrations - /manufacturer/{tokenId}: - get: - description: Retrieves NFT metadata for a given manufacturer. - parameters: - - description: token id - in: path - name: tokenId - required: true - type: integer - produces: + /integration/:tokenID/credentials: + post: + consumes: - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/internal_controllers.NFTMetadataResp' - "404": - description: Not Found - tags: - - nfts - /synthetic/device/{tokenId}: - get: - description: Retrieves NFT metadata for a given synthetic device. + description: Complete Tesla auth and get devices for authenticated user parameters: - - description: token id + - description: token id for integration in: path - name: tokenId + name: tokenID required: true - type: integer + type: string + - description: all fields are required + in: body + name: user_device + required: true + schema: + $ref: '#/definitions/internal_controllers.CompleteOAuthExchangeRequest' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/internal_controllers.NFTMetadataResp' - "404": - description: Not Found + $ref: '#/definitions/internal_controllers.CompleteOAuthExchangeResponseWrapper' + security: + - ApiKeyAuth: [] + - BearerAuth: [] tags: - - nfts + - user-devices /user/devices: post: consumes: @@ -2097,14 +1327,14 @@ paths: name: user_device required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDevice' + $ref: '#/definitions/internal_controllers.RegisterUserDevice' produces: - application/json responses: "201": description: Created schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceResponse' + $ref: '#/definitions/internal_controllers.RegisterUserDeviceResponse' security: - ApiKeyAuth: [] - BearerAuth: [] @@ -2167,7 +1397,7 @@ paths: name: userSignature required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiPairRequest' + $ref: '#/definitions/internal_controllers.AutoPiPairRequest' produces: - application/json responses: {} @@ -2206,7 +1436,7 @@ paths: name: userSignature required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.AutoPiPairRequest' + $ref: '#/definitions/internal_controllers.AutoPiPairRequest' produces: - application/json responses: {} @@ -2223,7 +1453,7 @@ paths: name: userDeviceIntegrationRegistration required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterDeviceIntegrationRequest' + $ref: '#/definitions/internal_controllers.RegisterDeviceIntegrationRequest' responses: "204": description: No Content @@ -2278,7 +1508,7 @@ paths: name: mintRequest required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.MintRequest' + $ref: '#/definitions/internal_controllers.MintRequest' responses: "200": description: OK @@ -2333,7 +1563,7 @@ paths: name: name required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UpdateCountryCodeReq' + $ref: '#/definitions/internal_controllers.UpdateCountryCodeReq' produces: - application/json responses: @@ -2359,7 +1589,7 @@ paths: "404": description: Vehicle not found schema: - $ref: '#/definitions/helpers.ErrorRes' + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes' security: - BearerAuth: [] summary: List all error code queries made for this vehicle. @@ -2386,7 +1616,7 @@ paths: "404": description: Vehicle not found schema: - $ref: '#/definitions/helpers.ErrorRes' + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes' security: - BearerAuth: [] summary: Obtain, store, and return descriptions for a list of error codes from @@ -2403,11 +1633,11 @@ paths: "404": description: Vehicle not found schema: - $ref: '#/definitions/helpers.ErrorRes' + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes' "429": description: Last query already cleared schema: - $ref: '#/definitions/helpers.ErrorRes' + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.ErrorRes' security: - BearerAuth: [] summary: Mark the most recent set of error codes as having been cleared. @@ -2429,7 +1659,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.GetUserDeviceIntegrationResponse' + $ref: '#/definitions/internal_controllers.GetUserDeviceIntegrationResponse' security: - BearerAuth: [] tags: @@ -2460,7 +1690,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandRequestStatusResp' + $ref: '#/definitions/internal_controllers.CommandRequestStatusResp' summary: Get the status of a submitted command. tags: - device @@ -2540,7 +1770,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse' + $ref: '#/definitions/internal_controllers.CommandResponse' summary: Lock the device's doors tags: - device @@ -2567,7 +1797,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse' + $ref: '#/definitions/internal_controllers.CommandResponse' summary: Unlock the device's doors tags: - device @@ -2595,7 +1825,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse' + $ref: '#/definitions/internal_controllers.CommandResponse' summary: Open the device's front trunk tags: - device @@ -2676,7 +1906,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CommandResponse' + $ref: '#/definitions/internal_controllers.CommandResponse' summary: Open the device's rear trunk tags: - device @@ -2693,7 +1923,7 @@ paths: name: name required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UpdateNameReq' + $ref: '#/definitions/internal_controllers.UpdateNameReq' - description: user id in: path name: user_device_id @@ -2708,26 +1938,6 @@ paths: - BearerAuth: [] tags: - user-devices - /user/devices/{userDeviceID}/range: - get: - description: gets the estimated range for a particular user device - parameters: - - description: user device id - in: path - name: userDeviceID - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.DeviceRange' - security: - - BearerAuth: [] - tags: - - user-devices /user/devices/{userDeviceID}/status: get: description: |- @@ -2761,7 +1971,7 @@ paths: name: vin required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UpdateVINReq' + $ref: '#/definitions/internal_controllers.UpdateVINReq' - description: user id in: path name: userDeviceID @@ -2790,7 +2000,7 @@ paths: name: nftIamges required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.NFTImageData' + $ref: '#/definitions/internal_controllers.NFTImageData' responses: "204": description: No Content @@ -2811,18 +2021,18 @@ paths: name: user_device required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceSmartcar' + $ref: '#/definitions/internal_controllers.RegisterUserDeviceSmartcar' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull' + $ref: '#/definitions/internal_controllers.UserDeviceFull' "201": description: Created schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull' + $ref: '#/definitions/internal_controllers.UserDeviceFull' "400": description: validation failure "409": @@ -2848,14 +2058,14 @@ paths: name: user_device required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.RegisterUserDeviceVIN' + $ref: '#/definitions/internal_controllers.RegisterUserDeviceVIN' produces: - application/json responses: "201": description: Created schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.UserDeviceFull' + $ref: '#/definitions/internal_controllers.UserDeviceFull' "400": description: validation failure "424": @@ -2876,7 +2086,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.MyDevicesResp' + $ref: '#/definitions/internal_controllers.MyDevicesResp' security: - BearerAuth: [] tags: @@ -2890,7 +2100,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.MyDevicesResp' + $ref: '#/definitions/internal_controllers.MyDevicesResp' security: - BearerAuth: [] tags: @@ -2905,7 +2115,7 @@ paths: description: OK schema: items: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.GetGeofence' + $ref: '#/definitions/internal_controllers.GetGeofence' type: array security: - ApiKeyAuth: [] @@ -2923,14 +2133,14 @@ paths: name: geofence required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CreateGeofence' + $ref: '#/definitions/internal_controllers.CreateGeofence' produces: - application/json responses: "201": description: Created schema: - $ref: '#/definitions/helpers.CreateResponse' + $ref: '#/definitions/github_com_DIMO-Network_devices-api_internal_controllers_helpers.CreateResponse' security: - ApiKeyAuth: [] - BearerAuth: [] @@ -2968,7 +2178,7 @@ paths: name: geofence required: true schema: - $ref: '#/definitions/github.com_DIMO-Network_devices-api_internal_controllers.CreateGeofence' + $ref: '#/definitions/internal_controllers.CreateGeofence' produces: - application/json responses: diff --git a/internal/controllers/user_integrations_auth_controller.go b/internal/controllers/user_integrations_auth_controller.go index 4411a5aa6..cea0844d0 100644 --- a/internal/controllers/user_integrations_auth_controller.go +++ b/internal/controllers/user_integrations_auth_controller.go @@ -7,16 +7,18 @@ import ( "strconv" "time" - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/internal/constants" - "github.com/DIMO-Network/devices-api/internal/controllers/helpers" - "github.com/DIMO-Network/devices-api/internal/services" "github.com/DIMO-Network/shared" pb "github.com/DIMO-Network/shared/api/users" "github.com/DIMO-Network/shared/db" "github.com/DIMO-Network/shared/redis" "github.com/gofiber/fiber/v2" "github.com/rs/zerolog" + + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/DIMO-Network/devices-api/internal/controllers/helpers" + "github.com/DIMO-Network/devices-api/internal/services" + "github.com/DIMO-Network/devices-api/internal/utils" ) const teslaFleetAuthCacheKey = "integration_credentials_%s" @@ -73,6 +75,11 @@ type CompleteOAuthExchangeResponse struct { Definition DeviceDefinition `json:"definition"` } +type GetCommandsByIntegrationResponse struct { + Enabled []string `json:"enabled,omitempty"` + Disabled []string `json:"disabled,omitempty"` +} + // DeviceDefinition inner definition object containing meta data for each tesla vehicle type DeviceDefinition struct { Make string `json:"make"` @@ -204,3 +211,70 @@ func (u *UserIntegrationAuthController) persistOauthCredentials(ctx context.Cont return nil } + +// GetCommandsByIntegration godoc +// @Description Get a list of available commands by integration +// @Tags integrations +// @Produce json +// @Accept json +// @Param tokenID path string true "token id for integration" +// @Security ApiKeyAuth +// @Success 200 {object} controllers.GetCommandsByIntegrationResponse +// @Security BearerAuth +// @Router /integration/:tokenID/commands [get] +func (u *UserIntegrationAuthController) GetCommandsByIntegration(c *fiber.Ctx) error { + tokenID := c.Params("tokenID") + tkID, err := strconv.ParseUint(tokenID, 10, 64) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "could not process the provided tokenId!") + } + + intd, err := u.DeviceDefSvc.GetIntegrationByTokenID(c.Context(), tkID) + if err != nil { + u.log.Err(err).Str("Calling Function", "GetIntegrationByTokenID").Uint64("tokenID", tkID).Msg("Error occurred trying to get integration using tokenID") + return fiber.NewError(fiber.StatusBadRequest, "could not find integration with token Id") + } + + var commands *services.UserDeviceAPIIntegrationsMetadataCommands + switch intd.Vendor { + case constants.TeslaVendor: + vrsn := c.Query("version") + version, err := strconv.Atoi(vrsn) + if err != nil { + version = constants.TeslaAPIV1 + } + commands = u.getTeslaCommands(version) + case constants.SmartCarVendor: + commands = u.getSmartCarCommands() + default: + return fiber.NewError(fiber.StatusBadRequest, "unsupported or invalid integration") + } + return c.JSON(commands) +} + +func (u *UserIntegrationAuthController) getTeslaCommands(version int) *services.UserDeviceAPIIntegrationsMetadataCommands { + var cmds *services.UserDeviceAPIIntegrationsMetadataCommands + if version == constants.TeslaAPIV2 { + cmds = u.teslaFleetAPISvc.GetAvailableCommands() + } else { + svc := services.NewTeslaService(u.Settings) + cmds = svc.GetAvailableCommands() + } + + return u.prepareCommandsResponse(cmds) +} + +func (u *UserIntegrationAuthController) getSmartCarCommands() *services.UserDeviceAPIIntegrationsMetadataCommands { + svc := services.NewSmartcarClient(u.Settings) + cmds := svc.GetAvailableCommands() + return u.prepareCommandsResponse(cmds) +} + +func (u *UserIntegrationAuthController) prepareCommandsResponse(cmds *services.UserDeviceAPIIntegrationsMetadataCommands) *services.UserDeviceAPIIntegrationsMetadataCommands { + disabled := utils.GetSliceDiff(cmds.Enabled, cmds.Capable) + return &services.UserDeviceAPIIntegrationsMetadataCommands{ + Enabled: cmds.Enabled, + Capable: cmds.Capable, + Disabled: disabled, + } +} diff --git a/internal/controllers/user_integrations_auth_controller_test.go b/internal/controllers/user_integrations_auth_controller_test.go index 2fa38dda4..9b3e6b937 100644 --- a/internal/controllers/user_integrations_auth_controller_test.go +++ b/internal/controllers/user_integrations_auth_controller_test.go @@ -9,11 +9,6 @@ import ( "time" ddgrpc "github.com/DIMO-Network/device-definitions-api/pkg/grpc" - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/internal/constants" - "github.com/DIMO-Network/devices-api/internal/services" - mock_services "github.com/DIMO-Network/devices-api/internal/services/mocks" - "github.com/DIMO-Network/devices-api/internal/test" "github.com/DIMO-Network/shared" "github.com/DIMO-Network/shared/api/users" "github.com/DIMO-Network/shared/db" @@ -21,9 +16,16 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/go-redis/redis/v8" "github.com/gofiber/fiber/v2" + "github.com/pkg/errors" "github.com/stretchr/testify/suite" "github.com/testcontainers/testcontainers-go" "go.uber.org/mock/gomock" + + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/DIMO-Network/devices-api/internal/services" + mock_services "github.com/DIMO-Network/devices-api/internal/services/mocks" + "github.com/DIMO-Network/devices-api/internal/test" ) type UserIntegrationAuthControllerTestSuite struct { @@ -62,6 +64,7 @@ func (s *UserIntegrationAuthControllerTestSuite) SetupSuite() { }, s.pdb.DBS, logger, s.deviceDefSvc, s.teslaFleetAPISvc, s.redisClient, s.cipher, s.usersClient) app := test.SetupAppFiber(*logger) app.Post("/integration/:tokenID/credentials", test.AuthInjectorTestHandler(s.testUserID), c.CompleteOAuthExchange) + app.Get("/integration/:tokenID/commands", test.AuthInjectorTestHandler(s.testUserID), c.GetCommandsByIntegration) s.controller = &c s.app = app @@ -270,3 +273,90 @@ func (s *UserIntegrationAuthControllerTestSuite) TestPersistOauthCredentials() { err = intCtrl.persistOauthCredentials(s.ctx, *mockAuthCodeResp, mockUserEthAddr) s.Assert().NoError(err) } + +func (s *UserIntegrationAuthControllerTestSuite) TestGetTeslaV1Commands() { + s.deviceDefSvc.EXPECT().GetIntegrationByTokenID(gomock.Any(), uint64(2)).Return(&ddgrpc.Integration{ + Vendor: constants.TeslaVendor, + }, nil) + + request := test.BuildRequest("GET", "/integration/2/commands", "") + response, _ := s.app.Test(request) + + s.Assert().Equal(fiber.StatusOK, response.StatusCode) + body, _ := io.ReadAll(response.Body) + + expected := services.UserDeviceAPIIntegrationsMetadataCommands{ + Enabled: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, + Capable: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, + Disabled: []string{}, + } + + var actual services.UserDeviceAPIIntegrationsMetadataCommands + err := json.Unmarshal(body, &actual) + s.Require().NoError(err) + + s.Assert().Equal(expected, actual) +} + +func (s *UserIntegrationAuthControllerTestSuite) TestGetTeslaV2Commands() { + s.deviceDefSvc.EXPECT().GetIntegrationByTokenID(gomock.Any(), uint64(2)).Return(&ddgrpc.Integration{ + Vendor: constants.TeslaVendor, + }, nil) + + logger := test.Logger() + teslaFleetSvc := services.NewTeslaFleetAPIService(nil, logger) + c := NewUserIntegrationAuthController(&config.Settings{}, nil, logger, s.deviceDefSvc, teslaFleetSvc, nil, nil, nil) + app := test.SetupAppFiber(*logger) + app.Get("/integration/:tokenID/commands", test.AuthInjectorTestHandler(s.testUserID), c.GetCommandsByIntegration) + + request := test.BuildRequest("GET", "/integration/2/commands?version=2", "") + response, _ := app.Test(request) + + s.Assert().Equal(fiber.StatusOK, response.StatusCode) + body, _ := io.ReadAll(response.Body) + + expected := services.UserDeviceAPIIntegrationsMetadataCommands{ + Enabled: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, + Capable: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit, constants.TelemetrySubscribe}, + Disabled: []string{constants.TelemetrySubscribe}, + } + + var actual services.UserDeviceAPIIntegrationsMetadataCommands + err := json.Unmarshal(body, &actual) + s.Require().NoError(err) + + s.Assert().Equal(expected, actual) +} + +func (s *UserIntegrationAuthControllerTestSuite) TestGetSmartCarCommands() { + s.deviceDefSvc.EXPECT().GetIntegrationByTokenID(gomock.Any(), uint64(1)).Return(&ddgrpc.Integration{ + Vendor: constants.SmartCarVendor, + }, nil) + + request := test.BuildRequest("GET", "/integration/1/commands", "") + response, _ := s.app.Test(request) + + s.Assert().Equal(fiber.StatusOK, response.StatusCode) + body, _ := io.ReadAll(response.Body) + + expected := services.UserDeviceAPIIntegrationsMetadataCommands{ + Enabled: []string{constants.DoorsUnlock, constants.DoorsLock}, + Capable: []string{constants.DoorsUnlock, constants.DoorsLock}, + Disabled: []string{}, + } + + var actual services.UserDeviceAPIIntegrationsMetadataCommands + err := json.Unmarshal(body, &actual) + s.Require().NoError(err) + + s.Assert().Equal(expected, actual) +} + +func (s *UserIntegrationAuthControllerTestSuite) TestGetInvalidIntegrationCommands() { + s.deviceDefSvc.EXPECT().GetIntegrationByTokenID(gomock.Any(), uint64(1)).Return(nil, errors.New("no device Id")) + + request := test.BuildRequest("GET", "/integration/1/commands", "") + response, _ := s.app.Test(request) + + s.Assert().Equal(fiber.StatusBadRequest, response.StatusCode) +} diff --git a/internal/services/models.go b/internal/services/models.go index afb1721c7..526e9a043 100644 --- a/internal/services/models.go +++ b/internal/services/models.go @@ -129,8 +129,9 @@ type UserDeviceAPIIntegrationsMetadata struct { } type UserDeviceAPIIntegrationsMetadataCommands struct { - Enabled []string `json:"enabled,omitempty"` - Capable []string `json:"capable,omitempty"` + Enabled []string `json:"enabled"` + Capable []string `json:"capable"` + Disabled []string `json:"disabled"` } type UserDeviceMetadata struct { diff --git a/internal/services/tesla_fleet_api_service.go b/internal/services/tesla_fleet_api_service.go index e85cc9ad0..93639a188 100644 --- a/internal/services/tesla_fleet_api_service.go +++ b/internal/services/tesla_fleet_api_service.go @@ -160,7 +160,7 @@ func (t *teslaFleetAPIService) WakeUpVehicle(ctx context.Context, token, region func (t *teslaFleetAPIService) GetAvailableCommands() *UserDeviceAPIIntegrationsMetadataCommands { return &UserDeviceAPIIntegrationsMetadataCommands{ Enabled: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, - Capable: []string{constants.TelemetrySubscribe, constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, + Capable: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit, constants.TelemetrySubscribe}, } } diff --git a/internal/services/tesla_service.go b/internal/services/tesla_service.go index 491e57ba9..4ecc7a203 100644 --- a/internal/services/tesla_service.go +++ b/internal/services/tesla_service.go @@ -82,6 +82,7 @@ func (t *teslaService) WakeUpVehicle(ownerAccessToken string, id int) error { func (t *teslaService) GetAvailableCommands() *UserDeviceAPIIntegrationsMetadataCommands { return &UserDeviceAPIIntegrationsMetadataCommands{ Enabled: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, + Capable: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, } } diff --git a/internal/utils/utils.go b/internal/utils/utils.go index c13cf0421..224385940 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -12,3 +12,22 @@ import ( func BigToDecimal(x *big.Int) types.Decimal { return types.NewDecimal(new(decimal.Big).SetBigMantScale(x, 0)) } + +type void struct{} + +// GetSliceDiff compares two slices and returns slice of differences +func GetSliceDiff(subset, superset []string) []string { + ma := make(map[string]void, len(subset)) + + diffs := make([]string, 0) + for _, ka := range subset { + ma[ka] = void{} + } + + for _, kb := range superset { + if _, ok := ma[kb]; !ok { + diffs = append(diffs, kb) + } + } + return diffs +} diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go new file mode 100644 index 000000000..e1eace716 --- /dev/null +++ b/internal/utils/utils_test.go @@ -0,0 +1,16 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/DIMO-Network/devices-api/internal/constants" +) + +func TestGetSliceDiff(t *testing.T) { + enabled := []string{constants.DoorsLock, constants.DoorsUnlock} + capable := []string{constants.TelemetrySubscribe, constants.DoorsLock, constants.DoorsUnlock} + actual := GetSliceDiff(enabled, capable) + assert.Equal(t, []string{constants.TelemetrySubscribe}, actual) +} From 89fc42f69cfd21153d8d901cc899ad7cdf15582e Mon Sep 17 00:00:00 2001 From: "Doyin.O" <111298305+0xdev22@users.noreply.github.com> Date: Tue, 30 Apr 2024 21:23:51 -0600 Subject: [PATCH 03/26] Add tesla virtual key status to integration status endpoint (#311) * Move vehice command to smartcar and tesla client, add telemetry/subscribe to tesla commands * Fix lint issues * Create endpoint for fetching all commands available to an integration * Create endpoint for fetching all commands available to an integration * Create endpoint for fetching all commands available to an integration * Add tesla virtual token status to integration status endpoint * Add tesla virtual token status to integration status endpoint * Fix failing test * Fix issues from PR review * Fix issues from PR review * Fix issues from PR review * Change name from virtual-token to virtual-key --- go.sum | 4 - .../synthetic_devices_controller_test.go | 13 +- .../user_integrations_auth_controller.go | 4 +- .../user_integrations_controller.go | 118 ++++++++++++- .../user_integrations_controller_test.go | 161 +++++++++++++++++- .../mocks/tesla_fleet_api_service_mock.go | 30 ++++ .../services/mocks/tesla_task_service_mock.go | 18 +- internal/services/models.go | 1 + internal/services/tesla_fleet_api_service.go | 91 +++++++++- internal/services/tesla_task_service.go | 42 ++++- 10 files changed, 457 insertions(+), 25 deletions(-) diff --git a/go.sum b/go.sum index 87576957b..d1a3c21b4 100644 --- a/go.sum +++ b/go.sum @@ -485,8 +485,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -1624,8 +1622,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= diff --git a/internal/controllers/synthetic_devices_controller_test.go b/internal/controllers/synthetic_devices_controller_test.go index 585483641..7973e6191 100644 --- a/internal/controllers/synthetic_devices_controller_test.go +++ b/internal/controllers/synthetic_devices_controller_test.go @@ -8,12 +8,6 @@ import ( "math/big" "testing" - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/internal/contracts" - mock_services "github.com/DIMO-Network/devices-api/internal/services/mocks" - "github.com/DIMO-Network/devices-api/internal/services/registry" - "github.com/DIMO-Network/devices-api/internal/test" - "github.com/DIMO-Network/devices-api/models" "github.com/DIMO-Network/shared" pb "github.com/DIMO-Network/shared/api/users" "github.com/DIMO-Network/shared/db" @@ -29,6 +23,13 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/volatiletech/sqlboiler/v4/types" "go.uber.org/mock/gomock" + + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/contracts" + mock_services "github.com/DIMO-Network/devices-api/internal/services/mocks" + "github.com/DIMO-Network/devices-api/internal/services/registry" + "github.com/DIMO-Network/devices-api/internal/test" + "github.com/DIMO-Network/devices-api/models" ) var signature = "0xa4438e5cb667dc63ebd694167ae3ad83585f2834c9b04895dd890f805c4c459a024ed9df1b03872536b4ac0c7720d02cb787884a093cfcde5c3bd7f94657e30c1b" diff --git a/internal/controllers/user_integrations_auth_controller.go b/internal/controllers/user_integrations_auth_controller.go index cea0844d0..bd02e8e0e 100644 --- a/internal/controllers/user_integrations_auth_controller.go +++ b/internal/controllers/user_integrations_auth_controller.go @@ -76,8 +76,8 @@ type CompleteOAuthExchangeResponse struct { } type GetCommandsByIntegrationResponse struct { - Enabled []string `json:"enabled,omitempty"` - Disabled []string `json:"disabled,omitempty"` + Enabled []string `json:"enabled"` + Disabled []string `json:"disabled"` } // DeviceDefinition inner definition object containing meta data for each tesla vehicle diff --git a/internal/controllers/user_integrations_controller.go b/internal/controllers/user_integrations_controller.go index e16f93842..fcdd347d3 100644 --- a/internal/controllers/user_integrations_controller.go +++ b/internal/controllers/user_integrations_controller.go @@ -58,6 +58,7 @@ func (udc *UserDevicesController) GetUserDeviceIntegration(c *fiber.Ctx) error { apiIntegration, err := models.UserDeviceAPIIntegrations( models.UserDeviceAPIIntegrationWhere.UserDeviceID.EQ(userDeviceID), models.UserDeviceAPIIntegrationWhere.IntegrationID.EQ(integrationID), + qm.Load(models.UserDeviceAPIIntegrationRels.UserDevice), ).One(c.Context(), udc.DBS().Reader) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -65,7 +66,112 @@ func (udc *UserDevicesController) GetUserDeviceIntegration(c *fiber.Ctx) error { } return err } - return c.JSON(GetUserDeviceIntegrationResponse{Status: apiIntegration.Status, ExternalID: apiIntegration.ExternalID, CreatedAt: apiIntegration.CreatedAt}) + + var meta services.UserDeviceAPIIntegrationsMetadata + err = apiIntegration.Metadata.Unmarshal(&meta) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "error occurred decoding integration metadata") + } + + resp := GetUserDeviceIntegrationResponse{ + Status: apiIntegration.Status, + ExternalID: apiIntegration.ExternalID, + CreatedAt: apiIntegration.CreatedAt, + } + + // Handle fetching virtual key status + intd, err := udc.DeviceDefSvc.GetIntegrationByID(c.Context(), integrationID) + if err != nil { + return shared.GrpcErrorToFiber(err, "invalid integration id") + } + + if intd.Vendor == constants.TeslaVendor && meta.TeslaAPIVersion == constants.TeslaAPIV2 { + if meta.TeslaRegion == "" { + return fiber.NewError(fiber.StatusFailedDependency, "missing tesla region") + } + + if !apiIntegration.ExternalID.Valid || !apiIntegration.AccessToken.Valid || !apiIntegration.R.UserDevice.VinConfirmed || !apiIntegration.R.UserDevice.VinIdentifier.Valid { + return fiber.NewError(fiber.StatusFailedDependency, "missing device or integration details") + } + + isConnected, err := udc.getDeviceVirtualKeyStatus(c.Context(), meta.TeslaRegion, apiIntegration) + if err != nil { + return fiber.NewError(fiber.StatusFailedDependency, fmt.Sprintf("error checking verifying tesla connection status %s", err.Error())) + } + + resp.Tesla = &TeslaConnectionStatus{ + IsVirtualKeyConnected: isConnected, + } + } + + return c.JSON(resp) +} + +func (udc *UserDevicesController) getDeviceVirtualKeyStatus(ctx context.Context, region string, integration *models.UserDeviceAPIIntegration) (bool, error) { + accessTk, err := udc.cipher.Decrypt(integration.AccessToken.String) + if err != nil { + return false, fmt.Errorf("couldn't decrypt access token: %w", err) + } + + refreshTk, err := udc.cipher.Decrypt(integration.RefreshToken.String) + if err != nil { + return false, fmt.Errorf("couldn't decrypt refresh token: %w", err) + } + + isNewToken, auth, err := udc.ensureTeslaTokenValidty(ctx, refreshTk, integration.AccessExpiresAt.Time) + if err != nil { + return false, errors.Wrap(err, "failed to get tesla credentials") + } + + if isNewToken { + encAccessTk, err := udc.cipher.Encrypt(auth.AccessToken) + if err != nil { + return false, fmt.Errorf("couldn't encrypt refresh token: %w", err) + } + + encRefreshTk, err := udc.cipher.Encrypt(auth.RefreshToken) + if err != nil { + return false, fmt.Errorf("couldn't encrypt access token: %w", err) + } + + integration.RefreshToken = null.StringFrom(encRefreshTk) + integration.AccessToken = null.StringFrom(encAccessTk) + integration.AccessExpiresAt = null.TimeFrom(auth.Expiry) + + cols := models.UserDeviceAPIIntegrationColumns + _, err = integration.Update(ctx, udc.DBS().Writer, boil.Whitelist(cols.RefreshToken, cols.AccessToken, cols.AccessExpiresAt)) + if err != nil { + return false, err + } + + err = udc.teslaTaskService.UpdateCredentials(integration, constants.TeslaAPIV2, region) + if err != nil { + return false, err + } + accessTk = auth.AccessToken + } + + isConnected, err := udc.teslaFleetAPISvc.VirtualKeyConnectionStatus(ctx, accessTk, region, integration.R.UserDevice.VinIdentifier.String) + if err != nil { + return false, fiber.NewError(fiber.StatusFailedDependency, err.Error()) + } + + return isConnected, nil +} + +func (udc *UserDevicesController) ensureTeslaTokenValidty(ctx context.Context, refreshToken string, expiry time.Time) (bool, *services.TeslaAuthCodeResponse, error) { + const delta = 10 * time.Second + + now := time.Now + if now().Before(expiry.Add(-delta)) { + return false, nil, nil + } + authResp, err := udc.teslaFleetAPISvc.RefreshToken(ctx, refreshToken) + if err != nil { + return false, nil, err + } + + return true, authResp, nil } func (udc *UserDevicesController) deleteDeviceIntegration(ctx context.Context, userID, userDeviceID, integrationID string, dd *ddgrpc.GetDeviceDefinitionItemResponse) error { @@ -1887,6 +1993,7 @@ func (udc *UserDevicesController) registerDeviceTesla(c *fiber.Ctx, logger *zero meta := services.UserDeviceAPIIntegrationsMetadata{ Commands: commands, TeslaAPIVersion: apiVersion, + TeslaRegion: region, } b, err := json.Marshal(meta) @@ -2060,12 +2167,21 @@ type RegisterDeviceIntegrationRequest struct { Version int `json:"version"` } +type TeslaConnectionStatus struct { + // Status of the virtual key connection + IsVirtualKeyConnected bool `json:"isVirtualKeyConnected"` +} + type GetUserDeviceIntegrationResponse struct { // Status is one of "Pending", "PendingFirstData", "Active", "Failed", "DuplicateIntegration". Status string `json:"status"` // ExternalID is the identifier used by the third party for the device. It may be absent if we // haven't authorized yet. ExternalID null.String `json:"externalId" swaggertype:"string"` + + // Contains further details about tesla integration status + Tesla *TeslaConnectionStatus `json:"tesla,omitempty"` + // CreatedAt is the creation time of this integration for this device. CreatedAt time.Time `json:"createdAt"` } diff --git a/internal/controllers/user_integrations_controller_test.go b/internal/controllers/user_integrations_controller_test.go index 481fc93c0..c7e5741fb 100644 --- a/internal/controllers/user_integrations_controller_test.go +++ b/internal/controllers/user_integrations_controller_test.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "math/big" + "net/http" "testing" "time" @@ -124,6 +125,8 @@ func (s *UserIntegrationsControllerTestSuite) SetupSuite() { app.Delete("/user/devices/:userDeviceID/integrations/:integrationID", test.AuthInjectorTestHandler(testUserID), c.DeleteUserDeviceIntegration) app.Post("/user2/devices/:userDeviceID/integrations/:integrationID", test.AuthInjectorTestHandler(testUser2), c.RegisterDeviceIntegration) + app.Get("/user/devices/:userDeviceID/integrations/:integrationID", test.AuthInjectorTestHandler(testUserID), c.GetUserDeviceIntegration) + s.app = app } @@ -938,7 +941,8 @@ func (s *UserIntegrationsControllerTestSuite) TestPostTesla_V2() { }, nil) s.teslaFleetAPISvc.EXPECT().WakeUpVehicle(gomock.Any(), "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", "na", 1145).Return(nil) s.teslaFleetAPISvc.EXPECT().GetAvailableCommands().Return(&services.UserDeviceAPIIntegrationsMetadataCommands{ - Enabled: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, + Enabled: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, + Disabled: []string{constants.TelemetrySubscribe}, }) s.deviceDefSvc.EXPECT().GetDeviceDefinitionByID(gomock.Any(), ud.DeviceDefinitionID).Times(2).Return(dd[0], nil) s.deviceDefSvc.EXPECT().FindDeviceDefinitionByMMY(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(dd[0], nil) @@ -1065,3 +1069,158 @@ func (s *UserIntegrationsControllerTestSuite) TestPostTesla_V2_MissingCredential s.Assert().Equal("Couldn't retrieve stored credentials: no credential found", gjson.GetBytes(body, "message").String()) } + +func (s *UserIntegrationsControllerTestSuite) TestGetUserDeviceIntegration() { + integration := test.BuildIntegrationGRPC(constants.TeslaVendor, 10, 0) + dd := test.BuildDeviceDefinitionGRPC(ksuid.New().String(), "Tesla", "Model S", 2012, integration) + ud := test.SetupCreateUserDevice(s.T(), testUserID, dd[0].DeviceDefinitionId, nil, "5YJSA1CN0CFP02439", s.pdb) + + accessTk := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" + refreshTk := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.UWfqdcCvyzObpI2gaIGcx2r7CcDjlQ0IzGyk8N0_vqw" + extID := "SomeID" + expectedExpiry := time.Now().Add(10 * time.Minute) + region := "na" + + encAccessTk, err := s.cipher.Encrypt(accessTk) + s.Require().NoError(err) + encRefreshTk, err := s.cipher.Encrypt(refreshTk) + s.Require().NoError(err) + + apIntd := models.UserDeviceAPIIntegration{ + UserDeviceID: ud.ID, + IntegrationID: integration.Id, + Status: models.UserDeviceAPIIntegrationStatusActive, + AccessToken: null.StringFrom(encAccessTk), + AccessExpiresAt: null.TimeFrom(expectedExpiry), + RefreshToken: null.StringFrom(encRefreshTk), + ExternalID: null.StringFrom(extID), + Metadata: null.JSONFrom([]byte(fmt.Sprintf(`{"teslaRegion":%q, "teslaApiVersion": 2}`, region))), + } + err = apIntd.Insert(s.ctx, s.pdb.DBS().Writer, boil.Infer()) + s.Require().NoError(err) + + s.deviceDefSvc.EXPECT().GetIntegrationByID(gomock.Any(), integration.Id).Return(integration, nil) + s.teslaFleetAPISvc.EXPECT().VirtualKeyConnectionStatus(gomock.Any(), accessTk, region, ud.VinIdentifier.String).Return(true, nil) + + request := test.BuildRequest(http.MethodGet, fmt.Sprintf("/user/devices/%s/integrations/%s", ud.ID, integration.Id), "") + res, err := s.app.Test(request, 60*1000) + s.Assert().NoError(err) + + s.Require().Equal(res.StatusCode, fiber.StatusOK) + body, _ := io.ReadAll(res.Body) + + defer res.Body.Close() + + actual := GetUserDeviceIntegrationResponse{} + s.Require().NoError(json.Unmarshal(body, &actual)) + + s.Assert().True(actual.Tesla.IsVirtualKeyConnected) + s.Assert().Equal(models.UserDeviceAPIIntegrationStatusActive, actual.Status) + s.Assert().Equal(extID, actual.ExternalID.String) +} + +type deviceIntegrationCredentialsMatcher struct { + accessToken string + refreshToken string + expireAt time.Time +} + +func (m *deviceIntegrationCredentialsMatcher) String() string { + return "" +} + +func (m *deviceIntegrationCredentialsMatcher) Matches(x interface{}) bool { + creds := x.(*models.UserDeviceAPIIntegration) + + if creds.AccessToken.String != m.accessToken { + return false + } + + if creds.RefreshToken.String != m.refreshToken { + return false + } + + if creds.AccessExpiresAt.Time != m.expireAt { + return false + } + + return true +} + +func (s *UserIntegrationsControllerTestSuite) TestGetUserDeviceIntegration_RefreshToken() { + integration := test.BuildIntegrationGRPC(constants.TeslaVendor, 10, 0) + dd := test.BuildDeviceDefinitionGRPC(ksuid.New().String(), "Tesla", "Model S", 2012, integration) + ud := test.SetupCreateUserDevice(s.T(), testUserID, dd[0].DeviceDefinitionId, nil, "5YJSA1CN0CFP02439", s.pdb) + + accessTk := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" + refreshTk := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.UWfqdcCvyzObpI2gaIGcx2r7CcDjlQ0IzGyk8N0_vqw" + extID := "SomeID" + expectedExpiry := time.Now().Add(-10 * time.Minute) + region := "na" + + newCredentials := &services.TeslaAuthCodeResponse{ + AccessToken: accessTk, + RefreshToken: refreshTk, + Expiry: time.Now().Add(10 * time.Hour), + Region: region, + } + + expiredRefreshTk := "SomeExpRefTk" + encExpiredAccessTk, err := s.cipher.Encrypt("SomeAccessTk") + s.Require().NoError(err) + encExpiredRefreshTk, err := s.cipher.Encrypt("SomeExpRefTk") + s.Require().NoError(err) + + encAccessTk, err := s.cipher.Encrypt(newCredentials.AccessToken) + s.Require().NoError(err) + encRefreshTk, err := s.cipher.Encrypt(newCredentials.RefreshToken) + s.Require().NoError(err) + + apIntd := models.UserDeviceAPIIntegration{ + UserDeviceID: ud.ID, + IntegrationID: integration.Id, + Status: models.UserDeviceAPIIntegrationStatusActive, + AccessToken: null.StringFrom(encExpiredAccessTk), + AccessExpiresAt: null.TimeFrom(expectedExpiry), + RefreshToken: null.StringFrom(encExpiredRefreshTk), + ExternalID: null.StringFrom(extID), + Metadata: null.JSONFrom([]byte(fmt.Sprintf(`{"teslaRegion":"%s", "teslaApiVersion":2}`, region))), + } + err = apIntd.Insert(s.ctx, s.pdb.DBS().Writer, boil.Infer()) + s.Require().NoError(err) + + s.deviceDefSvc.EXPECT().GetIntegrationByID(gomock.Any(), integration.Id).Return(integration, nil) + s.teslaFleetAPISvc.EXPECT().VirtualKeyConnectionStatus(gomock.Any(), accessTk, region, ud.VinIdentifier.String).Return(true, nil) + + s.teslaTaskService.EXPECT().UpdateCredentials(&deviceIntegrationCredentialsMatcher{ + accessToken: encAccessTk, + refreshToken: encRefreshTk, + expireAt: newCredentials.Expiry, + }, constants.TeslaAPIV2, region).Return(nil) + s.teslaFleetAPISvc.EXPECT().RefreshToken(gomock.Any(), expiredRefreshTk).Return(newCredentials, nil) + + request := test.BuildRequest(http.MethodGet, fmt.Sprintf("/user/devices/%s/integrations/%s", ud.ID, integration.Id), "") + res, err := s.app.Test(request, 60*1000) + s.Assert().NoError(err) + + s.Assert().True(res.StatusCode == fiber.StatusOK) + body, _ := io.ReadAll(res.Body) + + defer res.Body.Close() + + actual := GetUserDeviceIntegrationResponse{} + s.Assert().NoError(json.Unmarshal(body, &actual)) + + s.Assert().True(actual.Tesla.IsVirtualKeyConnected) + s.Assert().Equal(models.UserDeviceAPIIntegrationStatusActive, actual.Status) + s.Assert().Equal(extID, actual.ExternalID.String) + + newAPIInt, err := models.UserDeviceAPIIntegrations( + models.UserDeviceAPIIntegrationWhere.UserDeviceID.EQ(ud.ID), + models.UserDeviceAPIIntegrationWhere.IntegrationID.EQ(integration.Id), + ).One(s.ctx, s.pdb.DBS().Reader) + s.Require().NoError(err) + + s.Assert().Equal(encRefreshTk, newAPIInt.RefreshToken.String) + s.Assert().Equal(encAccessTk, newAPIInt.AccessToken.String) +} diff --git a/internal/services/mocks/tesla_fleet_api_service_mock.go b/internal/services/mocks/tesla_fleet_api_service_mock.go index 335466789..9cb462354 100644 --- a/internal/services/mocks/tesla_fleet_api_service_mock.go +++ b/internal/services/mocks/tesla_fleet_api_service_mock.go @@ -99,6 +99,36 @@ func (mr *MockTeslaFleetAPIServiceMockRecorder) GetVehicles(ctx, token, region a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVehicles", reflect.TypeOf((*MockTeslaFleetAPIService)(nil).GetVehicles), ctx, token, region) } +// RefreshToken mocks base method. +func (m *MockTeslaFleetAPIService) RefreshToken(ctx context.Context, refreshToken string) (*services.TeslaAuthCodeResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RefreshToken", ctx, refreshToken) + ret0, _ := ret[0].(*services.TeslaAuthCodeResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RefreshToken indicates an expected call of RefreshToken. +func (mr *MockTeslaFleetAPIServiceMockRecorder) RefreshToken(ctx, refreshToken any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshToken", reflect.TypeOf((*MockTeslaFleetAPIService)(nil).RefreshToken), ctx, refreshToken) +} + +// VirtualKeyConnectionStatus mocks base method. +func (m *MockTeslaFleetAPIService) VirtualKeyConnectionStatus(ctx context.Context, token, region, vin string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VirtualKeyConnectionStatus", ctx, token, region, vin) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// VirtualKeyConnectionStatus indicates an expected call of VirtualKeyConnectionStatus. +func (mr *MockTeslaFleetAPIServiceMockRecorder) VirtualKeyConnectionStatus(ctx, token, region, vin any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VirtualKeyConnectionStatus", reflect.TypeOf((*MockTeslaFleetAPIService)(nil).VirtualKeyConnectionStatus), ctx, token, region, vin) +} + // WakeUpVehicle mocks base method. func (m *MockTeslaFleetAPIService) WakeUpVehicle(ctx context.Context, token, region string, vehicleID int) error { m.ctrl.T.Helper() diff --git a/internal/services/mocks/tesla_task_service_mock.go b/internal/services/mocks/tesla_task_service_mock.go index 06f72c7a6..15999d4ef 100644 --- a/internal/services/mocks/tesla_task_service_mock.go +++ b/internal/services/mocks/tesla_task_service_mock.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: internal/services/tesla_task_service.go +// Source: tesla_task_service.go // // Generated by this command: // -// mockgen -source=internal/services/tesla_task_service.go -destination=internal/services/mocks/tesla_task_service_mock.go +// mockgen -source tesla_task_service.go -destination mocks/tesla_task_service_mock.go // // Package mock_services is a generated GoMock package. @@ -127,3 +127,17 @@ func (mr *MockTeslaTaskServiceMockRecorder) UnlockDoors(udai any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlockDoors", reflect.TypeOf((*MockTeslaTaskService)(nil).UnlockDoors), udai) } + +// UpdateCredentials mocks base method. +func (m *MockTeslaTaskService) UpdateCredentials(udai *models.UserDeviceAPIIntegration, version int, region string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateCredentials", udai, version, region) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateCredentials indicates an expected call of UpdateCredentials. +func (mr *MockTeslaTaskServiceMockRecorder) UpdateCredentials(udai, version, region any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateCredentials", reflect.TypeOf((*MockTeslaTaskService)(nil).UpdateCredentials), udai, version, region) +} diff --git a/internal/services/models.go b/internal/services/models.go index 526e9a043..9708bc1cc 100644 --- a/internal/services/models.go +++ b/internal/services/models.go @@ -126,6 +126,7 @@ type UserDeviceAPIIntegrationsMetadata struct { CANProtocol *string `json:"canProtocol,omitempty"` TeslaVehicleID int `json:"teslaVehicleId,omitempty"` TeslaAPIVersion int `json:"teslaApiVersion,omitempty"` + TeslaRegion string `json:"teslaRegion,omitempty"` } type UserDeviceAPIIntegrationsMetadataCommands struct { diff --git a/internal/services/tesla_fleet_api_service.go b/internal/services/tesla_fleet_api_service.go index 93639a188..46dccdc5d 100644 --- a/internal/services/tesla_fleet_api_service.go +++ b/internal/services/tesla_fleet_api_service.go @@ -5,10 +5,14 @@ import ( "encoding/json" "errors" "fmt" + "io" "net/http" + "net/url" + "strings" "time" "github.com/rs/zerolog" + "golang.org/x/exp/slices" "golang.org/x/oauth2" "github.com/DIMO-Network/devices-api/internal/config" @@ -22,6 +26,8 @@ type TeslaFleetAPIService interface { GetVehicle(ctx context.Context, token, region string, vehicleID int) (*TeslaVehicle, error) WakeUpVehicle(ctx context.Context, token, region string, vehicleID int) error GetAvailableCommands() *UserDeviceAPIIntegrationsMetadataCommands + VirtualKeyConnectionStatus(ctx context.Context, token, region, vin string) (bool, error) + RefreshToken(ctx context.Context, refreshToken string) (*TeslaAuthCodeResponse, error) } var teslaScopes = []string{"openid", "offline_access", "user_data", "vehicle_device_data", "vehicle_cmds", "vehicle_charging_cmds", "energy_device_data", "energy_device_data", "energy_cmds"} @@ -49,6 +55,15 @@ type TeslaAuthCodeResponse struct { Region string `json:"region"` } +type VirtualKeyConnectionStatusResponse struct { + Response VirtualKeyConnectionStatus `json:"response"` +} + +type VirtualKeyConnectionStatus struct { + UnpairedVins []string `json:"unpaired_vins"` + KeyPairedVins []string `json:"key_paired_vins"` +} + type teslaFleetAPIService struct { Settings *config.Settings HTTPClient *http.Client @@ -101,7 +116,7 @@ func (t *teslaFleetAPIService) GetVehicles(ctx context.Context, token, region st baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) url := baseURL + "/api/1/vehicles" - resp, err := t.performTeslaGetRequest(ctx, url, token) + resp, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { return nil, fmt.Errorf("could not fetch vehicles for user: %w", err) } @@ -124,7 +139,7 @@ func (t *teslaFleetAPIService) GetVehicle(ctx context.Context, token, region str baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) url := fmt.Sprintf("%s/api/1/vehicles/%d", baseURL, vehicleID) - resp, err := t.performTeslaGetRequest(ctx, url, token) + resp, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { return nil, fmt.Errorf("could not fetch vehicles for user: %w", err) } @@ -143,7 +158,7 @@ func (t *teslaFleetAPIService) WakeUpVehicle(ctx context.Context, token, region baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) url := fmt.Sprintf("%s/api/1/vehicles/%d/wake_up", baseURL, vehicleID) - resp, err := t.performTeslaGetRequest(ctx, url, token) + resp, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { return fmt.Errorf("could not fetch vehicles for user: %w", err) } @@ -164,12 +179,74 @@ func (t *teslaFleetAPIService) GetAvailableCommands() *UserDeviceAPIIntegrations } } -// performTeslaGetRequest a helper function for making http requests, it adds a timeout context and parses error response -func (t *teslaFleetAPIService) performTeslaGetRequest(ctx context.Context, url, token string) (*http.Response, error) { +// VirtualKeyConnectionStatus Checks whether vehicles can accept Tesla commands protocol for the partner's public key +func (t *teslaFleetAPIService) VirtualKeyConnectionStatus(ctx context.Context, token, region, vin string) (bool, error) { + baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) + url := fmt.Sprintf("%s/api/1/vehicles/fleet_status", baseURL) + + jsonBody := fmt.Sprintf(`{"vins": [%q]}`, vin) + body := strings.NewReader(jsonBody) + + resp, err := t.performRequest(ctx, url, token, http.MethodPost, body) + if err != nil { + return false, fmt.Errorf("could not fetch vehicles for user: %w", err) + } + + defer resp.Body.Close() + + bd, err := io.ReadAll(resp.Body) + if err != nil { + return false, fmt.Errorf("could not verify connection status %w", err) + } + + var v VirtualKeyConnectionStatusResponse + err = json.Unmarshal(bd, &v) + if err != nil { + return false, fmt.Errorf("error occurred decoding connection status %w", err) + } + + isConnected := slices.Contains(v.Response.KeyPairedVins, vin) + + return isConnected, nil +} + +func (t *teslaFleetAPIService) RefreshToken(ctx context.Context, refreshToken string) (*TeslaAuthCodeResponse, error) { + data := url.Values{} + data.Set("grant_type", "refresh_token") + data.Set("client_id", t.Settings.TeslaClientID) + data.Set("refresh_token", refreshToken) + + ctxTimeout, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() + + req, err := http.NewRequestWithContext(ctxTimeout, "POST", t.Settings.TeslaTokenURL, strings.NewReader(data.Encode())) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + resp, err := t.HTTPClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if code := resp.StatusCode; code != http.StatusOK { + return nil, fmt.Errorf("status code %d", code) + } + + tokResp := new(TeslaAuthCodeResponse) + if err := json.NewDecoder(resp.Body).Decode(tokResp); err != nil { + return nil, err + } + + return tokResp, nil +} + +// performRequest a helper function for making http requests, it adds a timeout context and parses error response +func (t *teslaFleetAPIService) performRequest(ctx context.Context, url, token, method string, body *strings.Reader) (*http.Response, error) { ctxTimeout, cancel := context.WithTimeout(ctx, time.Second*10) defer cancel() - req, err := http.NewRequestWithContext(ctxTimeout, "GET", url, nil) + req, err := http.NewRequestWithContext(ctxTimeout, method, url, body) if err != nil { return nil, err } @@ -189,7 +266,7 @@ func (t *teslaFleetAPIService) performTeslaGetRequest(ctx context.Context, url, Msg("An error occurred when attempting to decode the error message from the api.") return nil, fmt.Errorf("invalid response encountered while fetching user vehicles: %s", errBody.ErrorDescription) } - return nil, fmt.Errorf("error occurred fetching user vehicles: %s", errBody.ErrorDescription) + return nil, fmt.Errorf("error occurred calling tesla api: %s", errBody.ErrorDescription) } return resp, nil diff --git a/internal/services/tesla_task_service.go b/internal/services/tesla_task_service.go index d4b32ed5a..505212ff3 100644 --- a/internal/services/tesla_task_service.go +++ b/internal/services/tesla_task_service.go @@ -6,11 +6,12 @@ import ( "strconv" "time" - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/models" "github.com/DIMO-Network/shared" "github.com/Shopify/sarama" "github.com/segmentio/ksuid" + + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/models" ) //go:generate mockgen -source tesla_task_service.go -destination mocks/tesla_task_service_mock.go @@ -22,6 +23,7 @@ type TeslaTaskService interface { LockDoors(udai *models.UserDeviceAPIIntegration) (string, error) OpenTrunk(udai *models.UserDeviceAPIIntegration) (string, error) OpenFrunk(udai *models.UserDeviceAPIIntegration) (string, error) + UpdateCredentials(udai *models.UserDeviceAPIIntegration, version int, region string) error } func NewTeslaTaskService(settings *config.Settings, producer sarama.SyncProducer) TeslaTaskService { @@ -154,6 +156,42 @@ func (t *teslaTaskService) StartPoll(vehicle *TeslaVehicle, udai *models.UserDev return err } +func (t *teslaTaskService) UpdateCredentials(udai *models.UserDeviceAPIIntegration, version int, region string) error { + tc := TeslaCredentialsCloudEventV2{ + CloudEventHeaders: CloudEventHeaders{ + ID: ksuid.New().String(), + Source: "dimo/integration/" + udai.IntegrationID, + SpecVersion: "1.0", + Subject: udai.UserDeviceID, + Time: time.Now(), + Type: "zone.dimo.task.tesla.poll.credential.v2", + }, + Data: TeslaCredentialsV2{ + TaskID: udai.TaskID.String, + UserDeviceID: udai.UserDeviceID, + IntegrationID: udai.IntegrationID, + AccessToken: udai.AccessToken.String, + Expiry: udai.AccessExpiresAt.Time, + RefreshToken: udai.RefreshToken.String, + Version: version, + Region: region, + }, + } + + tcb, err := json.Marshal(tc) + if err != nil { + return err + } + + _, _, err = t.Producer.SendMessage(&sarama.ProducerMessage{ + Topic: t.Settings.TaskCredentialTopic, + Key: sarama.StringEncoder(udai.TaskID.String), + Value: sarama.ByteEncoder(tcb), + }) + + return err +} + func (t *teslaTaskService) StopPoll(udai *models.UserDeviceAPIIntegration) error { var taskKey string if udai.TaskID.Valid { From 2e4bad9bcb36bc355ca12c3fc241ef4928c546e8 Mon Sep 17 00:00:00 2001 From: "Doyin.O" <111298305+0xdev22@users.noreply.github.com> Date: Tue, 30 Apr 2024 21:52:53 -0600 Subject: [PATCH 04/26] Register device for tesla telemetry (#315) * Move vehice command to smartcar and tesla client, add telemetry/subscribe to tesla commands * Fix lint issues * Create endpoint for fetching all commands available to an integration * Create endpoint for fetching all commands available to an integration * Create endpoint for fetching all commands available to an integration * Add tesla virtual token status to integration status endpoint * Add tesla virtual token status to integration status endpoint * Fix failing test * Function and endpoint to register device for tesla telemetry * Add open telemetry config to environment * Fix issues from PR review * Fix issues from PR review * Fix issues from PR review * Fix issues from PR review * Fix issues from PR review * Fix issues from PR review * Change name from virtual-token to virtual-key --------- Co-authored-by: Dylan Moreland <79415431+elffjs@users.noreply.github.com> --- charts/devices-api/values.yaml | 3 + cmd/devices-api/api.go | 1 + internal/config/settings.go | 3 + .../user_integrations_controller.go | 103 +++++++++++ .../user_integrations_controller_test.go | 167 +++++++++++++++++- .../mocks/tesla_fleet_api_service_mock.go | 14 ++ internal/services/tesla_fleet_api_service.go | 106 +++++++++++ .../services/tesla_fleet_api_service_test.go | 129 ++++++++++++++ settings.sample.yaml | 6 +- 9 files changed, 528 insertions(+), 4 deletions(-) create mode 100644 internal/services/tesla_fleet_api_service_test.go diff --git a/charts/devices-api/values.yaml b/charts/devices-api/values.yaml index 75a0b72aa..c8f350e79 100644 --- a/charts/devices-api/values.yaml +++ b/charts/devices-api/values.yaml @@ -90,6 +90,9 @@ env: TESLA_TOKEN_URL: https://auth.tesla.com/oauth2/v3/token TESLA_FLEET_URL: https://fleet-api.prd.%s.vn.cloud.tesla.com META_TRANSACTION_PROCESSOR_GRPC_ADDR: meta-transaction-processor-dev:8086 + TESLA_TELEMETRY_HOST_NAME: ingest-tesla.dev.dimo.zone + TESLA_TELEMETRY_PORT: 443 + TESLA_TELEMETRY_CA_CERTIFICATE: -----BEGIN CERTIFICATE-----\nMIIBvDCCAWKgAwIBAgIRAL6QCUcK/8jy48V7ElERABowCgYIKoZIzj0EAwIwIzEh\nMB8GA1UEAxMYRElNTyBDQSBEZXZlbG9wbWVudCBSb290MCAXDTIyMDQyMzExMTEw\nM1oYDzIwNzIwNDEwMTExMTAzWjAyMTAwLgYDVQQDEydESU1PIENBIERldmVsb3Bt\nZW50IFNlcnZlciBJbnRlcm1lZGlhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\nAAQMyh6plUM3p4KWWfK0CqWXr1B9NWk53+c9ps8OpgZZIyXjxiw1EHxrpcqU7C9e\nhw+6JfmvTqqi3F4ES8K+Tt/mo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/\nBAgwBgEB/wIBADAdBgNVHQ4EFgQU+7zrfioO4bjNpD9KiG8fbTcIq8kwHwYDVR0j\nBBgwFoAUeMfSSqt+S65xQF82yRnjr+J5XC8wCgYIKoZIzj0EAwIDSAAwRQIhAK3s\nWtlk+d0fnkii091dTZGt+dtzEbM4HuizaG6mO5zPAiApi03qU/hdsAxXwlbhufH/\n5HuUiCLgBK8vPvL2YdMaKQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIBrTCCAVKgAwIBAgIQEgthFz9Ww3+VaErBc3nDFjAKBggqhkjOPQQDAjAjMSEw\nHwYDVQQDExhESU1PIENBIERldmVsb3BtZW50IFJvb3QwIBcNMjIwNDIzMTExMTAz\nWhgPMjEyMjAzMzAxMTExMDNaMCMxITAfBgNVBAMTGERJTU8gQ0EgRGV2ZWxvcG1l\nbnQgUm9vdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBuxEb6jTyfrUwI8RiBV\nKCQWqTAeLdHPj60Qk7HeMeaEcGjzF799xgpl6/8iNKaHN/w+705cdxp5pRswbUtu\nizWjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1Ud\nDgQWBBR4x9JKq35LrnFAXzbJGeOv4nlcLzAfBgNVHSMEGDAWgBR4x9JKq35LrnFA\nXzbJGeOv4nlcLzAKBggqhkjOPQQDAgNJADBGAiEAlslTE9mX+VjPSYLKEsy48Rzh\nOUCdaWovmF+28PyAi4wCIQDXRKpYK+VMFyUR1GJVoV3gWezQcJmFswuWq+7M+XPb\nGQ==\n-----END CERTIFICATE----- service: type: ClusterIP ports: diff --git a/cmd/devices-api/api.go b/cmd/devices-api/api.go index 2234f97ec..7df7c3c5f 100644 --- a/cmd/devices-api/api.go +++ b/cmd/devices-api/api.go @@ -322,6 +322,7 @@ func startWebAPI(logger zerolog.Logger, settings *config.Settings, pdb db.Store, udOwner.Post("/integrations/:integrationID/commands/trunk/open", userDeviceController.OpenTrunk) udOwner.Post("/integrations/:integrationID/commands/frunk/open", userDeviceController.OpenFrunk) udOwner.Get("/integrations/:integrationID/commands/:requestID", userDeviceController.GetCommandRequestStatus) + udOwner.Post("/integrations/:integrationID/commands/telemetry/subscribe", userDeviceController.TelemetrySubscribe) udOwner.Post("/commands/opt-in", userDeviceController.DeviceOptIn) diff --git a/internal/config/settings.go b/internal/config/settings.go index 905c7aac3..2fb9f00b7 100644 --- a/internal/config/settings.go +++ b/internal/config/settings.go @@ -88,6 +88,9 @@ type Settings struct { TeslaClientSecret string `yaml:"TESLA_CLIENT_SECRET"` TeslaTokenURL string `yaml:"TESLA_TOKEN_URL"` TeslaFleetURL string `yaml:"TESLA_FLEET_URL"` + TeslaTelemetryHostName string `yaml:"TESLA_TELEMETRY_HOST_NAME"` + TeslaTelemetryPort int `yaml:"TESLA_TELEMETRY_PORT"` + TeslaTelemetryCACertificate string `yaml:"TESLA_TELEMETRY_CA_CERTIFICATE"` } func (s *Settings) IsProduction() bool { diff --git a/internal/controllers/user_integrations_controller.go b/internal/controllers/user_integrations_controller.go index fcdd347d3..c59f03f36 100644 --- a/internal/controllers/user_integrations_controller.go +++ b/internal/controllers/user_integrations_controller.go @@ -539,6 +539,109 @@ func (udc *UserDevicesController) OpenFrunk(c *fiber.Ctx) error { return udc.handleEnqueueCommand(c, constants.FrunkOpen) } +// TelemetrySubscribe godoc +// @Summary Subscribe vehicle for Telemetry Data +// @Description Subscribe vehicle for Telemetry Data. Currently, this only works for Teslas connected through Tesla. +// @ID telemetry-subscribe +// @Tags device,integration,command +// @Success 200 {object} +// @Produce json +// @Param userDeviceID path string true "Device ID" +// @Param integrationID path string true "Integration ID" +// @Router /user/devices/{userDeviceID}/integrations/{integrationID}/commands/telemetry/subscribe [post] +func (udc *UserDevicesController) TelemetrySubscribe(c *fiber.Ctx) error { + userDeviceID := c.Params("userDeviceID") + integrationID := c.Params("integrationID") + + logger := helpers.GetLogger(c, udc.log).With(). + Str("IntegrationID", integrationID). + Str("Name", "Telemetry/Subscribe"). + Logger() + + logger.Info().Msg("Received command request.") + + device, err := models.UserDevices( + models.UserDeviceWhere.ID.EQ(userDeviceID), + ).One(c.Context(), udc.DBS().Reader) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return fiber.NewError(fiber.StatusNotFound, "Device not found.") + } + logger.Err(err).Msg("Failed to search for device.") + return opaqueInternalError + } + + udai, err := models.UserDeviceAPIIntegrations( + models.UserDeviceAPIIntegrationWhere.UserDeviceID.EQ(userDeviceID), + models.UserDeviceAPIIntegrationWhere.IntegrationID.EQ(integrationID), + ).One(c.Context(), udc.DBS().Reader) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return fiber.NewError(fiber.StatusNotFound, "Integration not found for this device.") + } + logger.Err(err).Msg("Failed to search for device integration record.") + return opaqueInternalError + } + + if udai.Status != models.UserDeviceAPIIntegrationStatusActive { + return fiber.NewError(fiber.StatusConflict, "Integration is not active for this device.") + } + + md := new(services.UserDeviceAPIIntegrationsMetadata) + if err := udai.Metadata.Unmarshal(md); err != nil { + logger.Err(err).Msg("Couldn't parse metadata JSON.") + return opaqueInternalError + } + + if md.TeslaRegion == "" || md.Commands == nil { + return fiber.NewError(fiber.StatusBadRequest, "No commands config for integration and device") + } + + if len(md.Commands.Capable) != 0 && !slices.Contains(md.Commands.Capable, constants.TelemetrySubscribe) { + return fiber.NewError(fiber.StatusBadRequest, "Telemetry command not available for device and integration combination") + } + + // Is telemetry already enabled, return early + if ok := slices.Contains(md.Commands.Enabled, constants.TelemetrySubscribe); ok { + return c.SendStatus(fiber.StatusOK) + } + + integration, err := udc.DeviceDefSvc.GetIntegrationByID(c.Context(), udai.IntegrationID) + if err != nil { + return shared.GrpcErrorToFiber(err, "deviceDefSvc error getting integration id: "+udai.IntegrationID) + } + + switch integration.Vendor { + case constants.TeslaVendor: + if err := udc.teslaFleetAPISvc.SubscribeForTelemetryData(c.Context(), + udai.AccessToken.String, + md.TeslaRegion, + device.VinIdentifier.String, + ); err != nil { + logger.Error().Err(err).Msg("error registering for telemetry") + return fiber.NewError(fiber.StatusFailedDependency, "could not register device for tesla telemetry: ", err.Error()) + } + + newEnabledCmd := append(md.Commands.Enabled, constants.TelemetrySubscribe) + md.Commands.Enabled = newEnabledCmd + newMeta, err := json.Marshal(md) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "could not save command state", err.Error()) + } + udai.Metadata = null.JSONFrom(newMeta) + _, err = udai.Update(c.Context(), udc.DBS().Writer, boil.Whitelist(models.UserDeviceAPIIntegrationColumns.Metadata)) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "could not save command state", err.Error()) + } + default: + return fiber.NewError(fiber.StatusBadRequest, "Integration not supported for this command") + } + + logger.Info().Msg("Successfully subscribed to telemetry") + + return c.SendStatus(fiber.StatusOK) +} + // GetAutoPiUnitInfo godoc // @Description gets the information about the aftermarket device by the hw serial // @Tags integrations diff --git a/internal/controllers/user_integrations_controller_test.go b/internal/controllers/user_integrations_controller_test.go index c7e5741fb..f621a8f67 100644 --- a/internal/controllers/user_integrations_controller_test.go +++ b/internal/controllers/user_integrations_controller_test.go @@ -11,9 +11,6 @@ import ( "testing" "time" - "github.com/nats-io/nats-server/v2/server" - "github.com/rs/zerolog" - "github.com/DIMO-Network/shared/redis/mocks" "github.com/ericlagergren/decimal" "github.com/ethereum/go-ethereum/common" @@ -22,6 +19,8 @@ import ( signer "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/go-redis/redis/v8" "github.com/google/uuid" + "github.com/nats-io/nats-server/v2/server" + "github.com/rs/zerolog" pbuser "github.com/DIMO-Network/shared/api/users" "github.com/DIMO-Network/shared/db" @@ -126,6 +125,10 @@ func (s *UserIntegrationsControllerTestSuite) SetupSuite() { app.Post("/user2/devices/:userDeviceID/integrations/:integrationID", test.AuthInjectorTestHandler(testUser2), c.RegisterDeviceIntegration) app.Get("/user/devices/:userDeviceID/integrations/:integrationID", test.AuthInjectorTestHandler(testUserID), c.GetUserDeviceIntegration) + app.Post("/user/devices/:userDeviceID/integrations/:integrationID/commands/telemetry/subscribe", + test.AuthInjectorTestHandler(testUserID), + c.TelemetrySubscribe, + ) s.app = app } @@ -1224,3 +1227,161 @@ func (s *UserIntegrationsControllerTestSuite) TestGetUserDeviceIntegration_Refre s.Assert().Equal(encRefreshTk, newAPIInt.RefreshToken.String) s.Assert().Equal(encAccessTk, newAPIInt.AccessToken.String) } + +func (s *UserIntegrationsControllerTestSuite) TestTelemetrySubscribe() { + integration := test.BuildIntegrationGRPC(constants.TeslaVendor, 10, 0) + dd := test.BuildDeviceDefinitionGRPC(ksuid.New().String(), "Tesla", "Model S", 2012, integration) + ud := test.SetupCreateUserDevice(s.T(), testUserID, dd[0].DeviceDefinitionId, nil, "5YJSA1CN0CFP02439", s.pdb) + + accessTk := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" + refreshTk := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.UWfqdcCvyzObpI2gaIGcx2r7CcDjlQ0IzGyk8N0_vqw" + extID := "SomeID" + expectedExpiry := time.Now().Add(10 * time.Minute) + region := "na" + + mtd, err := json.Marshal(services.UserDeviceAPIIntegrationsMetadata{ + TeslaRegion: region, + Commands: &services.UserDeviceAPIIntegrationsMetadataCommands{ + Enabled: []string{}, + Capable: []string{constants.TelemetrySubscribe}, + }, + }) + s.Require().NoError(err) + + apIntd := models.UserDeviceAPIIntegration{ + UserDeviceID: ud.ID, + IntegrationID: integration.Id, + Status: models.UserDeviceAPIIntegrationStatusActive, + AccessToken: null.StringFrom(accessTk), + AccessExpiresAt: null.TimeFrom(expectedExpiry), + RefreshToken: null.StringFrom(refreshTk), + ExternalID: null.StringFrom(extID), + Metadata: null.JSONFrom(mtd), + } + err = apIntd.Insert(s.ctx, s.pdb.DBS().Writer, boil.Infer()) + s.Require().NoError(err) + + s.deviceDefSvc.EXPECT().GetIntegrationByID(gomock.Any(), integration.Id).Return(integration, nil) + s.teslaFleetAPISvc.EXPECT().SubscribeForTelemetryData(gomock.Any(), accessTk, region, ud.VinIdentifier.String).Return(nil) + + request := test.BuildRequest(http.MethodPost, fmt.Sprintf("/user/devices/%s/integrations/%s/commands/telemetry/subscribe", ud.ID, integration.Id), "") + res, err := s.app.Test(request, 60*1000) + s.Assert().NoError(err) + + s.Assert().True(res.StatusCode == fiber.StatusOK) + + udai, err := models.UserDeviceAPIIntegrations( + models.UserDeviceAPIIntegrationWhere.IntegrationID.EQ(integration.Id), + models.UserDeviceAPIIntegrationWhere.UserDeviceID.EQ(ud.ID), + ).One(s.ctx, s.pdb.DBS().Reader) + s.Require().NoError(err) + + md := new(services.UserDeviceAPIIntegrationsMetadata) + err = udai.Metadata.Unmarshal(md) + s.Require().NoError(err) + + s.T().Log(md.Commands.Enabled, "-0------") + s.Assert().Equal(md.Commands.Enabled, []string{constants.TelemetrySubscribe}) +} + +func (s *UserIntegrationsControllerTestSuite) Test_NoUserDevice_TelemetrySubscribe() { + request := test.BuildRequest(http.MethodPost, fmt.Sprintf("/user/devices/%s/integrations/%s/commands/telemetry/subscribe", "mockUserDeviceID", "mockIntID"), "") + res, err := s.app.Test(request, 60*1000) + s.Assert().NoError(err) + + s.Assert().True(res.StatusCode == fiber.StatusNotFound) +} + +func (s *UserIntegrationsControllerTestSuite) Test_InactiveIntegration_TelemetrySubscribe() { + integration := test.BuildIntegrationGRPC(constants.TeslaVendor, 10, 0) + dd := test.BuildDeviceDefinitionGRPC(ksuid.New().String(), "Tesla", "Model S", 2012, integration) + ud := test.SetupCreateUserDevice(s.T(), testUserID, dd[0].DeviceDefinitionId, nil, "5YJSA1CN0CFP02439", s.pdb) + + apIntd := models.UserDeviceAPIIntegration{ + UserDeviceID: ud.ID, + IntegrationID: integration.Id, + Status: models.DeviceCommandRequestStatusPending, + } + err := apIntd.Insert(s.ctx, s.pdb.DBS().Writer, boil.Infer()) + s.Require().NoError(err) + + request := test.BuildRequest(http.MethodPost, fmt.Sprintf("/user/devices/%s/integrations/%s/commands/telemetry/subscribe", ud.ID, integration.Id), "") + res, err := s.app.Test(request, 60*1000) + s.Assert().NoError(err) + + s.Assert().True(res.StatusCode == fiber.StatusConflict) +} + +func (s *UserIntegrationsControllerTestSuite) Test_MissingRegionAndCapable_TelemetrySubscribe() { + integration := test.BuildIntegrationGRPC(constants.TeslaVendor, 10, 0) + dd := test.BuildDeviceDefinitionGRPC(ksuid.New().String(), "Tesla", "Model S", 2012, integration) + ud := test.SetupCreateUserDevice(s.T(), testUserID, dd[0].DeviceDefinitionId, nil, "5YJSA1CN0CFP02439", s.pdb) + + apIntd := models.UserDeviceAPIIntegration{ + UserDeviceID: ud.ID, + IntegrationID: integration.Id, + Status: models.UserDeviceAPIIntegrationStatusActive, + } + err := apIntd.Insert(s.ctx, s.pdb.DBS().Writer, boil.Infer()) + s.Require().NoError(err) + + request := test.BuildRequest(http.MethodPost, fmt.Sprintf("/user/devices/%s/integrations/%s/commands/telemetry/subscribe", ud.ID, integration.Id), "") + res, err := s.app.Test(request, 60*1000) + s.Assert().NoError(err) + + s.Assert().True(res.StatusCode == fiber.StatusBadRequest) +} + +func (s *UserIntegrationsControllerTestSuite) Test_TelemetrySubscribe_AlreadyEnabled() { + integration := test.BuildIntegrationGRPC(constants.TeslaVendor, 10, 0) + dd := test.BuildDeviceDefinitionGRPC(ksuid.New().String(), "Tesla", "Model S", 2012, integration) + ud := test.SetupCreateUserDevice(s.T(), testUserID, dd[0].DeviceDefinitionId, nil, "5YJSA1CN0CFP02439", s.pdb) + + mtd, err := json.Marshal(services.UserDeviceAPIIntegrationsMetadata{ + TeslaRegion: "na", + Commands: &services.UserDeviceAPIIntegrationsMetadataCommands{ + Enabled: []string{constants.TelemetrySubscribe}, + Capable: []string{constants.TelemetrySubscribe}, + }, + }) + s.Require().NoError(err) + apIntd := models.UserDeviceAPIIntegration{ + UserDeviceID: ud.ID, + IntegrationID: integration.Id, + Status: models.UserDeviceAPIIntegrationStatusActive, + Metadata: null.JSONFrom(mtd), + } + err = apIntd.Insert(s.ctx, s.pdb.DBS().Writer, boil.Infer()) + s.Require().NoError(err) + + request := test.BuildRequest(http.MethodPost, fmt.Sprintf("/user/devices/%s/integrations/%s/commands/telemetry/subscribe", ud.ID, integration.Id), "") + res, err := s.app.Test(request, 60*1000) + s.Assert().NoError(err) + + s.Assert().True(res.StatusCode == fiber.StatusOK) +} + +func (s *UserIntegrationsControllerTestSuite) Test_TelemetrySubscribe_NotCapable() { + integration := test.BuildIntegrationGRPC(constants.TeslaVendor, 10, 0) + dd := test.BuildDeviceDefinitionGRPC(ksuid.New().String(), "Tesla", "Model S", 2012, integration) + ud := test.SetupCreateUserDevice(s.T(), testUserID, dd[0].DeviceDefinitionId, nil, "5YJSA1CN0CFP02439", s.pdb) + + mtd, err := json.Marshal(services.UserDeviceAPIIntegrationsMetadata{ + TeslaRegion: "na", + }) + s.Require().NoError(err) + apIntd := models.UserDeviceAPIIntegration{ + UserDeviceID: ud.ID, + IntegrationID: integration.Id, + Status: models.UserDeviceAPIIntegrationStatusActive, + Metadata: null.JSONFrom(mtd), + } + err = apIntd.Insert(s.ctx, s.pdb.DBS().Writer, boil.Infer()) + s.Require().NoError(err) + + request := test.BuildRequest(http.MethodPost, fmt.Sprintf("/user/devices/%s/integrations/%s/commands/telemetry/subscribe", ud.ID, integration.Id), "") + res, err := s.app.Test(request, 60*1000) + s.Assert().NoError(err) + + s.Assert().True(res.StatusCode == fiber.StatusBadRequest) +} diff --git a/internal/services/mocks/tesla_fleet_api_service_mock.go b/internal/services/mocks/tesla_fleet_api_service_mock.go index 9cb462354..cfe9a5548 100644 --- a/internal/services/mocks/tesla_fleet_api_service_mock.go +++ b/internal/services/mocks/tesla_fleet_api_service_mock.go @@ -114,6 +114,20 @@ func (mr *MockTeslaFleetAPIServiceMockRecorder) RefreshToken(ctx, refreshToken a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshToken", reflect.TypeOf((*MockTeslaFleetAPIService)(nil).RefreshToken), ctx, refreshToken) } +// SubscribeForTelemetryData mocks base method. +func (m *MockTeslaFleetAPIService) SubscribeForTelemetryData(ctx context.Context, token, region, vin string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubscribeForTelemetryData", ctx, token, region, vin) + ret0, _ := ret[0].(error) + return ret0 +} + +// SubscribeForTelemetryData indicates an expected call of SubscribeForTelemetryData. +func (mr *MockTeslaFleetAPIServiceMockRecorder) SubscribeForTelemetryData(ctx, token, region, vin any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscribeForTelemetryData", reflect.TypeOf((*MockTeslaFleetAPIService)(nil).SubscribeForTelemetryData), ctx, token, region, vin) +} + // VirtualKeyConnectionStatus mocks base method. func (m *MockTeslaFleetAPIService) VirtualKeyConnectionStatus(ctx context.Context, token, region, vin string) (bool, error) { m.ctrl.T.Helper() diff --git a/internal/services/tesla_fleet_api_service.go b/internal/services/tesla_fleet_api_service.go index 46dccdc5d..84abc9992 100644 --- a/internal/services/tesla_fleet_api_service.go +++ b/internal/services/tesla_fleet_api_service.go @@ -28,6 +28,7 @@ type TeslaFleetAPIService interface { GetAvailableCommands() *UserDeviceAPIIntegrationsMetadataCommands VirtualKeyConnectionStatus(ctx context.Context, token, region, vin string) (bool, error) RefreshToken(ctx context.Context, refreshToken string) (*TeslaAuthCodeResponse, error) + SubscribeForTelemetryData(ctx context.Context, token, region, vin string) error } var teslaScopes = []string{"openid", "offline_access", "user_data", "vehicle_device_data", "vehicle_cmds", "vehicle_charging_cmds", "energy_device_data", "energy_device_data", "energy_cmds"} @@ -64,6 +65,41 @@ type VirtualKeyConnectionStatus struct { KeyPairedVins []string `json:"key_paired_vins"` } +type SubscribeForTelemetryDataRequest struct { + Vins []string `json:"vins"` + Config TelemetryConfigRequest `json:"config"` +} + +type Interval struct { + IntervalSeconds int `json:"interval_seconds"` +} + +type TelemetryFields map[string]Interval + +type TelemetryConfigRequest struct { + HostName string `json:"hostName"` + PublicCACertificate string `json:"ca"` + Fields TelemetryFields `json:"fields"` + AlertTypes []string `json:"alert_types,omitempty"` + Expiration int64 `json:"exp"` + Port int `json:"port"` +} + +type SkippedVehicles struct { + MissingKey []string `json:"missing_key"` + UnsupportedHardware []string `json:"unsupported_hardware"` + UnsupportedFirmware []string `json:"unsupported_firmware"` +} + +type SubscribeForTelemetryDataResponse struct { + UpdatedVehicles int `json:"updated_vehicles"` + SkippedVehicles SkippedVehicles `json:"skipped_vehicles"` +} + +type SubscribeForTelemetryDataResponseWrapper struct { + Response SubscribeForTelemetryDataResponse `json:"response"` +} + type teslaFleetAPIService struct { Settings *config.Settings HTTPClient *http.Client @@ -241,6 +277,76 @@ func (t *teslaFleetAPIService) RefreshToken(ctx context.Context, refreshToken st return tokResp, nil } +var fields = TelemetryFields{ + "ChargeState": {IntervalSeconds: 300}, + "Location": {IntervalSeconds: 10}, + "OriginLocation": {IntervalSeconds: 300}, + "DestinationLocation": {IntervalSeconds: 300}, + "DestinationName": {IntervalSeconds: 300}, + "EnergyRemaining": {IntervalSeconds: 300}, + "VehicleSpeed": {IntervalSeconds: 60}, + "Odometer": {IntervalSeconds: 300}, + "EstBatteryRange": {IntervalSeconds: 300}, + "Soc": {IntervalSeconds: 300}, + "BatteryLevel": {IntervalSeconds: 60}, +} + +func (t *teslaFleetAPIService) SubscribeForTelemetryData(ctx context.Context, token, region, vin string) error { + baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) + u, err := url.JoinPath(baseURL, "/api/1/vehicles/fleet_telemetry_config") + if err != nil { + return err + } + + r := SubscribeForTelemetryDataRequest{ + Vins: []string{vin}, + Config: TelemetryConfigRequest{ + HostName: t.Settings.TeslaTelemetryHostName, + PublicCACertificate: t.Settings.TeslaTelemetryCACertificate, + Port: t.Settings.TeslaTelemetryPort, + Fields: fields, + AlertTypes: []string{"service"}, + }, + } + + b, err := json.Marshal(r) + if err != nil { + return err + } + + req := strings.NewReader(string(b)) + + resp, err := t.performRequest(ctx, u, token, http.MethodPost, req) + if err != nil { + return err + } + + defer resp.Body.Close() + + subResp := SubscribeForTelemetryDataResponseWrapper{} + if err := json.NewDecoder(resp.Body).Decode(&subResp); err != nil { + return err + } + + if subResp.Response.UpdatedVehicles == 1 { + return nil + } + + if slices.Contains(subResp.Response.SkippedVehicles.MissingKey, vin) { + return fmt.Errorf("vehicle has not approved virtual token connection") + } + + if slices.Contains(subResp.Response.SkippedVehicles.UnsupportedHardware, vin) { + return fmt.Errorf("vehicle hardware not supported") + } + + if slices.Contains(subResp.Response.SkippedVehicles.UnsupportedFirmware, vin) { + return fmt.Errorf("vehicle firmware not supported") + } + + return nil +} + // performRequest a helper function for making http requests, it adds a timeout context and parses error response func (t *teslaFleetAPIService) performRequest(ctx context.Context, url, token, method string, body *strings.Reader) (*http.Response, error) { ctxTimeout, cancel := context.WithTimeout(ctx, time.Second*10) diff --git a/internal/services/tesla_fleet_api_service_test.go b/internal/services/tesla_fleet_api_service_test.go new file mode 100644 index 000000000..2f94f2d1c --- /dev/null +++ b/internal/services/tesla_fleet_api_service_test.go @@ -0,0 +1,129 @@ +package services + +import ( + "context" + "fmt" + "net/http" + "testing" + + "github.com/jarcoal/httpmock" + "github.com/stretchr/testify/suite" + + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/test" +) + +const mockTeslaFleetBaeURL = "https://fleet-mock-api.%s.tesla.com" + +type TeslaFleetAPIServiceTestSuite struct { + suite.Suite + ctx context.Context + SUT TeslaFleetAPIService + settings *config.Settings +} + +func (t *TeslaFleetAPIServiceTestSuite) SetupSuite() { + t.ctx = context.Background() + logger := test.Logger() + t.settings = &config.Settings{TeslaFleetURL: mockTeslaFleetBaeURL, TeslaTelemetryCACertificate: "Ca-Cert", TeslaTelemetryPort: 443, TeslaTelemetryHostName: "tel.dimo.com"} + + t.SUT = NewTeslaFleetAPIService(t.settings, logger) +} + +func TestTeslaFleetAPIServiceTestSuite(t *testing.T) { + suite.Run(t, new(TeslaFleetAPIServiceTestSuite)) +} + +func (t *TeslaFleetAPIServiceTestSuite) TestSubscribeForTelemetryData() { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + token := "someToken" + region := "mockRegion" + vin := "RandomVin" + + baseURL := fmt.Sprintf(mockTeslaFleetBaeURL, region) + u := fmt.Sprintf("%s/api/1/vehicles/fleet_telemetry_config", baseURL) + + respBody := SubscribeForTelemetryDataResponseWrapper{ + SubscribeForTelemetryDataResponse{ + UpdatedVehicles: 1, + SkippedVehicles: SkippedVehicles{}, + }, + } + + jsonResp, err := httpmock.NewJsonResponder(http.StatusOK, respBody) + t.Require().NoError(err) + httpmock.RegisterResponder(http.MethodPost, u, jsonResp) + + err = t.SUT.SubscribeForTelemetryData(t.ctx, token, region, vin) + + t.Require().NoError(err) +} + +func (t *TeslaFleetAPIServiceTestSuite) TestSubscribeForTelemetryData_Errror_Cases() { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + vin := "RandomVin" + tests := []struct { + response SubscribeForTelemetryDataResponseWrapper + expectedError string + }{ + { + response: SubscribeForTelemetryDataResponseWrapper{ + SubscribeForTelemetryDataResponse{ + UpdatedVehicles: 0, + SkippedVehicles: SkippedVehicles{ + MissingKey: []string{vin}, + UnsupportedHardware: nil, + UnsupportedFirmware: nil, + }, + }, + }, + expectedError: "vehicle has not approved virtual token connection", + }, + { + response: SubscribeForTelemetryDataResponseWrapper{ + SubscribeForTelemetryDataResponse{ + UpdatedVehicles: 0, + SkippedVehicles: SkippedVehicles{ + MissingKey: nil, + UnsupportedHardware: []string{vin}, + UnsupportedFirmware: nil, + }, + }, + }, + expectedError: "vehicle hardware not supported", + }, + { + response: SubscribeForTelemetryDataResponseWrapper{ + SubscribeForTelemetryDataResponse{ + UpdatedVehicles: 0, + SkippedVehicles: SkippedVehicles{ + MissingKey: nil, + UnsupportedHardware: nil, + UnsupportedFirmware: []string{vin}, + }, + }, + }, + expectedError: "vehicle firmware not supported", + }, + } + + for _, tst := range tests { + token := "someToken" + region := "mockRegion" + + baseURL := fmt.Sprintf(mockTeslaFleetBaeURL, region) + u := fmt.Sprintf("%s/api/1/vehicles/fleet_telemetry_config", baseURL) + + responder, err := httpmock.NewJsonResponder(http.StatusOK, tst.response) + t.Require().NoError(err) + httpmock.RegisterResponder(http.MethodPost, u, responder) + + err = t.SUT.SubscribeForTelemetryData(t.ctx, token, region, vin) + + t.Require().EqualError(err, tst.expectedError) + } +} diff --git a/settings.sample.yaml b/settings.sample.yaml index 0427f1cb5..6a6d8fd49 100644 --- a/settings.sample.yaml +++ b/settings.sample.yaml @@ -75,4 +75,8 @@ SYNTHETIC_FINGERPRINT_CONSUMER_GROUP: consumer.synthetic.fingerprint TESLA_CLIENT_ID: TESLA_CLIENT_SECRET: TESLA_TOKEN_URL: -TESLA_FLEET_URL: \ No newline at end of file +TESLA_FLEET_URL: + +TESLA_TELEMETRY_HOST_NAME: +TESLA_TELEMETRY_PORT: +TESLA_TELEMETRY_CA_CERTIFICATE: \ No newline at end of file From 42f119a0fff59a861d8a632fa3b04acd34e611d5 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 11:06:19 -0400 Subject: [PATCH 05/26] Try to cut down on big import diff --- .golangci.yml | 24 +----------------------- cmd/devices-api/api.go | 19 +++++++++---------- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index ff7c83e80..355fd6eb8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -12,30 +12,8 @@ linters: - staticcheck - typecheck - unused -# - zerologlint issues: fix: true -linters-settings: - gci: - # DEPRECATED: use `sections` and `prefix(github.com/org/project)` instead. - # local-prefixes: github.com/org/project - # Section configuration to compare against. - # Section names are case-insensitive and may contain parameters in (). - # The default order of sections is `standard > default > custom > blank > dot`, - # If `custom-order` is `true`, it follows the order of `sections` option. - # Default: ["standard", "default"] - sections: - - standard # Standard section: captures all standard packages. - - default # Default section: contains all imports that could not be matched to another section type. - - prefix(github.com/DIMO-Network/devices-api) # Custom section: groups all imports with the specified Prefix. - - blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled. - - dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled. - # Skip generated files. - # Default: true - skip-generated: true - # Enable custom order of sections. - # If `true`, make the section order the same as the order of `sections`. - # Default: false - custom-order: false \ No newline at end of file +linters-settings: {} diff --git a/cmd/devices-api/api.go b/cmd/devices-api/api.go index 52c4c797d..268e8ea17 100644 --- a/cmd/devices-api/api.go +++ b/cmd/devices-api/api.go @@ -11,12 +11,21 @@ import ( "syscall" "time" + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/DIMO-Network/devices-api/internal/controllers" "github.com/DIMO-Network/devices-api/internal/controllers/helpers" "github.com/DIMO-Network/devices-api/internal/middleware" "github.com/DIMO-Network/devices-api/internal/middleware/metrics" "github.com/DIMO-Network/devices-api/internal/middleware/owner" "github.com/DIMO-Network/devices-api/internal/rpc" + "github.com/DIMO-Network/devices-api/internal/services" + "github.com/DIMO-Network/devices-api/internal/services/autopi" "github.com/DIMO-Network/devices-api/internal/services/fingerprint" + "github.com/DIMO-Network/devices-api/internal/services/issuer" + "github.com/DIMO-Network/devices-api/internal/services/macaron" + "github.com/DIMO-Network/devices-api/internal/services/registry" + pb "github.com/DIMO-Network/devices-api/pkg/grpc" "github.com/DIMO-Network/shared" pbuser "github.com/DIMO-Network/shared/api/users" "github.com/DIMO-Network/shared/db" @@ -41,16 +50,6 @@ import ( "github.com/rs/zerolog" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/internal/constants" - "github.com/DIMO-Network/devices-api/internal/controllers" - "github.com/DIMO-Network/devices-api/internal/services" - "github.com/DIMO-Network/devices-api/internal/services/autopi" - "github.com/DIMO-Network/devices-api/internal/services/issuer" - "github.com/DIMO-Network/devices-api/internal/services/macaron" - "github.com/DIMO-Network/devices-api/internal/services/registry" - pb "github.com/DIMO-Network/devices-api/pkg/grpc" ) func startWebAPI(logger zerolog.Logger, settings *config.Settings, pdb db.Store, eventService services.EventService, producer sarama.SyncProducer, s3ServiceClient *s3.Client, s3NFTServiceClient *s3.Client) { From 8dd5db0bd1350eec00cdd2f5cd2a0c0ebf5f720c Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 11:13:13 -0400 Subject: [PATCH 06/26] More import formatting --- cmd/devices-api/set_command_compat.go | 9 ++++----- internal/controllers/nft_controller.go | 14 +++++++------- .../synthetic_devices_controller_test.go | 13 ++++++------- .../user_integrations_auth_controller.go | 11 +++++------ .../user_integrations_auth_controller_test.go | 11 +++++------ internal/services/smartcar_client.go | 5 ++--- internal/services/tesla_task_service.go | 5 ++--- 7 files changed, 31 insertions(+), 37 deletions(-) diff --git a/cmd/devices-api/set_command_compat.go b/cmd/devices-api/set_command_compat.go index c7469fa77..2b7143197 100644 --- a/cmd/devices-api/set_command_compat.go +++ b/cmd/devices-api/set_command_compat.go @@ -13,15 +13,14 @@ import ( "github.com/DIMO-Network/shared/db" - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - "github.com/volatiletech/sqlboiler/v4/boil" - "github.com/volatiletech/sqlboiler/v4/queries/qm" - "github.com/DIMO-Network/devices-api/internal/config" "github.com/DIMO-Network/devices-api/internal/constants" "github.com/DIMO-Network/devices-api/internal/services" "github.com/DIMO-Network/devices-api/models" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries/qm" ) type setCommandCompatibilityCmd struct { diff --git a/internal/controllers/nft_controller.go b/internal/controllers/nft_controller.go index b6f947967..9f7821a19 100644 --- a/internal/controllers/nft_controller.go +++ b/internal/controllers/nft_controller.go @@ -9,12 +9,18 @@ import ( "strconv" "strings" + "github.com/DIMO-Network/devices-api/internal/services/registry" + "github.com/DIMO-Network/devices-api/internal/utils" "github.com/DIMO-Network/shared" "github.com/segmentio/ksuid" + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/DIMO-Network/devices-api/internal/controllers/helpers" + "github.com/DIMO-Network/devices-api/internal/services" "github.com/DIMO-Network/devices-api/internal/services/registry" "github.com/DIMO-Network/devices-api/internal/utils" - + "github.com/DIMO-Network/devices-api/models" "github.com/DIMO-Network/go-mnemonic" pb "github.com/DIMO-Network/shared/api/users" "github.com/DIMO-Network/shared/db" @@ -32,12 +38,6 @@ import ( "github.com/volatiletech/sqlboiler/v4/queries/qm" "github.com/volatiletech/sqlboiler/v4/types" "golang.org/x/exp/slices" - - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/internal/constants" - "github.com/DIMO-Network/devices-api/internal/controllers/helpers" - "github.com/DIMO-Network/devices-api/internal/services" - "github.com/DIMO-Network/devices-api/models" ) type NFTController struct { diff --git a/internal/controllers/synthetic_devices_controller_test.go b/internal/controllers/synthetic_devices_controller_test.go index d26a03b4d..f63ebb4ce 100644 --- a/internal/controllers/synthetic_devices_controller_test.go +++ b/internal/controllers/synthetic_devices_controller_test.go @@ -9,6 +9,12 @@ import ( "testing" "github.com/DIMO-Network/device-definitions-api/pkg/grpc" + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/contracts" + mock_services "github.com/DIMO-Network/devices-api/internal/services/mocks" + "github.com/DIMO-Network/devices-api/internal/services/registry" + "github.com/DIMO-Network/devices-api/internal/test" + "github.com/DIMO-Network/devices-api/models" "github.com/DIMO-Network/shared" pb "github.com/DIMO-Network/shared/api/users" "github.com/DIMO-Network/shared/db" @@ -24,13 +30,6 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/volatiletech/sqlboiler/v4/types" "go.uber.org/mock/gomock" - - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/internal/contracts" - mock_services "github.com/DIMO-Network/devices-api/internal/services/mocks" - "github.com/DIMO-Network/devices-api/internal/services/registry" - "github.com/DIMO-Network/devices-api/internal/test" - "github.com/DIMO-Network/devices-api/models" ) var signature = "0xa4438e5cb667dc63ebd694167ae3ad83585f2834c9b04895dd890f805c4c459a024ed9df1b03872536b4ac0c7720d02cb787884a093cfcde5c3bd7f94657e30c1b" diff --git a/internal/controllers/user_integrations_auth_controller.go b/internal/controllers/user_integrations_auth_controller.go index 2a9b7b3a2..0a659301e 100644 --- a/internal/controllers/user_integrations_auth_controller.go +++ b/internal/controllers/user_integrations_auth_controller.go @@ -7,18 +7,17 @@ import ( "strconv" "time" + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/DIMO-Network/devices-api/internal/controllers/helpers" + "github.com/DIMO-Network/devices-api/internal/services" + "github.com/DIMO-Network/devices-api/internal/utils" "github.com/DIMO-Network/shared" pb "github.com/DIMO-Network/shared/api/users" "github.com/DIMO-Network/shared/db" "github.com/DIMO-Network/shared/redis" "github.com/gofiber/fiber/v2" "github.com/rs/zerolog" - - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/internal/constants" - "github.com/DIMO-Network/devices-api/internal/controllers/helpers" - "github.com/DIMO-Network/devices-api/internal/services" - "github.com/DIMO-Network/devices-api/internal/utils" ) const teslaFleetAuthCacheKey = "integration_credentials_%s" diff --git a/internal/controllers/user_integrations_auth_controller_test.go b/internal/controllers/user_integrations_auth_controller_test.go index 9b3e6b937..56fe15b83 100644 --- a/internal/controllers/user_integrations_auth_controller_test.go +++ b/internal/controllers/user_integrations_auth_controller_test.go @@ -9,6 +9,11 @@ import ( "time" ddgrpc "github.com/DIMO-Network/device-definitions-api/pkg/grpc" + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/DIMO-Network/devices-api/internal/services" + mock_services "github.com/DIMO-Network/devices-api/internal/services/mocks" + "github.com/DIMO-Network/devices-api/internal/test" "github.com/DIMO-Network/shared" "github.com/DIMO-Network/shared/api/users" "github.com/DIMO-Network/shared/db" @@ -20,12 +25,6 @@ import ( "github.com/stretchr/testify/suite" "github.com/testcontainers/testcontainers-go" "go.uber.org/mock/gomock" - - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/internal/constants" - "github.com/DIMO-Network/devices-api/internal/services" - mock_services "github.com/DIMO-Network/devices-api/internal/services/mocks" - "github.com/DIMO-Network/devices-api/internal/test" ) type UserIntegrationAuthControllerTestSuite struct { diff --git a/internal/services/smartcar_client.go b/internal/services/smartcar_client.go index aa02482a6..65dda645e 100644 --- a/internal/services/smartcar_client.go +++ b/internal/services/smartcar_client.go @@ -11,11 +11,10 @@ import ( "strings" "time" - "github.com/pkg/errors" - smartcar "github.com/smartcar/go-sdk" - "github.com/DIMO-Network/devices-api/internal/config" "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/pkg/errors" + smartcar "github.com/smartcar/go-sdk" ) //go:generate mockgen -source smartcar_client.go -destination mocks/smartcar_client_mock.go diff --git a/internal/services/tesla_task_service.go b/internal/services/tesla_task_service.go index 505212ff3..25bae324f 100644 --- a/internal/services/tesla_task_service.go +++ b/internal/services/tesla_task_service.go @@ -6,12 +6,11 @@ import ( "strconv" "time" + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/models" "github.com/DIMO-Network/shared" "github.com/Shopify/sarama" "github.com/segmentio/ksuid" - - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/models" ) //go:generate mockgen -source tesla_task_service.go -destination mocks/tesla_task_service_mock.go From ef226b6b0729954ba8ac0094859152133ebb6599 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 11:16:18 -0400 Subject: [PATCH 07/26] More imports --- internal/controllers/nft_controller.go | 2 -- settings.sample.yaml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/controllers/nft_controller.go b/internal/controllers/nft_controller.go index 9f7821a19..794ecb88d 100644 --- a/internal/controllers/nft_controller.go +++ b/internal/controllers/nft_controller.go @@ -18,8 +18,6 @@ import ( "github.com/DIMO-Network/devices-api/internal/constants" "github.com/DIMO-Network/devices-api/internal/controllers/helpers" "github.com/DIMO-Network/devices-api/internal/services" - "github.com/DIMO-Network/devices-api/internal/services/registry" - "github.com/DIMO-Network/devices-api/internal/utils" "github.com/DIMO-Network/devices-api/models" "github.com/DIMO-Network/go-mnemonic" pb "github.com/DIMO-Network/shared/api/users" diff --git a/settings.sample.yaml b/settings.sample.yaml index 6a6d8fd49..b8fe7aa0a 100644 --- a/settings.sample.yaml +++ b/settings.sample.yaml @@ -79,4 +79,4 @@ TESLA_FLEET_URL: TESLA_TELEMETRY_HOST_NAME: TESLA_TELEMETRY_PORT: -TESLA_TELEMETRY_CA_CERTIFICATE: \ No newline at end of file +TESLA_TELEMETRY_CA_CERTIFICATE: From 6fd4fcec7428ad2fa94b43b8041dfcaf24d2ea5c Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 11:18:33 -0400 Subject: [PATCH 08/26] Machine comment --- internal/controllers/nft_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controllers/nft_controller.go b/internal/controllers/nft_controller.go index 794ecb88d..42656babc 100644 --- a/internal/controllers/nft_controller.go +++ b/internal/controllers/nft_controller.go @@ -697,7 +697,7 @@ func (udc *UserDevicesController) GetBurnDevice(c *fiber.Ctx) error { if err != nil { return err } - defer tx.Rollback() // nolint + defer tx.Rollback() //nolint vehicleNFT, err := models.VehicleNFTS( models.VehicleNFTWhere.TokenID.EQ(tid), From c164b6f222e2de645227e0e77d325dfa63d50f55 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 11:44:40 -0400 Subject: [PATCH 09/26] Multiline string for CA --- charts/devices-api/values.yaml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/charts/devices-api/values.yaml b/charts/devices-api/values.yaml index f26240aa8..77f087f13 100644 --- a/charts/devices-api/values.yaml +++ b/charts/devices-api/values.yaml @@ -92,7 +92,31 @@ env: META_TRANSACTION_PROCESSOR_GRPC_ADDR: meta-transaction-processor-dev:8086 TESLA_TELEMETRY_HOST_NAME: ingest-tesla.dev.dimo.zone TESLA_TELEMETRY_PORT: 443 - TESLA_TELEMETRY_CA_CERTIFICATE: -----BEGIN CERTIFICATE-----\nMIIBvDCCAWKgAwIBAgIRAL6QCUcK/8jy48V7ElERABowCgYIKoZIzj0EAwIwIzEh\nMB8GA1UEAxMYRElNTyBDQSBEZXZlbG9wbWVudCBSb290MCAXDTIyMDQyMzExMTEw\nM1oYDzIwNzIwNDEwMTExMTAzWjAyMTAwLgYDVQQDEydESU1PIENBIERldmVsb3Bt\nZW50IFNlcnZlciBJbnRlcm1lZGlhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC\nAAQMyh6plUM3p4KWWfK0CqWXr1B9NWk53+c9ps8OpgZZIyXjxiw1EHxrpcqU7C9e\nhw+6JfmvTqqi3F4ES8K+Tt/mo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/\nBAgwBgEB/wIBADAdBgNVHQ4EFgQU+7zrfioO4bjNpD9KiG8fbTcIq8kwHwYDVR0j\nBBgwFoAUeMfSSqt+S65xQF82yRnjr+J5XC8wCgYIKoZIzj0EAwIDSAAwRQIhAK3s\nWtlk+d0fnkii091dTZGt+dtzEbM4HuizaG6mO5zPAiApi03qU/hdsAxXwlbhufH/\n5HuUiCLgBK8vPvL2YdMaKQ==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIBrTCCAVKgAwIBAgIQEgthFz9Ww3+VaErBc3nDFjAKBggqhkjOPQQDAjAjMSEw\nHwYDVQQDExhESU1PIENBIERldmVsb3BtZW50IFJvb3QwIBcNMjIwNDIzMTExMTAz\nWhgPMjEyMjAzMzAxMTExMDNaMCMxITAfBgNVBAMTGERJTU8gQ0EgRGV2ZWxvcG1l\nbnQgUm9vdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBuxEb6jTyfrUwI8RiBV\nKCQWqTAeLdHPj60Qk7HeMeaEcGjzF799xgpl6/8iNKaHN/w+705cdxp5pRswbUtu\nizWjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1Ud\nDgQWBBR4x9JKq35LrnFAXzbJGeOv4nlcLzAfBgNVHSMEGDAWgBR4x9JKq35LrnFA\nXzbJGeOv4nlcLzAKBggqhkjOPQQDAgNJADBGAiEAlslTE9mX+VjPSYLKEsy48Rzh\nOUCdaWovmF+28PyAi4wCIQDXRKpYK+VMFyUR1GJVoV3gWezQcJmFswuWq+7M+XPb\nGQ==\n-----END CERTIFICATE----- + TESLA_TELEMETRY_CA_CERTIFICATE: | + -----BEGIN CERTIFICATE----- + MIIBvDCCAWKgAwIBAgIRAL6QCUcK/8jy48V7ElERABowCgYIKoZIzj0EAwIwIzEh + MB8GA1UEAxMYRElNTyBDQSBEZXZlbG9wbWVudCBSb290MCAXDTIyMDQyMzExMTEw + M1oYDzIwNzIwNDEwMTExMTAzWjAyMTAwLgYDVQQDEydESU1PIENBIERldmVsb3Bt + ZW50IFNlcnZlciBJbnRlcm1lZGlhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC + AAQMyh6plUM3p4KWWfK0CqWXr1B9NWk53+c9ps8OpgZZIyXjxiw1EHxrpcqU7C9e + hw+6JfmvTqqi3F4ES8K+Tt/mo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/ + BAgwBgEB/wIBADAdBgNVHQ4EFgQU+7zrfioO4bjNpD9KiG8fbTcIq8kwHwYDVR0j + BBgwFoAUeMfSSqt+S65xQF82yRnjr+J5XC8wCgYIKoZIzj0EAwIDSAAwRQIhAK3s + Wtlk+d0fnkii091dTZGt+dtzEbM4HuizaG6mO5zPAiApi03qU/hdsAxXwlbhufH/ + 5HuUiCLgBK8vPvL2YdMaKQ== + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + MIIBrTCCAVKgAwIBAgIQEgthFz9Ww3+VaErBc3nDFjAKBggqhkjOPQQDAjAjMSEw + HwYDVQQDExhESU1PIENBIERldmVsb3BtZW50IFJvb3QwIBcNMjIwNDIzMTExMTAz + WhgPMjEyMjAzMzAxMTExMDNaMCMxITAfBgNVBAMTGERJTU8gQ0EgRGV2ZWxvcG1l + bnQgUm9vdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBuxEb6jTyfrUwI8RiBV + KCQWqTAeLdHPj60Qk7HeMeaEcGjzF799xgpl6/8iNKaHN/w+705cdxp5pRswbUtu + izWjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1Ud + DgQWBBR4x9JKq35LrnFAXzbJGeOv4nlcLzAfBgNVHSMEGDAWgBR4x9JKq35LrnFA + XzbJGeOv4nlcLzAKBggqhkjOPQQDAgNJADBGAiEAlslTE9mX+VjPSYLKEsy48Rzh + OUCdaWovmF+28PyAi4wCIQDXRKpYK+VMFyUR1GJVoV3gWezQcJmFswuWq+7M+XPb + GQ== + -----END CERTIFICATE----- service: type: ClusterIP ports: From 200083661786f635b74affdfd6902304601c51c2 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 13:12:56 -0400 Subject: [PATCH 10/26] Remove some Fiber middleware we didn't use --- cmd/devices-api/api.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cmd/devices-api/api.go b/cmd/devices-api/api.go index 268e8ea17..d152e6153 100644 --- a/cmd/devices-api/api.go +++ b/cmd/devices-api/api.go @@ -40,7 +40,6 @@ import ( jwtware "github.com/gofiber/contrib/jwt" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cache" - "github.com/gofiber/fiber/v2/middleware/cors" fiberrecover "github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/swagger" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" @@ -138,9 +137,6 @@ func startWebAPI(logger zerolog.Logger, settings *config.Settings, pdb db.Store, countriesController := controllers.NewCountriesController() userIntegrationAuthController := controllers.NewUserIntegrationAuthController(settings, pdb.DBS, &logger, ddSvc, teslaFleetAPISvc, redisCache, cipher, usersClient) - // commenting this out b/c the library includes the path in the metrics which saturates prometheus queries - need to fork / make our own - // prometheus := fiberprometheus.New("devices-api") - // app.Use(prometheus.Middleware) app.Use(metrics.HTTPMetricsMiddleware) app.Use(fiberrecover.New(fiberrecover.Config{ @@ -148,10 +144,8 @@ func startWebAPI(logger zerolog.Logger, settings *config.Settings, pdb db.Store, EnableStackTrace: true, StackTraceHandler: nil, })) - // cors - app.Use(cors.New()) // request logging - app.Use(zflogger.New(logger, nil)) + app.Use(zflogger.New(logger, nil)) // TODO(elffjs): Is this even printing? // cache cacheHandler := cache.New(cache.Config{ Next: func(c *fiber.Ctx) bool { From 218e74eba4482f66161643dde1c4a6a310fdb8c7 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 13:16:24 -0400 Subject: [PATCH 11/26] Weird comment formatting --- cmd/devices-api/api.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/devices-api/api.go b/cmd/devices-api/api.go index d152e6153..57171b9e0 100644 --- a/cmd/devices-api/api.go +++ b/cmd/devices-api/api.go @@ -144,9 +144,10 @@ func startWebAPI(logger zerolog.Logger, settings *config.Settings, pdb db.Store, EnableStackTrace: true, StackTraceHandler: nil, })) - // request logging + + // Request logging. app.Use(zflogger.New(logger, nil)) // TODO(elffjs): Is this even printing? - // cache + cacheHandler := cache.New(cache.Config{ Next: func(c *fiber.Ctx) bool { return c.Query("refresh") == "true" From b356dd5b38c463290c4a015f34532aea07e75773 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 13:19:19 -0400 Subject: [PATCH 12/26] Comment for machine --- internal/controllers/nft_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controllers/nft_controller.go b/internal/controllers/nft_controller.go index 42656babc..1f509ad3a 100644 --- a/internal/controllers/nft_controller.go +++ b/internal/controllers/nft_controller.go @@ -755,7 +755,7 @@ func (udc *UserDevicesController) PostBurnDevice(c *fiber.Ctx) error { if err != nil { return err } - defer tx.Rollback() // nolint + defer tx.Rollback() //nolint vehicleNFT, err := models.VehicleNFTS( models.VehicleNFTWhere.TokenID.EQ(tid), From 5243bf1c0e82f8ab08a916fbcc476641465e64d9 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 13:26:28 -0400 Subject: [PATCH 13/26] Swap order of set difference. I feel like this is more normal --- .../user_integrations_auth_controller.go | 4 ++-- internal/utils/utils.go | 22 +++++++++---------- internal/utils/utils_test.go | 6 ++--- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/internal/controllers/user_integrations_auth_controller.go b/internal/controllers/user_integrations_auth_controller.go index 0a659301e..53aa931bb 100644 --- a/internal/controllers/user_integrations_auth_controller.go +++ b/internal/controllers/user_integrations_auth_controller.go @@ -270,10 +270,10 @@ func (u *UserIntegrationAuthController) getSmartCarCommands() *services.UserDevi } func (u *UserIntegrationAuthController) prepareCommandsResponse(cmds *services.UserDeviceAPIIntegrationsMetadataCommands) *services.UserDeviceAPIIntegrationsMetadataCommands { - disabled := utils.GetSliceDiff(cmds.Enabled, cmds.Capable) + disabled := utils.SliceDiff(cmds.Capable, cmds.Enabled) return &services.UserDeviceAPIIntegrationsMetadataCommands{ - Enabled: cmds.Enabled, Capable: cmds.Capable, + Enabled: cmds.Enabled, Disabled: disabled, } } diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 224385940..102181d45 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -13,21 +13,19 @@ func BigToDecimal(x *big.Int) types.Decimal { return types.NewDecimal(new(decimal.Big).SetBigMantScale(x, 0)) } -type void struct{} +// SliceDiff compares two slices and returns slice of differences +func SliceDiff(set, other []string) []string { + otherSet := make(map[string]struct{}, len(other)) -// GetSliceDiff compares two slices and returns slice of differences -func GetSliceDiff(subset, superset []string) []string { - ma := make(map[string]void, len(subset)) - - diffs := make([]string, 0) - for _, ka := range subset { - ma[ka] = void{} + for _, x := range other { + otherSet[x] = struct{}{} } - for _, kb := range superset { - if _, ok := ma[kb]; !ok { - diffs = append(diffs, kb) + var diff []string + for _, x := range set { + if _, ok := otherSet[x]; !ok { + diff = append(diff, x) } } - return diffs + return diff } diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go index e1eace716..df52b7da6 100644 --- a/internal/utils/utils_test.go +++ b/internal/utils/utils_test.go @@ -9,8 +9,8 @@ import ( ) func TestGetSliceDiff(t *testing.T) { - enabled := []string{constants.DoorsLock, constants.DoorsUnlock} capable := []string{constants.TelemetrySubscribe, constants.DoorsLock, constants.DoorsUnlock} - actual := GetSliceDiff(enabled, capable) - assert.Equal(t, []string{constants.TelemetrySubscribe}, actual) + enabled := []string{constants.DoorsLock, constants.DoorsUnlock} + actual := SliceDiff(capable, enabled) + assert.ElementsMatch(t, []string{constants.TelemetrySubscribe}, actual) } From e8adb02ad822475e41a88ec26f46dbf928d8845b Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 13:33:20 -0400 Subject: [PATCH 14/26] Misspelling --- internal/services/tesla_fleet_api_service_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/services/tesla_fleet_api_service_test.go b/internal/services/tesla_fleet_api_service_test.go index 2f94f2d1c..d0046a301 100644 --- a/internal/services/tesla_fleet_api_service_test.go +++ b/internal/services/tesla_fleet_api_service_test.go @@ -13,7 +13,7 @@ import ( "github.com/DIMO-Network/devices-api/internal/test" ) -const mockTeslaFleetBaeURL = "https://fleet-mock-api.%s.tesla.com" +const mockTeslaFleetBaseURL = "https://fleet-mock-api.%s.tesla.com" type TeslaFleetAPIServiceTestSuite struct { suite.Suite @@ -25,7 +25,7 @@ type TeslaFleetAPIServiceTestSuite struct { func (t *TeslaFleetAPIServiceTestSuite) SetupSuite() { t.ctx = context.Background() logger := test.Logger() - t.settings = &config.Settings{TeslaFleetURL: mockTeslaFleetBaeURL, TeslaTelemetryCACertificate: "Ca-Cert", TeslaTelemetryPort: 443, TeslaTelemetryHostName: "tel.dimo.com"} + t.settings = &config.Settings{TeslaFleetURL: mockTeslaFleetBaseURL, TeslaTelemetryCACertificate: "Ca-Cert", TeslaTelemetryPort: 443, TeslaTelemetryHostName: "tel.dimo.com"} t.SUT = NewTeslaFleetAPIService(t.settings, logger) } @@ -42,7 +42,7 @@ func (t *TeslaFleetAPIServiceTestSuite) TestSubscribeForTelemetryData() { region := "mockRegion" vin := "RandomVin" - baseURL := fmt.Sprintf(mockTeslaFleetBaeURL, region) + baseURL := fmt.Sprintf(mockTeslaFleetBaseURL, region) u := fmt.Sprintf("%s/api/1/vehicles/fleet_telemetry_config", baseURL) respBody := SubscribeForTelemetryDataResponseWrapper{ @@ -115,7 +115,7 @@ func (t *TeslaFleetAPIServiceTestSuite) TestSubscribeForTelemetryData_Errror_Cas token := "someToken" region := "mockRegion" - baseURL := fmt.Sprintf(mockTeslaFleetBaeURL, region) + baseURL := fmt.Sprintf(mockTeslaFleetBaseURL, region) u := fmt.Sprintf("%s/api/1/vehicles/fleet_telemetry_config", baseURL) responder, err := httpmock.NewJsonResponder(http.StatusOK, tst.response) From 2a05c643654318452656636227b38829819a0f12 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 13:55:39 -0400 Subject: [PATCH 15/26] More import --- internal/controllers/user_integrations_controller.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/controllers/user_integrations_controller.go b/internal/controllers/user_integrations_controller.go index 47e6a8035..8b3e6610f 100644 --- a/internal/controllers/user_integrations_controller.go +++ b/internal/controllers/user_integrations_controller.go @@ -14,6 +14,11 @@ import ( smartcar "github.com/smartcar/go-sdk" ddgrpc "github.com/DIMO-Network/device-definitions-api/pkg/grpc" + "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/DIMO-Network/devices-api/internal/controllers/helpers" + "github.com/DIMO-Network/devices-api/internal/services" + "github.com/DIMO-Network/devices-api/internal/services/registry" + "github.com/DIMO-Network/devices-api/models" "github.com/DIMO-Network/shared" pb "github.com/DIMO-Network/shared/api/users" "github.com/ethereum/go-ethereum/common" @@ -28,12 +33,6 @@ import ( "github.com/volatiletech/sqlboiler/v4/queries/qm" "golang.org/x/exp/slices" "golang.org/x/mod/semver" - - "github.com/DIMO-Network/devices-api/internal/constants" - "github.com/DIMO-Network/devices-api/internal/controllers/helpers" - "github.com/DIMO-Network/devices-api/internal/services" - "github.com/DIMO-Network/devices-api/internal/services/registry" - "github.com/DIMO-Network/devices-api/models" ) // GetUserDeviceIntegration godoc From 180d595d08ebe4797e05b97c344d8868073b125d Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 13:56:29 -0400 Subject: [PATCH 16/26] More encryption --- .../user_integrations_controller_test.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/internal/controllers/user_integrations_controller_test.go b/internal/controllers/user_integrations_controller_test.go index 08d19fafc..dee174f81 100644 --- a/internal/controllers/user_integrations_controller_test.go +++ b/internal/controllers/user_integrations_controller_test.go @@ -26,6 +26,15 @@ import ( "github.com/DIMO-Network/shared/db" ddgrpc "github.com/DIMO-Network/device-definitions-api/pkg/grpc" + "github.com/DIMO-Network/devices-api/internal/config" + "github.com/DIMO-Network/devices-api/internal/constants" + "github.com/DIMO-Network/devices-api/internal/contracts" + "github.com/DIMO-Network/devices-api/internal/middleware/owner" + "github.com/DIMO-Network/devices-api/internal/services" + mock_services "github.com/DIMO-Network/devices-api/internal/services/mocks" + "github.com/DIMO-Network/devices-api/internal/services/registry" + "github.com/DIMO-Network/devices-api/internal/test" + "github.com/DIMO-Network/devices-api/models" "github.com/DIMO-Network/shared" smock "github.com/Shopify/sarama/mocks" "github.com/gofiber/fiber/v2" @@ -41,16 +50,6 @@ import ( "github.com/volatiletech/sqlboiler/v4/boil" "github.com/volatiletech/sqlboiler/v4/types" "go.uber.org/mock/gomock" - - "github.com/DIMO-Network/devices-api/internal/config" - "github.com/DIMO-Network/devices-api/internal/constants" - "github.com/DIMO-Network/devices-api/internal/contracts" - "github.com/DIMO-Network/devices-api/internal/middleware/owner" - "github.com/DIMO-Network/devices-api/internal/services" - mock_services "github.com/DIMO-Network/devices-api/internal/services/mocks" - "github.com/DIMO-Network/devices-api/internal/services/registry" - "github.com/DIMO-Network/devices-api/internal/test" - "github.com/DIMO-Network/devices-api/models" ) type UserIntegrationsControllerTestSuite struct { From cb0f18a46dd5f9766a0716fca4cc28653f49e83b Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 13:58:39 -0400 Subject: [PATCH 17/26] Another machine comment --- internal/controllers/user_integrations_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controllers/user_integrations_controller.go b/internal/controllers/user_integrations_controller.go index 8b3e6610f..3096a96d7 100644 --- a/internal/controllers/user_integrations_controller.go +++ b/internal/controllers/user_integrations_controller.go @@ -178,7 +178,7 @@ func (udc *UserDevicesController) deleteDeviceIntegration(ctx context.Context, u if err != nil { return err } - defer tx.Rollback() // nolint + defer tx.Rollback() //nolint apiInt, err := models.UserDeviceAPIIntegrations( models.UserDeviceAPIIntegrationWhere.UserDeviceID.EQ(userDeviceID), From f7910f91c52b9ec2158ce076489f57d9c000bbd3 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 13:59:13 -0400 Subject: [PATCH 18/26] Nolint --- internal/controllers/user_integrations_controller.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/controllers/user_integrations_controller.go b/internal/controllers/user_integrations_controller.go index 3096a96d7..d9c6dcff1 100644 --- a/internal/controllers/user_integrations_controller.go +++ b/internal/controllers/user_integrations_controller.go @@ -274,7 +274,7 @@ func (udc *UserDevicesController) DeleteUserDeviceIntegration(c *fiber.Ctx) erro return err } - defer tx.Rollback() // nolint + defer tx.Rollback() //nolint device, err := models.UserDevices( models.UserDeviceWhere.ID.EQ(userDeviceID), @@ -1144,7 +1144,7 @@ func (udc *UserDevicesController) PostAftermarketDevicePair(c *fiber.Ctx) error if err != nil { return err } - defer tx.Rollback() // nolint + defer tx.Rollback() //nolint vnft, ad, err := udc.checkPairable(c.Context(), tx, userDeviceID, pairReq.ExternalID) if err != nil { @@ -1480,7 +1480,7 @@ func (udc *UserDevicesController) UnpairAutoPi(c *fiber.Ctx) error { if err != nil { return err } - defer tx.Rollback() // nolint + defer tx.Rollback() //nolint vnft, apnft, err := udc.checkUnpairable(c.Context(), tx, userDeviceID) if err != nil { @@ -1640,7 +1640,7 @@ func (udc *UserDevicesController) registerDeviceIntegrationInner(c *fiber.Ctx, u if err != nil { return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("failed to create transaction: %s", err)) } - defer tx.Rollback() // nolint + defer tx.Rollback() //nolint ud, err := models.UserDevices( models.UserDeviceWhere.ID.EQ(userDeviceID), ).One(c.Context(), tx) From a1a6134d540d9bc9c769056b8f4fedd158ca3796 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 14:01:42 -0400 Subject: [PATCH 19/26] Get rid of the global integration command list for now --- .../user_integrations_auth_controller.go | 68 ------------------- internal/utils/utils.go | 17 ----- internal/utils/utils_test.go | 16 ----- 3 files changed, 101 deletions(-) delete mode 100644 internal/utils/utils_test.go diff --git a/internal/controllers/user_integrations_auth_controller.go b/internal/controllers/user_integrations_auth_controller.go index 53aa931bb..038337d33 100644 --- a/internal/controllers/user_integrations_auth_controller.go +++ b/internal/controllers/user_integrations_auth_controller.go @@ -11,7 +11,6 @@ import ( "github.com/DIMO-Network/devices-api/internal/constants" "github.com/DIMO-Network/devices-api/internal/controllers/helpers" "github.com/DIMO-Network/devices-api/internal/services" - "github.com/DIMO-Network/devices-api/internal/utils" "github.com/DIMO-Network/shared" pb "github.com/DIMO-Network/shared/api/users" "github.com/DIMO-Network/shared/db" @@ -210,70 +209,3 @@ func (u *UserIntegrationAuthController) persistOauthCredentials(ctx context.Cont return nil } - -// GetCommandsByIntegration godoc -// @Description Get a list of available commands by integration -// @Tags integrations -// @Produce json -// @Accept json -// @Param tokenID path string true "token id for integration" -// @Security ApiKeyAuth -// @Success 200 {object} controllers.GetCommandsByIntegrationResponse -// @Security BearerAuth -// @Router /integration/:tokenID/commands [get] -func (u *UserIntegrationAuthController) GetCommandsByIntegration(c *fiber.Ctx) error { - tokenID := c.Params("tokenID") - tkID, err := strconv.ParseUint(tokenID, 10, 64) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "could not process the provided tokenId!") - } - - intd, err := u.DeviceDefSvc.GetIntegrationByTokenID(c.Context(), tkID) - if err != nil { - u.log.Err(err).Str("Calling Function", "GetIntegrationByTokenID").Uint64("tokenID", tkID).Msg("Error occurred trying to get integration using tokenID") - return fiber.NewError(fiber.StatusBadRequest, "could not find integration with token Id") - } - - var commands *services.UserDeviceAPIIntegrationsMetadataCommands - switch intd.Vendor { - case constants.TeslaVendor: - vrsn := c.Query("version") - version, err := strconv.Atoi(vrsn) - if err != nil { - version = constants.TeslaAPIV1 - } - commands = u.getTeslaCommands(version) - case constants.SmartCarVendor: - commands = u.getSmartCarCommands() - default: - return fiber.NewError(fiber.StatusBadRequest, "unsupported or invalid integration") - } - return c.JSON(commands) -} - -func (u *UserIntegrationAuthController) getTeslaCommands(version int) *services.UserDeviceAPIIntegrationsMetadataCommands { - var cmds *services.UserDeviceAPIIntegrationsMetadataCommands - if version == constants.TeslaAPIV2 { - cmds = u.teslaFleetAPISvc.GetAvailableCommands() - } else { - svc := services.NewTeslaService(u.Settings) - cmds = svc.GetAvailableCommands() - } - - return u.prepareCommandsResponse(cmds) -} - -func (u *UserIntegrationAuthController) getSmartCarCommands() *services.UserDeviceAPIIntegrationsMetadataCommands { - svc := services.NewSmartcarClient(u.Settings) - cmds := svc.GetAvailableCommands() - return u.prepareCommandsResponse(cmds) -} - -func (u *UserIntegrationAuthController) prepareCommandsResponse(cmds *services.UserDeviceAPIIntegrationsMetadataCommands) *services.UserDeviceAPIIntegrationsMetadataCommands { - disabled := utils.SliceDiff(cmds.Capable, cmds.Enabled) - return &services.UserDeviceAPIIntegrationsMetadataCommands{ - Capable: cmds.Capable, - Enabled: cmds.Enabled, - Disabled: disabled, - } -} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 102181d45..c13cf0421 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -12,20 +12,3 @@ import ( func BigToDecimal(x *big.Int) types.Decimal { return types.NewDecimal(new(decimal.Big).SetBigMantScale(x, 0)) } - -// SliceDiff compares two slices and returns slice of differences -func SliceDiff(set, other []string) []string { - otherSet := make(map[string]struct{}, len(other)) - - for _, x := range other { - otherSet[x] = struct{}{} - } - - var diff []string - for _, x := range set { - if _, ok := otherSet[x]; !ok { - diff = append(diff, x) - } - } - return diff -} diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go deleted file mode 100644 index df52b7da6..000000000 --- a/internal/utils/utils_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package utils - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/DIMO-Network/devices-api/internal/constants" -) - -func TestGetSliceDiff(t *testing.T) { - capable := []string{constants.TelemetrySubscribe, constants.DoorsLock, constants.DoorsUnlock} - enabled := []string{constants.DoorsLock, constants.DoorsUnlock} - actual := SliceDiff(capable, enabled) - assert.ElementsMatch(t, []string{constants.TelemetrySubscribe}, actual) -} From 732e667e4edbc07493413c46894e1b4ff7c05822 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 14:07:14 -0400 Subject: [PATCH 20/26] Clean up tests --- cmd/devices-api/api.go | 1 - .../user_integrations_auth_controller_test.go | 89 ------------------- 2 files changed, 90 deletions(-) diff --git a/cmd/devices-api/api.go b/cmd/devices-api/api.go index 57171b9e0..3d67bf689 100644 --- a/cmd/devices-api/api.go +++ b/cmd/devices-api/api.go @@ -217,7 +217,6 @@ func startWebAPI(logger zerolog.Logger, settings *config.Settings, pdb db.Store, v1Auth.Post("/user/devices/fromsmartcar", userDeviceController.RegisterDeviceForUserFromSmartcar) v1Auth.Post("/user/devices", userDeviceController.RegisterDeviceForUser) v1Auth.Post("/integration/:tokenID/credentials", userIntegrationAuthController.CompleteOAuthExchange) - v1Auth.Get("/integration/:tokenID/commands", userIntegrationAuthController.GetCommandsByIntegration) // Autopi specific routes. amdOwnerMw := owner.AftermarketDevice(pdb, usersClient, &logger) diff --git a/internal/controllers/user_integrations_auth_controller_test.go b/internal/controllers/user_integrations_auth_controller_test.go index 56fe15b83..2fa38dda4 100644 --- a/internal/controllers/user_integrations_auth_controller_test.go +++ b/internal/controllers/user_integrations_auth_controller_test.go @@ -21,7 +21,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/go-redis/redis/v8" "github.com/gofiber/fiber/v2" - "github.com/pkg/errors" "github.com/stretchr/testify/suite" "github.com/testcontainers/testcontainers-go" "go.uber.org/mock/gomock" @@ -63,7 +62,6 @@ func (s *UserIntegrationAuthControllerTestSuite) SetupSuite() { }, s.pdb.DBS, logger, s.deviceDefSvc, s.teslaFleetAPISvc, s.redisClient, s.cipher, s.usersClient) app := test.SetupAppFiber(*logger) app.Post("/integration/:tokenID/credentials", test.AuthInjectorTestHandler(s.testUserID), c.CompleteOAuthExchange) - app.Get("/integration/:tokenID/commands", test.AuthInjectorTestHandler(s.testUserID), c.GetCommandsByIntegration) s.controller = &c s.app = app @@ -272,90 +270,3 @@ func (s *UserIntegrationAuthControllerTestSuite) TestPersistOauthCredentials() { err = intCtrl.persistOauthCredentials(s.ctx, *mockAuthCodeResp, mockUserEthAddr) s.Assert().NoError(err) } - -func (s *UserIntegrationAuthControllerTestSuite) TestGetTeslaV1Commands() { - s.deviceDefSvc.EXPECT().GetIntegrationByTokenID(gomock.Any(), uint64(2)).Return(&ddgrpc.Integration{ - Vendor: constants.TeslaVendor, - }, nil) - - request := test.BuildRequest("GET", "/integration/2/commands", "") - response, _ := s.app.Test(request) - - s.Assert().Equal(fiber.StatusOK, response.StatusCode) - body, _ := io.ReadAll(response.Body) - - expected := services.UserDeviceAPIIntegrationsMetadataCommands{ - Enabled: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, - Capable: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, - Disabled: []string{}, - } - - var actual services.UserDeviceAPIIntegrationsMetadataCommands - err := json.Unmarshal(body, &actual) - s.Require().NoError(err) - - s.Assert().Equal(expected, actual) -} - -func (s *UserIntegrationAuthControllerTestSuite) TestGetTeslaV2Commands() { - s.deviceDefSvc.EXPECT().GetIntegrationByTokenID(gomock.Any(), uint64(2)).Return(&ddgrpc.Integration{ - Vendor: constants.TeslaVendor, - }, nil) - - logger := test.Logger() - teslaFleetSvc := services.NewTeslaFleetAPIService(nil, logger) - c := NewUserIntegrationAuthController(&config.Settings{}, nil, logger, s.deviceDefSvc, teslaFleetSvc, nil, nil, nil) - app := test.SetupAppFiber(*logger) - app.Get("/integration/:tokenID/commands", test.AuthInjectorTestHandler(s.testUserID), c.GetCommandsByIntegration) - - request := test.BuildRequest("GET", "/integration/2/commands?version=2", "") - response, _ := app.Test(request) - - s.Assert().Equal(fiber.StatusOK, response.StatusCode) - body, _ := io.ReadAll(response.Body) - - expected := services.UserDeviceAPIIntegrationsMetadataCommands{ - Enabled: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit}, - Capable: []string{constants.DoorsUnlock, constants.DoorsLock, constants.TrunkOpen, constants.FrunkOpen, constants.ChargeLimit, constants.TelemetrySubscribe}, - Disabled: []string{constants.TelemetrySubscribe}, - } - - var actual services.UserDeviceAPIIntegrationsMetadataCommands - err := json.Unmarshal(body, &actual) - s.Require().NoError(err) - - s.Assert().Equal(expected, actual) -} - -func (s *UserIntegrationAuthControllerTestSuite) TestGetSmartCarCommands() { - s.deviceDefSvc.EXPECT().GetIntegrationByTokenID(gomock.Any(), uint64(1)).Return(&ddgrpc.Integration{ - Vendor: constants.SmartCarVendor, - }, nil) - - request := test.BuildRequest("GET", "/integration/1/commands", "") - response, _ := s.app.Test(request) - - s.Assert().Equal(fiber.StatusOK, response.StatusCode) - body, _ := io.ReadAll(response.Body) - - expected := services.UserDeviceAPIIntegrationsMetadataCommands{ - Enabled: []string{constants.DoorsUnlock, constants.DoorsLock}, - Capable: []string{constants.DoorsUnlock, constants.DoorsLock}, - Disabled: []string{}, - } - - var actual services.UserDeviceAPIIntegrationsMetadataCommands - err := json.Unmarshal(body, &actual) - s.Require().NoError(err) - - s.Assert().Equal(expected, actual) -} - -func (s *UserIntegrationAuthControllerTestSuite) TestGetInvalidIntegrationCommands() { - s.deviceDefSvc.EXPECT().GetIntegrationByTokenID(gomock.Any(), uint64(1)).Return(nil, errors.New("no device Id")) - - request := test.BuildRequest("GET", "/integration/1/commands", "") - response, _ := s.app.Test(request) - - s.Assert().Equal(fiber.StatusBadRequest, response.StatusCode) -} From 52e5c4ff3d4de66c4791d194a60110acaaca08dc Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 14:15:05 -0400 Subject: [PATCH 21/26] Kill struct --- internal/controllers/user_integrations_auth_controller.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/controllers/user_integrations_auth_controller.go b/internal/controllers/user_integrations_auth_controller.go index 038337d33..b40a08dab 100644 --- a/internal/controllers/user_integrations_auth_controller.go +++ b/internal/controllers/user_integrations_auth_controller.go @@ -73,11 +73,6 @@ type CompleteOAuthExchangeResponse struct { Definition DeviceDefinition `json:"definition"` } -type GetCommandsByIntegrationResponse struct { - Enabled []string `json:"enabled"` - Disabled []string `json:"disabled"` -} - // DeviceDefinition inner definition object containing meta data for each tesla vehicle type DeviceDefinition struct { Make string `json:"make"` From c628ace35dbe52f1643bba927320dbda93bd9225 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 14:17:19 -0400 Subject: [PATCH 22/26] Regen --- docs/docs.go | 56 ----------------------------------------------- docs/swagger.json | 56 ----------------------------------------------- docs/swagger.yaml | 34 ---------------------------- 3 files changed, 146 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 7010bcac9..6d5aa4ebd 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -498,45 +498,6 @@ const docTemplate = `{ } } }, - "/integration/:tokenID/commands": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - }, - { - "BearerAuth": [] - } - ], - "description": "Get a list of available commands by integration", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "integrations" - ], - "parameters": [ - { - "type": "string", - "description": "token id for integration", - "name": "tokenID", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/internal_controllers.GetCommandsByIntegrationResponse" - } - } - } - } - }, "/integration/{tokenID}": { "get": { "description": "gets an integration using its tokenID", @@ -3076,23 +3037,6 @@ const docTemplate = `{ } } }, - "internal_controllers.GetCommandsByIntegrationResponse": { - "type": "object", - "properties": { - "disabled": { - "type": "array", - "items": { - "type": "string" - } - }, - "enabled": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, "internal_controllers.GetGeofence": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 6f4ab09cb..5546edab7 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -490,45 +490,6 @@ } } }, - "/integration/:tokenID/commands": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - }, - { - "BearerAuth": [] - } - ], - "description": "Get a list of available commands by integration", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "integrations" - ], - "parameters": [ - { - "type": "string", - "description": "token id for integration", - "name": "tokenID", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/internal_controllers.GetCommandsByIntegrationResponse" - } - } - } - } - }, "/integration/{tokenID}": { "get": { "description": "gets an integration using its tokenID", @@ -3068,23 +3029,6 @@ } } }, - "internal_controllers.GetCommandsByIntegrationResponse": { - "type": "object", - "properties": { - "disabled": { - "type": "array", - "items": { - "type": "string" - } - }, - "enabled": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, "internal_controllers.GetGeofence": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ee5ea6bb9..88203895d 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -523,17 +523,6 @@ definitions: userDeviceId: type: string type: object - internal_controllers.GetCommandsByIntegrationResponse: - properties: - disabled: - items: - type: string - type: array - enabled: - items: - type: string - type: array - type: object internal_controllers.GetGeofence: properties: createdAt: @@ -1262,29 +1251,6 @@ paths: - BearerAuth: [] tags: - documents - /integration/:tokenID/commands: - get: - consumes: - - application/json - description: Get a list of available commands by integration - parameters: - - description: token id for integration - in: path - name: tokenID - required: true - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/internal_controllers.GetCommandsByIntegrationResponse' - security: - - ApiKeyAuth: [] - - BearerAuth: [] - tags: - - integrations /integration/{tokenID}: get: description: gets an integration using its tokenID From b7c52606d1bdf5528a501a8d190b307a65481f54 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 14:27:41 -0400 Subject: [PATCH 23/26] More Joins, some variable names --- internal/services/tesla_fleet_api_service.go | 25 ++++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/internal/services/tesla_fleet_api_service.go b/internal/services/tesla_fleet_api_service.go index 84abc9992..bdb7ed130 100644 --- a/internal/services/tesla_fleet_api_service.go +++ b/internal/services/tesla_fleet_api_service.go @@ -8,6 +8,8 @@ import ( "io" "net/http" "net/url" + "path" + "strconv" "strings" "time" @@ -61,12 +63,12 @@ type VirtualKeyConnectionStatusResponse struct { } type VirtualKeyConnectionStatus struct { - UnpairedVins []string `json:"unpaired_vins"` - KeyPairedVins []string `json:"key_paired_vins"` + UnpairedVINs []string `json:"unpaired_vins"` + KeyPairedVINs []string `json:"key_paired_vins"` } type SubscribeForTelemetryDataRequest struct { - Vins []string `json:"vins"` + VINs []string `json:"vins"` Config TelemetryConfigRequest `json:"config"` } @@ -150,7 +152,7 @@ func (t *teslaFleetAPIService) CompleteTeslaAuthCodeExchange(ctx context.Context // GetVehicles calls Tesla Fleet API to get a list of vehicles using authorization token func (t *teslaFleetAPIService) GetVehicles(ctx context.Context, token, region string) ([]TeslaVehicle, error) { baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) - url := baseURL + "/api/1/vehicles" + url := path.Join(baseURL, "/api/1/vehicles") resp, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { @@ -173,7 +175,7 @@ func (t *teslaFleetAPIService) GetVehicles(ctx context.Context, token, region st // GetVehicle calls Tesla Fleet API to get a single vehicle by ID func (t *teslaFleetAPIService) GetVehicle(ctx context.Context, token, region string, vehicleID int) (*TeslaVehicle, error) { baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) - url := fmt.Sprintf("%s/api/1/vehicles/%d", baseURL, vehicleID) + url := path.Join(baseURL, "/api/1/vehicles", strconv.Itoa(vehicleID)) resp, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { @@ -192,7 +194,7 @@ func (t *teslaFleetAPIService) GetVehicle(ctx context.Context, token, region str // WakeUpVehicle Calls Tesla Fleet API to wake a vehicle from sleep func (t *teslaFleetAPIService) WakeUpVehicle(ctx context.Context, token, region string, vehicleID int) error { baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) - url := fmt.Sprintf("%s/api/1/vehicles/%d/wake_up", baseURL, vehicleID) + url := path.Join(baseURL, "/api/1/vehicles", strconv.Itoa(vehicleID), "wake_up") resp, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { @@ -218,7 +220,7 @@ func (t *teslaFleetAPIService) GetAvailableCommands() *UserDeviceAPIIntegrations // VirtualKeyConnectionStatus Checks whether vehicles can accept Tesla commands protocol for the partner's public key func (t *teslaFleetAPIService) VirtualKeyConnectionStatus(ctx context.Context, token, region, vin string) (bool, error) { baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) - url := fmt.Sprintf("%s/api/1/vehicles/fleet_status", baseURL) + url := path.Join(baseURL, "/api/1/vehicles/fleet_status") jsonBody := fmt.Sprintf(`{"vins": [%q]}`, vin) body := strings.NewReader(jsonBody) @@ -241,7 +243,7 @@ func (t *teslaFleetAPIService) VirtualKeyConnectionStatus(ctx context.Context, t return false, fmt.Errorf("error occurred decoding connection status %w", err) } - isConnected := slices.Contains(v.Response.KeyPairedVins, vin) + isConnected := slices.Contains(v.Response.KeyPairedVINs, vin) return isConnected, nil } @@ -293,13 +295,10 @@ var fields = TelemetryFields{ func (t *teslaFleetAPIService) SubscribeForTelemetryData(ctx context.Context, token, region, vin string) error { baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) - u, err := url.JoinPath(baseURL, "/api/1/vehicles/fleet_telemetry_config") - if err != nil { - return err - } + u := path.Join(baseURL, "/api/1/vehicles/fleet_telemetry_config") r := SubscribeForTelemetryDataRequest{ - Vins: []string{vin}, + VINs: []string{vin}, Config: TelemetryConfigRequest{ HostName: t.Settings.TeslaTelemetryHostName, PublicCACertificate: t.Settings.TeslaTelemetryCACertificate, From aa5e85a84688061d799cc4aec9c439c3490f7c9f Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 14:38:57 -0400 Subject: [PATCH 24/26] Response wrapper --- internal/services/tesla_fleet_api_service.go | 50 ++++++++------------ 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/internal/services/tesla_fleet_api_service.go b/internal/services/tesla_fleet_api_service.go index bdb7ed130..0783bfdcf 100644 --- a/internal/services/tesla_fleet_api_service.go +++ b/internal/services/tesla_fleet_api_service.go @@ -35,12 +35,8 @@ type TeslaFleetAPIService interface { var teslaScopes = []string{"openid", "offline_access", "user_data", "vehicle_device_data", "vehicle_cmds", "vehicle_charging_cmds", "energy_device_data", "energy_device_data", "energy_cmds"} -type GetVehiclesResponse struct { - Response []TeslaVehicle `json:"response"` -} - -type GetSingleVehicleItemResponse struct { - Response TeslaVehicle `json:"response"` +type TeslaResponseWrapper[A any] struct { + Response A `json:"response"` } type TeslaFleetAPIError struct { @@ -98,10 +94,6 @@ type SubscribeForTelemetryDataResponse struct { SkippedVehicles SkippedVehicles `json:"skipped_vehicles"` } -type SubscribeForTelemetryDataResponseWrapper struct { - Response SubscribeForTelemetryDataResponse `json:"response"` -} - type teslaFleetAPIService struct { Settings *config.Settings HTTPClient *http.Client @@ -151,8 +143,7 @@ func (t *teslaFleetAPIService) CompleteTeslaAuthCodeExchange(ctx context.Context // GetVehicles calls Tesla Fleet API to get a list of vehicles using authorization token func (t *teslaFleetAPIService) GetVehicles(ctx context.Context, token, region string) ([]TeslaVehicle, error) { - baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) - url := path.Join(baseURL, "/api/1/vehicles") + url := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles") resp, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { @@ -160,8 +151,8 @@ func (t *teslaFleetAPIService) GetVehicles(ctx context.Context, token, region st } defer resp.Body.Close() - vehicles := new(GetVehiclesResponse) - if err := json.NewDecoder(resp.Body).Decode(vehicles); err != nil { + var vehicles TeslaResponseWrapper[[]TeslaVehicle] + if err := json.NewDecoder(resp.Body).Decode(&vehicles); err != nil { return nil, fmt.Errorf("invalid response encountered while fetching user vehicles: %w", err) } @@ -174,8 +165,7 @@ func (t *teslaFleetAPIService) GetVehicles(ctx context.Context, token, region st // GetVehicle calls Tesla Fleet API to get a single vehicle by ID func (t *teslaFleetAPIService) GetVehicle(ctx context.Context, token, region string, vehicleID int) (*TeslaVehicle, error) { - baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) - url := path.Join(baseURL, "/api/1/vehicles", strconv.Itoa(vehicleID)) + url := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles", strconv.Itoa(vehicleID)) resp, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { @@ -183,8 +173,8 @@ func (t *teslaFleetAPIService) GetVehicle(ctx context.Context, token, region str } defer resp.Body.Close() - vehicle := new(GetSingleVehicleItemResponse) - if err := json.NewDecoder(resp.Body).Decode(vehicle); err != nil { + var vehicle TeslaResponseWrapper[TeslaVehicle] + if err := json.NewDecoder(resp.Body).Decode(&vehicle); err != nil { return nil, fmt.Errorf("invalid response encountered while fetching user vehicles: %w", err) } @@ -193,8 +183,7 @@ func (t *teslaFleetAPIService) GetVehicle(ctx context.Context, token, region str // WakeUpVehicle Calls Tesla Fleet API to wake a vehicle from sleep func (t *teslaFleetAPIService) WakeUpVehicle(ctx context.Context, token, region string, vehicleID int) error { - baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) - url := path.Join(baseURL, "/api/1/vehicles", strconv.Itoa(vehicleID), "wake_up") + url := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles", strconv.Itoa(vehicleID), "wake_up") resp, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { @@ -219,8 +208,7 @@ func (t *teslaFleetAPIService) GetAvailableCommands() *UserDeviceAPIIntegrations // VirtualKeyConnectionStatus Checks whether vehicles can accept Tesla commands protocol for the partner's public key func (t *teslaFleetAPIService) VirtualKeyConnectionStatus(ctx context.Context, token, region, vin string) (bool, error) { - baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) - url := path.Join(baseURL, "/api/1/vehicles/fleet_status") + url := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles/fleet_status") jsonBody := fmt.Sprintf(`{"vins": [%q]}`, vin) body := strings.NewReader(jsonBody) @@ -237,7 +225,7 @@ func (t *teslaFleetAPIService) VirtualKeyConnectionStatus(ctx context.Context, t return false, fmt.Errorf("could not verify connection status %w", err) } - var v VirtualKeyConnectionStatusResponse + var v TeslaResponseWrapper[VirtualKeyConnectionStatus] err = json.Unmarshal(bd, &v) if err != nil { return false, fmt.Errorf("error occurred decoding connection status %w", err) @@ -271,12 +259,12 @@ func (t *teslaFleetAPIService) RefreshToken(ctx context.Context, refreshToken st return nil, fmt.Errorf("status code %d", code) } - tokResp := new(TeslaAuthCodeResponse) - if err := json.NewDecoder(resp.Body).Decode(tokResp); err != nil { + var tokResp TeslaAuthCodeResponse + if err := json.NewDecoder(resp.Body).Decode(&tokResp); err != nil { return nil, err } - return tokResp, nil + return &tokResp, nil } var fields = TelemetryFields{ @@ -293,9 +281,12 @@ var fields = TelemetryFields{ "BatteryLevel": {IntervalSeconds: 60}, } +func (t *teslaFleetAPIService) fleetURLForRegion(region string) string { + return fmt.Sprintf(t.Settings.TeslaFleetURL, region) +} + func (t *teslaFleetAPIService) SubscribeForTelemetryData(ctx context.Context, token, region, vin string) error { - baseURL := fmt.Sprintf(t.Settings.TeslaFleetURL, region) - u := path.Join(baseURL, "/api/1/vehicles/fleet_telemetry_config") + u := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles/fleet_telemetry_config") r := SubscribeForTelemetryDataRequest{ VINs: []string{vin}, @@ -304,7 +295,6 @@ func (t *teslaFleetAPIService) SubscribeForTelemetryData(ctx context.Context, to PublicCACertificate: t.Settings.TeslaTelemetryCACertificate, Port: t.Settings.TeslaTelemetryPort, Fields: fields, - AlertTypes: []string{"service"}, }, } @@ -322,7 +312,7 @@ func (t *teslaFleetAPIService) SubscribeForTelemetryData(ctx context.Context, to defer resp.Body.Close() - subResp := SubscribeForTelemetryDataResponseWrapper{} + var subResp TeslaResponseWrapper[SubscribeForTelemetryDataResponse] if err := json.NewDecoder(resp.Body).Decode(&subResp); err != nil { return err } From b71abf4326abfa42d78d33032c77e3eaee9cec10 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 14:48:51 -0400 Subject: [PATCH 25/26] More unmarshal cleanup --- internal/services/tesla_fleet_api_service.go | 66 ++++++++----------- .../services/tesla_fleet_api_service_test.go | 10 +-- 2 files changed, 33 insertions(+), 43 deletions(-) diff --git a/internal/services/tesla_fleet_api_service.go b/internal/services/tesla_fleet_api_service.go index 0783bfdcf..b823d976a 100644 --- a/internal/services/tesla_fleet_api_service.go +++ b/internal/services/tesla_fleet_api_service.go @@ -1,6 +1,7 @@ package services import ( + "bytes" "context" "encoding/json" "errors" @@ -78,8 +79,6 @@ type TelemetryConfigRequest struct { HostName string `json:"hostName"` PublicCACertificate string `json:"ca"` Fields TelemetryFields `json:"fields"` - AlertTypes []string `json:"alert_types,omitempty"` - Expiration int64 `json:"exp"` Port int `json:"port"` } @@ -145,14 +144,14 @@ func (t *teslaFleetAPIService) CompleteTeslaAuthCodeExchange(ctx context.Context func (t *teslaFleetAPIService) GetVehicles(ctx context.Context, token, region string) ([]TeslaVehicle, error) { url := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles") - resp, err := t.performRequest(ctx, url, token, http.MethodGet, nil) + body, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { return nil, fmt.Errorf("could not fetch vehicles for user: %w", err) } - defer resp.Body.Close() var vehicles TeslaResponseWrapper[[]TeslaVehicle] - if err := json.NewDecoder(resp.Body).Decode(&vehicles); err != nil { + err = json.Unmarshal(body, &vehicles) + if err != nil { return nil, fmt.Errorf("invalid response encountered while fetching user vehicles: %w", err) } @@ -167,14 +166,14 @@ func (t *teslaFleetAPIService) GetVehicles(ctx context.Context, token, region st func (t *teslaFleetAPIService) GetVehicle(ctx context.Context, token, region string, vehicleID int) (*TeslaVehicle, error) { url := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles", strconv.Itoa(vehicleID)) - resp, err := t.performRequest(ctx, url, token, http.MethodGet, nil) + body, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { return nil, fmt.Errorf("could not fetch vehicles for user: %w", err) } - defer resp.Body.Close() var vehicle TeslaResponseWrapper[TeslaVehicle] - if err := json.NewDecoder(resp.Body).Decode(&vehicle); err != nil { + err = json.Unmarshal(body, &vehicle) + if err != nil { return nil, fmt.Errorf("invalid response encountered while fetching user vehicles: %w", err) } @@ -185,18 +184,12 @@ func (t *teslaFleetAPIService) GetVehicle(ctx context.Context, token, region str func (t *teslaFleetAPIService) WakeUpVehicle(ctx context.Context, token, region string, vehicleID int) error { url := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles", strconv.Itoa(vehicleID), "wake_up") - resp, err := t.performRequest(ctx, url, token, http.MethodGet, nil) + _, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { return fmt.Errorf("could not fetch vehicles for user: %w", err) } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("got status code %d waking up vehicle %d", resp.StatusCode, vehicleID) - } - - return nil + return err } func (t *teslaFleetAPIService) GetAvailableCommands() *UserDeviceAPIIntegrationsMetadataCommands { @@ -211,27 +204,20 @@ func (t *teslaFleetAPIService) VirtualKeyConnectionStatus(ctx context.Context, t url := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles/fleet_status") jsonBody := fmt.Sprintf(`{"vins": [%q]}`, vin) - body := strings.NewReader(jsonBody) + inBody := strings.NewReader(jsonBody) - resp, err := t.performRequest(ctx, url, token, http.MethodPost, body) + body, err := t.performRequest(ctx, url, token, http.MethodPost, inBody) if err != nil { return false, fmt.Errorf("could not fetch vehicles for user: %w", err) } - defer resp.Body.Close() - - bd, err := io.ReadAll(resp.Body) - if err != nil { - return false, fmt.Errorf("could not verify connection status %w", err) - } - - var v TeslaResponseWrapper[VirtualKeyConnectionStatus] - err = json.Unmarshal(bd, &v) + var keyConn TeslaResponseWrapper[VirtualKeyConnectionStatus] + err = json.Unmarshal(body, &keyConn) if err != nil { return false, fmt.Errorf("error occurred decoding connection status %w", err) } - isConnected := slices.Contains(v.Response.KeyPairedVINs, vin) + isConnected := slices.Contains(keyConn.Response.KeyPairedVINs, vin) return isConnected, nil } @@ -303,17 +289,14 @@ func (t *teslaFleetAPIService) SubscribeForTelemetryData(ctx context.Context, to return err } - req := strings.NewReader(string(b)) - - resp, err := t.performRequest(ctx, u, token, http.MethodPost, req) + body, err := t.performRequest(ctx, u, token, http.MethodPost, bytes.NewReader(b)) if err != nil { return err } - defer resp.Body.Close() - var subResp TeslaResponseWrapper[SubscribeForTelemetryDataResponse] - if err := json.NewDecoder(resp.Body).Decode(&subResp); err != nil { + err = json.Unmarshal(body, &subResp) + if err != nil { return err } @@ -337,7 +320,7 @@ func (t *teslaFleetAPIService) SubscribeForTelemetryData(ctx context.Context, to } // performRequest a helper function for making http requests, it adds a timeout context and parses error response -func (t *teslaFleetAPIService) performRequest(ctx context.Context, url, token, method string, body *strings.Reader) (*http.Response, error) { +func (t *teslaFleetAPIService) performRequest(ctx context.Context, url, token, method string, body io.Reader) ([]byte, error) { ctxTimeout, cancel := context.WithTimeout(ctx, time.Second*10) defer cancel() @@ -352,9 +335,11 @@ func (t *teslaFleetAPIService) performRequest(ctx context.Context, url, token, m return nil, fmt.Errorf("error occurred calling tesla fleet api: %w", err) } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { - errBody := new(TeslaFleetAPIError) - if err := json.NewDecoder(resp.Body).Decode(errBody); err != nil { + var errBody TeslaFleetAPIError + if err := json.NewDecoder(resp.Body).Decode(&errBody); err != nil { t.log. Err(err). Str("url", url). @@ -364,5 +349,10 @@ func (t *teslaFleetAPIService) performRequest(ctx context.Context, url, token, m return nil, fmt.Errorf("error occurred calling tesla api: %s", errBody.ErrorDescription) } - return resp, nil + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + + return b, nil } diff --git a/internal/services/tesla_fleet_api_service_test.go b/internal/services/tesla_fleet_api_service_test.go index d0046a301..47efdcf6e 100644 --- a/internal/services/tesla_fleet_api_service_test.go +++ b/internal/services/tesla_fleet_api_service_test.go @@ -45,7 +45,7 @@ func (t *TeslaFleetAPIServiceTestSuite) TestSubscribeForTelemetryData() { baseURL := fmt.Sprintf(mockTeslaFleetBaseURL, region) u := fmt.Sprintf("%s/api/1/vehicles/fleet_telemetry_config", baseURL) - respBody := SubscribeForTelemetryDataResponseWrapper{ + respBody := TeslaResponseWrapper[SubscribeForTelemetryDataResponse]{ SubscribeForTelemetryDataResponse{ UpdatedVehicles: 1, SkippedVehicles: SkippedVehicles{}, @@ -67,11 +67,11 @@ func (t *TeslaFleetAPIServiceTestSuite) TestSubscribeForTelemetryData_Errror_Cas vin := "RandomVin" tests := []struct { - response SubscribeForTelemetryDataResponseWrapper + response TeslaResponseWrapper[SubscribeForTelemetryDataResponse] expectedError string }{ { - response: SubscribeForTelemetryDataResponseWrapper{ + response: TeslaResponseWrapper[SubscribeForTelemetryDataResponse]{ SubscribeForTelemetryDataResponse{ UpdatedVehicles: 0, SkippedVehicles: SkippedVehicles{ @@ -84,7 +84,7 @@ func (t *TeslaFleetAPIServiceTestSuite) TestSubscribeForTelemetryData_Errror_Cas expectedError: "vehicle has not approved virtual token connection", }, { - response: SubscribeForTelemetryDataResponseWrapper{ + response: TeslaResponseWrapper[SubscribeForTelemetryDataResponse]{ SubscribeForTelemetryDataResponse{ UpdatedVehicles: 0, SkippedVehicles: SkippedVehicles{ @@ -97,7 +97,7 @@ func (t *TeslaFleetAPIServiceTestSuite) TestSubscribeForTelemetryData_Errror_Cas expectedError: "vehicle hardware not supported", }, { - response: SubscribeForTelemetryDataResponseWrapper{ + response: TeslaResponseWrapper[SubscribeForTelemetryDataResponse]{ SubscribeForTelemetryDataResponse{ UpdatedVehicles: 0, SkippedVehicles: SkippedVehicles{ From fea6b89d98ac6a38bf601f9c7c8a4268336295d6 Mon Sep 17 00:00:00 2001 From: Dylan Moreland Date: Wed, 1 May 2024 15:02:16 -0400 Subject: [PATCH 26/26] URL join --- internal/services/tesla_fleet_api_service.go | 28 +++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/internal/services/tesla_fleet_api_service.go b/internal/services/tesla_fleet_api_service.go index b823d976a..c5a4d694e 100644 --- a/internal/services/tesla_fleet_api_service.go +++ b/internal/services/tesla_fleet_api_service.go @@ -9,7 +9,6 @@ import ( "io" "net/http" "net/url" - "path" "strconv" "strings" "time" @@ -142,7 +141,10 @@ func (t *teslaFleetAPIService) CompleteTeslaAuthCodeExchange(ctx context.Context // GetVehicles calls Tesla Fleet API to get a list of vehicles using authorization token func (t *teslaFleetAPIService) GetVehicles(ctx context.Context, token, region string) ([]TeslaVehicle, error) { - url := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles") + url, err := url.JoinPath(t.fleetURLForRegion(region), "/api/1/vehicles") + if err != nil { + return nil, err + } body, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { @@ -164,7 +166,10 @@ func (t *teslaFleetAPIService) GetVehicles(ctx context.Context, token, region st // GetVehicle calls Tesla Fleet API to get a single vehicle by ID func (t *teslaFleetAPIService) GetVehicle(ctx context.Context, token, region string, vehicleID int) (*TeslaVehicle, error) { - url := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles", strconv.Itoa(vehicleID)) + url, err := url.JoinPath(t.fleetURLForRegion(region), "/api/1/vehicles", strconv.Itoa(vehicleID)) + if err != nil { + return nil, fmt.Errorf("error constructing URL: %w", err) + } body, err := t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { @@ -182,9 +187,12 @@ func (t *teslaFleetAPIService) GetVehicle(ctx context.Context, token, region str // WakeUpVehicle Calls Tesla Fleet API to wake a vehicle from sleep func (t *teslaFleetAPIService) WakeUpVehicle(ctx context.Context, token, region string, vehicleID int) error { - url := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles", strconv.Itoa(vehicleID), "wake_up") + url, err := url.JoinPath(t.fleetURLForRegion(region), "/api/1/vehicles", strconv.Itoa(vehicleID), "wake_up") + if err != nil { + return fmt.Errorf("error constructing URL: %w", err) + } - _, err := t.performRequest(ctx, url, token, http.MethodGet, nil) + _, err = t.performRequest(ctx, url, token, http.MethodGet, nil) if err != nil { return fmt.Errorf("could not fetch vehicles for user: %w", err) } @@ -201,7 +209,10 @@ func (t *teslaFleetAPIService) GetAvailableCommands() *UserDeviceAPIIntegrations // VirtualKeyConnectionStatus Checks whether vehicles can accept Tesla commands protocol for the partner's public key func (t *teslaFleetAPIService) VirtualKeyConnectionStatus(ctx context.Context, token, region, vin string) (bool, error) { - url := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles/fleet_status") + url, err := url.JoinPath(t.fleetURLForRegion(region), "/api/1/vehicles/fleet_status") + if err != nil { + return false, fmt.Errorf("error constructing URL: %w", err) + } jsonBody := fmt.Sprintf(`{"vins": [%q]}`, vin) inBody := strings.NewReader(jsonBody) @@ -272,7 +283,10 @@ func (t *teslaFleetAPIService) fleetURLForRegion(region string) string { } func (t *teslaFleetAPIService) SubscribeForTelemetryData(ctx context.Context, token, region, vin string) error { - u := path.Join(t.fleetURLForRegion(region), "/api/1/vehicles/fleet_telemetry_config") + u, err := url.JoinPath(t.fleetURLForRegion(region), "/api/1/vehicles/fleet_telemetry_config") + if err != nil { + return fmt.Errorf("error constructing URL: %w", err) + } r := SubscribeForTelemetryDataRequest{ VINs: []string{vin},