feat: persisting remotes and commands in database

This commit is contained in:
Fritz Heiden 2025-04-12 16:35:03 +02:00
parent db1beac033
commit 6cefa7392c
12 changed files with 576 additions and 64 deletions

75
data/command.go Normal file
View File

@ -0,0 +1,75 @@
package data
const SAMSUNG_PROTOCOL = "samsung"
const NEC_PROTOCOL = "nec"
const ONKYO_PROTOCOL = "onkyo"
const APPLE_PROTOCOL = "apple"
const DENON_PROTOCOL = "denon"
const SHARP_PROTOCOL = "sharp"
const PANASONIC_PROTOCOL = "panasonic"
const KASEIKYO_PROTOCOL = "kaseikyo"
const JVC_PROTOCOL = "jvc"
const LG_PROTOCOL = "lg"
const SONY_PROTOCOL = "sony"
const RC5_PROTOCOL = "rc5"
const RC6_PROTOCOL = "rc6"
const UNIVERSAL_PULSE_DISTANCE_PROTOCOL = "universal_pulse_distance"
const UNIVERSAL_PULSE_WIDTH_PROTOCOL = "universal_pulse_width"
const UNIVERSAL_PULSE_DISTANCE_WIDTH_PROTOCOL = "universal_pulse_distance_width"
const HASH_PROTOCOL = "hash"
const PRONTO_PROTOCOL = "pronto"
const BOSE_WAVE_PROTOCOL = "bose_wave"
const BANG_OLUFSEN_PROTOCOL = "bang_olufsen"
const LEGO_PROTOCOL = "lego"
const FAST_PROTOCOL = "fast"
const WHYNTER_PROTOCOL = "whynter"
const MAGIQUEST_PROTOCOL = "magiquest"
const POWER_COMMAND_TYPE = "power"
const INPUT_COMMAND_TYPE = "input"
const ONE_COMMAND_TYPE = "1"
const TWO_COMMAND_TYPE = "2"
const THREE_COMMAND_TYPE = "3"
const FOUR_COMMAND_TYPE = "4"
const FIVE_COMMAND_TYPE = "5"
const SIX_COMMAND_TYPE = "6"
const SEVEN_COMMAND_TYPE = "7"
const EIGHT_COMMAND_TYPE = "8"
const NINE_COMMAND_TYPE = "9"
const ZERO_COMMAND_TYPE = "0"
const VOLUME_UP_COMMAND_TYPE = "volume_up"
const VOLUME_DOWN_COMMAND_TYPE = "volume_down"
const MUTE_COMMAND_TYPE = "mute"
const CHANNEL_UP_COMMAND_TYPE = "channel_up"
const CHANNEL_DOWN_COMMAND_TYPE = "channel_down"
const MENU_COMMAND_TYPE = "menu"
const HOME_COMMAND_TYPE = "home"
const SETTINGS_COMMAND_TYPE = "settings"
const OPTIONS_COMMAND_TYPE = "options"
const UP_COMMAND_TYPE = "up"
const DOWN_COMMAND_TYPE = "down"
const LEFT_COMMAND_TYPE = "left"
const RIGHT_COMMAND_TYPE = "right"
const ENTER_COMMAND_TYPE = "enter"
const INFO_COMMAND_TYPE = "info"
const RETURN_COMMAND_TYPE = "return"
const EXIT_COMMAND_TYPE = "exit"
const RED_COMMAND_TYPE = "red"
const GREEN_COMMAND_TYPE = "green"
const YELLOW_COMMAND_TYPE = "yellow"
const BLUE_COMMAND_TYPE = "blue"
const REWIND_COMMAND_TYPE = "rewind"
const PLAY_COMMAND_TYPE = "play"
const PAUSE_COMMAND_TYPE = "pause"
const STOP_COMMAND_TYPE = "stop"
const FORWARD_COMMAND_TYPE = "forward"
const OTHER_COMMAND_TYPE = "other"
type Command struct {
Id string `json:"id"`
Protocol string `json:"protocol"`
CommandNumber int `json:"commandNumber"`
Device int `json:"device"`
CommandType string `json:"commandType"`
Title string `json:"title"`
}

7
data/remote.go Normal file
View File

@ -0,0 +1,7 @@
package data
type Remote struct {
Id string `json:"id"`
Title string `json:"title"`
Commands []Command `json:"commands"`
}

