diff --git a/data/device_database.go b/data/device_database.go index 3c802a7..48d1068 100644 --- a/data/device_database.go +++ b/data/device_database.go @@ -4,7 +4,6 @@ import ( "database/sql" "fmt" "path/filepath" - "time" gonanoid "github.com/matoous/go-nanoid" _ "github.com/mattn/go-sqlite3" @@ -31,11 +30,10 @@ func (db *DeviceDatabase) Initialize() error { return fmt.Errorf("error creating devices table: %s", error) } - _, error = db.Connection.Exec(`CREATE TABLE IF NOT EXISTS sessions ( - token TEXT PRIMARY KEY, - device_id INTEGER, - expiry_date TIMESTAMP, - FOREIGN KEY (device_id) REFERENCES devices(id) + _, error = db.Connection.Exec(`CREATE TABLE IF NOT EXISTS integrations ( + id TEXT PRIMARY KEY, + name TEXT UNIQUE, + token TEXT )`) if error != nil { return fmt.Errorf("error creating device sessions table: %s", error) @@ -55,18 +53,6 @@ func (db *DeviceDatabase) CreateDevice(device_name string, description string) ( return deviceID, err } -func (db *DeviceDatabase) CreateSession(deviceID string, expiryDate time.Time) (string, error) { - sessionToken, err := gonanoid.Nanoid(16) - if err != nil { - return "", err - } - _, err = db.Connection.Exec("INSERT INTO sessions (device_id, token, expiry_date) VALUES (?, ?, ?)", deviceID, sessionToken, expiryDate) - if err != nil { - return "", err - } - return sessionToken, nil -} - func (db *DeviceDatabase) GetDeviceById(id string) (*PlaybackDevice, error) { var device PlaybackDevice err := db.Connection.QueryRow("SELECT id, device_name, description FROM devices WHERE id = ?", id).Scan(&device.ID, &device.Name, &device.Description) @@ -90,24 +76,6 @@ func (db *DeviceDatabase) UpdateDevice(device *PlaybackDevice) error { return err } -func (db *DeviceDatabase) GetSession(sessionToken string) (*DeviceSession, error) { - var session DeviceSession - row := db.Connection.QueryRow("SELECT token, device_id, expiry_date FROM sessions WHERE token = ?", sessionToken) - err := row.Scan(&session.Token, &session.DeviceID, &session.ExpiryDate) - if err != nil { - if err == sql.ErrNoRows { - return nil, nil - } - return nil, err - } - return &session, nil -} - -func (db *DeviceDatabase) DeleteSessionByToken(token string) error { - _, err := db.Connection.Exec("DELETE FROM sessions WHERE token = ?", token) - return err -} - func (db *DeviceDatabase) GetDevices() (*[]PlaybackDevice, error) { var devices []PlaybackDevice @@ -136,6 +104,47 @@ func (db *DeviceDatabase) DeleteDevice(ID string) error { return err } +func (db *DeviceDatabase) CreateIntegration(name string) (string, string, error) { + id, err := gonanoid.Nanoid(10) + if err != nil { + return "", "", err + } + + token, err := gonanoid.Nanoid(16) + if err != nil { + return "", "", err + } + + hashed_token, err := hashPassword(token) + if err != nil { + return "", "", err + } + + _, err = db.Connection.Exec("INSERT INTO integrations (id, name, token) VALUES (?, ?, ?)", id, name, hashed_token) + if err != nil { + return "", "", err + } + return id, token, nil +} + +func (db *DeviceDatabase) GetSession(sessionToken string) (*DeviceSession, error) { + var session DeviceSession + row := db.Connection.QueryRow("SELECT token, device_id, expiry_date FROM sessions WHERE token = ?", sessionToken) + err := row.Scan(&session.Token, &session.DeviceID, &session.ExpiryDate) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + return &session, nil +} + +func (db *DeviceDatabase) DeleteSessionByToken(token string) error { + _, err := db.Connection.Exec("DELETE FROM sessions WHERE token = ?", token) + return err +} + func (db *DeviceDatabase) SetDirectory(directory string) { db.databaseDirectory = directory } diff --git a/management/device_manager.go b/management/device_manager.go index 98f6b23..a3c9132 100644 --- a/management/device_manager.go +++ b/management/device_manager.go @@ -2,10 +2,13 @@ package management import ( d "playback-device-server/data" + + gonanoid "github.com/matoous/go-nanoid" ) type DeviceManager struct { - deviceDatabase *d.DeviceDatabase + deviceDatabase *d.DeviceDatabase + registrationCodes []string } func (um *DeviceManager) Initialize(deviceDatabase *d.DeviceDatabase) error { @@ -82,3 +85,9 @@ func (um *DeviceManager) DeleteDevice(ID string) error { error := um.deviceDatabase.DeleteDevice(ID) return error } + +func (um *DeviceManager) GetRegistrationCode() (string, error) { + code := gonanoid.MustGenerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 6) + um.registrationCodes = append(um.registrationCodes, code) + return code, nil +} diff --git a/server/device_api_handler.go b/server/device_api_handler.go index 878d835..65a0440 100644 --- a/server/device_api_handler.go +++ b/server/device_api_handler.go @@ -15,14 +15,33 @@ type DeviceApiHandler struct { func (r *DeviceApiHandler) Initialize(authenticator *Authenticator) { r.router.Use(authenticator.Authenticate("/api/devices", []string{})) - usersApi := r.router.Group("/api/devices") + devicesApi := r.router.Group("/api/devices") //usersApi.POST("/register", r.handleRegister) //usersApi.POST("/deregister", r.handleDeregister) - usersApi.GET("/session/info", r.handleGetSessionInfo) - usersApi.PUT("/:id", r.handleUpdateConfig) - usersApi.GET("", r.handleGetDevices) - usersApi.POST("", r.handleCreateDevice) - usersApi.DELETE("/:id", r.handleDeleteDevice) + devicesApi.GET("/session/info", r.handleGetSessionInfo) + devicesApi.PUT("/:id", r.handleUpdateConfig) + devicesApi.GET("", r.handleGetDevices) + devicesApi.POST("", r.handleCreateDevice) + devicesApi.DELETE("/:id", r.handleDeleteDevice) + integrationsApi := r.router.Group("/api/integrations") + integrationsApi.GET("/register", r.handleIntegrationRegistration) +} + +func (r *DeviceApiHandler) handleIntegrationRegistration(context echo.Context) error { + code, error := r.deviceManager.GetRegistrationCode() + + if error != nil { + SendError(500, context, fmt.Sprintf("failed to get registration code: %s", error)) + return error + } + + response := struct { + Code string `json:"code"` + }{ + Code: code, + } + + return context.JSON(200, response) } //func (r DeviceApiHandler) handleRegister(context echo.Context) error { diff --git a/www/src/modals/modal-handler.js b/www/src/modals/modal-handler.js index 0ccd7db..2f7f35a 100644 --- a/www/src/modals/modal-handler.js +++ b/www/src/modals/modal-handler.js @@ -19,12 +19,26 @@ function ModalHandler() { function setModalId(modalId) { _modalId = modalId; } + + function onHidden(callback) { + _ref.addEventListener('hidden.bs.modal', () => { + callback(); + }); + } + + function onShow(callback) { + _ref.addEventListener('show.bs.modal', () => { + callback(); + }); + } return { setRef, show, hide, setModalId, + onHidden, + onShow, }; } diff --git a/www/src/modals/modal-registry.jsx b/www/src/modals/modal-registry.jsx index eb531c1..84f9829 100644 --- a/www/src/modals/modal-registry.jsx +++ b/www/src/modals/modal-registry.jsx @@ -4,6 +4,7 @@ import CreateUserModal from "./create-user-modal.jsx"; import DeleteUserModal from "./delete-user-modal.jsx"; import UserSettingsModal from "./user-settings-modal.jsx"; import CreateDeviceModal from "./create-device-modal.jsx"; +import ShowRegistrationCodeModal from "./show-registration-code-modal.jsx"; const ModalRegistry = (function () { const modals = [ @@ -27,6 +28,11 @@ const ModalRegistry = (function () { component: CreateDeviceModal, ref: null, }, + { + id: "showRegistrationCodeModal", + component: ShowRegistrationCodeModal, + ref: null, + }, ]; function getModals(props) { diff --git a/www/src/modals/show-registration-code-modal.jsx b/www/src/modals/show-registration-code-modal.jsx new file mode 100644 index 0000000..c983fae --- /dev/null +++ b/www/src/modals/show-registration-code-modal.jsx @@ -0,0 +1,66 @@ +import { + createEffect, + createSignal, + Show, + createResource, + onMount, +} from "solid-js"; +import Modal from "./modal.jsx"; +import ModalHandler from "./modal-handler.js"; +import DeviceService from "../services/device-service.js"; + +function ShowRegistrationCodeModal(props) { + const [error, setError] = createSignal(""); + const [code, { mutate, refetch }] = createResource(async () => + DeviceService.getRegistrationCode() + ); + + onMount(() => { + ShowRegistrationCodeModal.Handler.onHidden(() => mutate(null)); + ShowRegistrationCodeModal.Handler.onShow(() => refetch()); + }); + + function handleClose() { + ShowRegistrationCodeModal.Handler.hide(); + } + + return ( + + + + + ); +} + +ShowRegistrationCodeModal.Handler = new ModalHandler(); + +export default ShowRegistrationCodeModal; diff --git a/www/src/services/device-service.js b/www/src/services/device-service.js index 415f299..c6b64d5 100644 --- a/www/src/services/device-service.js +++ b/www/src/services/device-service.js @@ -63,11 +63,27 @@ function DeviceService() { } } + async function getRegistrationCode() { + let response = await Net.sendRequest({ + method: "GET", + url: "/api/integrations/register", + }); + + if (response.status !== 200) { + let responseData = JSON.parse(response.data); + throw new Error(responseData.error); + } + + let codeObject = JSON.parse(response.data); + return codeObject.code; + } + return { getDevices, createDevice, updateDevice, deleteDevice, + getRegistrationCode, }; } diff --git a/www/src/views/devices-view.jsx b/www/src/views/devices-view.jsx index c095137..0f7b108 100644 --- a/www/src/views/devices-view.jsx +++ b/www/src/views/devices-view.jsx @@ -1,8 +1,9 @@ -import { createSignal, onMount } from "solid-js"; +import { createSignal, onMount, Show } from "solid-js"; import List from "../modules/list"; import CreateDeviceModal from "../modals/create-device-modal"; import DeviceService from "../services/device-service"; +import ShowRegistrationCodeModal from "../modals/show-registration-code-modal"; function DevicesView(props) { const DEVICES_LIST_TYPE = "devices"; @@ -27,6 +28,10 @@ function DevicesView(props) { let devices = await DeviceService.getDevices(); setDevices(devices); } + + function handleRegisterIntegration() { + ShowRegistrationCodeModal.Handler.show(); + } return (
- + + + + + +
- {}} - items={devices().map((device) => ({ - id: { - html: {device.getId()}, - }, - name: { - text: device.getName(), - }, - description: { - text: device.getDescription(), - }, - device, - }))} - class={"flex-fill"} - columns={[ - { - id: "id", - name: "id", - width: 6, - }, - { - id: "name", - name: "Name", - width: 10, - }, - { - id: "description", - name: "Description", - }, - ]} - > + + {}} + items={devices().map((device) => ({ + id: { + html: {device.getId()}, + }, + name: { + text: device.getName(), + }, + description: { + text: device.getDescription(), + }, + device, + }))} + class={"flex-fill"} + columns={[ + { + id: "id", + name: "id", + width: 6, + }, + { + id: "name", + name: "Name", + width: 10, + }, + { + id: "description", + name: "Description", + }, + ]} + > + + + {}} + items={[].map((integration) => ({ + id: { + html: {integration.getId()}, + }, + name: { + text: integration.getName(), + }, + integration: integration, + }))} + class={"flex-fill"} + columns={[ + { + id: "id", + name: "id", + width: 6, + }, + { + id: "name", + name: "Name", + }, + ]} + > + ); }