Initial commit
Connects to zwave-js, syncs all locks and codeslots with database, and records an event log. No support for updating code slots.
This commit is contained in:
commit
054008eb1f
20 changed files with 1165 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
lockserver.db
|
38
cmd/lockserver/main.go
Normal file
38
cmd/lockserver/main.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"git.janky.solutions/finn/lockserver/config"
|
||||
"git.janky.solutions/finn/lockserver/db"
|
||||
"git.janky.solutions/finn/lockserver/zwavejs"
|
||||
)
|
||||
|
||||
func main() {
|
||||
run()
|
||||
}
|
||||
|
||||
func run() {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer cancel()
|
||||
|
||||
if err := db.Migrate(); err != nil {
|
||||
logrus.WithError(err).Fatal("error migrating db")
|
||||
}
|
||||
|
||||
zwaveClient, err := zwavejs.New(ctx, config.C.ZWaveJSServer)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("error initializing ZWaveJS connection")
|
||||
}
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
if err := zwaveClient.Shutdown(); err != nil {
|
||||
logrus.WithError(err).Error("error shutting down ZWaveJS client")
|
||||
}
|
||||
}
|
11
config/config.go
Normal file
11
config/config.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package config
|
||||
|
||||
type Config struct {
|
||||
ZWaveJSServer string
|
||||
Database string
|
||||
}
|
||||
|
||||
var C = Config{
|
||||
ZWaveJSServer: "ws://home-assistant:3000",
|
||||
Database: "lockserver.db",
|
||||
}
|
31
db/db.go
Normal file
31
db/db.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.20.0
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
|
||||
PrepareContext(context.Context, string) (*sql.Stmt, error)
|
||||
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
|
||||
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||
}
|
||||
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
db DBTX
|
||||
}
|
||||
|
||||
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
}
|
||||
}
|
53
db/helpers.go
Normal file
53
db/helpers.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
goose "github.com/pressly/goose/v3"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"git.janky.solutions/finn/lockserver/config"
|
||||
)
|
||||
|
||||
func Get() (*Queries, *sql.DB, error) {
|
||||
db, err := sql.Open("sqlite3", config.C.Database)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return New(db), db, nil
|
||||
}
|
||||
|
||||
//go:embed migrations
|
||||
var migrations embed.FS
|
||||
|
||||
func Migrate() error {
|
||||
logrus.WithField("dbfile", config.C.Database).Info("migrating database")
|
||||
|
||||
_, conn, err := Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
goose.SetBaseFS(migrations)
|
||||
|
||||
if err := goose.SetDialect("sqlite3"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := goose.Up(conn, "migrations"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NullString(s string) sql.NullString {
|
||||
return sql.NullString{
|
||||
Valid: s != "",
|
||||
String: s,
|
||||
}
|
||||
}
|
88
db/lock_code_slots.sql.go
Normal file
88
db/lock_code_slots.sql.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.20.0
|
||||
// source: lock_code_slots.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const getLockCodeBySlot = `-- name: GetLockCodeBySlot :one
|
||||
SELECT id, lock, code, slot, name, enabled FROM lock_code_slots WHERE lock = ? AND slot = ?
|
||||
`
|
||||
|
||||
type GetLockCodeBySlotParams struct {
|
||||
Lock int64
|
||||
Slot int64
|
||||
}
|
||||
|
||||
func (q *Queries) GetLockCodeBySlot(ctx context.Context, arg GetLockCodeBySlotParams) (LockCodeSlot, error) {
|
||||
row := q.db.QueryRowContext(ctx, getLockCodeBySlot, arg.Lock, arg.Slot)
|
||||
var i LockCodeSlot
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Lock,
|
||||
&i.Code,
|
||||
&i.Slot,
|
||||
&i.Name,
|
||||
&i.Enabled,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getLockCodes = `-- name: GetLockCodes :many
|
||||
SELECT id, lock, code, slot, name, enabled FROM lock_code_slots WHERE lock = ?
|
||||
`
|
||||
|
||||
func (q *Queries) GetLockCodes(ctx context.Context, lock int64) ([]LockCodeSlot, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getLockCodes, lock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []LockCodeSlot
|
||||
for rows.Next() {
|
||||
var i LockCodeSlot
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Lock,
|
||||
&i.Code,
|
||||
&i.Slot,
|
||||
&i.Name,
|
||||
&i.Enabled,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const upsertCodeSlot = `-- name: UpsertCodeSlot :exec
|
||||
INSERT INTO lock_code_slots (lock, slot, code, enabled, name) VALUES (?, ?, ?, ?, "") ON CONFLICT (lock, slot) DO UPDATE SET code=excluded.code, enabled=excluded.enabled
|
||||
`
|
||||
|
||||
type UpsertCodeSlotParams struct {
|
||||
Lock int64
|
||||
Slot int64
|
||||
Code string
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
func (q *Queries) UpsertCodeSlot(ctx context.Context, arg UpsertCodeSlotParams) error {
|
||||
_, err := q.db.ExecContext(ctx, upsertCodeSlot,
|
||||
arg.Lock,
|
||||
arg.Slot,
|
||||
arg.Code,
|
||||
arg.Enabled,
|
||||
)
|
||||
return err
|
||||
}
|
58
db/lock_log.sql.go
Normal file
58
db/lock_log.sql.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.20.0
|
||||
// source: lock_log.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
const addLogEntry = `-- name: AddLogEntry :exec
|
||||
INSERT INTO lock_log (lock, state, code) VALUES (?, ?, ?)
|
||||
`
|
||||
|
||||
type AddLogEntryParams struct {
|
||||
Lock int64
|
||||
State string
|
||||
Code sql.NullInt64
|
||||
}
|
||||
|
||||
func (q *Queries) AddLogEntry(ctx context.Context, arg AddLogEntryParams) error {
|
||||
_, err := q.db.ExecContext(ctx, addLogEntry, arg.Lock, arg.State, arg.Code)
|
||||
return err
|
||||
}
|
||||
|
||||
const getLogForLock = `-- name: GetLogForLock :many
|
||||
SELECT lock, timestamp, state, code FROM lock_log WHERE lock = ? ORDER BY timestamp DESC
|
||||
`
|
||||
|
||||
func (q *Queries) GetLogForLock(ctx context.Context, lock int64) ([]LockLog, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getLogForLock, lock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []LockLog
|
||||
for rows.Next() {
|
||||
var i LockLog
|
||||
if err := rows.Scan(
|
||||
&i.Lock,
|
||||
&i.Timestamp,
|
||||
&i.State,
|
||||
&i.Code,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
73
db/locks.sql.go
Normal file
73
db/locks.sql.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.20.0
|
||||
// source: locks.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const createLock = `-- name: CreateLock :one
|
||||
INSERT INTO locks (zwave_device_id, name) VALUES (?, "") RETURNING id, name, zwave_device_id
|
||||
`
|
||||
|
||||
func (q *Queries) CreateLock(ctx context.Context, zwaveDeviceID int64) (Lock, error) {
|
||||
row := q.db.QueryRowContext(ctx, createLock, zwaveDeviceID)
|
||||
var i Lock
|
||||
err := row.Scan(&i.ID, &i.Name, &i.ZwaveDeviceID)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getLockByDeviceID = `-- name: GetLockByDeviceID :one
|
||||
SELECT id, name, zwave_device_id FROM locks WHERE zwave_device_id = ?
|
||||
`
|
||||
|
||||
func (q *Queries) GetLockByDeviceID(ctx context.Context, zwaveDeviceID int64) (Lock, error) {
|
||||
row := q.db.QueryRowContext(ctx, getLockByDeviceID, zwaveDeviceID)
|
||||
var i Lock
|
||||
err := row.Scan(&i.ID, &i.Name, &i.ZwaveDeviceID)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getLocks = `-- name: GetLocks :many
|
||||
SELECT id, name, zwave_device_id FROM locks
|
||||
`
|
||||
|
||||
func (q *Queries) GetLocks(ctx context.Context) ([]Lock, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getLocks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Lock
|
||||
for rows.Next() {
|
||||
var i Lock
|
||||
if err := rows.Scan(&i.ID, &i.Name, &i.ZwaveDeviceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateLockName = `-- name: UpdateLockName :exec
|
||||
UPDATE locks SET name = ? WHERE id = ?
|
||||
`
|
||||
|
||||
type UpdateLockNameParams struct {
|
||||
Name string
|
||||
ID int64
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateLockName(ctx context.Context, arg UpdateLockNameParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateLockName, arg.Name, arg.ID)
|
||||
return err
|
||||
}
|
35
db/migrations/1_init.sql
Normal file
35
db/migrations/1_init.sql
Normal file
|
@ -0,0 +1,35 @@
|
|||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE locks (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
zwave_device_id INTEGER NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE lock_code_slots (
|
||||
id INTEGER PRIMARY KEY,
|
||||
lock INTEGER NOT NULL REFERENCES locks(id),
|
||||
code TEXT NOT NULL,
|
||||
slot INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
enabled BOOLEAN NOT NULL DEFAULT 0,
|
||||
|
||||
UNIQUE (lock, slot)
|
||||
);
|
||||
|
||||
CREATE TABLE lock_log (
|
||||
lock INTEGER NOT NULL REFERENCES locks(id),
|
||||
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
state TEXT NOT NULL,
|
||||
code INTEGER REFERENCES lock_code_slots(id)
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE lock_log;
|
||||
DROP TABLE lock_code_slots;
|
||||
DROP TABLE locks;
|
||||
-- +goose StatementEnd
|
32
db/models.go
Normal file
32
db/models.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.20.0
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Lock struct {
|
||||
ID int64
|
||||
Name string
|
||||
ZwaveDeviceID int64
|
||||
}
|
||||
|
||||
type LockCodeSlot struct {
|
||||
ID int64
|
||||
Lock int64
|
||||
Code string
|
||||
Slot int64
|
||||
Name string
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
type LockLog struct {
|
||||
Lock int64
|
||||
Timestamp time.Time
|
||||
State string
|
||||
Code sql.NullInt64
|
||||
}
|
8
db/queries/lock_code_slots.sql
Normal file
8
db/queries/lock_code_slots.sql
Normal file
|
@ -0,0 +1,8 @@
|
|||
-- name: UpsertCodeSlot :exec
|
||||
INSERT INTO lock_code_slots (lock, slot, code, enabled, name) VALUES (?, ?, ?, ?, "") ON CONFLICT (lock, slot) DO UPDATE SET code=excluded.code, enabled=excluded.enabled;
|
||||
|
||||
-- name: GetLockCodeBySlot :one
|
||||
SELECT * FROM lock_code_slots WHERE lock = ? AND slot = ?;
|
||||
|
||||
-- name: GetLockCodes :many
|
||||
SELECT * FROM lock_code_slots WHERE lock = ?;
|
5
db/queries/lock_log.sql
Normal file
5
db/queries/lock_log.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
-- name: AddLogEntry :exec
|
||||
INSERT INTO lock_log (lock, state, code) VALUES (?, ?, ?);
|
||||
|
||||
-- name: GetLogForLock :many
|
||||
SELECT * FROM lock_log WHERE lock = ? ORDER BY timestamp DESC;
|
11
db/queries/locks.sql
Normal file
11
db/queries/locks.sql
Normal file
|
@ -0,0 +1,11 @@
|
|||
-- name: CreateLock :one
|
||||
INSERT INTO locks (zwave_device_id, name) VALUES (?, "") RETURNING *;
|
||||
|
||||
-- name: GetLocks :many
|
||||
SELECT * FROM locks;
|
||||
|
||||
-- name: GetLockByDeviceID :one
|
||||
SELECT * FROM locks WHERE zwave_device_id = ?;
|
||||
|
||||
-- name: UpdateLockName :exec
|
||||
UPDATE locks SET name = ? WHERE id = ?;
|
19
go.mod
Normal file
19
go.mod
Normal file
|
@ -0,0 +1,19 @@
|
|||
module git.janky.solutions/finn/lockserver
|
||||
|
||||
go 1.21.8
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/pressly/goose/v3 v3.19.2
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/sethvargo/go-retry v0.2.4 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
)
|
187
go.sum
Normal file
187
go.sum
Normal file
|
@ -0,0 +1,187 @@
|
|||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0=
|
||||
github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.17.1 h1:ZCmAYWpu75IyEi7+Yrs/uaAjiCGY5wfW5kXo64exkX4=
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.17.1/go.mod h1:rkGTvFDTLqLIm0ma+13xmcCfr/08Gvs7KmFt1tgiWHQ=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
|
||||
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg=
|
||||
github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
||||
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elastic/go-sysinfo v1.11.2 h1:mcm4OSYVMyws6+n2HIVMGkln5HOpo5Ie1ZmbbNn0jg4=
|
||||
github.com/elastic/go-sysinfo v1.11.2/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ=
|
||||
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
|
||||
github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
|
||||
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
|
||||
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
|
||||
github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI=
|
||||
github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY=
|
||||
github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
|
||||
github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
|
||||
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
|
||||
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
||||
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475 h1:6PfEMwfInASh9hkN83aR0j4W/eKaAZt/AURtXAXlas0=
|
||||
github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475/go.mod h1:20nXSmcf0nAscrzqsXeC2/tA3KkV2eCiJqYuyAgl+ss=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
|
||||
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
|
||||
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
|
||||
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
|
||||
github.com/paulmach/orb v0.10.0 h1:guVYVqzxHE/CQ1KpfGO077TR0ATHSNjp4s6XGLn3W9s=
|
||||
github.com/paulmach/orb v0.10.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
||||
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pressly/goose/v3 v3.19.2 h1:z1yuD41jS4iaqLkyjkzGkKBz4rgyz/BYtCyMMGHlgzQ=
|
||||
github.com/pressly/goose/v3 v3.19.2/go.mod h1:BHkf3LzSBmO8E5FTMPupUYIpMTIh/ZuQVy+YTfhZLD4=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec=
|
||||
github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240220085343-4ae0eb9d0898 h1:1MvEhzI5pvP27e9Dzz861mxk9WzXZLSJwzOU67cKTbU=
|
||||
github.com/tursodatabase/libsql-client-go v0.0.0-20240220085343-4ae0eb9d0898/go.mod h1:9bKuHS7eZh/0mJndbUOrCx8Ej3PlsRDszj4L7oVYMPQ=
|
||||
github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw=
|
||||
github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf h1:ckwNHVo4bv2tqNkgx3W3HANh3ta1j6TR5qw08J1A7Tw=
|
||||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I=
|
||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.55.1 h1:Ebo6J5AMXgJ3A438ECYotA0aK7ETqjQx9WoZvVxzKBE=
|
||||
github.com/ydb-platform/ydb-go-sdk/v3 v3.55.1/go.mod h1:udNPW8eupyH/EZocecFmaSNJacKKYjzQa7cVgX5U2nc=
|
||||
go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc=
|
||||
go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs=
|
||||
go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ=
|
||||
go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
|
||||
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/sqlite v1.29.5 h1:8l/SQKAjDtZFo9lkJLdk8g9JEOeYRG4/ghStDCCTiTE=
|
||||
modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
9
sqlc.yaml
Normal file
9
sqlc.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
version: "2"
|
||||
sql:
|
||||
- engine: "sqlite"
|
||||
schema: "db/migrations"
|
||||
queries: "db/queries"
|
||||
gen:
|
||||
go:
|
||||
package: "db"
|
||||
out: "db"
|
204
zwavejs/client.go
Normal file
204
zwavejs/client.go
Normal file
|
@ -0,0 +1,204 @@
|
|||
package zwavejs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.janky.solutions/finn/lockserver/db"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
conn *websocket.Conn
|
||||
}
|
||||
|
||||
func New(ctx context.Context, server string) (*Client, error) {
|
||||
c := &Client{}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
if err := c.DialAndListen(ctx, server); err != nil {
|
||||
logrus.WithError(err).Error("error from ZWaveJS server")
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * 5)
|
||||
}
|
||||
}()
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialAndListen(ctx context.Context, server string) error {
|
||||
for {
|
||||
conn, _, err := websocket.DefaultDialer.DialContext(ctx, server, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.conn = conn
|
||||
|
||||
if err := c.listen(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c Client) listen(ctx context.Context) error {
|
||||
for {
|
||||
_, msg, err := c.conn.ReadMessage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var parsed IncomingMessage
|
||||
if err := json.Unmarshal(msg, &parsed); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch parsed.Type {
|
||||
case "version":
|
||||
if err := c.conn.WriteJSON(OutgoingMessage{Command: StartListeningCommand}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "result":
|
||||
if err := syncState(ctx, *parsed.Result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "event":
|
||||
if err := handleEvent(ctx, *parsed.Event); err != nil {
|
||||
logrus.WithError(err).Error("error handling event")
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Println(string(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c Client) Shutdown() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func syncState(ctx context.Context, result Result) error {
|
||||
queries, dbc, err := db.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dbc.Close()
|
||||
|
||||
for _, node := range result.State.Nodes {
|
||||
slots := make(map[int]db.LockCodeSlot)
|
||||
lockID := int64(-1)
|
||||
for _, value := range node.Values {
|
||||
if value.CommandClass != CommandClassUserCode {
|
||||
continue
|
||||
}
|
||||
|
||||
slotNumber, err := value.PropertyKey.Int()
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithField("value", value.PropertyKey.String()).Warn("unexpected non-int PropertyKey")
|
||||
continue
|
||||
}
|
||||
|
||||
lockID = int64(node.NodeID)
|
||||
|
||||
slot := slots[slotNumber] // check if there's an existing entry
|
||||
slot.Slot = int64(slotNumber)
|
||||
|
||||
switch value.PropertyName {
|
||||
case PropertyUserCode:
|
||||
slot.Code = value.Value.String
|
||||
case PropertyUserIDStatus:
|
||||
slot.Enabled = value.Value.Int > 0
|
||||
}
|
||||
|
||||
slots[slotNumber] = slot
|
||||
}
|
||||
|
||||
if len(slots) == 0 || lockID < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
lock, err := queries.GetLockByDeviceID(ctx, lockID)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
lock, err = queries.CreateLock(ctx, lockID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, slot := range slots {
|
||||
err := queries.UpsertCodeSlot(ctx, db.UpsertCodeSlotParams{
|
||||
Lock: lock.ID,
|
||||
Code: slot.Code,
|
||||
Slot: slot.Slot,
|
||||
Enabled: slot.Enabled,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error upserting slot: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleEvent(ctx context.Context, event Event) error {
|
||||
if event.Source != EventSourceNode || event.Event != EventTypeNotification {
|
||||
return nil
|
||||
}
|
||||
|
||||
queries, dbc, err := db.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dbc.Close()
|
||||
|
||||
lock, err := queries.GetLockByDeviceID(ctx, int64(event.NodeID))
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("error getting lock: %v", err)
|
||||
}
|
||||
|
||||
code := sql.NullInt64{}
|
||||
if event.Parameters.UserID > 0 {
|
||||
slot, err := queries.GetLockCodeBySlot(ctx, db.GetLockCodeBySlotParams{
|
||||
Lock: lock.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting code slot: %v", err)
|
||||
}
|
||||
code = sql.NullInt64{
|
||||
Int64: slot.ID,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
err = queries.AddLogEntry(ctx, db.AddLogEntryParams{
|
||||
Lock: lock.ID,
|
||||
Code: code,
|
||||
State: event.NotificationLabel,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error adding log entry: %v", err)
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"lock": lock.ID,
|
||||
"code": code,
|
||||
"state": event.NotificationLabel,
|
||||
}).Debug("processed lock event")
|
||||
|
||||
return nil
|
||||
}
|
16
zwavejs/consts.go
Normal file
16
zwavejs/consts.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package zwavejs
|
||||
|
||||
var (
|
||||
CommandClassDoorLock = 98
|
||||
CommandClassUserCode = 99
|
||||
|
||||
PropertyUserCode = "userCode"
|
||||
PropertyUserIDStatus = "userIdStatus"
|
||||
|
||||
EventSourceController = "controller"
|
||||
EventSourceNode = "node"
|
||||
|
||||
EventTypeValueUpdated = "value updated"
|
||||
EventTypeStatisticsUpdated = "statistics updated"
|
||||
EventTypeNotification = "notification"
|
||||
)
|
267
zwavejs/messages-incoming.go
Normal file
267
zwavejs/messages-incoming.go
Normal file
|
@ -0,0 +1,267 @@
|
|||
package zwavejs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type IncomingMessage struct {
|
||||
Type string `json:"type"`
|
||||
MessageID string `json:"messageId"`
|
||||
Success bool `json:"success"`
|
||||
|
||||
// Values for type = version
|
||||
DriverVersion string `json:"driverVersion"`
|
||||
ServerVersion string `json:"serverVersion"`
|
||||
HomeID int `json:"homeId"`
|
||||
|
||||
Event *Event
|
||||
Result *Result
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
Source string `json:"source"`
|
||||
Event string `json:"event"`
|
||||
|
||||
NodeID int `json:"nodeId"`
|
||||
Args NodeEventArgs `json:"args"`
|
||||
NotificationLabel string `json:"notificationLabel"`
|
||||
Parameters EventParameters `json:"parameters"`
|
||||
}
|
||||
|
||||
type NodeEventArgs struct {
|
||||
CommandClassName string `json:"commandClassName"`
|
||||
CommandClass int `json:"commandClass"`
|
||||
Property string `json:"property"`
|
||||
Endpoint int `json:"endpoint"`
|
||||
NewValue NodePropertyValue `json:"newValue"`
|
||||
PrevValue NodePropertyValue `json:"prevValue"`
|
||||
PropertyName string `json:"propertyName"`
|
||||
}
|
||||
|
||||
type EventParameters struct {
|
||||
UserID int `json:"userId"`
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
State struct {
|
||||
Controller Controller
|
||||
Driver Driver
|
||||
Nodes []Node
|
||||
}
|
||||
}
|
||||
|
||||
type Controller struct {
|
||||
Type int `json:"type"`
|
||||
HomeID int64 `json:"homeId"`
|
||||
OwnNodeID int `json:"ownNodeId"`
|
||||
IsUsingHomeIDFromOtherNetwork bool `json:"isUsingHomeIdFromOtherNetwork"`
|
||||
IsSISPresent bool `json:"isSISPresent"`
|
||||
WasRealPrimary bool `json:"wasRealPrimary"`
|
||||
ManufacturerID int `json:"manufacturerId"`
|
||||
ProductType int `json:"productType"`
|
||||
ProductID int `json:"productId"`
|
||||
SupportedFunctionTypes []int `json:"supportedFunctionTypes"`
|
||||
SucNodeID int `json:"sucNodeId"`
|
||||
SupportsTimers bool `json:"supportsTimers"`
|
||||
Statistics ControllerStatistics `json:"statistics"`
|
||||
}
|
||||
|
||||
type ControllerStatistics struct {
|
||||
MessagesTX int `json:"messagesTX"`
|
||||
MessagesRX int `json:"messagesRX"`
|
||||
MessagesDroppedRX int `json:"messagesDroppedRX"`
|
||||
Nak int `json:"NAK"`
|
||||
Can int `json:"CAN"`
|
||||
TimeoutACK int `json:"timeoutACK"`
|
||||
TimeoutResponse int `json:"timeoutResponse"`
|
||||
TimeoutCallback int `json:"timeoutCallback"`
|
||||
MessagesDroppedTX int `json:"messagesDroppedTX"`
|
||||
InclusionState int `json:"inclusionState"`
|
||||
IsSecondary bool `json:"isSecondary"`
|
||||
IsStaticUpdateController bool `json:"isStaticUpdateController"`
|
||||
IsSlave bool `json:"isSlave"`
|
||||
IsHealNetworkActive bool `json:"isHealNetworkActive"`
|
||||
LibraryVersion string `json:"libraryVersion"`
|
||||
SerialAPIVersion string `json:"serialApiVersion"`
|
||||
}
|
||||
|
||||
type Driver struct {
|
||||
LogConfig DriverLogConfig
|
||||
StatisticsEnabled bool
|
||||
}
|
||||
|
||||
type DriverLogConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Level int `json:"level"`
|
||||
LogToFile bool `json:"logToFile"`
|
||||
MaxFiles int `json:"maxFiles"`
|
||||
Filename string `json:"filename"`
|
||||
ForceConsole bool `json:"forceConsole"`
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
NodeID int `json:"nodeId"`
|
||||
Index int `json:"index"`
|
||||
Status int `json:"status"`
|
||||
Ready bool `json:"ready"`
|
||||
IsListening bool `json:"isListening"`
|
||||
IsRouting bool `json:"isRouting"`
|
||||
ManufacturerID int `json:"manufacturerId"`
|
||||
ProductID int `json:"productId"`
|
||||
ProductType int `json:"productType"`
|
||||
FirmwareVersion string `json:"firmwareVersion"`
|
||||
DeviceConfig NodeDeviceConfig `json:"deviceConfig"`
|
||||
Label string `json:"label"`
|
||||
InterviewAttempts int `json:"interviewAttempts"`
|
||||
Endpoints []NodeEndpoint `json:"endpoints"`
|
||||
Values []NodeValues `json:"values"`
|
||||
InterviewStage int `json:"interviewStage"`
|
||||
IsFrequentListening any `json:"isFrequentListening"`
|
||||
MaxBaudRate int `json:"maxBaudRate"`
|
||||
Version int `json:"version"`
|
||||
IsBeaming bool `json:"isBeaming"`
|
||||
DeviceClass NodeDeviceClass `json:"deviceClass"`
|
||||
InstallerIcon int `json:"installerIcon,omitempty"`
|
||||
UserIcon int `json:"userIcon,omitempty"`
|
||||
IsSecure bool `json:"isSecure,omitempty"`
|
||||
ZwavePlusVersion int `json:"zwavePlusVersion,omitempty"`
|
||||
NodeType int `json:"nodeType,omitempty"`
|
||||
RoleType int `json:"roleType,omitempty"`
|
||||
EndpointCountIsDynamic bool `json:"endpointCountIsDynamic,omitempty"`
|
||||
EndpointsHaveIdenticalCapabilities bool `json:"endpointsHaveIdenticalCapabilities,omitempty"`
|
||||
IndividualEndpointCount int `json:"individualEndpointCount,omitempty"`
|
||||
AggregatedEndpointCount int `json:"aggregatedEndpointCount,omitempty"`
|
||||
}
|
||||
|
||||
type NodeDeviceConfig struct {
|
||||
Filename string `json:"filename"`
|
||||
IsEmbedded bool `json:"isEmbedded"`
|
||||
Manufacturer string `json:"manufacturer"`
|
||||
ManufacturerID int `json:"manufacturerId"`
|
||||
Label string `json:"label"`
|
||||
Description string `json:"description"`
|
||||
Devices []NodeDeviceConfigDevice `json:"devices"`
|
||||
FirmwareVersion NodeDeviceFirmwareVersion `json:"firmwareVersion"`
|
||||
Preferred bool `json:"preferred"`
|
||||
Metadata NodeDeviceMetadata `json:"metadata"`
|
||||
}
|
||||
|
||||
type NodeDeviceConfigDevice struct {
|
||||
ProductType int `json:"productType"`
|
||||
ProductID int `json:"productId"`
|
||||
}
|
||||
|
||||
type NodeDeviceFirmwareVersion struct {
|
||||
Min string `json:"min"`
|
||||
Max string `json:"max"`
|
||||
}
|
||||
|
||||
type NodeDeviceMetadata struct {
|
||||
Comments []NodeDeviceMetadataComments `json:"comments"`
|
||||
Wakeup string `json:"wakeup"`
|
||||
Inclusion string `json:"inclusion"`
|
||||
Exclusion string `json:"exclusion"`
|
||||
Reset string `json:"reset"`
|
||||
Manual string `json:"manual"`
|
||||
}
|
||||
|
||||
type NodeDeviceMetadataComments struct {
|
||||
Level string `json:"level"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type NodeEndpoint struct {
|
||||
NodeID int `json:"nodeId"`
|
||||
Index int `json:"index"`
|
||||
}
|
||||
|
||||
type NodeValues struct {
|
||||
Endpoint int `json:"endpoint"`
|
||||
CommandClass int `json:"commandClass"`
|
||||
CommandClassName string `json:"commandClassName"`
|
||||
PropertyName string `json:"propertyName"`
|
||||
PropertyKey StringOrInt `json:"propertyKey"`
|
||||
CcVersion int `json:"ccVersion"`
|
||||
Metadata NodeValuesMetadata `json:"metadata"`
|
||||
Value NodePropertyValue `json:"value"`
|
||||
}
|
||||
|
||||
type NodeValuesMetadata struct {
|
||||
Type string `json:"type"`
|
||||
Readable bool `json:"readable"`
|
||||
Writeable bool `json:"writeable"`
|
||||
Label string `json:"label"`
|
||||
Min int `json:"min"`
|
||||
Max int `json:"max"`
|
||||
}
|
||||
|
||||
type NodeDeviceClass struct {
|
||||
Basic string `json:"basic"`
|
||||
Generic string `json:"generic"`
|
||||
Specific string `json:"specific"`
|
||||
MandatorySupportedCCs []string `json:"mandatorySupportedCCs"`
|
||||
MandatoryControlCCs []string `json:"mandatoryControlCCs"`
|
||||
}
|
||||
|
||||
type StringOrInt string
|
||||
|
||||
func (s *StringOrInt) UnmarshalJSON(data []byte) error {
|
||||
var str string
|
||||
if bytes.HasPrefix(data, []byte("\"")) {
|
||||
if err := json.Unmarshal(data, &str); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if bytes.Equal(data, []byte("true")) || bytes.Equal(data, []byte("false")) {
|
||||
str = string(data)
|
||||
} else {
|
||||
var i int
|
||||
if err := json.Unmarshal(data, &i); err != nil {
|
||||
return err
|
||||
}
|
||||
str = fmt.Sprintf("%d", i)
|
||||
}
|
||||
|
||||
*s = StringOrInt(str)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s StringOrInt) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (s StringOrInt) Int() (int, error) {
|
||||
return strconv.Atoi(string(s))
|
||||
}
|
||||
|
||||
type NodePropertyValue struct {
|
||||
String string
|
||||
Int int
|
||||
Bool bool
|
||||
Values []NodePropertyValue
|
||||
}
|
||||
|
||||
func (n *NodePropertyValue) UnmarshalJSON(data []byte) error {
|
||||
if bytes.HasPrefix(data, []byte("\"")) {
|
||||
return json.Unmarshal(data, &n.String)
|
||||
}
|
||||
|
||||
if bytes.Equal(data, []byte("true")) || bytes.Equal(data, []byte("false")) {
|
||||
return json.Unmarshal(data, &n.Bool)
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(data, []byte("[")) {
|
||||
return json.Unmarshal(data, &n.Values)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &n.Int); err != nil {
|
||||
logrus.WithField("value", string(data)).Debug("error while parsing node property value of ambiguous type")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
19
zwavejs/messages-outgoing.go
Normal file
19
zwavejs/messages-outgoing.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package zwavejs
|
||||
|
||||
type Command string
|
||||
|
||||
var (
|
||||
InitializeCommand Command = "initialize"
|
||||
StartListeningCommand Command = "start_listening"
|
||||
)
|
||||
|
||||
type OutgoingMessage struct {
|
||||
MessageID string `json:"messageId"`
|
||||
Command Command `json:"command"`
|
||||
}
|
||||
|
||||
type Initialize struct {
|
||||
OutgoingMessage
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
AdditionalUserAgentComponents map[string]string `json:"additionalUserAgentComponents"`
|
||||
}
|
Loading…
Reference in a new issue