200
data/remote_database.go Normal file
View File

@ -0,0 +1,200 @@
package data
import (
"database/sql"
"fmt"
"path/filepath"
gonanoid "github.com/matoous/go-nanoid"
)
type RemoteDatabase struct {
Connection *sql.DB
databaseDirectory string
}
func (db *RemoteDatabase) Initialize() error {
connection, error := sql.Open("sqlite3", filepath.Join(db.databaseDirectory, "remotes.db"))
if error != nil {
return error
}
db.Connection = connection
_, error = db.Connection.Exec(`PRAGMA foreign_keys = ON;`)
if error != nil {
return fmt.Errorf("error enabling foreign keys: %s", error)
}
_, error = db.Connection.Exec(`
CREATE TABLE IF NOT EXISTS Remotes (
id TEXT PRIMARY KEY,
title TEXT NOT NULL
);`)
if error != nil {
return fmt.Errorf("error creating remotes table: %s", error)
}
_, error = db.Connection.Exec(`
CREATE TABLE IF NOT EXISTS Commands (
id TEXT PRIMARY KEY,
protocol TEXT,
commandNumber INTEGER,
device INTEGER,
commandType TEXT,
title TEXT
);`)
if error != nil {
return fmt.Errorf("error creating commands table: %s", error)
}
_, error = db.Connection.Exec(`
CREATE TABLE IF NOT EXISTS RemoteCommands (
remote_id TEXT NOT NULL,
command_id TEXT NOT NULL,
PRIMARY KEY (remote_id, command_id),
FOREIGN KEY (remote_id) REFERENCES Remotes(id) ON DELETE CASCADE,
FOREIGN KEY (command_id) REFERENCES Commands(id) ON DELETE CASCADE
);`)
if error != nil {
return fmt.Errorf("error creating remote-commands table: %s", error)
}
return nil
}
func (db *RemoteDatabase) Close() error {
return db.Connection.Close()
}
func (db *RemoteDatabase) CreateRemote(remote Remote) (string, error) {
remoteId, err := gonanoid.Nanoid(8)
if err != nil {
return "", err
}
queryString := "INSERT INTO Remotes (id, title) VALUES (?, ?)"
_, err = db.Connection.Exec(queryString, remoteId, remote.Title)
if err != nil {
return "", err
}
commandIds := []string{}
for _, command := range remote.Commands {
commandIds = append(commandIds, command.Id)
}
err = db.CreateRemoteCommands(remoteId, commandIds)
if err != nil {
return "", err
}
return remoteId, nil
}
func (db *RemoteDatabase) CreateRemoteCommands(remoteId string, commandIds []string) error {
for _, commandId := range commandIds {
queryString := "INSERT INTO RemoteCommands (remote_id, command_id) VALUES (?, ?)"
_, err := db.Connection.Exec(queryString, remoteId, commandId)
if err != nil {
return err
}
}
return nil
}
func (db *RemoteDatabase) GetRemotes() ([]Remote, error) {
rows, error := db.Connection.Query("SELECT id, title FROM Remotes")
if error != nil {
return nil, fmt.Errorf("error querying remotes: %s", error)
}
defer rows.Close()
remotes := []Remote{}
for rows.Next() {
var remote Remote
error = rows.Scan(&remote.Id, &remote.Title)
if error != nil {
return nil, fmt.Errorf("error scanning remote: %s", error)
}
remotes = append(remotes, remote)
}
return remotes, nil
}
func (db *RemoteDatabase) DeleteRemote(remoteId string) error {
queryString := "DELETE FROM Remotes WHERE id = ?"
_, error := db.Connection.Exec(queryString, remoteId)
if error != nil {
return fmt.Errorf("error deleting remote %s: %s", remoteId, error)
}
return nil
}
func (db *RemoteDatabase) GetCommandsByRemoteId(remoteId string) ([]Command, error) {
rows, error := db.Connection.Query("SELECT id, protocol, commandNumber, device, commandType, title FROM Commands WHERE id IN (SELECT command_id FROM RemoteCommands WHERE remote_id = ?)", remoteId)
if error != nil {
return nil, fmt.Errorf("error querying commands for remote %s: %s", remoteId, error)
}
defer rows.Close()
commands := []Command{}
for rows.Next() {
var command Command
error = rows.Scan(&command.Id, &command.Protocol, &command.CommandNumber, &command.Device, &command.CommandType, &command.Title)
if error != nil {
return nil, fmt.Errorf("error scanning command: %s", error)
}
commands = append(commands, command)
}
return commands, nil
}
func (db *RemoteDatabase) CreateCommand(command Command) (string, error) {
commandId, err := gonanoid.Nanoid(8)
if err != nil {
return "", err
}
queryString := "INSERT INTO Commands (id, protocol, commandNumber, device, commandType, title) VALUES (?, ?, ?, ?, ?, ?)"
_, err = db.Connection.Exec(queryString, commandId, command.Protocol, command.CommandNumber, command.Device, command.CommandType, command.Title)
if err != nil {
return "", err
}
return commandId, nil
}
func (db *RemoteDatabase) CreateCommands(commands []Command) error {
for _, command := range commands {
_, err := db.CreateCommand(command)
if err != nil {
return err
}
}
return nil
}
func (db *RemoteDatabase) GetCommands() ([]Command, error) {
rows, error := db.Connection.Query("SELECT id, protocol, commandNumber, device, commandType, title FROM Commands")
if error != nil {
return nil, fmt.Errorf("error querying commands: %s", error)
}
defer rows.Close()
commands := []Command{}
for rows.Next() {
var command Command
error = rows.Scan(&command.Id, &command.Protocol, &command.CommandNumber, &command.Device, &command.CommandType, &command.Title)
if error != nil {
return nil, fmt.Errorf("error scanning command: %s", error)
}
commands = append(commands, command)
}
return commands, nil
}
func (db *RemoteDatabase) DeleteCommand(commandId string) error {
queryString := "DELETE FROM Commands WHERE id = ?"
_, error := db.Connection.Exec(queryString, commandId)
if error != nil {
return fmt.Errorf("error deleting command %s: %s", commandId, error)
}
return nil
}
func (db *RemoteDatabase) SetDirectory(directory string) {
db.databaseDirectory = directory
}

