feat: add create integration with code

This commit is contained in:
Fritz Heiden 2025-03-18 10:20:42 +01:00
parent d869e8d119
commit 54a2b7418a
6 changed files with 202 additions and 13 deletions

View File

@ -104,27 +104,31 @@ func (db *DeviceDatabase) DeleteDevice(ID string) error {
return err
}
func (db *DeviceDatabase) CreateIntegration(name string) (string, string, error) {
func (db *DeviceDatabase) CreateIntegration(name, token string) (string, error) {
id, err := gonanoid.Nanoid(10)
if err != nil {
return "", "", err
}
token, err := gonanoid.Nanoid(16)
if err != nil {
return "", "", err
return "", err
}
hashed_token, err := hashPassword(token)
if err != nil {
return "", "", err
return "", err
}
_, err = db.Connection.Exec("INSERT INTO integrations (id, name, token) VALUES (?, ?, ?)", id, name, hashed_token)
if err != nil {
return "", "", err
return "", err
}
return id, token, nil
return id, nil
}
func (db *DeviceDatabase) IntegrationNameExists(name string) (bool, error) {
var exists bool
err := db.Connection.QueryRow("SELECT EXISTS(SELECT 1 FROM integrations WHERE name = ?)", name).Scan(&exists)
if err != nil {
return false, err
}
return exists, nil
}
func (db *DeviceDatabase) GetSession(sessionToken string) (*DeviceSession, error) {

7
data/integration.go Normal file
View File

@ -0,0 +1,7 @@
package data
type Integration struct {
ID string `json:"id"`
Name string `json:"name"`
Token string `json:"token"`
}

View File

@ -1,7 +1,9 @@
package management
import (
"fmt"
d "playback-device-server/data"
"slices"
gonanoid "github.com/matoous/go-nanoid"
)
@ -87,7 +89,68 @@ func (um *DeviceManager) DeleteDevice(ID string) error {
}
func (um *DeviceManager) GetRegistrationCode() (string, error) {
code := gonanoid.MustGenerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 6)
um.registrationCodes = append(um.registrationCodes, code)
code := um.createCode()
return code, nil
}
func (um *DeviceManager) CreateIntegration(name, code string) (d.Integration, error) {
exists, err := um.deviceDatabase.IntegrationNameExists(name)
if err != nil {
return d.Integration{}, err
}
if exists {
return d.Integration{}, fmt.Errorf("Integration name already exists")
}
if err := um.redeemCode(code); err != nil {
return d.Integration{}, err
}
token, err := gonanoid.Nanoid(16)
if err != nil {
return d.Integration{}, err
}
id, err := um.deviceDatabase.CreateIntegration(name, token)
if err != nil {
return d.Integration{}, err
}
integration := d.Integration{
ID: id,
Name: name,
Token: token,
}
return integration, nil
}
func (um *DeviceManager) checkCode(code string) bool {
for _, c := range um.registrationCodes {
if c == code {
return true
}
}
return false
}
func (um *DeviceManager) createCode() string {
code := gonanoid.MustGenerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 6)
um.registrationCodes = append(um.registrationCodes, code)
return code
}
func (um *DeviceManager) redeemCode(code string) error {
if !um.checkCode(code) {
return fmt.Errorf("Invalid registration code")
}
for i, c := range um.registrationCodes {
if c == code {
um.registrationCodes = slices.Delete(um.registrationCodes, i, i+1)
break
}
}
return nil
}

View File

@ -25,6 +25,7 @@ func (r *DeviceApiHandler) Initialize(authenticator *Authenticator) {
devicesApi.DELETE("/:id", r.handleDeleteDevice)
integrationsApi := r.router.Group("/api/integrations")
integrationsApi.GET("/register", r.handleIntegrationRegistration)
integrationsApi.POST("", r.handleCreateIntegration)
}
func (r *DeviceApiHandler) handleIntegrationRegistration(context echo.Context) error {
@ -44,6 +45,28 @@ func (r *DeviceApiHandler) handleIntegrationRegistration(context echo.Context) e
return context.JSON(200, response)
}
func (r *DeviceApiHandler) handleCreateIntegration(context echo.Context) error {
var data struct {
Name string `json:"name"`
Code string `json:"code"`
}
error := context.Bind(&data)
if error != nil {
SendError(500, context, fmt.Sprintf("Failed to create integration: %s", error))
return error
}
integration, error := r.deviceManager.CreateIntegration(data.Name, data.Code)
if error != nil {
SendError(400, context, fmt.Sprintf("Failed to create integration: %s", error))
return error
}
return context.JSON(200, integration)
}
//func (r DeviceApiHandler) handleRegister(context echo.Context) error {
// var registrationData struct {
// Code string `json:"code"`

92
www/integration.html Normal file
View File

@ -0,0 +1,92 @@
<!DOCTYPE html>
<html lang="de" data-bs-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Integration</title>
</head>
<body>
<div>
<div style="padding: 1em; border: 1px solid #444">
<p style="font-weight: bold">Register</p>
<label for="register-name">Name:</label>
<input type="text" id="register-name" name="register-name" required />
<label for="register-code">Code:</label>
<input type="text" id="register-code" name="register-code" required />
<button id="register-button">Register</button>
</div>
</div>
<script type="module">
let registerButton = document.getElementById("register-button");
registerButton.addEventListener("click", register);
async function register() {
let name = document.getElementById("register-name").value;
let code = document.getElementById("register-code").value;
console.log("Registering with name:", name, "and code:", code);
let response = await sendJsonRequest({
method: "POST",
url: "/api/integrations",
data: { name, code },
});
if (response.status === 200) {
console.log("Registration successful");
console.log(JSON.parse(response.data));
} else {
console.error("Registration failed with status code:", response.status);
console.log(JSON.parse(response.data));
}
}
async function sendJsonRequest({ method, url, data, headers }) {
if (typeof data !== "string") {
data = JSON.stringify(data);
}
if (!headers) headers = {};
headers["Content-Type"] = "application/json";
return sendRequest({ method, url, data, headers });
}
async function sendRequest({ method, url, data, headers, queryParams }) {
if (queryParams) {
let params = new URLSearchParams();
for (var queryParam in queryParams) {
params.append(queryParam, queryParams[queryParam]);
}
let queryString = params.toString();
queryString = queryString ? "?" + queryString : "";
url = url + queryString;
}
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.addEventListener("load", function () {
resolve({ status: xhr.status, data: xhr.response });
});
xhr.addEventListener("readystatechange", function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
resolve({ status: xhr.status, data: xhr.response });
}
});
xhr.addEventListener("error", function () {
reject(xhr.response);
});
xhr.open(method, url, true);
for (var header in headers) {
xhr.setRequestHeader(header, headers[header]);
}
try {
xhr.send(data);
} catch (error) {
reject(error);
}
});
}
</script>
</body>
</html>

View File

@ -5,7 +5,7 @@
"scripts": {
"start": "vite",
"dev": "vite",
"build": "rm -rf dist; vite build && cp -r src/lib dist",
"build": "rm -rf dist; vite build && cp -r src/lib dist; cp integration.html dist",
"serve": "vite preview"
},
"license": "MIT",