Compare commits

...

3 Commits

9 changed files with 235 additions and 42 deletions

View File

@ -122,6 +122,15 @@ func (db *DeviceDatabase) CreateIntegration(name, token string) (string, error)
return id, nil return id, nil
} }
func (db *DeviceDatabase) GetIntegration(id string) (*Integration, error) {
var integration Integration
err := db.Connection.QueryRow("SELECT id, name FROM integrations WHERE id = ?", id).Scan(&integration.ID, &integration.Name)
if err != nil {
return nil, err
}
return &integration, nil
}
func (db *DeviceDatabase) GetIntegrations() ([]Integration, error) { func (db *DeviceDatabase) GetIntegrations() ([]Integration, error) {
var integrations []Integration var integrations []Integration
@ -145,6 +154,11 @@ func (db *DeviceDatabase) GetIntegrations() ([]Integration, error) {
return integrations, nil return integrations, nil
} }
func (db *DeviceDatabase) DeleteIntegration(id string) error {
_, err := db.Connection.Exec("DELETE FROM integrations WHERE id = ?", id)
return err
}
func (db *DeviceDatabase) IntegrationNameExists(name string) (bool, error) { func (db *DeviceDatabase) IntegrationNameExists(name string) (bool, error) {
var exists bool var exists bool
err := db.Connection.QueryRow("SELECT EXISTS(SELECT 1 FROM integrations WHERE name = ?)", name).Scan(&exists) err := db.Connection.QueryRow("SELECT EXISTS(SELECT 1 FROM integrations WHERE name = ?)", name).Scan(&exists)

View File

@ -13,35 +13,35 @@ type DeviceManager struct {
registrationCodes []string registrationCodes []string
} }
func (um *DeviceManager) Initialize(deviceDatabase *d.DeviceDatabase) error { func (dm *DeviceManager) Initialize(deviceDatabase *d.DeviceDatabase) error {
um.deviceDatabase = deviceDatabase dm.deviceDatabase = deviceDatabase
return nil return nil
} }
func (um *DeviceManager) CreateDevice(device *d.PlaybackDevice) (string, error) { func (dm *DeviceManager) CreateDevice(device *d.PlaybackDevice) (string, error) {
id, error := um.deviceDatabase.CreateDevice(device.Name, device.Description) id, error := dm.deviceDatabase.CreateDevice(device.Name, device.Description)
if error != nil { if error != nil {
return "", error return "", error
} }
return id, nil return id, nil
} }
func (um *DeviceManager) DeviceIdExists(id string) (bool, error) { func (dm *DeviceManager) DeviceIdExists(id string) (bool, error) {
exists, error := um.deviceDatabase.DeviceIdExists(id) exists, error := dm.deviceDatabase.DeviceIdExists(id)
if error != nil { if error != nil {
return false, error return false, error
} }
return exists, nil return exists, nil
} }
//func (um *DeviceManager) Register(code string) (string, error) { //func (dm *DeviceManager) Register(code string) (string, error) {
// device, error := um.GetDeviceByCode(code) // device, error := dm.GetDeviceByCode(code)
// if error != nil { // if error != nil {
// return "", error // return "", error
// } // }
// //
// expiryDate := time.Now().AddDate(0, 0, 30) // expiryDate := time.Now().AddDate(0, 0, 30)
// token, error := um.deviceDatabase.CreateSession(device.ID, expiryDate) // token, error := dm.deviceDatabase.CreateSession(device.ID, expiryDate)
// if error != nil { // if error != nil {
// return "", error // return "", error
// } // }
@ -49,52 +49,52 @@ func (um *DeviceManager) DeviceIdExists(id string) (bool, error) {
// return token, nil // return token, nil
//} //}
func (um *DeviceManager) GetSession(sessionToken string) (*d.DeviceSession, error) { func (dm *DeviceManager) GetSession(sessionToken string) (*d.DeviceSession, error) {
session, error := um.deviceDatabase.GetSession(sessionToken) session, error := dm.deviceDatabase.GetSession(sessionToken)
if error != nil { if error != nil {
return nil, error return nil, error
} }
return session, nil return session, nil
} }
func (um *DeviceManager) GetDeviceById(id string) (*d.PlaybackDevice, error) { func (dm *DeviceManager) GetDeviceById(id string) (*d.PlaybackDevice, error) {
device, error := um.deviceDatabase.GetDeviceById(id) device, error := dm.deviceDatabase.GetDeviceById(id)
if error != nil { if error != nil {
return nil, error return nil, error
} }
return device, nil return device, nil
} }
func (um *DeviceManager) UpdateDevice(device *d.PlaybackDevice) error { func (dm *DeviceManager) UpdateDevice(device *d.PlaybackDevice) error {
error := um.deviceDatabase.UpdateDevice(device) error := dm.deviceDatabase.UpdateDevice(device)
return error return error
} }
func (um *DeviceManager) DeleteSession(token string) error { func (dm *DeviceManager) DeleteSession(token string) error {
error := um.deviceDatabase.DeleteSessionByToken(token) error := dm.deviceDatabase.DeleteSessionByToken(token)
if error != nil { if error != nil {
return error return error
} }
return nil return nil
} }
func (um *DeviceManager) GetDevices() (*[]d.PlaybackDevice, error) { func (dm *DeviceManager) GetDevices() (*[]d.PlaybackDevice, error) {
users, error := um.deviceDatabase.GetDevices() users, error := dm.deviceDatabase.GetDevices()
return users, error return users, error
} }
func (um *DeviceManager) DeleteDevice(ID string) error { func (dm *DeviceManager) DeleteDevice(ID string) error {
error := um.deviceDatabase.DeleteDevice(ID) error := dm.deviceDatabase.DeleteDevice(ID)
return error return error
} }
func (um *DeviceManager) GetRegistrationCode() (string, error) { func (dm *DeviceManager) GetRegistrationCode() (string, error) {
code := um.createCode() code := dm.createCode()
return code, nil return code, nil
} }
func (um *DeviceManager) CreateIntegration(name, code string) (d.Integration, error) { func (dm *DeviceManager) CreateIntegration(name, code string) (d.Integration, error) {
exists, err := um.deviceDatabase.IntegrationNameExists(name) exists, err := dm.deviceDatabase.IntegrationNameExists(name)
if err != nil { if err != nil {
return d.Integration{}, err return d.Integration{}, err
} }
@ -102,7 +102,7 @@ func (um *DeviceManager) CreateIntegration(name, code string) (d.Integration, er
return d.Integration{}, fmt.Errorf("Integration name already exists") return d.Integration{}, fmt.Errorf("Integration name already exists")
} }
if err := um.redeemCode(code); err != nil { if err := dm.redeemCode(code); err != nil {
return d.Integration{}, err return d.Integration{}, err
} }
@ -111,7 +111,7 @@ func (um *DeviceManager) CreateIntegration(name, code string) (d.Integration, er
return d.Integration{}, err return d.Integration{}, err
} }
id, err := um.deviceDatabase.CreateIntegration(name, token) id, err := dm.deviceDatabase.CreateIntegration(name, token)
if err != nil { if err != nil {
return d.Integration{}, err return d.Integration{}, err
} }
@ -125,16 +125,29 @@ func (um *DeviceManager) CreateIntegration(name, code string) (d.Integration, er
return integration, nil return integration, nil
} }
func (um *DeviceManager) GetIntegrations() ([]d.Integration, error) { func (dm *DeviceManager) GetIntegration(id string) (*d.Integration, error) {
integrations, err := um.deviceDatabase.GetIntegrations() integration, err := dm.deviceDatabase.GetIntegration(id)
if err != nil {
return nil, err
}
return integration, nil
}
func (dm *DeviceManager) GetIntegrations() ([]d.Integration, error) {
integrations, err := dm.deviceDatabase.GetIntegrations()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return integrations, nil return integrations, nil
} }
func (um *DeviceManager) checkCode(code string) bool { func (dm *DeviceManager) DeleteIntegration(id string) error {
for _, c := range um.registrationCodes { error := dm.deviceDatabase.DeleteIntegration(id)
return error
}
func (dm *DeviceManager) checkCode(code string) bool {
for _, c := range dm.registrationCodes {
if c == code { if c == code {
return true return true
} }
@ -142,20 +155,20 @@ func (um *DeviceManager) checkCode(code string) bool {
return false return false
} }
func (um *DeviceManager) createCode() string { func (dm *DeviceManager) createCode() string {
code := gonanoid.MustGenerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 6) code := gonanoid.MustGenerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 6)
um.registrationCodes = append(um.registrationCodes, code) dm.registrationCodes = append(dm.registrationCodes, code)
return code return code
} }
func (um *DeviceManager) redeemCode(code string) error { func (dm *DeviceManager) redeemCode(code string) error {
if !um.checkCode(code) { if !dm.checkCode(code) {
return fmt.Errorf("Invalid registration code") return fmt.Errorf("Invalid registration code")
} }
for i, c := range um.registrationCodes { for i, c := range dm.registrationCodes {
if c == code { if c == code {
um.registrationCodes = slices.Delete(um.registrationCodes, i, i+1) dm.registrationCodes = slices.Delete(dm.registrationCodes, i, i+1)
break break
} }
} }

View File

@ -27,6 +27,8 @@ func (r *DeviceApiHandler) Initialize(authenticator *Authenticator) {
integrationsApi.GET("/register", r.handleIntegrationRegistration) integrationsApi.GET("/register", r.handleIntegrationRegistration)
integrationsApi.POST("", r.handleCreateIntegration) integrationsApi.POST("", r.handleCreateIntegration)
integrationsApi.GET("", r.handleGetIntegrations) integrationsApi.GET("", r.handleGetIntegrations)
integrationsApi.GET("/:id", r.handleGetIntegration)
integrationsApi.DELETE("/:id", r.handleDeleteIntegration)
} }
func (r *DeviceApiHandler) handleIntegrationRegistration(context echo.Context) error { func (r *DeviceApiHandler) handleIntegrationRegistration(context echo.Context) error {
@ -65,7 +67,38 @@ func (r *DeviceApiHandler) handleCreateIntegration(context echo.Context) error {
return error return error
} }
return context.JSON(200, integration) integrationData := struct {
ID string `json:"id"`
Name string `json:"name"`
Token string `json:"token"`
}{
ID: integration.ID,
Name: integration.Name,
Token: integration.Token,
}
return context.JSON(200, integrationData)
}
func (r *DeviceApiHandler) handleGetIntegration(context echo.Context) error {
id := context.Param("id")
integration, error := r.deviceManager.GetIntegration(id)
if error != nil {
SendError(500, context, fmt.Sprintf("Failed to get integration: %s", error))
return error
}
integrationData := struct {
ID string `json:"id"`
Name string `json:"name"`
}{
ID: integration.ID,
Name: integration.Name,
}
return context.JSON(200, integrationData)
} }
func (r *DeviceApiHandler) handleGetIntegrations(context echo.Context) error { func (r *DeviceApiHandler) handleGetIntegrations(context echo.Context) error {
@ -79,6 +112,19 @@ func (r *DeviceApiHandler) handleGetIntegrations(context echo.Context) error {
return context.JSON(200, integrations) return context.JSON(200, integrations)
} }
func (r *DeviceApiHandler) handleDeleteIntegration(context echo.Context) error {
id := context.Param("id")
error := r.deviceManager.DeleteIntegration(id)
if error != nil {
SendError(500, context, fmt.Sprintf("Failed to delete integration: %s", error))
return error
}
return context.JSON(200, "")
}
//func (r DeviceApiHandler) handleRegister(context echo.Context) error { //func (r DeviceApiHandler) handleRegister(context echo.Context) error {
// var registrationData struct { // var registrationData struct {
// Code string `json:"code"` // Code string `json:"code"`

View File

@ -1,4 +1,4 @@
function Integration({ id, name }) { function Integration({ id, name } = {}) {
let _id = id; let _id = id;
let _name = name; let _name = name;

View File

@ -25,6 +25,7 @@ const Serializer = (function () {
} }
function deserializeIntegrations(objects) { function deserializeIntegrations(objects) {
if (!objects) return [];
return objects.map((object) => deserializeIntegration(object)); return objects.map((object) => deserializeIntegration(object));
} }

View File

@ -0,0 +1,69 @@
import { createEffect, createSignal } from "solid-js";
import Modal from "./modal.jsx";
import UserService from "../services/user-service.js";
import EventEmitter from "../tools/event-emitter.js";
import User from "../data/user.js";
import ValidatedTextInput from "../modules/validated-text-input.jsx";
import ModalHandler from "./modal-handler.js";
import Integration from "../data/integration.js";
import DeviceService from "../services/device-service.js";
const [integration, setIntegration] = createSignal(new Integration());
const eventEmitter = new EventEmitter();
const INTEGRATION_DELETED_EVENT = "success";
function DeleteIntegrationModal(props) {
const [error, setError] = createSignal("");
async function handleDeleteIntegration() {
try {
await DeviceService.deleteIntegration(integration().getId());
} catch (e) {
setError(e.message);
throw e;
}
DeleteIntegrationModal.Handler.hide();
eventEmitter.dispatchEvent(INTEGRATION_DELETED_EVENT, integration);
}
return (
<Modal
ref={props.ref}
id="deleteIntegrationModal"
modalTitle="Delete integration"
centered={true}
>
<div class="modal-body">
<Show when={error() !== ""}>
<div class="alert alert-danger" role="alert">
{error()}
</div>
</Show>
<div class="mb-3 row">
<span>
Do you really want to delete the integration {integration().getName()}?
</span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Cancel
</button>
<button
type="button"
onClick={handleDeleteIntegration}
class="btn btn-danger"
>
Delete
</button>
</div>
</Modal>
);
}
DeleteIntegrationModal.Handler = new ModalHandler();
DeleteIntegrationModal.setIntegration = setIntegration;
DeleteIntegrationModal.onIntegrationDeleted = (callback) =>
eventEmitter.on(INTEGRATION_DELETED_EVENT, callback);
export default DeleteIntegrationModal;

View File

@ -5,6 +5,7 @@ import DeleteUserModal from "./delete-user-modal.jsx";
import UserSettingsModal from "./user-settings-modal.jsx"; import UserSettingsModal from "./user-settings-modal.jsx";
import CreateDeviceModal from "./create-device-modal.jsx"; import CreateDeviceModal from "./create-device-modal.jsx";
import ShowRegistrationCodeModal from "./show-registration-code-modal.jsx"; import ShowRegistrationCodeModal from "./show-registration-code-modal.jsx";
import DeleteIntegrationModal from "./delete-integration-modal.jsx";
const ModalRegistry = (function () { const ModalRegistry = (function () {
const modals = [ const modals = [
@ -33,6 +34,11 @@ const ModalRegistry = (function () {
component: ShowRegistrationCodeModal, component: ShowRegistrationCodeModal,
ref: null, ref: null,
}, },
{
id: "deleteIntegrationModal",
component: DeleteIntegrationModal,
ref: null,
},
]; ];
function getModals(props) { function getModals(props) {

View File

@ -94,6 +94,18 @@ function DeviceService() {
return integrations; return integrations;
} }
async function deleteIntegration(id) {
let response = await Net.sendJsonRequest({
method: "DELETE",
url: "/api/integrations/" + id,
});
if (response.status !== 200) {
let responseData = JSON.parse(response.data);
throw new Error(responseData.error);
}
}
return { return {
getDevices, getDevices,
createDevice, createDevice,
@ -101,6 +113,7 @@ function DeviceService() {
deleteDevice, deleteDevice,
getRegistrationCode, getRegistrationCode,
getIntegrations, getIntegrations,
deleteIntegration,
}; };
} }

View File

@ -4,6 +4,7 @@ import List from "../modules/list";
import CreateDeviceModal from "../modals/create-device-modal"; import CreateDeviceModal from "../modals/create-device-modal";
import DeviceService from "../services/device-service"; import DeviceService from "../services/device-service";
import ShowRegistrationCodeModal from "../modals/show-registration-code-modal"; import ShowRegistrationCodeModal from "../modals/show-registration-code-modal";
import DeleteIntegrationModal from "../modals/delete-integration-modal";
function DevicesView(props) { function DevicesView(props) {
const DEVICES_LIST_TYPE = "devices"; const DEVICES_LIST_TYPE = "devices";
@ -11,7 +12,9 @@ function DevicesView(props) {
const [listType, setListType] = createSignal(DEVICES_LIST_TYPE); const [listType, setListType] = createSignal(DEVICES_LIST_TYPE);
const [devices, setDevices] = createSignal([]); const [devices, setDevices] = createSignal([]);
const [integrations] = createResource(DeviceService.getIntegrations); const [integrations, { refetch: refetchIntegrations }] = createResource(
DeviceService.getIntegrations
);
CreateDeviceModal.onDeviceCreated(() => { CreateDeviceModal.onDeviceCreated(() => {
handleRefreshDevices(); handleRefreshDevices();
@ -19,6 +22,9 @@ function DevicesView(props) {
onMount(() => { onMount(() => {
handleRefreshDevices(); handleRefreshDevices();
DeleteIntegrationModal.onIntegrationDeleted(() => {
refetchIntegrations();
});
}); });
function handleNewDevice() { function handleNewDevice() {
@ -34,6 +40,11 @@ function DevicesView(props) {
ShowRegistrationCodeModal.Handler.show(); ShowRegistrationCodeModal.Handler.show();
} }
function handleDeleteIntegration(integration) {
DeleteIntegrationModal.setIntegration(integration);
DeleteIntegrationModal.Handler.show();
}
return ( return (
<div <div
class={ class={
@ -76,7 +87,10 @@ function DevicesView(props) {
</button> </button>
</Show> </Show>
<Show when={listType() === INTEGRATION_LIST_TYPE}> <Show when={listType() === INTEGRATION_LIST_TYPE}>
<button class="btn btn-dark me-2 mb-3" onClick={handleRegisterIntegration}> <button
class="btn btn-dark me-2 mb-3"
onClick={handleRegisterIntegration}
>
<i class="bi bi-plus-square me-2"></i> <i class="bi bi-plus-square me-2"></i>
Register Register
</button> </button>
@ -127,6 +141,18 @@ function DevicesView(props) {
name: { name: {
text: integration.getName(), text: integration.getName(),
}, },
options: {
html: (
<>
<button
class="btn btn-outline-secondary me-2"
onClick={() => handleDeleteIntegration(integration)}
>
<i class="bi bi-trash-fill"></i>
</button>
</>
),
},
integration: integration, integration: integration,
}))} }))}
class={"flex-fill"} class={"flex-fill"}
@ -140,6 +166,11 @@ function DevicesView(props) {
id: "name", id: "name",
name: "Name", name: "Name",
}, },
{
id: "options",
name: "",
width: 4,
},
]} ]}
></List> ></List>
</Show> </Show>