lockserver/httpserver/codes.go

127 lines
3.7 KiB
Go

package httpserver
import (
"database/sql"
"errors"
"fmt"
"math/rand"
"strings"
"git.janky.solutions/finn/lockserver/config"
"git.janky.solutions/finn/lockserver/db"
"git.janky.solutions/finn/lockserver/zwavejs"
"github.com/labstack/echo/v4"
"github.com/sirupsen/logrus"
)
func addCode(c echo.Context) error {
// name := c.FormValue("name")
code := c.FormValue("code")
queries, dbc, err := db.Get()
if err != nil {
return err
}
defer dbc.Close()
ctx := c.Request().Context()
// generate a code that isn't used on any lock or if the user
// supplied a code, check if it's already used on any lock and
// error out if it is
noCodeSupplied := code == "" // if no code is supplied, we must generate one
for {
if noCodeSupplied {
code = generateCode()
logrus.WithField("code", code).Debug("generated code")
}
row, err := queries.GetLockCodesByCode(ctx, code)
if err != nil {
return fmt.Errorf("error checking if code was already in use: %v", err)
}
if len(row) == 0 {
break
}
if noCodeSupplied {
continue
}
return fmt.Errorf("requested code (%s) is already in use on some or all locks", code)
}
locks, err := queries.GetLocks(ctx)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return errors.New("no locks currently registered, cannot add code")
}
return fmt.Errorf("error getting locks: %v", err)
}
client := c.Get(contextKeyZWaveClient).(*zwavejs.Client)
for _, lock := range locks {
slot, err := queries.GetEmptySlot(ctx, lock.ID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("no empty code slots found on lock %s (ZWaveDeviceID=%d ID=%d)", lock.Name, lock.ZwaveDeviceID, lock.ID)
}
return fmt.Errorf("error looking for empty code slot on lock %s (ZWaveDeviceID=%d ID=%d): %v", lock.Name, lock.ZwaveDeviceID, lock.ID, err)
}
logrus.WithFields(logrus.Fields{"lock": lock.ID, "code": code}).Debug("pushing code to lock")
// send the code to the lock
// sample from https://github.com/FutureTense/keymaster/blob/f4f1046bddb7901cbd3ce7820886be1ff7895fe7/tests/test_services.py#L88
//
// {
// "ccVersion": 1,
// "commandClassName": "User Code",
// "commandClass": 99,
// "endpoint": 0,
// "property": "userCode",
// "propertyName": "userCode",
// "propertyKey": 1,
// "propertyKeyName": "1",
// "metadata": {
// "type": "string",
// "readable": True,
// "writeable": True,
// "minLength": 4,
// "maxLength": 10,
// "label": "User Code (1)",
// },
// "value": "123456",
// }
err = client.SetNodeValue(ctx, int(lock.ZwaveDeviceID), zwavejs.NodeValue{
CCVersion: 1,
CommandClassName: zwavejs.CommandClassNameUserCode,
CommandClass: zwavejs.CommandClassUserCode,
Endpoint: 0,
Property: zwavejs.AnyType{Type: zwavejs.AnyTypeString, String: string(zwavejs.PropertyUserCode)},
PropertyName: zwavejs.AnyType{Type: zwavejs.AnyTypeString, String: string(zwavejs.PropertyUserCode)},
PropertyKey: zwavejs.AnyType{Type: zwavejs.AnyTypeInt, Int: int(slot.ID)},
}, zwavejs.AnyType{Type: zwavejs.AnyTypeString, String: code})
if err != nil {
return fmt.Errorf("error pushing code to lock %s (ZWaveDeviceID=%d ID=%d): %v", lock.Name, lock.ZwaveDeviceID, lock.ID, err)
}
// set the code on the lock
// set the code in the db
}
// add row to user_codes
// add row to user_code_slots
// redirect to /user-codes/<code id>
return errors.New("not yet implemented")
}
var codeCharSet = "0123456789"
func generateCode() string {
var builder strings.Builder
for i := 0; i < config.C.GeneratedCodeLength; i++ {
builder.WriteByte(codeCharSet[rand.Int31n(int32(len(codeCharSet)))])
}
return builder.String()
}