View File

@ -39,6 +39,15 @@ func main() {
} }
defer deviceDatabase.Close() defer deviceDatabase.Close()
remoteDatabase := data.RemoteDatabase{}
remoteDatabase.SetDirectory(configuration.DatabaseDirectory)
err = remoteDatabase.Initialize()
if err != nil {
log.Error().Err(err).Msg("failed to initialize remote database")
os.Exit(1)
}
defer remoteDatabase.Close()
userManager := management.UserManager{} userManager := management.UserManager{}
err = userManager.Initialize(&userDatabase) err = userManager.Initialize(&userDatabase)
if err != nil { if err != nil {
@ -51,6 +60,12 @@ func main() {
log.Error().Err(err).Msg("failed to initialize device manager") log.Error().Err(err).Msg("failed to initialize device manager")
} }
remoteManager := management.RemoteManager{}
err = remoteManager.Initialize(&remoteDatabase)
if err != nil {
log.Error().Err(err).Msg("failed to initialize remote manager")
}
webServer := server.WebServer{} webServer := server.WebServer{}
webServer.SetWebAppDirectoryPath("www") webServer.SetWebAppDirectoryPath("www")
webServer.SetPort(configuration.Port) webServer.SetPort(configuration.Port)
@ -71,6 +86,11 @@ func main() {
deviceApiHandler.SetRouter(webServer.Router()) deviceApiHandler.SetRouter(webServer.Router())
deviceApiHandler.Initialize(&authenticator) deviceApiHandler.Initialize(&authenticator)
remoteApiHandler := server.RemoteApiHandler{}
remoteApiHandler.SetRemoteManager(&remoteManager)
remoteApiHandler.SetRouter(webServer.Router())
remoteApiHandler.Initialize(&authenticator)
webSocketServer := server.WebsocketServer{} webSocketServer := server.WebsocketServer{}
webSocketServer.SetRouter(webServer.Router()) webSocketServer.SetRouter(webServer.Router())
webSocketServer.Initialize(&authenticator) webSocketServer.Initialize(&authenticator)

View File

@ -0,0 +1,50 @@
package management
import (
d "playback-device-server/data"
)
type RemoteManager struct {
remoteDatabase *d.RemoteDatabase
}
func (rm *RemoteManager) Initialize(remoteDatabase *d.RemoteDatabase) error {
rm.remoteDatabase = remoteDatabase
return nil
}
func (rm *RemoteManager) CreateRemote(remote d.Remote) (string, error) {
return rm.remoteDatabase.CreateRemote(remote)
}
func (rm *RemoteManager) GetRemotes() ([]d.Remote, error) {
remotes, err := rm.remoteDatabase.GetRemotes()
if err != nil {
return nil, err
}
return remotes, nil
}
func (rm *RemoteManager) DeleteRemote(remoteID string) error {
return rm.remoteDatabase.DeleteRemote(remoteID)
}
func (rm *RemoteManager) CreateCommand(command d.Command) (string, error) {
return rm.remoteDatabase.CreateCommand(command)
}
func (rm *RemoteManager) CreateCommands(commands []d.Command) error {
return rm.remoteDatabase.CreateCommands(commands)
}
func (rm *RemoteManager) GetCommands() ([]d.Command, error) {
return rm.remoteDatabase.GetCommands()
}
func (rm *RemoteManager) SetRemoteDatabase(remoteDatabase *d.RemoteDatabase) {
rm.remoteDatabase = remoteDatabase
}
func (rm *RemoteManager) DeleteCommand(commandID string) error {
return rm.remoteDatabase.DeleteCommand(commandID)
}

View File

@ -0,0 +1,103 @@
package server
import (
d "playback-device-server/data"
m "playback-device-server/management"
"github.com/labstack/echo/v4"
)
type RemoteApiHandler struct {
router *echo.Echo
remoteManager *m.RemoteManager
}
func (r *RemoteApiHandler) Initialize(authenticator *Authenticator) {
r.router.Use(authenticator.Authenticate("/api/remotes", []string{}))
remotesApi := r.router.Group("/api/remotes")
remotesApi.GET("", r.handleGetRemotes)
remotesApi.POST("", r.handleCreateRemote)
remotesApi.DELETE("/:id", r.handleDelteRemote)
r.router.Use(authenticator.Authenticate("/api/commands", []string{}))
commandsApi := r.router.Group("/api/commands")
commandsApi.GET("", r.handleGetCommands)
commandsApi.POST("", r.handleCreateCommands)
commandsApi.DELETE("/:id", r.handleDeleteCommand)
}
func (r *RemoteApiHandler) handleCreateRemote(context echo.Context) error {
remote := d.Remote{}
if err := context.Bind(&remote); err != nil {
SendError(400, context, err.Error())
return nil
}
id, err := r.remoteManager.CreateRemote(remote)
if err != nil {
SendError(500, context, err.Error())
return err
}
remote.Id = id
return context.JSON(200, remote)
}
func (r *RemoteApiHandler) handleGetRemotes(context echo.Context) error {
remotes, err := r.remoteManager.GetRemotes()
if err != nil {
SendError(500, context, err.Error())
return err
}
return context.JSON(200, remotes)
}
func (r *RemoteApiHandler) handleDelteRemote(context echo.Context) error {
id := context.Param("id")
err := r.remoteManager.DeleteRemote(id)
if err != nil {
SendError(500, context, err.Error())
return err
}
return context.JSON(200, "")
}
func (r *RemoteApiHandler) handleCreateCommands(context echo.Context) error {
commands := []d.Command{}
if err := context.Bind(&commands); err != nil {
SendError(400, context, err.Error())
return nil
}
err := r.remoteManager.CreateCommands(commands)
if err != nil {
SendError(500, context, err.Error())
return err
}
return context.JSON(200, "")
}
func (r *RemoteApiHandler) handleGetCommands(context echo.Context) error {
commands, err := r.remoteManager.GetCommands()
if err != nil {
SendError(500, context, err.Error())
return err
}
return context.JSON(200, commands)
}
func (r *RemoteApiHandler) handleDeleteCommand(context echo.Context) error {
id := context.Param("id")
err := r.remoteManager.DeleteCommand(id)
if err != nil {
SendError(500, context, err.Error())
return err
}
return context.JSON(200, "")
}
func (r *RemoteApiHandler) SetRouter(router *echo.Echo) {
r.router = router
}
func (r *RemoteApiHandler) SetRemoteManager(remoteManager *m.RemoteManager) {
r.remoteManager = remoteManager
}

View File

@ -1,11 +1,14 @@
function Command({ function Command({
id, id = "",
protocol, protocol = "",
commandNumber, commandNumber = -1,
device, device = -1,
commandType, commandType = "",
title, title = "",
} = {}) { } = {}) {
if (typeof commandNumber !== "number")
throw new Error("Command number must be a number");
if (typeof device !== "number") throw new Error("Device must be a number");
let _id = id; let _id = id;
let _protocol = protocol; let _protocol = protocol;
let _commandNumber = commandNumber; let _commandNumber = commandNumber;
@ -34,6 +37,8 @@ function Command({
} }
function setCommandNumber(commandNumber) { function setCommandNumber(commandNumber) {
if (typeof commandNumber !== "number")
throw new Error("Command number must be a number");
_commandNumber = commandNumber; _commandNumber = commandNumber;
} }
@ -42,6 +47,7 @@ function Command({
} }
function setDevice(device) { function setDevice(device) {
if (typeof device !== "number") throw new Error("Device must be a number");
_device = device; _device = device;
} }

View File

@ -31,20 +31,46 @@ const Serializer = (function () {
if (!objects) return []; if (!objects) return [];
return objects.map((object) => deserializeIntegration(object)); return objects.map((object) => deserializeIntegration(object));
} }
function serializeCommand(command) {
return {
id: command.getId(),
protocol: command.getProtocol(),
commandNumber: command.getCommandNumber(),
device: command.getDevice(),
commandType: command.getCommandType(),
title: command.getTitle(),
};
}
function serializeCommands(commands) {
if (!commands) return [];
return commands.map((command) => serializeCommand(command));
}
function deserializeCommand(object) { function deserializeCommand(object) {
return new Command(object); return new Command(object);
} }
function deserializeCommands(objects) { function deserializeCommands(objects) {
if (!objects) return []; if (!objects) return [];
return objects.map((object) => deserializeCommand(object)); return objects.map((object) => deserializeCommand(object));
} }
function serializeRemote(remote) {
return {
id: remote.getId(),
title: remote.getTitle(),
commands: remote.getCommands().map((command) => ({
id: command.getId(),
})),
};
}
function deserializeRemote(object) { function deserializeRemote(object) {
return new Remote(object); return new Remote(object);
} }
function deserializeRemotes(objects) { function deserializeRemotes(objects) {
if (!objects) return []; if (!objects) return [];
return objects.map((object) => deserializeRemote(object)); return objects.map((object) => deserializeRemote(object));
@ -57,8 +83,11 @@ const Serializer = (function () {
deserializeDevices, deserializeDevices,
deserializeIntegration, deserializeIntegration,
deserializeIntegrations, deserializeIntegrations,
serializeCommand,
serializeCommands,
deserializeCommand, deserializeCommand,
deserializeCommands, deserializeCommands,
serializeRemote,
deserializeRemote, deserializeRemote,
deserializeRemotes, deserializeRemotes,
}; };

View File

@ -24,7 +24,9 @@ function CreateCommandModal(props) {
); );
const isDeviceValid = createMemo(() => device() !== "" && !isNaN(device())); const isDeviceValid = createMemo(() => device() !== "" && !isNaN(device()));
const isCommandTypeValid = createMemo(() => commandType() !== ""); const isCommandTypeValid = createMemo(() => commandType() !== "");
const isTitleValid = createMemo(() => title().length >= MIN_TITLE_LENGTH); const isTitleValid = createMemo(
() => title() === "" || title().length >= MIN_TITLE_LENGTH
);
const isFormValid = createMemo( const isFormValid = createMemo(
() => () =>
@ -41,8 +43,8 @@ function CreateCommandModal(props) {
command = await RemotesService.createCommand( command = await RemotesService.createCommand(
new Command({ new Command({
protocol: protocol(), protocol: protocol(),
commandNumber: commandNumber(), commandNumber: parseInt(commandNumber()),
device: device(), device: parseInt(device()),
commandType: commandType(), commandType: commandType(),
title: title(), title: title(),
}) })

View File

@ -37,6 +37,7 @@ function CreateRemoteModal(props) {
); );
} catch (e) { } catch (e) {
setError(e.message); setError(e.message);
console.error(e);
return; return;
} }
resetFields(); resetFields();

View File

@ -360,7 +360,11 @@ function ImportCommandsModal(props) {
let protocol = valueMapping()[PROTOCOL_FIELD][row[protocolField]]; let protocol = valueMapping()[PROTOCOL_FIELD][row[protocolField]];
if (!protocol) return null; if (!protocol) return null;
let commandNumber = row[commandNumberField]; let commandNumber = row[commandNumberField];
if (isNaN(commandNumber)) return null;
commandNumber = parseInt(commandNumber);
let device = row[deviceField]; let device = row[deviceField];
if (isNaN(device)) return null;
device = parseInt(device);
let commandType = let commandType =
valueMapping()[COMMAND_TYPE_FIELD][row[commandTypeField]]; valueMapping()[COMMAND_TYPE_FIELD][row[commandTypeField]];
if (!commandType) return null; if (!commandType) return null;

View File

@ -1,78 +1,93 @@
import Command from "../data/command"; import Command from "../data/command";
import Serializer from "../data/serializer"; import Serializer from "../data/serializer";
import Net from "../tools/net";
function RemotesService() { function RemoteService() {
let _commands = [
new Command({
id: 1,
protocol: "samsung",
commandNumber: 1,
device: 7,
commandType: "power",
title: "Power Samsung",
}),
new Command({
id: 2,
protocol: "samsung",
commandNumber: 2,
device: 7,
commandType: "input",
title: "Input Samsung",
}),
new Command({
id: 3,
protocol: "samsung",
commandNumber: 3,
device: 7,
commandType: "volume_up",
title: "Volume Up Samsung",
}),
];
let remotes = []; let remotes = [];
async function getRemotes() { async function getRemotes() {
return [].concat(remotes); let response = await Net.sendRequest({
method: "GET",
url: "/api/remotes",
});
if (response.status !== 200) {
let responseData = JSON.parse(response.data);
throw new Error(responseData.error);
}
let remoteObjects = JSON.parse(response.data);
return Serializer.deserializeRemotes(remoteObjects);
} }
async function createRemote(remote) { async function createRemote(remote) {
let id = Math.random().toString(36).substr(2, 9); let remoteObject = Serializer.serializeRemote(remote);
remote.setId(id); let response = await Net.sendJsonRequest({
remotes.push(remote); method: "POST",
return remote; url: "/api/remotes",
data: remoteObject,
});
if (response.status !== 200) {
let responseData = JSON.parse(response.data);
throw new Error(responseData.error);
}
} }
async function deleteRemote(remoteId) { async function deleteRemote(remoteId) {
let index = remotes.findIndex((remote) => remote.getId() === remoteId); let response = await Net.sendRequest({
if (index >= 0) { method: "DELETE",
remotes.splice(index, 1); url: "/api/remotes/" + remoteId,
});
if (response.status !== 200) {
let responseData = JSON.parse(response.data);
throw new Error(responseData.error);
} }
} }
async function getCommands() { async function getCommands() {
return [].concat(_commands); let response = await Net.sendRequest({
method: "GET",
url: "/api/commands",
});
if (response.status !== 200) {
let responseData = JSON.parse(response.data);
throw new Error(responseData.error);
}
let commandObjects = JSON.parse(response.data);
return Serializer.deserializeCommands(commandObjects);
} }
async function createCommand(command) { async function createCommand(command) {
let id = Math.random().toString(36).substr(2, 9); return createCommands([command]);
command.setId(id);
_commands.push(command);
return command;
} }
async function createCommands(commands) { async function createCommands(commands) {
if (!commands || commands.length === 0) return []; let commandObjects = Serializer.serializeCommands(commands);
commands.forEach((command) => { let response = await Net.sendJsonRequest({
let id = Math.random().toString(36).substr(2, 9); method: "POST",
command.setId(id); url: "/api/commands",
_commands.push(command); data: commandObjects,
}); });
return commands;
if (response.status !== 200) {
let responseData = JSON.parse(response.data);
throw new Error(responseData.error);
}
} }
async function deleteCommand(commandId) { async function deleteCommand(commandId) {
let index = _commands.findIndex((command) => command.getId() === commandId); let response = await Net.sendRequest({
if (index >= 0) { method: "DELETE",
_commands.splice(index, 1); url: "/api/commands/" + commandId,
});
if (response.status !== 200) {
let responseData = JSON.parse(response.data);
throw new Error(responseData.error);
} }
} }
@ -87,6 +102,6 @@ function RemotesService() {
}; };
} }
RemotesService = new RemotesService(); RemoteService = new RemoteService();
export default RemotesService; export default RemoteService;