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/ 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() }