wip
This commit is contained in:
parent
5e05f7960f
commit
40f10ce514
23 changed files with 170 additions and 71 deletions
1
go.mod
1
go.mod
|
@ -39,6 +39,7 @@ require (
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||||
|
github.com/el-mike/restrict/v2 v2.0.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||||
github.com/go-audio/audio v1.0.0 // indirect
|
github.com/go-audio/audio v1.0.0 // indirect
|
||||||
github.com/go-audio/riff v1.0.0 // indirect
|
github.com/go-audio/riff v1.0.0 // indirect
|
||||||
|
|
18
go.sum
18
go.sum
|
@ -8,6 +8,12 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
||||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||||
|
github.com/casbin/casbin/v2 v2.103.0 h1:dHElatNXNrr8XcseUov0ZSiWjauwmZZE6YMV3eU1yic=
|
||||||
|
github.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco=
|
||||||
|
github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc=
|
||||||
|
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
@ -28,6 +34,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
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/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/el-mike/restrict/v2 v2.0.0 h1:OuVBseAejSHyfHMUr15c4Gz3WRCEKuuD8IOR/mOIV/o=
|
||||||
|
github.com/el-mike/restrict/v2 v2.0.0/go.mod h1:ClycXfCKWIZRU1qi2CJIOpHEuonBOj/2GKc+w1lZtrQ=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||||
|
@ -61,6 +69,7 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
|
github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
|
||||||
github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks=
|
github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks=
|
||||||
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
@ -157,12 +166,16 @@ github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7
|
||||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
@ -183,6 +196,7 @@ go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HY
|
||||||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
@ -194,8 +208,11 @@ golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4
|
||||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
golang.org/x/mobile v0.0.0-20241108191957-fa514ef75a0f h1:23H/YlmTHfmmvpZ+ajKZL0qLz0+IwFOIqQA0mQbmLeM=
|
golang.org/x/mobile v0.0.0-20241108191957-fa514ef75a0f h1:23H/YlmTHfmmvpZ+ajKZL0qLz0+IwFOIqQA0mQbmLeM=
|
||||||
golang.org/x/mobile v0.0.0-20241108191957-fa514ef75a0f/go.mod h1:UbSUP4uu/C9hw9R2CkojhXlAxvayHjBdU9aRvE+c1To=
|
golang.org/x/mobile v0.0.0-20241108191957-fa514ef75a0f/go.mod h1:UbSUP4uu/C9hw9R2CkojhXlAxvayHjBdU9aRvE+c1To=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
@ -208,6 +225,7 @@ golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/auth"
|
"dynatron.me/x/stillbox/pkg/auth"
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
"dynatron.me/x/stillbox/pkg/sources"
|
"dynatron.me/x/stillbox/pkg/sources"
|
||||||
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
@ -62,16 +63,16 @@ func TestMarshal(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
submitter auth.UserID
|
submitter users.UserID
|
||||||
apiKey string
|
apiKey string
|
||||||
call calls.Call
|
call calls.Call
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "base",
|
name: "base",
|
||||||
submitter: auth.UserID(1),
|
submitter: users.UserID(1),
|
||||||
call: calls.Call{
|
call: calls.Call{
|
||||||
ID: uuid.UUID([16]byte{0x52, 0xfd, 0xfc, 0x07, 0x21, 0x82, 0x45, 0x4f, 0x96, 0x3f, 0x5f, 0x0f, 0x9a, 0x62, 0x1d, 0x72}),
|
ID: uuid.UUID([16]byte{0x52, 0xfd, 0xfc, 0x07, 0x21, 0x82, 0x45, 0x4f, 0x96, 0x3f, 0x5f, 0x0f, 0x9a, 0x62, 0x1d, 0x72}),
|
||||||
Submitter: common.PtrTo(auth.UserID(1)),
|
Submitter: common.PtrTo(users.UserID(1)),
|
||||||
System: 197,
|
System: 197,
|
||||||
Talkgroup: 10101,
|
Talkgroup: 10101,
|
||||||
DateTime: time.Date(2024, 11, 10, 23, 33, 02, 0, time.Local),
|
DateTime: time.Date(2024, 11, 10, 23, 33, 02, 0, time.Local),
|
||||||
|
|
|
@ -7,18 +7,19 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type apiKeyAuth interface {
|
type apiKeyAuth interface {
|
||||||
// CheckAPIKey validates the provided key and returns the API owner's UserID.
|
// CheckAPIKey validates the provided key and returns the API owner's users.UserID.
|
||||||
// An error is returned if validation fails for any reason.
|
// An error is returned if validation fails for any reason.
|
||||||
CheckAPIKey(ctx context.Context, key string) (*UserID, error)
|
CheckAPIKey(ctx context.Context, key string) (*users.UserID, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) CheckAPIKey(ctx context.Context, key string) (*UserID, error) {
|
func (a *Auth) CheckAPIKey(ctx context.Context, key string) (*users.UserID, error) {
|
||||||
keyUuid, err := uuid.Parse(key)
|
keyUuid, err := uuid.Parse(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Str("apikey", key).Msg("cannot parse key")
|
log.Error().Str("apikey", key).Msg("cannot parse key")
|
||||||
|
@ -44,7 +45,7 @@ func (a *Auth) CheckAPIKey(ctx context.Context, key string) (*UserID, error) {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
owner := UserID(apik.Owner)
|
owner := users.UserID(apik.Owner)
|
||||||
|
|
||||||
return &owner, nil
|
return &owner, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,18 +13,6 @@ import (
|
||||||
"github.com/go-chi/jwtauth/v5"
|
"github.com/go-chi/jwtauth/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserID int
|
|
||||||
|
|
||||||
func (u *UserID) Int32Ptr() *int32 {
|
|
||||||
if u == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
i := int32(*u)
|
|
||||||
|
|
||||||
return &i
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticator performs API key and user JWT authentication.
|
// Authenticator performs API key and user JWT authentication.
|
||||||
type Authenticator interface {
|
type Authenticator interface {
|
||||||
jwtAuth
|
jwtAuth
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/jwtauth/v5"
|
"github.com/go-chi/jwtauth/v5"
|
||||||
|
@ -44,7 +45,8 @@ type jwtAuth interface {
|
||||||
|
|
||||||
type claims map[string]interface{}
|
type claims map[string]interface{}
|
||||||
|
|
||||||
func UIDFrom(ctx context.Context) *int32 {
|
// TODO: change this to UserFrom() *users.User
|
||||||
|
func UIDFrom(ctx context.Context) *users.UserID {
|
||||||
tok, _, err := jwtauth.FromContext(ctx)
|
tok, _, err := jwtauth.FromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -56,7 +58,7 @@ func UIDFrom(ctx context.Context) *int32 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
uid := int32(uidInt)
|
uid := users.UserID(int32(uidInt))
|
||||||
|
|
||||||
return &uid
|
return &uid
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ import (
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/audio"
|
"dynatron.me/x/stillbox/internal/audio"
|
||||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
"dynatron.me/x/stillbox/pkg/auth"
|
|
||||||
"dynatron.me/x/stillbox/pkg/pb"
|
"dynatron.me/x/stillbox/pkg/pb"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
@ -52,23 +52,23 @@ type CallAudio struct {
|
||||||
// further transformation. relayOut exists for compatibility with http
|
// further transformation. relayOut exists for compatibility with http
|
||||||
// source CallUploadRequest as used in the relay sink.
|
// source CallUploadRequest as used in the relay sink.
|
||||||
type Call struct {
|
type Call struct {
|
||||||
ID uuid.UUID `json:"id" relayOut:"id"`
|
ID uuid.UUID `json:"id" relayOut:"id"`
|
||||||
Audio []byte `json:"audio,omitempty" relayOut:"audio,omitempty" filenameField:"AudioName"`
|
Audio []byte `json:"audio,omitempty" relayOut:"audio,omitempty" filenameField:"AudioName"`
|
||||||
AudioName string `json:"audioName,omitempty" relayOut:"audioName,omitempty"`
|
AudioName string `json:"audioName,omitempty" relayOut:"audioName,omitempty"`
|
||||||
AudioType string `json:"audioType,omitempty" relayOut:"audioType,omitempty"`
|
AudioType string `json:"audioType,omitempty" relayOut:"audioType,omitempty"`
|
||||||
Duration CallDuration `json:"duration,omitempty" relayOut:"duration,omitempty"`
|
Duration CallDuration `json:"duration,omitempty" relayOut:"duration,omitempty"`
|
||||||
DateTime time.Time `json:"call_date,omitempty" relayOut:"dateTime,omitempty"`
|
DateTime time.Time `json:"call_date,omitempty" relayOut:"dateTime,omitempty"`
|
||||||
Frequencies []int `json:"frequencies,omitempty" relayOut:"frequencies,omitempty"`
|
Frequencies []int `json:"frequencies,omitempty" relayOut:"frequencies,omitempty"`
|
||||||
Frequency int `json:"frequency,omitempty" relayOut:"frequency,omitempty"`
|
Frequency int `json:"frequency,omitempty" relayOut:"frequency,omitempty"`
|
||||||
Patches []int `json:"patches,omitempty" relayOut:"patches,omitempty"`
|
Patches []int `json:"patches,omitempty" relayOut:"patches,omitempty"`
|
||||||
Source int `json:"source,omitempty" relayOut:"source,omitempty"`
|
Source int `json:"source,omitempty" relayOut:"source,omitempty"`
|
||||||
System int `json:"system_id,omitempty" relayOut:"system,omitempty"`
|
System int `json:"system_id,omitempty" relayOut:"system,omitempty"`
|
||||||
Submitter *auth.UserID `json:"submitter,omitempty" relayOut:"submitter,omitempty"`
|
Submitter *users.UserID `json:"submitter,omitempty" relayOut:"submitter,omitempty"`
|
||||||
SystemLabel string `json:"system_name,omitempty" relayOut:"systemLabel,omitempty"`
|
SystemLabel string `json:"system_name,omitempty" relayOut:"systemLabel,omitempty"`
|
||||||
Talkgroup int `json:"tgid,omitempty" relayOut:"talkgroup,omitempty"`
|
Talkgroup int `json:"tgid,omitempty" relayOut:"talkgroup,omitempty"`
|
||||||
TalkgroupGroup *string `json:"talkgroupGroup,omitempty" relayOut:"talkgroupGroup,omitempty"`
|
TalkgroupGroup *string `json:"talkgroupGroup,omitempty" relayOut:"talkgroupGroup,omitempty"`
|
||||||
TalkgroupLabel *string `json:"talkgroupLabel,omitempty" relayOut:"talkgroupLabel,omitempty"`
|
TalkgroupLabel *string `json:"talkgroupLabel,omitempty" relayOut:"talkgroupLabel,omitempty"`
|
||||||
TGAlphaTag *string `json:"tg_name,omitempty" relayOut:"talkgroupTag,omitempty"`
|
TGAlphaTag *string `json:"tg_name,omitempty" relayOut:"talkgroupTag,omitempty"`
|
||||||
|
|
||||||
shouldStore bool `json:"-"`
|
shouldStore bool `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ const createIncident = `-- name: CreateIncident :one
|
||||||
INSERT INTO incidents (
|
INSERT INTO incidents (
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
owner,
|
||||||
description,
|
description,
|
||||||
start_time,
|
start_time,
|
||||||
end_time,
|
end_time,
|
||||||
|
@ -56,14 +57,16 @@ INSERT INTO incidents (
|
||||||
$4,
|
$4,
|
||||||
$5,
|
$5,
|
||||||
$6,
|
$6,
|
||||||
$7
|
$7,
|
||||||
|
$8
|
||||||
)
|
)
|
||||||
RETURNING id, name, description, start_time, end_time, location, metadata
|
RETURNING id, name, owner, description, start_time, end_time, location, metadata
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateIncidentParams struct {
|
type CreateIncidentParams struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Owner int `json:"owner"`
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description"`
|
||||||
StartTime pgtype.Timestamptz `json:"start_time"`
|
StartTime pgtype.Timestamptz `json:"start_time"`
|
||||||
EndTime pgtype.Timestamptz `json:"end_time"`
|
EndTime pgtype.Timestamptz `json:"end_time"`
|
||||||
|
@ -75,6 +78,7 @@ func (q *Queries) CreateIncident(ctx context.Context, arg CreateIncidentParams)
|
||||||
row := q.db.QueryRow(ctx, createIncident,
|
row := q.db.QueryRow(ctx, createIncident,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
arg.Name,
|
arg.Name,
|
||||||
|
arg.Owner,
|
||||||
arg.Description,
|
arg.Description,
|
||||||
arg.StartTime,
|
arg.StartTime,
|
||||||
arg.EndTime,
|
arg.EndTime,
|
||||||
|
@ -85,6 +89,7 @@ func (q *Queries) CreateIncident(ctx context.Context, arg CreateIncidentParams)
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.Name,
|
&i.Name,
|
||||||
|
&i.Owner,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.StartTime,
|
&i.StartTime,
|
||||||
&i.EndTime,
|
&i.EndTime,
|
||||||
|
@ -107,6 +112,7 @@ const getIncident = `-- name: GetIncident :one
|
||||||
SELECT
|
SELECT
|
||||||
i.id,
|
i.id,
|
||||||
i.name,
|
i.name,
|
||||||
|
i.owner,
|
||||||
i.description,
|
i.description,
|
||||||
i.start_time,
|
i.start_time,
|
||||||
i.end_time,
|
i.end_time,
|
||||||
|
@ -122,6 +128,7 @@ func (q *Queries) GetIncident(ctx context.Context, id uuid.UUID) (Incident, erro
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.Name,
|
&i.Name,
|
||||||
|
&i.Owner,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.StartTime,
|
&i.StartTime,
|
||||||
&i.EndTime,
|
&i.EndTime,
|
||||||
|
@ -262,6 +269,7 @@ const listIncidentsP = `-- name: ListIncidentsP :many
|
||||||
SELECT
|
SELECT
|
||||||
i.id,
|
i.id,
|
||||||
i.name,
|
i.name,
|
||||||
|
i.owner,
|
||||||
i.description,
|
i.description,
|
||||||
i.start_time,
|
i.start_time,
|
||||||
i.end_time,
|
i.end_time,
|
||||||
|
@ -299,6 +307,7 @@ type ListIncidentsPParams struct {
|
||||||
type ListIncidentsPRow struct {
|
type ListIncidentsPRow struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Owner int `json:"owner"`
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description"`
|
||||||
StartTime pgtype.Timestamptz `json:"start_time"`
|
StartTime pgtype.Timestamptz `json:"start_time"`
|
||||||
EndTime pgtype.Timestamptz `json:"end_time"`
|
EndTime pgtype.Timestamptz `json:"end_time"`
|
||||||
|
@ -326,6 +335,7 @@ func (q *Queries) ListIncidentsP(ctx context.Context, arg ListIncidentsPParams)
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.Name,
|
&i.Name,
|
||||||
|
&i.Owner,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.StartTime,
|
&i.StartTime,
|
||||||
&i.EndTime,
|
&i.EndTime,
|
||||||
|
@ -375,7 +385,7 @@ SET
|
||||||
metadata = COALESCE($6, metadata)
|
metadata = COALESCE($6, metadata)
|
||||||
WHERE
|
WHERE
|
||||||
id = $7
|
id = $7
|
||||||
RETURNING id, name, description, start_time, end_time, location, metadata
|
RETURNING id, name, owner, description, start_time, end_time, location, metadata
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateIncidentParams struct {
|
type UpdateIncidentParams struct {
|
||||||
|
@ -402,6 +412,7 @@ func (q *Queries) UpdateIncident(ctx context.Context, arg UpdateIncidentParams)
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.Name,
|
&i.Name,
|
||||||
|
&i.Owner,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.StartTime,
|
&i.StartTime,
|
||||||
&i.EndTime,
|
&i.EndTime,
|
||||||
|
|
|
@ -58,6 +58,7 @@ type Call struct {
|
||||||
type Incident struct {
|
type Incident struct {
|
||||||
ID uuid.UUID `json:"id,omitempty"`
|
ID uuid.UUID `json:"id,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
|
Owner int `json:"owner,omitempty"`
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
StartTime pgtype.Timestamptz `json:"start_time,omitempty"`
|
StartTime pgtype.Timestamptz `json:"start_time,omitempty"`
|
||||||
EndTime pgtype.Timestamptz `json:"end_time,omitempty"`
|
EndTime pgtype.Timestamptz `json:"end_time,omitempty"`
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/incidents"
|
"dynatron.me/x/stillbox/pkg/incidents"
|
||||||
|
"dynatron.me/x/stillbox/pkg/rbac"
|
||||||
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
@ -75,12 +77,19 @@ func (s *store) CreateIncident(ctx context.Context, inc incidents.Incident) (*in
|
||||||
db := database.FromCtx(ctx)
|
db := database.FromCtx(ctx)
|
||||||
var dbInc database.Incident
|
var dbInc database.Incident
|
||||||
|
|
||||||
|
// TODO: replace this with a real RBAC check
|
||||||
|
owner := auth.UIDFrom(ctx)
|
||||||
|
if owner == nil {
|
||||||
|
return nil, rbac.ErrNotAuthorized
|
||||||
|
}
|
||||||
|
|
||||||
id := uuid.New()
|
id := uuid.New()
|
||||||
|
|
||||||
txErr := db.InTx(ctx, func(db database.Store) error {
|
txErr := db.InTx(ctx, func(db database.Store) error {
|
||||||
var err error
|
var err error
|
||||||
dbInc, err = db.CreateIncident(ctx, database.CreateIncidentParams{
|
dbInc, err = db.CreateIncident(ctx, database.CreateIncidentParams{
|
||||||
ID: id,
|
ID: id,
|
||||||
|
Owner: owner.Int(),
|
||||||
Name: inc.Name,
|
Name: inc.Name,
|
||||||
Description: inc.Description,
|
Description: inc.Description,
|
||||||
StartTime: inc.StartTime.PGTypeTSTZ(),
|
StartTime: inc.StartTime.PGTypeTSTZ(),
|
||||||
|
@ -228,7 +237,7 @@ func fromDBCalls(d []database.GetIncidentCallsRow) []incidents.IncidentCall {
|
||||||
r := make([]incidents.IncidentCall, 0, len(d))
|
r := make([]incidents.IncidentCall, 0, len(d))
|
||||||
for _, v := range d {
|
for _, v := range d {
|
||||||
dur := calls.CallDuration(time.Duration(common.ZeroIfNil(v.Duration)) * time.Millisecond)
|
dur := calls.CallDuration(time.Duration(common.ZeroIfNil(v.Duration)) * time.Millisecond)
|
||||||
sub := common.PtrTo(auth.UserID(common.ZeroIfNil(v.Submitter)))
|
sub := common.PtrTo(users.UserID(common.ZeroIfNil(v.Submitter)))
|
||||||
r = append(r, incidents.IncidentCall{
|
r = append(r, incidents.IncidentCall{
|
||||||
Call: calls.Call{
|
Call: calls.Call{
|
||||||
ID: v.CallID,
|
ID: v.CallID,
|
||||||
|
|
|
@ -1 +1,26 @@
|
||||||
package rbac
|
package rbac
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/el-mike/restrict/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotAuthorized = errors.New("not authorized")
|
||||||
|
)
|
||||||
|
|
||||||
|
var policy = &restrict.PolicyDefinition{
|
||||||
|
Roles: restrict.Roles{
|
||||||
|
"User": {
|
||||||
|
Grants: restrict.GrantsMap{
|
||||||
|
"Conversation": {
|
||||||
|
&restrict.Permission{Action: "read"},
|
||||||
|
&restrict.Permission{Action: "create"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Guest": {},
|
||||||
|
"Admin": {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/common"
|
"dynatron.me/x/stillbox/internal/common"
|
||||||
|
"dynatron.me/x/stillbox/pkg/rbac"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
@ -131,6 +132,7 @@ var statusMapping = map[error]errResponder{
|
||||||
ErrBadUID: unauthErrText,
|
ErrBadUID: unauthErrText,
|
||||||
ErrBadAppName: unauthErrText,
|
ErrBadAppName: unauthErrText,
|
||||||
common.ErrPageOutOfRange: badRequestErrText,
|
common.ErrPageOutOfRange: badRequestErrText,
|
||||||
|
rbac.ErrNotAuthorized: unauthErrText,
|
||||||
}
|
}
|
||||||
|
|
||||||
func autoError(err error) render.Renderer {
|
func autoError(err error) render.Renderer {
|
||||||
|
|
|
@ -47,9 +47,11 @@ func (s *Server) setupRoutes() {
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
// auth routes get rate-limited heavily, but not using middleware
|
// auth/share routes get rate-limited heavily, but not using middleware
|
||||||
|
s.rateLimit(r)
|
||||||
r.Use(render.SetContentType(render.ContentTypeJSON))
|
r.Use(render.SetContentType(render.ContentTypeJSON))
|
||||||
s.auth.PublicRoutes(r)
|
s.auth.PublicRoutes(r)
|
||||||
|
// r.Mount("/share", s.share.ShareRouter(s.rest))
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
|
|
|
@ -30,7 +30,7 @@ func (s *service) Go(ctx context.Context) {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <- tick.C:
|
case <-tick.C:
|
||||||
err := s.store.Prune(ctx)
|
err := s.store.Prune(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("share prune failed")
|
log.Error().Err(err).Msg("share prune failed")
|
||||||
|
|
|
@ -18,15 +18,15 @@ type EntityType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EntityIncident EntityType = "incident"
|
EntityIncident EntityType = "incident"
|
||||||
EntityCall EntityType = "call"
|
EntityCall EntityType = "call"
|
||||||
)
|
)
|
||||||
|
|
||||||
// If an incident is shared, all calls that are part of it must be shared too, but this can be through the incident share (/share/bLaH/callID[.mp3])
|
// If an incident is shared, all calls that are part of it must be shared too, but this can be through the incident share (/share/bLaH/callID[.mp3])
|
||||||
|
|
||||||
type Share struct {
|
type Share struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Type EntityType `json:"entityType"`
|
Type EntityType `json:"entityType"`
|
||||||
EntityID uuid.UUID `json:"entityID"`
|
EntityID uuid.UUID `json:"entityID"`
|
||||||
Expiration *jsontypes.Time `json:"expiration"`
|
Expiration *jsontypes.Time `json:"expiration"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,9 +46,9 @@ func (s *service) NewShare(ctx context.Context, shType EntityType, shID uuid.UUI
|
||||||
}
|
}
|
||||||
|
|
||||||
share := &Share{
|
share := &Share{
|
||||||
ID: id,
|
ID: id,
|
||||||
Type: shType,
|
Type: shType,
|
||||||
EntityID: shID,
|
EntityID: shID,
|
||||||
Expiration: expT,
|
Expiration: expT,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,9 @@ type postgresStore struct {
|
||||||
|
|
||||||
func recToShare(share database.Share) *Share {
|
func recToShare(share database.Share) *Share {
|
||||||
return &Share{
|
return &Share{
|
||||||
ID: share.ID,
|
ID: share.ID,
|
||||||
Type: EntityType(share.EntityType),
|
Type: EntityType(share.EntityType),
|
||||||
EntityID: share.EntityID,
|
EntityID: share.EntityID,
|
||||||
Expiration: jsontypes.TimePtrFromTSTZ(share.Expiration),
|
Expiration: jsontypes.TimePtrFromTSTZ(share.Expiration),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,9 +46,9 @@ func (s *postgresStore) Get(ctx context.Context, id string) (*Share, error) {
|
||||||
func (s *postgresStore) Create(ctx context.Context, share *Share) error {
|
func (s *postgresStore) Create(ctx context.Context, share *Share) error {
|
||||||
db := database.FromCtx(ctx)
|
db := database.FromCtx(ctx)
|
||||||
err := db.CreateShare(ctx, database.CreateShareParams{
|
err := db.CreateShare(ctx, database.CreateShareParams{
|
||||||
ID: share.ID,
|
ID: share.ID,
|
||||||
EntityType: string(share.Type),
|
EntityType: string(share.Type),
|
||||||
EntityID: share.EntityID,
|
EntityID: share.EntityID,
|
||||||
Expiration: share.Expiration.PGTypeTSTZ(),
|
Expiration: share.Expiration.PGTypeTSTZ(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,10 @@ import (
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/common"
|
"dynatron.me/x/stillbox/internal/common"
|
||||||
"dynatron.me/x/stillbox/internal/forms"
|
"dynatron.me/x/stillbox/internal/forms"
|
||||||
"dynatron.me/x/stillbox/pkg/auth"
|
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
"dynatron.me/x/stillbox/pkg/config"
|
"dynatron.me/x/stillbox/pkg/config"
|
||||||
"dynatron.me/x/stillbox/pkg/sources"
|
"dynatron.me/x/stillbox/pkg/sources"
|
||||||
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
@ -32,16 +32,16 @@ func TestRelay(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
submitter auth.UserID
|
submitter users.UserID
|
||||||
apiKey string
|
apiKey string
|
||||||
call calls.Call
|
call calls.Call
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "base",
|
name: "base",
|
||||||
submitter: auth.UserID(1),
|
submitter: users.UserID(1),
|
||||||
call: calls.Call{
|
call: calls.Call{
|
||||||
ID: uuid.UUID([16]byte{0x52, 0xfd, 0xfc, 0x07, 0x21, 0x82, 0x45, 0x4f, 0x96, 0x3f, 0x5f, 0x0f, 0x9a, 0x62, 0x1d, 0x72}),
|
ID: uuid.UUID([16]byte{0x52, 0xfd, 0xfc, 0x07, 0x21, 0x82, 0x45, 0x4f, 0x96, 0x3f, 0x5f, 0x0f, 0x9a, 0x62, 0x1d, 0x72}),
|
||||||
Submitter: common.PtrTo(auth.UserID(1)),
|
Submitter: common.PtrTo(users.UserID(1)),
|
||||||
System: 197,
|
System: 197,
|
||||||
Talkgroup: 10101,
|
Talkgroup: 10101,
|
||||||
DateTime: time.Date(2024, 11, 10, 23, 33, 02, 0, time.Local),
|
DateTime: time.Date(2024, 11, 10, 23, 33, 02, 0, time.Local),
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/internal/forms"
|
"dynatron.me/x/stillbox/internal/forms"
|
||||||
"dynatron.me/x/stillbox/pkg/auth"
|
"dynatron.me/x/stillbox/pkg/auth"
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
|
"dynatron.me/x/stillbox/pkg/users"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
@ -70,7 +71,7 @@ func (car *CallUploadRequest) mimeType() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (car *CallUploadRequest) ToCall(submitter auth.UserID) (*calls.Call, error) {
|
func (car *CallUploadRequest) ToCall(submitter users.UserID) (*calls.Call, error) {
|
||||||
return calls.Make(&calls.Call{
|
return calls.Make(&calls.Call{
|
||||||
Submitter: &submitter,
|
Submitter: &submitter,
|
||||||
System: car.System,
|
System: car.System,
|
||||||
|
|
|
@ -528,7 +528,7 @@ func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupPara
|
||||||
return oerr
|
return oerr
|
||||||
}
|
}
|
||||||
versionBatch := db.StoreTGVersion(ctx, []database.StoreTGVersionParams{{
|
versionBatch := db.StoreTGVersion(ctx, []database.StoreTGVersionParams{{
|
||||||
Submitter: auth.UIDFrom(ctx),
|
Submitter: auth.UIDFrom(ctx).Int32Ptr(),
|
||||||
TGID: *input.TGID,
|
TGID: *input.TGID,
|
||||||
}})
|
}})
|
||||||
defer versionBatch.Close()
|
defer versionBatch.Close()
|
||||||
|
@ -578,7 +578,7 @@ func (t *cache) DeleteTG(ctx context.Context, id tgsp.ID) error {
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
|
|
||||||
err := database.FromCtx(ctx).InTx(ctx, func(db database.Store) error {
|
err := database.FromCtx(ctx).InTx(ctx, func(db database.Store) error {
|
||||||
err := db.StoreDeletedTGVersion(ctx, common.PtrTo(int32(id.System)), common.PtrTo(int32(id.Talkgroup)), auth.UIDFrom(ctx))
|
err := db.StoreDeletedTGVersion(ctx, common.PtrTo(int32(id.System)), common.PtrTo(int32(id.Talkgroup)), auth.UIDFrom(ctx).Int32Ptr())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -670,7 +670,7 @@ func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.Upse
|
||||||
versionParams = append(versionParams, database.StoreTGVersionParams{
|
versionParams = append(versionParams, database.StoreTGVersionParams{
|
||||||
SystemID: int32(system),
|
SystemID: int32(system),
|
||||||
TGID: r.TGID,
|
TGID: r.TGID,
|
||||||
Submitter: auth.UIDFrom(ctx),
|
Submitter: auth.UIDFrom(ctx).Int32Ptr(),
|
||||||
})
|
})
|
||||||
tgs = append(tgs, &tgsp.Talkgroup{
|
tgs = append(tgs, &tgsp.Talkgroup{
|
||||||
Talkgroup: r,
|
Talkgroup: r,
|
||||||
|
|
|
@ -14,11 +14,11 @@ type Store interface {
|
||||||
SetUserPrefs(ctx context.Context, uid int32, appName string, prefs []byte) error
|
SetUserPrefs(ctx context.Context, uid int32, appName string, prefs []byte) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type store struct {
|
type postgresStore struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStore() *store {
|
func NewStore() *postgresStore {
|
||||||
return new(store)
|
return new(postgresStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
type storeCtxKey string
|
type storeCtxKey string
|
||||||
|
@ -38,7 +38,7 @@ func FromCtx(ctx context.Context) Store {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) UserPrefs(ctx context.Context, uid int32, appName string) ([]byte, error) {
|
func (s *postgresStore) UserPrefs(ctx context.Context, uid int32, appName string) ([]byte, error) {
|
||||||
db := database.FromCtx(ctx)
|
db := database.FromCtx(ctx)
|
||||||
|
|
||||||
prefs, err := db.GetAppPrefs(ctx, appName, int(uid))
|
prefs, err := db.GetAppPrefs(ctx, appName, int(uid))
|
||||||
|
@ -49,8 +49,10 @@ func (s *store) UserPrefs(ctx context.Context, uid int32, appName string) ([]byt
|
||||||
return []byte(prefs), err
|
return []byte(prefs), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *store) SetUserPrefs(ctx context.Context, uid int32, appName string, prefs []byte) error {
|
func (s *postgresStore) SetUserPrefs(ctx context.Context, uid int32, appName string, prefs []byte) error {
|
||||||
db := database.FromCtx(ctx)
|
db := database.FromCtx(ctx)
|
||||||
|
|
||||||
return db.SetAppPrefs(ctx, appName, prefs, int(uid))
|
return db.SetAppPrefs(ctx, appName, prefs, int(uid))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//func (s *postgresStore)
|
||||||
|
|
30
pkg/users/user.go
Normal file
30
pkg/users/user.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserID int
|
||||||
|
|
||||||
|
func (u *UserID) Int32Ptr() *int32 {
|
||||||
|
if u == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
i := int32(*u)
|
||||||
|
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UserID) Int() int {
|
||||||
|
return int(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID UserID
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
Email string
|
||||||
|
IsAdmin bool
|
||||||
|
Prefs json.RawMessage
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
CREATE TABLE IF NOT EXISTS users(
|
CREATE TABLE IF NOT EXISTS users(
|
||||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY (START WITH 1),
|
||||||
username VARCHAR (255) UNIQUE NOT NULL,
|
username VARCHAR (255) UNIQUE NOT NULL,
|
||||||
password TEXT NOT NULL,
|
password TEXT NOT NULL,
|
||||||
email TEXT NOT NULL,
|
email TEXT NOT NULL,
|
||||||
|
@ -141,6 +141,7 @@ CREATE TABLE IF NOT EXISTS settings(
|
||||||
CREATE TABLE IF NOT EXISTS incidents(
|
CREATE TABLE IF NOT EXISTS incidents(
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
|
owner INTEGER NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
start_time TIMESTAMPTZ,
|
start_time TIMESTAMPTZ,
|
||||||
end_time TIMESTAMPTZ,
|
end_time TIMESTAMPTZ,
|
||||||
|
|
|
@ -33,6 +33,7 @@ WHERE incident_id = @incident_id AND call_id = @call_id;
|
||||||
INSERT INTO incidents (
|
INSERT INTO incidents (
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
owner,
|
||||||
description,
|
description,
|
||||||
start_time,
|
start_time,
|
||||||
end_time,
|
end_time,
|
||||||
|
@ -41,6 +42,7 @@ INSERT INTO incidents (
|
||||||
) VALUES (
|
) VALUES (
|
||||||
@id,
|
@id,
|
||||||
@name,
|
@name,
|
||||||
|
@owner,
|
||||||
sqlc.narg('description'),
|
sqlc.narg('description'),
|
||||||
sqlc.narg('start_time'),
|
sqlc.narg('start_time'),
|
||||||
sqlc.narg('end_time'),
|
sqlc.narg('end_time'),
|
||||||
|
@ -54,6 +56,7 @@ RETURNING *;
|
||||||
SELECT
|
SELECT
|
||||||
i.id,
|
i.id,
|
||||||
i.name,
|
i.name,
|
||||||
|
i.owner,
|
||||||
i.description,
|
i.description,
|
||||||
i.start_time,
|
i.start_time,
|
||||||
i.end_time,
|
i.end_time,
|
||||||
|
@ -148,6 +151,7 @@ ORDER BY ic.call_date ASC;
|
||||||
SELECT
|
SELECT
|
||||||
i.id,
|
i.id,
|
||||||
i.name,
|
i.name,
|
||||||
|
i.owner,
|
||||||
i.description,
|
i.description,
|
||||||
i.start_time,
|
i.start_time,
|
||||||
i.end_time,
|
i.end_time,
|
||||||
|
|
Loading…
Reference in a new issue