Compare commits
6 Commits
v2023.4.20
...
v2023.4.21
Author | SHA1 | Date | |
---|---|---|---|
2dff948e00 | |||
1606ff5937 | |||
90020d6ea6 | |||
7b6e39088d | |||
9944a37670 | |||
78307b6850 |
8
Makefile
8
Makefile
@ -15,8 +15,8 @@ SHELL := bash
|
||||
|
||||
build: build-edge-cli build-client-sdk-test-app
|
||||
|
||||
watch:
|
||||
go run -mod=readonly github.com/cortesi/modd/cmd/modd@latest
|
||||
watch: tools/modd/bin/modd
|
||||
tools/modd/bin/modd
|
||||
|
||||
.PHONY: test
|
||||
test: test-go
|
||||
@ -102,3 +102,7 @@ tools/yq/bin/yq:
|
||||
mkdir -p tools/yq/bin
|
||||
curl -L --output tools/yq/bin/yq https://github.com/mikefarah/yq/releases/download/v4.31.1/yq_linux_amd64
|
||||
chmod +x tools/yq/bin/yq
|
||||
|
||||
tools/modd/bin/modd:
|
||||
mkdir -p tools/modd/bin
|
||||
GOBIN=$(PWD)/tools/modd/bin go install -mod=readonly github.com/cortesi/modd/cmd/modd@latest
|
@ -26,8 +26,10 @@ import (
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/fetch"
|
||||
netModule "forge.cadoles.com/arcad/edge/pkg/module/net"
|
||||
shareModule "forge.cadoles.com/arcad/edge/pkg/module/share"
|
||||
shareSqlite "forge.cadoles.com/arcad/edge/pkg/module/share/sqlite"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
|
||||
storageSqlite "forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||
@ -76,6 +78,11 @@ func RunCommand() *cli.Command {
|
||||
Usage: "use `FILE` for SQLite storage database",
|
||||
Value: ".edge/%APPID%/data.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "shared-resources-file",
|
||||
Usage: "use `FILE` for SQLite shared resources database",
|
||||
Value: ".edge/shared-resources.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "accounts-file",
|
||||
Usage: "use `FILE` as local accounts",
|
||||
@ -90,6 +97,7 @@ func RunCommand() *cli.Command {
|
||||
logLevel := ctx.Int("log-level")
|
||||
storageFile := ctx.String("storage-file")
|
||||
accountsFile := ctx.String("accounts-file")
|
||||
sharedResourcesFile := ctx.String("shared-resources-file")
|
||||
|
||||
logger.SetFormat(logger.Format(logFormat))
|
||||
logger.SetLevel(logger.Level(logLevel))
|
||||
@ -135,7 +143,7 @@ func RunCommand() *cli.Command {
|
||||
|
||||
appCtx := logger.With(cmdCtx, logger.F("address", address))
|
||||
|
||||
if err := runApp(appCtx, path, address, storageFile, accountsFile, appsRepository); err != nil {
|
||||
if err := runApp(appCtx, path, address, storageFile, accountsFile, appsRepository, sharedResourcesFile); err != nil {
|
||||
logger.Error(appCtx, "could not run app", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
}(p, port, idx)
|
||||
@ -148,7 +156,7 @@ func RunCommand() *cli.Command {
|
||||
}
|
||||
}
|
||||
|
||||
func runApp(ctx context.Context, path string, address string, storageFile string, accountsFile string, appRepository appModule.Repository) error {
|
||||
func runApp(ctx context.Context, path string, address string, storageFile string, accountsFile string, appRepository appModule.Repository, sharedResourcesFile string) error {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not resolve path '%s'", path)
|
||||
@ -172,44 +180,37 @@ func runApp(ctx context.Context, path string, address string, storageFile string
|
||||
|
||||
ctx = logger.With(ctx, logger.F("appID", manifest.ID))
|
||||
|
||||
storageFile = injectAppID(storageFile, manifest.ID)
|
||||
|
||||
if err := ensureDir(storageFile); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
db, err := sqlite.Open(storageFile)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
accountsFile = injectAppID(accountsFile, manifest.ID)
|
||||
|
||||
accounts, err := loadLocalAccounts(accountsFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load local accounts")
|
||||
}
|
||||
|
||||
// Add auth handler
|
||||
key, err := dummyKey()
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
ds := sqlite.NewDocumentStoreWithDB(db)
|
||||
bs := sqlite.NewBlobStoreWithDB(db)
|
||||
bus := memory.NewBus()
|
||||
deps := &moduleDeps{}
|
||||
funcs := []ModuleDepFunc{
|
||||
initMemoryBus,
|
||||
initDatastores(storageFile, manifest.ID),
|
||||
initAccounts(accountsFile, manifest.ID),
|
||||
initShareRepository(sharedResourcesFile),
|
||||
initAppRepository(appRepository),
|
||||
}
|
||||
|
||||
for _, fn := range funcs {
|
||||
if err := fn(deps); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
handler := appHTTP.NewHandler(
|
||||
appHTTP.WithBus(bus),
|
||||
appHTTP.WithServerModules(getServerModules(bus, ds, bs, appRepository)...),
|
||||
appHTTP.WithBus(deps.Bus),
|
||||
appHTTP.WithServerModules(getServerModules(deps)...),
|
||||
appHTTP.WithHTTPMounts(
|
||||
appModule.Mount(appRepository),
|
||||
authModule.Mount(
|
||||
authHTTP.NewLocalHandler(
|
||||
jwa.HS256, key,
|
||||
authHTTP.WithRoutePrefix("/auth"),
|
||||
authHTTP.WithAccounts(accounts...),
|
||||
authHTTP.WithAccounts(deps.Accounts...),
|
||||
),
|
||||
authModule.WithJWT(dummyKeySet),
|
||||
),
|
||||
@ -235,21 +236,34 @@ func runApp(ctx context.Context, path string, address string, storageFile string
|
||||
return nil
|
||||
}
|
||||
|
||||
func getServerModules(bus bus.Bus, ds storage.DocumentStore, bs storage.BlobStore, appRepository appModule.Repository) []app.ServerModuleFactory {
|
||||
type moduleDeps struct {
|
||||
AppID app.ID
|
||||
Bus bus.Bus
|
||||
DocumentStore storage.DocumentStore
|
||||
BlobStore storage.BlobStore
|
||||
AppRepository appModule.Repository
|
||||
ShareRepository shareModule.Repository
|
||||
Accounts []authHTTP.LocalAccount
|
||||
}
|
||||
|
||||
type ModuleDepFunc func(*moduleDeps) error
|
||||
|
||||
func getServerModules(deps *moduleDeps) []app.ServerModuleFactory {
|
||||
return []app.ServerModuleFactory{
|
||||
module.ContextModuleFactory(),
|
||||
module.ConsoleModuleFactory(),
|
||||
cast.CastModuleFactory(),
|
||||
module.LifecycleModuleFactory(),
|
||||
netModule.ModuleFactory(bus),
|
||||
module.RPCModuleFactory(bus),
|
||||
module.StoreModuleFactory(ds),
|
||||
blob.ModuleFactory(bus, bs),
|
||||
netModule.ModuleFactory(deps.Bus),
|
||||
module.RPCModuleFactory(deps.Bus),
|
||||
module.StoreModuleFactory(deps.DocumentStore),
|
||||
blob.ModuleFactory(deps.Bus, deps.BlobStore),
|
||||
authModule.ModuleFactory(
|
||||
authModule.WithJWT(dummyKeySet),
|
||||
),
|
||||
appModule.ModuleFactory(appRepository),
|
||||
fetch.ModuleFactory(bus),
|
||||
appModule.ModuleFactory(deps.AppRepository),
|
||||
fetch.ModuleFactory(deps.Bus),
|
||||
shareModule.ModuleFactory(deps.AppID, deps.ShareRepository),
|
||||
}
|
||||
}
|
||||
|
||||
@ -367,7 +381,11 @@ func findMatchingDeviceAddress(ctx context.Context, from string, defaultAddr str
|
||||
continue
|
||||
}
|
||||
|
||||
return ip.String(), nil
|
||||
if ip.To4() == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return ip.To4().String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,6 +393,10 @@ func findMatchingDeviceAddress(ctx context.Context, from string, defaultAddr str
|
||||
}
|
||||
|
||||
func newAppRepository(host string, basePort uint64, manifests ...*app.Manifest) *appModuleMemory.Repository {
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
|
||||
return appModuleMemory.NewRepository(
|
||||
func(ctx context.Context, id app.ID, from string) (string, error) {
|
||||
appIndex := 0
|
||||
@ -395,3 +417,65 @@ func newAppRepository(host string, basePort uint64, manifests ...*app.Manifest)
|
||||
manifests...,
|
||||
)
|
||||
}
|
||||
|
||||
func initAppRepository(repo appModule.Repository) ModuleDepFunc {
|
||||
return func(deps *moduleDeps) error {
|
||||
deps.AppRepository = repo
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func initMemoryBus(deps *moduleDeps) error {
|
||||
deps.Bus = memory.NewBus()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initDatastores(storageFile string, appID app.ID) ModuleDepFunc {
|
||||
return func(deps *moduleDeps) error {
|
||||
storageFile = injectAppID(storageFile, appID)
|
||||
|
||||
if err := ensureDir(storageFile); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
db, err := storageSqlite.Open(storageFile)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
deps.DocumentStore = storageSqlite.NewDocumentStoreWithDB(db)
|
||||
deps.BlobStore = storageSqlite.NewBlobStoreWithDB(db)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func initAccounts(accountsFile string, appID app.ID) ModuleDepFunc {
|
||||
return func(deps *moduleDeps) error {
|
||||
accountsFile = injectAppID(accountsFile, appID)
|
||||
|
||||
accounts, err := loadLocalAccounts(accountsFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load local accounts")
|
||||
}
|
||||
|
||||
deps.Accounts = accounts
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func initShareRepository(shareRepositoryFile string) ModuleDepFunc {
|
||||
return func(deps *moduleDeps) error {
|
||||
if err := ensureDir(shareRepositoryFile); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
repo := shareSqlite.NewRepository(shareRepositoryFile)
|
||||
|
||||
deps.ShareRepository = repo
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/barnybug/go-cast/discovery"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@ -14,21 +14,26 @@ func ScanCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "scan",
|
||||
Usage: "Scan network for casting devices",
|
||||
Flags: []cli.Flag{},
|
||||
Flags: []cli.Flag{
|
||||
&cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Aliases: []string{"t"},
|
||||
Value: 30 * time.Second,
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
service := discovery.NewService(ctx.Context)
|
||||
defer service.Stop()
|
||||
timeout := ctx.Duration("timeout")
|
||||
|
||||
go func() {
|
||||
if err := service.Run(ctx.Context, time.Second); err != nil && !errors.Is(err, context.DeadlineExceeded) {
|
||||
searchCtx, cancel := context.WithTimeout(ctx.Context, timeout)
|
||||
defer cancel()
|
||||
|
||||
devices, err := cast.SearchDevices(searchCtx)
|
||||
if err != nil {
|
||||
log.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
}()
|
||||
|
||||
found := service.Found()
|
||||
|
||||
for device := range found {
|
||||
log.Printf("[DEVICE] %s %s %s:%d", device.Uuid(), device.Name(), device.IP().String(), device.Port())
|
||||
for dev := range devices {
|
||||
log.Printf("[DEVICE] %s %s %s:%d", dev.UUID, dev.Name, dev.Host.String(), dev.Port)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -29,4 +29,5 @@ Listes des modules disponibles côté serveur.
|
||||
- [`fetch`](./fetch.md)
|
||||
- [`net`](./net.md)
|
||||
- [`rpc`](./rpc.md)
|
||||
- [`share`](./share.md)
|
||||
- [`store`](./store.md)
|
||||
|
143
doc/apps/server-api/share.md
Normal file
143
doc/apps/server-api/share.md
Normal file
@ -0,0 +1,143 @@
|
||||
# Module `share`
|
||||
|
||||
Ce module permet partager des ressources à destination des autres applications exécutées dans l'environnement Edge.
|
||||
|
||||
## Propriétés
|
||||
|
||||
### `share.ANY_TYPE`, `share.ANY_NAME`
|
||||
|
||||
Les propriétés `share.ANY_TYPE` et `share.ANY_NAME` sont utilisées dans la méthode `share.findResources()` pour récupérer ne pas appliquer de filtre spécifique au type ou au nom des attributs respectivement.
|
||||
|
||||
### `share.TYPE_TEXT`, `share.TYPE_NUMBER`, `share.TYPE_PATH`, `share.TYPE_BOOL`
|
||||
|
||||
Ces propriétés correspondant aux types d'attributs possibles dans une ressource.
|
||||
|
||||
Le type `share.TYPE_PATH` décrit un "chemin" destiné à être transformé en URL par l'application consommatrice de la ressource sous la forme `${APP_URL}${PATH}`. Ce type d'attribut peut être utilisé pour partager des URLs (médias, pages, etc) entre applications.
|
||||
|
||||
## Méthodes
|
||||
|
||||
### `share.upsertResource(ctx: Context, resourceId: string, ...attributes: Attribute[]): Resource`
|
||||
|
||||
Cette méthode permet de créer une ressource ou de la mettre à jour si elle existe déjà. Elle prend en paramètre le contexte d'exécution, l'identifiant de la ressource et une liste d'attributs.
|
||||
|
||||
Si la ressource n'existe pas, elle sera créée avec les attributs fournis. Si elle existe, les attributs existants seront mis à jour avec les valeurs fournies.
|
||||
|
||||
#### Arguments
|
||||
|
||||
- `ctx: Context`: Le contexte d'exécution.
|
||||
- `resourceId: string`: L'identifiant de la ressource.
|
||||
- `...attributes: Attribute[]`: Une liste d'attributs. Chaque attribut est représenté par un objet de type `Attribute`.
|
||||
|
||||
#### Valeur de retour
|
||||
|
||||
La méthode retourne un objet de type `Resource` qui représente la ressource créée ou mise à jour.
|
||||
|
||||
#### Usage
|
||||
|
||||
```javascript
|
||||
const resource = share.upsertResource(ctx, "my-resource", { name: "color", type: share.TYPE_TEXT, value: "red" });
|
||||
console.log(resource);
|
||||
// Output: { id: "my-resource", origin: "my.app", attributes: [{ name: "color", type: "text", value: "red", createdAt: "2023-04-21T14:30:00Z", updatedAt: "2023-04-21T14:30:00Z" }] }
|
||||
```
|
||||
|
||||
### `share.findResources(ctx: Context, withAttribute?: string, withType?: string): []Resource`
|
||||
|
||||
Cette méthode permet de rechercher des ressources en fonction de leurs attributs. Elle prend en paramètre le contexte d'exécution et deux paramètres optionnels qui permettent de filtrer les ressources.
|
||||
|
||||
#### Arguments
|
||||
|
||||
- `ctx: Context`: Le contexte d'exécution.
|
||||
- `withAttribute?: string`: (optionnel) Le nom de l'attribut à rechercher (`share.ANY_NAME` par défaut)
|
||||
- `withType?: string`: (optionnel) Le type de l'attribut à rechercher (`share.ANY_TYPE` par défaut)
|
||||
|
||||
#### Valeur de retour
|
||||
|
||||
La méthode retourne un tableau d'objets de type `Resource` qui représentent les ressources correspondant aux critères de recherche.
|
||||
|
||||
#### Usage
|
||||
|
||||
```typescript
|
||||
const resources = share.findResources(ctx, "color", share.TYPE_TEXT);
|
||||
console.log(resources);
|
||||
// Output: [{ id: "my-resource", origin: "my/app", attributes: [{ name: "color", type: "text", value: "red", createdAt: "2023-04-21T14:30:00Z", updatedAt: "2023-04-21T14:30:00Z" }] }]
|
||||
```
|
||||
|
||||
### `share.deleteAttributes(ctx: Context, resourceId: string, ...names: string[]): Resource`
|
||||
|
||||
Cette méthode supprime un ou plusieurs attributs de la ressource spécifiée.
|
||||
|
||||
#### Arguments
|
||||
|
||||
- `ctx: Context`: contexte d'exécution
|
||||
- `resourceId: string`: identifiant unique de la ressource à modifier
|
||||
- `...names: string[]`: tableau de noms d'attributs à supprimer
|
||||
|
||||
#### Valeur de retour
|
||||
|
||||
La méthode retourne un objet de type `Resource` qui représente la ressource modifiée.
|
||||
|
||||
#### Usage
|
||||
|
||||
```typescript
|
||||
const resource = share.upsertResource(ctx, "my-resource", { name: "color", type: share.TYPE_TEXT, value: "red" });
|
||||
console.log(resource);
|
||||
// Output: { id: "my-resource", origin: "my.app", attributes: [{ name: "color", type: "text", value: "red", createdAt: "2023-04-21T14:30:00Z", updatedAt: "2023-04-21T14:30:00Z" }] }
|
||||
```
|
||||
|
||||
### `share.deleteResource(ctx: Context, resourceId: string)`
|
||||
|
||||
Cette méthode supprime la ressource spécifiée.
|
||||
|
||||
#### Arguments
|
||||
|
||||
- `ctx: Context`: contexte d'exécution
|
||||
- `resourceId: string`: identifiant unique de la ressource à supprimer
|
||||
|
||||
#### Valeur de retour
|
||||
|
||||
La méthode ne retourne pas de valeur.
|
||||
|
||||
#### Usage
|
||||
|
||||
```typescript
|
||||
const resource = share.deleteResource(ctx, "my-resource");
|
||||
```
|
||||
|
||||
## Objets
|
||||
|
||||
### `Context`
|
||||
|
||||
Voir la documentation du module [`context`](./context.md)
|
||||
|
||||
|
||||
### `Resource`
|
||||
|
||||
```typescript
|
||||
interface Resource {
|
||||
id: string
|
||||
origin: string
|
||||
attributes: Attribute[]
|
||||
}
|
||||
```
|
||||
|
||||
### `Attribute`
|
||||
|
||||
```typescript
|
||||
interface Attribute {
|
||||
name: string
|
||||
type: ValueType
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
```
|
||||
|
||||
### `ValueType`
|
||||
|
||||
```typescript
|
||||
enum ValueType {
|
||||
TYPE_TEXT = "text",
|
||||
TYPE_PATH = "path",
|
||||
TYPE_NUMBER = "number",
|
||||
TYPE_BOOL = "bool"
|
||||
}
|
||||
```
|
29
go.mod
29
go.mod
@ -3,37 +3,37 @@ module forge.cadoles.com/arcad/edge
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/hashicorp/mdns v1.0.5
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.8
|
||||
modernc.org/sqlite v1.20.4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/brutella/dnssd v1.2.6 // indirect
|
||||
cloud.google.com/go v0.75.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||
github.com/go-playground/locales v0.12.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.16.0 // indirect
|
||||
github.com/goccy/go-json v0.9.11 // indirect
|
||||
github.com/gogo/protobuf v0.0.0-20161014173244-50d1bd39ce4e // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/hashicorp/go.net v0.0.0-20151006203346-104dcad90073 // indirect
|
||||
github.com/hashicorp/mdns v0.0.0-20151206042412-9d85cf22f9f8 // indirect
|
||||
github.com/leodido/go-urn v1.1.0 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc v1.0.4 // indirect
|
||||
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||
github.com/lestrrat-go/option v1.0.0 // indirect
|
||||
github.com/miekg/dns v1.1.50 // indirect
|
||||
github.com/miekg/dns v1.1.53 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705 // indirect
|
||||
google.golang.org/grpc v1.35.0 // indirect
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cdr.dev/slog v1.4.0 // indirect
|
||||
cdr.dev/slog v1.4.0
|
||||
github.com/alecthomas/chroma v0.7.0 // indirect
|
||||
github.com/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||
github.com/dop251/goja v0.0.0-20230203172422-5460598cfa32
|
||||
github.com/dop251/goja_nodejs v0.0.0-20230207183254-2229640ea097
|
||||
@ -55,18 +55,17 @@ require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/afero v1.9.3 // indirect
|
||||
github.com/urfave/cli/v2 v2.24.3
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230419082146-a94d9ed7202b
|
||||
go.opencensus.io v0.22.5 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/term v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
golang.org/x/crypto v0.7.0
|
||||
golang.org/x/mod v0.10.0
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/tools v0.8.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
|
114
go.sum
114
go.sum
@ -5,7 +5,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY=
|
||||
@ -18,7 +17,7 @@ cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZ
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0 h1:XgtDnVJRCPEUG21gjFiRPz4zI1Mjg16R+NYQjfmU4XY=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
@ -37,25 +36,25 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||
github.com/alecthomas/chroma v0.7.0 h1:z+0HgTUmkpRDRz0SRSdMaqOLfJV4F+N1FPDZUZIDUzw=
|
||||
github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||
github.com/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692 h1:JW4WZlqyaNWUUahfr7MigeDW6jmtam5cTzzo1lwsFhE=
|
||||
github.com/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692/go.mod h1:Au0ipPuCBA7zsOC61SnyrYetm8VT3vo1UJtwHeYke44=
|
||||
github.com/brutella/dnssd v1.2.6 h1:/0P13JkHLRzeLQkWRPEn4hJCr4T3NfknIFw3aNPIC34=
|
||||
github.com/brutella/dnssd v1.2.6/go.mod h1:JoW2sJUrmVIef25G6lrLj7HS6Xdwh6q8WUIvMkkBYXs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
@ -78,7 +77,6 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
|
||||
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
||||
@ -119,8 +117,6 @@ github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gogo/protobuf v0.0.0-20161014173244-50d1bd39ce4e h1:eeyMpoxANuWNQ9O2auv4wXxJsrXzLUhdHaOmNWEGkRY=
|
||||
github.com/gogo/protobuf v0.0.0-20161014173244-50d1bd39ce4e/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -147,6 +143,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
@ -160,6 +157,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
@ -171,8 +169,8 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@ -180,7 +178,6 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
||||
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
@ -188,13 +185,13 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+
|
||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/go.net v0.0.0-20151006203346-104dcad90073 h1:9dodOMuH6u7LvPEkVydBv6KTHdm+SqsHOxHTzRW+1+w=
|
||||
github.com/hashicorp/go.net v0.0.0-20151006203346-104dcad90073/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/mdns v0.0.0-20151206042412-9d85cf22f9f8 h1:yupxZNIxm5U8Tfb8g65irIuHkgF8c4koHC7daPSyMTE=
|
||||
github.com/hashicorp/mdns v0.0.0-20151206042412-9d85cf22f9f8/go.mod h1:aa76Av3qgPeIQp9Y3qIkTBPieQYNkQ13Kxe7pze9Wb0=
|
||||
github.com/hashicorp/mdns v1.0.5 h1:1M5hW1cunYeoXOqHwEb/GBDDHAFo0Yqb/uz/beC6LbE=
|
||||
github.com/hashicorp/mdns v1.0.5/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/igm/sockjs-go/v3 v3.0.2 h1:2m0k53w0DBiGozeQUIEPR6snZFmpFpYvVsGnfLPNXbE=
|
||||
@ -205,12 +202,13 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
@ -231,14 +229,14 @@ github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaa
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/miekg/dns v0.0.0-20161006100029-fc4e1e2843d8 h1:ALvJ9V8nNf04PFHMR2sot56N/pjrx5LzZGvUlnhdiCE=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/miekg/dns v0.0.0-20161006100029-fc4e1e2843d8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
|
||||
github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
@ -253,19 +251,19 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
|
||||
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
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=
|
||||
@ -275,9 +273,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
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.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.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli/v2 v2.24.3 h1:7Q1w8VN8yE0MJEHP06bv89PjYsN4IHWED2s1v/Zlfm0=
|
||||
@ -290,10 +288,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230206085656-dec695f0e2e9 h1:6JlkcdjYVQglPWYuemK2MoZAtRE4vFx85zLXflGIyI8=
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230206085656-dec695f0e2e9/go.mod h1:3sus4zjoUv1GB7eDLL60QaPkUnXJCWBpjvbe0jWifeY=
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230419082146-a94d9ed7202b h1:nkvOl8TCj/mErADnwFFynjxBtC+hHsrESw6rw56JGmg=
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230419082146-a94d9ed7202b/go.mod h1:3sus4zjoUv1GB7eDLL60QaPkUnXJCWBpjvbe0jWifeY=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@ -309,11 +304,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
@ -349,12 +340,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20161013035702-8b4af36cd21a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -384,18 +372,15 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -404,7 +389,6 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -414,9 +398,9 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -450,44 +434,32 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -534,15 +506,10 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
||||
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -566,7 +533,6 @@ google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSr
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@ -606,9 +572,8 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705 h1:PYBmACG+YEv8uQPW0r1kJj8tR+gkF0UWq7iFdUezwEw=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
@ -625,6 +590,7 @@ google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
@ -635,11 +601,14 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
@ -647,6 +616,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@ -661,6 +631,8 @@ modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
|
||||
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
@ -673,8 +645,10 @@ modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
|
||||
modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
|
||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
@ -4,7 +4,7 @@ ARG HTTP_PROXY=
|
||||
ARG HTTPS_PROXY=
|
||||
ARG http_proxy=
|
||||
ARG https_proxy=
|
||||
ARG GO_VERSION=1.19.2
|
||||
ARG GO_VERSION=1.20.2
|
||||
|
||||
# Install dev environment dependencies
|
||||
RUN export DEBIAN_FRONTEND=noninteractive &&\
|
||||
|
@ -3,10 +3,10 @@ package cast
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/barnybug/go-cast"
|
||||
"github.com/barnybug/go-cast/discovery"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
@ -18,6 +18,15 @@ type Device struct {
|
||||
Name string `goja:"name" json:"name"`
|
||||
}
|
||||
|
||||
type CachedDevice struct {
|
||||
Device
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
func (d CachedDevice) Expired() bool {
|
||||
return d.UpdatedAt.Add(30 * time.Minute).Before(time.Now())
|
||||
}
|
||||
|
||||
type DeviceStatus struct {
|
||||
CurrentApp DeviceStatusCurrentApp `goja:"currentApp" json:"currentApp"`
|
||||
Volume DeviceStatusVolume `goja:"volume" json:"volume"`
|
||||
@ -35,9 +44,36 @@ type DeviceStatusVolume struct {
|
||||
}
|
||||
|
||||
const (
|
||||
serviceDiscoveryPollingInterval time.Duration = 2 * time.Second
|
||||
serviceDiscoveryPollingInterval time.Duration = 500 * time.Millisecond
|
||||
)
|
||||
|
||||
var cache sync.Map
|
||||
|
||||
func getCachedDevice(uuid string) (Device, bool) {
|
||||
value, exists := cache.Load(uuid)
|
||||
if !exists {
|
||||
return Device{}, false
|
||||
}
|
||||
|
||||
cachedDevice, ok := value.(CachedDevice)
|
||||
if !ok {
|
||||
return Device{}, false
|
||||
}
|
||||
|
||||
if cachedDevice.Expired() {
|
||||
return Device{}, false
|
||||
}
|
||||
|
||||
return cachedDevice.Device, true
|
||||
}
|
||||
|
||||
func cacheDevice(dev Device) {
|
||||
cache.Store(dev.UUID, CachedDevice{
|
||||
Device: dev,
|
||||
UpdatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
func getDeviceClientByUUID(ctx context.Context, uuid string) (*cast.Client, error) {
|
||||
device, err := FindDeviceByUUID(ctx, uuid)
|
||||
if err != nil {
|
||||
@ -49,82 +85,114 @@ func getDeviceClientByUUID(ctx context.Context, uuid string) (*cast.Client, erro
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func FindDeviceByUUID(ctx context.Context, uuid string) (*Device, error) {
|
||||
service := discovery.NewService(ctx)
|
||||
defer service.Stop()
|
||||
|
||||
go func() {
|
||||
if err := service.Run(ctx, serviceDiscoveryPollingInterval); err != nil {
|
||||
logger.Error(ctx, "error while running cast service discovery", logger.E(errors.WithStack(err)))
|
||||
func FindDeviceByUUID(ctx context.Context, uuid string) (Device, error) {
|
||||
device, exists := getCachedDevice(uuid)
|
||||
if exists {
|
||||
return device, nil
|
||||
}
|
||||
}()
|
||||
|
||||
LOOP:
|
||||
for {
|
||||
select {
|
||||
case c := <-service.Found():
|
||||
if c.Uuid() == uuid {
|
||||
return &Device{
|
||||
Host: c.IP().To4(),
|
||||
Port: c.Port(),
|
||||
Name: c.Name(),
|
||||
UUID: c.Uuid(),
|
||||
}, nil
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
devices, err := SearchDevices(ctx)
|
||||
if err != nil {
|
||||
return Device{}, nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
break LOOP
|
||||
|
||||
for dev := range devices {
|
||||
if dev.UUID == uuid {
|
||||
return dev, nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := ctx.Err(); err != nil {
|
||||
return Device{}, errors.Errorf("could not find device '%s'", uuid)
|
||||
}
|
||||
|
||||
func ListDevices(ctx context.Context, refresh bool) ([]Device, error) {
|
||||
devices := make([]Device, 0)
|
||||
|
||||
if !refresh {
|
||||
cache.Range(func(key, value any) bool {
|
||||
cached, ok := value.(CachedDevice)
|
||||
if !ok || cached.Expired() {
|
||||
return true
|
||||
}
|
||||
|
||||
devices = append(devices, cached.Device)
|
||||
return true
|
||||
})
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
ch, err := SearchDevices(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil, errors.WithStack(ErrDeviceNotFound)
|
||||
for dev := range ch {
|
||||
devices = append(devices, dev)
|
||||
}
|
||||
|
||||
func FindDevices(ctx context.Context) ([]*Device, error) {
|
||||
service := discovery.NewService(ctx)
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
var searchDevicesMutex sync.Mutex
|
||||
|
||||
func SearchDevices(ctx context.Context) (chan Device, error) {
|
||||
service := NewService(ctx)
|
||||
defer service.Stop()
|
||||
|
||||
go func() {
|
||||
searchDevicesMutex.Lock()
|
||||
defer searchDevicesMutex.Unlock()
|
||||
|
||||
if err := service.Run(ctx, serviceDiscoveryPollingInterval); err != nil && !errors.Is(err, context.DeadlineExceeded) {
|
||||
logger.Error(ctx, "error while running cast service discovery", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
}()
|
||||
|
||||
devices := make([]*Device, 0)
|
||||
devices := make(chan Device)
|
||||
|
||||
go func() {
|
||||
defer close(devices)
|
||||
|
||||
found := make(map[string]struct{})
|
||||
|
||||
LOOP:
|
||||
for {
|
||||
select {
|
||||
case c := <-service.Found():
|
||||
if _, exists := found[c.Uuid()]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
devices = append(devices, &Device{
|
||||
dev := Device{
|
||||
Host: c.IP().To4(),
|
||||
Port: c.Port(),
|
||||
Name: c.Name(),
|
||||
UUID: c.Uuid(),
|
||||
})
|
||||
found[c.Uuid()] = struct{}{}
|
||||
}
|
||||
|
||||
if _, exists := found[dev.UUID]; !exists {
|
||||
devices <- dev
|
||||
|
||||
found[dev.UUID] = struct{}{}
|
||||
}
|
||||
|
||||
cacheDevice(dev)
|
||||
|
||||
case <-ctx.Done():
|
||||
break LOOP
|
||||
}
|
||||
}
|
||||
|
||||
if err := ctx.Err(); err != nil && !errors.Is(err, context.DeadlineExceeded) {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
var loadURLMutex sync.Mutex
|
||||
|
||||
func LoadURL(ctx context.Context, deviceUUID string, url string) error {
|
||||
loadURLMutex.Lock()
|
||||
defer loadURLMutex.Unlock()
|
||||
|
||||
client, err := getDeviceClientByUUID(ctx, deviceUUID)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
@ -153,7 +221,12 @@ func isLoadURLContextExceeded(err error) bool {
|
||||
return err.Error() == "Failed to send load command: context deadline exceeded"
|
||||
}
|
||||
|
||||
var stopCastMutex sync.Mutex
|
||||
|
||||
func StopCast(ctx context.Context, deviceUUID string) error {
|
||||
stopCastMutex.Lock()
|
||||
defer stopCastMutex.Unlock()
|
||||
|
||||
client, err := getDeviceClientByUUID(ctx, deviceUUID)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
@ -171,7 +244,12 @@ func StopCast(ctx context.Context, deviceUUID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var getStatusMutex sync.Mutex
|
||||
|
||||
func getStatus(ctx context.Context, deviceUUID string) (*DeviceStatus, error) {
|
||||
getStatusMutex.Lock()
|
||||
defer getStatusMutex.Unlock()
|
||||
|
||||
client, err := getDeviceClientByUUID(ctx, deviceUUID)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
|
@ -26,11 +26,24 @@ func TestCastLoadURL(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
devices, err := FindDevices(ctx)
|
||||
devices, err := ListDevices(ctx, true)
|
||||
if err != nil {
|
||||
t.Error(errors.WithStack(err))
|
||||
}
|
||||
|
||||
t.Logf("DEVICES: %s", spew.Sdump(devices))
|
||||
|
||||
if e, g := 1, len(devices); e != g {
|
||||
t.Fatalf("len(devices): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
devices, err = ListDevices(ctx, false)
|
||||
if err != nil {
|
||||
t.Error(errors.WithStack(err))
|
||||
}
|
||||
|
||||
t.Logf("CACHED DEVICES: %s", spew.Sdump(devices))
|
||||
|
||||
if e, g := 1, len(devices); e != g {
|
||||
t.Fatalf("len(devices): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
@ -52,7 +65,7 @@ func TestCastLoadURL(t *testing.T) {
|
||||
t.Error(errors.WithStack(err))
|
||||
}
|
||||
|
||||
spew.Dump(status)
|
||||
t.Logf("DEVICE STATUS: %s", spew.Sdump(status))
|
||||
|
||||
ctx, cancel4 := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel4()
|
||||
|
250
pkg/module/cast/discovery.go
Normal file
250
pkg/module/cast/discovery.go
Normal file
@ -0,0 +1,250 @@
|
||||
package cast
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
|
||||
"github.com/barnybug/go-cast"
|
||||
"github.com/barnybug/go-cast/log"
|
||||
"github.com/hashicorp/mdns"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
found chan *cast.Client
|
||||
entriesCh chan *mdns.ServiceEntry
|
||||
|
||||
stopPeriodic chan struct{}
|
||||
}
|
||||
|
||||
func NewService(ctx context.Context) *Service {
|
||||
s := &Service{
|
||||
found: make(chan *cast.Client),
|
||||
entriesCh: make(chan *mdns.ServiceEntry, 10),
|
||||
}
|
||||
|
||||
go s.listener(ctx)
|
||||
return s
|
||||
}
|
||||
|
||||
func (d *Service) Run(ctx context.Context, interval time.Duration) error {
|
||||
ifaces, err := findMulticastInterfaces(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, iface := range ifaces {
|
||||
hasIPv4, hasIPv6, err := retrieveSupportedProtocols(iface)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if !hasIPv4 && !hasIPv6 {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := d.queryIface(iface, !hasIPv4, !hasIPv6); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
pollCtx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
go func(ctx context.Context, iface net.Interface) {
|
||||
defer wg.Done()
|
||||
|
||||
if err := d.pollInterface(ctx, iface, interval, !hasIPv4, !hasIPv6); err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(
|
||||
ctx, "could not poll interface",
|
||||
logger.E(errors.WithStack(err)), logger.F("iface", iface.Name),
|
||||
)
|
||||
}
|
||||
}(pollCtx, iface)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Service) queryIface(iface net.Interface, disableIPv4, disableIPv6 bool) error {
|
||||
err := mdns.Query(&mdns.QueryParam{
|
||||
Service: "_googlecast._tcp",
|
||||
Domain: "local",
|
||||
Timeout: 3 * time.Second,
|
||||
Entries: d.entriesCh,
|
||||
Interface: &iface,
|
||||
DisableIPv6: disableIPv6,
|
||||
DisableIPv4: disableIPv4,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Service) pollInterface(ctx context.Context, iface net.Interface, interval time.Duration, disableIPv4, disableIPv6 bool) error {
|
||||
ticker := time.NewTicker(interval)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := d.queryIface(iface, disableIPv4, disableIPv6); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
if err := ctx.Err(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Service) Stop() {
|
||||
if d.stopPeriodic != nil {
|
||||
close(d.stopPeriodic)
|
||||
d.stopPeriodic = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Service) Found() chan *cast.Client {
|
||||
return d.found
|
||||
}
|
||||
|
||||
func (d *Service) listener(ctx context.Context) {
|
||||
for entry := range d.entriesCh {
|
||||
name := strings.Split(entry.Name, "._googlecast")
|
||||
// Skip everything that doesn't have googlecast in the fdqn
|
||||
if len(name) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("New entry: %#v\n", entry)
|
||||
client := cast.NewClient(entry.AddrV4, entry.Port)
|
||||
info := decodeTxtRecord(entry.Info)
|
||||
client.SetName(info["fn"])
|
||||
client.SetInfo(info)
|
||||
|
||||
select {
|
||||
case d.found <- client:
|
||||
case <-time.After(time.Second):
|
||||
case <-ctx.Done():
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func decodeDnsEntry(text string) string {
|
||||
text = strings.Replace(text, `\.`, ".", -1)
|
||||
text = strings.Replace(text, `\ `, " ", -1)
|
||||
|
||||
re := regexp.MustCompile(`([\\][0-9][0-9][0-9])`)
|
||||
text = re.ReplaceAllStringFunc(text, func(source string) string {
|
||||
i, err := strconv.Atoi(source[1:])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return string([]byte{byte(i)})
|
||||
})
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
func decodeTxtRecord(txt string) map[string]string {
|
||||
m := make(map[string]string)
|
||||
|
||||
s := strings.Split(txt, "|")
|
||||
for _, v := range s {
|
||||
s := strings.Split(v, "=")
|
||||
if len(s) == 2 {
|
||||
m[s[0]] = s[1]
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func isIPv4(ip net.IP) bool {
|
||||
return strings.Count(ip.String(), ":") < 2
|
||||
}
|
||||
|
||||
func isIPv6(ip net.IP) bool {
|
||||
return strings.Count(ip.String(), ":") >= 2
|
||||
}
|
||||
|
||||
func findMulticastInterfaces(ctx context.Context) ([]net.Interface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
multicastIfaces := make([]net.Interface, 0)
|
||||
|
||||
for _, iface := range ifaces {
|
||||
if iface.Flags&net.FlagLoopback == net.FlagLoopback {
|
||||
continue
|
||||
}
|
||||
|
||||
if iface.Flags&net.FlagRunning != net.FlagRunning {
|
||||
continue
|
||||
}
|
||||
|
||||
if iface.Flags&net.FlagMulticast != net.FlagMulticast {
|
||||
continue
|
||||
}
|
||||
|
||||
multicastIfaces = append(multicastIfaces, iface)
|
||||
}
|
||||
|
||||
return multicastIfaces, nil
|
||||
}
|
||||
|
||||
func retrieveSupportedProtocols(iface net.Interface) (bool, bool, error) {
|
||||
adresses, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return false, false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
hasIPv4 := false
|
||||
hasIPv6 := false
|
||||
|
||||
for _, addr := range adresses {
|
||||
ip, _, err := net.ParseCIDR(addr.String())
|
||||
if err != nil {
|
||||
return false, false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if isIPv4(ip) {
|
||||
hasIPv4 = true
|
||||
}
|
||||
|
||||
if isIPv6(ip) {
|
||||
hasIPv6 = true
|
||||
}
|
||||
|
||||
if hasIPv4 && hasIPv6 {
|
||||
return hasIPv4, hasIPv6, nil
|
||||
}
|
||||
}
|
||||
|
||||
return hasIPv4, hasIPv6, nil
|
||||
}
|
@ -2,7 +2,6 @@ package cast
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
@ -19,14 +18,6 @@ const (
|
||||
type Module struct {
|
||||
ctx context.Context
|
||||
server *app.Server
|
||||
mutex struct {
|
||||
devices sync.RWMutex
|
||||
refreshDevices sync.Mutex
|
||||
loadURL sync.Mutex
|
||||
quitApp sync.Mutex
|
||||
getStatus sync.Mutex
|
||||
}
|
||||
devices []*Device
|
||||
}
|
||||
|
||||
func (m *Module) Name() string {
|
||||
@ -66,14 +57,11 @@ func (m *Module) refreshDevices(call goja.FunctionCall, rt *goja.Runtime) goja.V
|
||||
promise := m.server.NewPromise()
|
||||
|
||||
go func() {
|
||||
m.mutex.refreshDevices.Lock()
|
||||
defer m.mutex.refreshDevices.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
devices, err := FindDevices(ctx)
|
||||
if err != nil && !errors.Is(err, context.DeadlineExceeded) {
|
||||
devices, err := ListDevices(ctx, true)
|
||||
if err != nil {
|
||||
err = errors.WithStack(err)
|
||||
logger.Error(ctx, "error refreshing casting devices list", logger.E(errors.WithStack(err)))
|
||||
|
||||
@ -82,24 +70,19 @@ func (m *Module) refreshDevices(call goja.FunctionCall, rt *goja.Runtime) goja.V
|
||||
return
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
m.mutex.devices.Lock()
|
||||
m.devices = devices
|
||||
m.mutex.devices.Unlock()
|
||||
}
|
||||
|
||||
devicesCopy := m.getDevicesCopy(devices)
|
||||
promise.Resolve(devicesCopy)
|
||||
promise.Resolve(devices)
|
||||
}()
|
||||
|
||||
return rt.ToValue(promise)
|
||||
}
|
||||
|
||||
func (m *Module) getDevices(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
||||
m.mutex.devices.RLock()
|
||||
defer m.mutex.devices.RUnlock()
|
||||
ctx := context.Background()
|
||||
|
||||
devices := m.getDevicesCopy(m.devices)
|
||||
devices, err := ListDevices(ctx, false)
|
||||
if err != nil {
|
||||
panic(rt.ToValue(errors.WithStack(err)))
|
||||
}
|
||||
|
||||
return rt.ToValue(devices)
|
||||
}
|
||||
@ -122,9 +105,6 @@ func (m *Module) loadUrl(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
||||
promise := m.server.NewPromise()
|
||||
|
||||
go func() {
|
||||
m.mutex.loadURL.Lock()
|
||||
defer m.mutex.loadURL.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
@ -160,9 +140,6 @@ func (m *Module) stopCast(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
||||
promise := m.server.NewPromise()
|
||||
|
||||
go func() {
|
||||
m.mutex.quitApp.Lock()
|
||||
defer m.mutex.quitApp.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
@ -198,9 +175,6 @@ func (m *Module) getStatus(call goja.FunctionCall, rt *goja.Runtime) goja.Value
|
||||
promise := m.server.NewPromise()
|
||||
|
||||
go func() {
|
||||
m.mutex.getStatus.Lock()
|
||||
defer m.mutex.getStatus.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
@ -220,21 +194,6 @@ func (m *Module) getStatus(call goja.FunctionCall, rt *goja.Runtime) goja.Value
|
||||
return m.server.ToValue(promise)
|
||||
}
|
||||
|
||||
func (m *Module) getDevicesCopy(devices []*Device) []Device {
|
||||
devicesCopy := make([]Device, 0, len(m.devices))
|
||||
|
||||
for _, d := range devices {
|
||||
devicesCopy = append(devicesCopy, Device{
|
||||
UUID: d.UUID,
|
||||
Name: d.Name,
|
||||
Host: d.Host,
|
||||
Port: d.Port,
|
||||
})
|
||||
}
|
||||
|
||||
return devicesCopy
|
||||
}
|
||||
|
||||
func (m *Module) parseTimeout(rawTimeout string) (time.Duration, error) {
|
||||
var (
|
||||
timeout time.Duration
|
||||
@ -257,7 +216,6 @@ func CastModuleFactory() app.ServerModuleFactory {
|
||||
return func(server *app.Server) app.ServerModule {
|
||||
return &Module{
|
||||
server: server,
|
||||
devices: make([]*Device, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
8
pkg/module/share/error.go
Normal file
8
pkg/module/share/error.go
Normal file
@ -0,0 +1,8 @@
|
||||
package share
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("not found")
|
||||
ErrAttributeRequired = errors.New("attribute required")
|
||||
)
|
341
pkg/module/share/module.go
Normal file
341
pkg/module/share/module.go
Normal file
@ -0,0 +1,341 @@
|
||||
package share
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/util"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
AnyType ValueType = "*"
|
||||
AnyName string = "*"
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
appID app.ID
|
||||
repository Repository
|
||||
}
|
||||
|
||||
func (m *Module) Name() string {
|
||||
return "share"
|
||||
}
|
||||
|
||||
func (m *Module) Export(export *goja.Object) {
|
||||
if err := export.Set("upsertResource", m.upsertResource); err != nil {
|
||||
panic(errors.Wrap(err, "could not set 'upsertResource' function"))
|
||||
}
|
||||
|
||||
if err := export.Set("findResources", m.findResources); err != nil {
|
||||
panic(errors.Wrap(err, "could not set 'findResources' function"))
|
||||
}
|
||||
|
||||
if err := export.Set("deleteAttributes", m.deleteAttributes); err != nil {
|
||||
panic(errors.Wrap(err, "could not set 'deleteAttributes' function"))
|
||||
}
|
||||
|
||||
if err := export.Set("deleteResource", m.deleteResource); err != nil {
|
||||
panic(errors.Wrap(err, "could not set 'deleteResource' function"))
|
||||
}
|
||||
|
||||
if err := export.Set("ANY_TYPE", AnyType); err != nil {
|
||||
panic(errors.Wrap(err, "could not set 'ANY_TYPE' property"))
|
||||
}
|
||||
|
||||
if err := export.Set("ANY_NAME", AnyName); err != nil {
|
||||
panic(errors.Wrap(err, "could not set 'ANY_NAME' property"))
|
||||
}
|
||||
|
||||
if err := export.Set("TYPE_TEXT", TypeText); err != nil {
|
||||
panic(errors.Wrap(err, "could not set 'TYPE_TEXT' property"))
|
||||
}
|
||||
|
||||
if err := export.Set("TYPE_NUMBER", TypeNumber); err != nil {
|
||||
panic(errors.Wrap(err, "could not set 'TYPE_NUMBER' property"))
|
||||
}
|
||||
|
||||
if err := export.Set("TYPE_BOOL", TypeBool); err != nil {
|
||||
panic(errors.Wrap(err, "could not set 'TYPE_BOOL' property"))
|
||||
}
|
||||
|
||||
if err := export.Set("TYPE_PATH", TypePath); err != nil {
|
||||
panic(errors.Wrap(err, "could not set 'TYPE_PATH' property"))
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) upsertResource(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
||||
ctx := util.AssertContext(call.Argument(0), rt)
|
||||
resourceID := assertResourceID(call.Argument(1), rt)
|
||||
|
||||
var attributes []Attribute
|
||||
if len(call.Arguments) > 2 {
|
||||
attributes = assertAttributes(call.Arguments[2:], rt)
|
||||
} else {
|
||||
attributes = make([]Attribute, 0)
|
||||
}
|
||||
|
||||
for _, attr := range attributes {
|
||||
if err := AssertType(attr.Value(), attr.Type()); err != nil {
|
||||
panic(rt.ToValue(errors.WithStack(err)))
|
||||
}
|
||||
}
|
||||
|
||||
resource, err := m.repository.UpdateAttributes(ctx, m.appID, resourceID, attributes...)
|
||||
if err != nil {
|
||||
panic(rt.ToValue(errors.WithStack(err)))
|
||||
}
|
||||
|
||||
return rt.ToValue(toGojaResource(resource))
|
||||
}
|
||||
|
||||
func (m *Module) deleteAttributes(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
||||
ctx := util.AssertContext(call.Argument(0), rt)
|
||||
resourceID := assertResourceID(call.Argument(1), rt)
|
||||
|
||||
var names []string
|
||||
if len(call.Arguments) > 2 {
|
||||
names = assertStrings(call.Arguments[2:], rt)
|
||||
} else {
|
||||
names = make([]string, 0)
|
||||
}
|
||||
|
||||
err := m.repository.DeleteAttributes(ctx, m.appID, resourceID, names...)
|
||||
if err != nil {
|
||||
panic(rt.ToValue(errors.WithStack(err)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) findResources(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
||||
ctx := util.AssertContext(call.Argument(0), rt)
|
||||
|
||||
funcs := make([]FindResourcesOptionFunc, 0)
|
||||
|
||||
if len(call.Arguments) > 1 {
|
||||
name := util.AssertString(call.Argument(1), rt)
|
||||
if name != AnyName {
|
||||
funcs = append(funcs, WithName(name))
|
||||
}
|
||||
}
|
||||
|
||||
if len(call.Arguments) > 2 {
|
||||
valueType := assertValueType(call.Argument(2), rt)
|
||||
if valueType != AnyType {
|
||||
funcs = append(funcs, WithType(valueType))
|
||||
}
|
||||
}
|
||||
|
||||
resources, err := m.repository.FindResources(ctx, funcs...)
|
||||
if err != nil {
|
||||
panic(rt.ToValue(errors.WithStack(err)))
|
||||
}
|
||||
|
||||
return rt.ToValue(toGojaResources(resources))
|
||||
}
|
||||
|
||||
func (m *Module) deleteResource(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
||||
ctx := util.AssertContext(call.Argument(0), rt)
|
||||
resourceID := assertResourceID(call.Argument(1), rt)
|
||||
|
||||
err := m.repository.DeleteResource(ctx, m.appID, resourceID)
|
||||
if err != nil {
|
||||
panic(rt.ToValue(errors.WithStack(err)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ModuleFactory(appID app.ID, repository Repository) app.ServerModuleFactory {
|
||||
return func(server *app.Server) app.ServerModule {
|
||||
return &Module{
|
||||
appID: appID,
|
||||
repository: repository,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertResourceID(v goja.Value, r *goja.Runtime) ResourceID {
|
||||
value := v.Export()
|
||||
switch typ := value.(type) {
|
||||
case string:
|
||||
return ResourceID(typ)
|
||||
case ResourceID:
|
||||
return typ
|
||||
default:
|
||||
panic(r.ToValue(errors.Errorf("expected value to be a string or ResourceID, got '%T'", value)))
|
||||
}
|
||||
}
|
||||
|
||||
func assertAttributes(values []goja.Value, r *goja.Runtime) []Attribute {
|
||||
attributes := make([]Attribute, len(values))
|
||||
|
||||
for idx, val := range values {
|
||||
export := val.Export()
|
||||
|
||||
rawAttr, ok := export.(map[string]any)
|
||||
if !ok {
|
||||
panic(r.ToValue(errors.Errorf("unexpected attribute value, got '%v'", export)))
|
||||
}
|
||||
|
||||
rawName, exists := rawAttr["name"]
|
||||
if !exists {
|
||||
panic(r.ToValue(errors.Errorf("could not find 'name' property on attribute '%v'", export)))
|
||||
}
|
||||
|
||||
name, ok := rawName.(string)
|
||||
if !ok {
|
||||
panic(r.ToValue(errors.Errorf("unexpected value for attribute property 'name': expected 'string', got '%T'", rawName)))
|
||||
}
|
||||
|
||||
rawType, exists := rawAttr["type"]
|
||||
if !exists {
|
||||
panic(r.ToValue(errors.Errorf("could not find 'type' property on attribute '%v'", export)))
|
||||
}
|
||||
|
||||
var valueType ValueType
|
||||
switch typ := rawType.(type) {
|
||||
case ValueType:
|
||||
valueType = typ
|
||||
case string:
|
||||
valueType = ValueType(typ)
|
||||
|
||||
default:
|
||||
panic(r.ToValue(errors.Errorf("unexpected value for attribute property 'type': expected 'string' or 'ValueType', got '%T'", rawType)))
|
||||
}
|
||||
|
||||
value, exists := rawAttr["value"]
|
||||
if !exists {
|
||||
panic(r.ToValue(errors.Errorf("could not find 'value' property on attribute '%v'", export)))
|
||||
}
|
||||
|
||||
attributes[idx] = NewBaseAttribute(
|
||||
name,
|
||||
valueType,
|
||||
value,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
return attributes
|
||||
}
|
||||
|
||||
func assertStrings(values []goja.Value, r *goja.Runtime) []string {
|
||||
strings := make([]string, len(values))
|
||||
|
||||
for idx, v := range values {
|
||||
strings[idx] = util.AssertString(v, r)
|
||||
}
|
||||
|
||||
return strings
|
||||
}
|
||||
|
||||
func assertValueType(v goja.Value, r *goja.Runtime) ValueType {
|
||||
value := v.Export()
|
||||
switch typ := value.(type) {
|
||||
case string:
|
||||
return ValueType(typ)
|
||||
case ValueType:
|
||||
return typ
|
||||
default:
|
||||
panic(r.ToValue(errors.Errorf("expected value to be a string or ValueType, got '%T'", value)))
|
||||
}
|
||||
}
|
||||
|
||||
type gojaResource struct {
|
||||
ID ResourceID `goja:"id" json:"id"`
|
||||
Origin app.ID `goja:"origin" json:"origin"`
|
||||
Attributes []*gojaAttribute `goja:"attributes" json:"attributes"`
|
||||
}
|
||||
|
||||
func (r *gojaResource) Has(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
||||
name := util.AssertString(call.Argument(0), rt)
|
||||
valueType := assertValueType(call.Argument(1), rt)
|
||||
|
||||
hasAttr := HasAttribute(toResource(r), name, valueType)
|
||||
|
||||
return rt.ToValue(hasAttr)
|
||||
}
|
||||
|
||||
func (r *gojaResource) Get(call goja.FunctionCall, rt *goja.Runtime) goja.Value {
|
||||
name := util.AssertString(call.Argument(0), rt)
|
||||
valueType := assertValueType(call.Argument(1), rt)
|
||||
|
||||
var defaultValue any
|
||||
if len(call.Arguments) > 2 {
|
||||
defaultValue = call.Argument(2).Export()
|
||||
}
|
||||
|
||||
attr := GetAttribute(toResource(r), name, valueType)
|
||||
|
||||
if attr == nil {
|
||||
return rt.ToValue(defaultValue)
|
||||
}
|
||||
|
||||
return rt.ToValue(attr.Value())
|
||||
}
|
||||
|
||||
type gojaAttribute struct {
|
||||
Name string `goja:"name" json:"name"`
|
||||
Type ValueType `goja:"type" json:"type"`
|
||||
Value any `goja:"value" json:"value"`
|
||||
CreatedAt time.Time `goja:"createdAt" json:"createdAt"`
|
||||
UpdatedAt time.Time `goja:"updatedAt" json:"updatedAt"`
|
||||
}
|
||||
|
||||
func toGojaResource(res Resource) *gojaResource {
|
||||
attributes := make([]*gojaAttribute, len(res.Attributes()))
|
||||
|
||||
for idx, attr := range res.Attributes() {
|
||||
attributes[idx] = &gojaAttribute{
|
||||
Name: attr.Name(),
|
||||
Type: attr.Type(),
|
||||
Value: attr.Value(),
|
||||
CreatedAt: attr.CreatedAt(),
|
||||
UpdatedAt: attr.UpdatedAt(),
|
||||
}
|
||||
}
|
||||
|
||||
return &gojaResource{
|
||||
ID: res.ID(),
|
||||
Origin: res.Origin(),
|
||||
Attributes: attributes,
|
||||
}
|
||||
}
|
||||
|
||||
func toGojaResources(resources []Resource) []*gojaResource {
|
||||
gojaResources := make([]*gojaResource, len(resources))
|
||||
for idx, res := range resources {
|
||||
gojaResources[idx] = toGojaResource(res)
|
||||
}
|
||||
return gojaResources
|
||||
}
|
||||
|
||||
func toResource(res *gojaResource) Resource {
|
||||
return NewBaseResource(
|
||||
res.Origin,
|
||||
res.ID,
|
||||
toAttributes(res.Attributes)...,
|
||||
)
|
||||
}
|
||||
|
||||
func toAttributes(gojaAttributes []*gojaAttribute) []Attribute {
|
||||
attributes := make([]Attribute, len(gojaAttributes))
|
||||
|
||||
for idx, gojaAttr := range gojaAttributes {
|
||||
attr := NewBaseAttribute(
|
||||
gojaAttr.Name,
|
||||
gojaAttr.Type,
|
||||
gojaAttr.Value,
|
||||
)
|
||||
|
||||
attr.SetCreatedAt(gojaAttr.CreatedAt)
|
||||
attr.SetUpdatedAt(gojaAttr.UpdatedAt)
|
||||
|
||||
attributes[idx] = attr
|
||||
}
|
||||
|
||||
return attributes
|
||||
}
|
30
pkg/module/share/options.go
Normal file
30
pkg/module/share/options.go
Normal file
@ -0,0 +1,30 @@
|
||||
package share
|
||||
|
||||
type FindResourcesOptionFunc func(*FindResourcesOptions)
|
||||
|
||||
type FindResourcesOptions struct {
|
||||
Name *string
|
||||
ValueType *ValueType
|
||||
}
|
||||
|
||||
func FillFindResourcesOptions(funcs ...FindResourcesOptionFunc) *FindResourcesOptions {
|
||||
opts := &FindResourcesOptions{}
|
||||
|
||||
for _, fn := range funcs {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func WithName(name string) FindResourcesOptionFunc {
|
||||
return func(opts *FindResourcesOptions) {
|
||||
opts.Name = &name
|
||||
}
|
||||
}
|
||||
|
||||
func WithType(valueType ValueType) FindResourcesOptionFunc {
|
||||
return func(opts *FindResourcesOptions) {
|
||||
opts.ValueType = &valueType
|
||||
}
|
||||
}
|
32
pkg/module/share/repository.go
Normal file
32
pkg/module/share/repository.go
Normal file
@ -0,0 +1,32 @@
|
||||
package share
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
)
|
||||
|
||||
type ResourceID string
|
||||
|
||||
type Resource interface {
|
||||
ID() ResourceID
|
||||
Origin() app.ID
|
||||
Attributes() []Attribute
|
||||
}
|
||||
|
||||
type Attribute interface {
|
||||
Name() string
|
||||
Value() any
|
||||
Type() ValueType
|
||||
UpdatedAt() time.Time
|
||||
CreatedAt() time.Time
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
DeleteResource(ctx context.Context, origin app.ID, resourceID ResourceID) error
|
||||
FindResources(ctx context.Context, funcs ...FindResourcesOptionFunc) ([]Resource, error)
|
||||
GetResource(ctx context.Context, origin app.ID, resourceID ResourceID) (Resource, error)
|
||||
UpdateAttributes(ctx context.Context, origin app.ID, resourceID ResourceID, attributes ...Attribute) (Resource, error)
|
||||
DeleteAttributes(ctx context.Context, origin app.ID, resourceID ResourceID, names ...string) error
|
||||
}
|
121
pkg/module/share/resource.go
Normal file
121
pkg/module/share/resource.go
Normal file
@ -0,0 +1,121 @@
|
||||
package share
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
)
|
||||
|
||||
type BaseResource struct {
|
||||
id ResourceID
|
||||
origin app.ID
|
||||
attributes []Attribute
|
||||
}
|
||||
|
||||
// Attributes implements Resource
|
||||
func (r *BaseResource) Attributes() []Attribute {
|
||||
return r.attributes
|
||||
}
|
||||
|
||||
// ID implements Resource
|
||||
func (r *BaseResource) ID() ResourceID {
|
||||
return r.id
|
||||
}
|
||||
|
||||
// Origin implements Resource
|
||||
func (r *BaseResource) Origin() app.ID {
|
||||
return r.origin
|
||||
}
|
||||
|
||||
func (r *BaseResource) SetAttribute(attr Attribute) {
|
||||
for idx, rAttr := range r.attributes {
|
||||
if rAttr.Name() != attr.Name() {
|
||||
continue
|
||||
}
|
||||
|
||||
r.attributes[idx] = attr
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
r.attributes = append(r.attributes, attr)
|
||||
}
|
||||
|
||||
func NewBaseResource(origin app.ID, resourceID ResourceID, attributes ...Attribute) *BaseResource {
|
||||
return &BaseResource{
|
||||
id: resourceID,
|
||||
origin: origin,
|
||||
attributes: attributes,
|
||||
}
|
||||
}
|
||||
|
||||
var _ Resource = &BaseResource{}
|
||||
|
||||
type BaseAttribute struct {
|
||||
name string
|
||||
valueType ValueType
|
||||
value any
|
||||
createdAt time.Time
|
||||
updatedAt time.Time
|
||||
}
|
||||
|
||||
// CreatedAt implements Attribute
|
||||
func (a *BaseAttribute) CreatedAt() time.Time {
|
||||
return a.createdAt
|
||||
}
|
||||
|
||||
// Name implements Attribute
|
||||
func (a *BaseAttribute) Name() string {
|
||||
return a.name
|
||||
}
|
||||
|
||||
// Type implements Attribute
|
||||
func (a *BaseAttribute) Type() ValueType {
|
||||
return a.valueType
|
||||
}
|
||||
|
||||
// UpdatedAt implements Attribute
|
||||
func (a *BaseAttribute) UpdatedAt() time.Time {
|
||||
return a.updatedAt
|
||||
}
|
||||
|
||||
// Value implements Attribute
|
||||
func (a *BaseAttribute) Value() any {
|
||||
return a.value
|
||||
}
|
||||
|
||||
func (a *BaseAttribute) SetCreatedAt(createdAt time.Time) {
|
||||
a.createdAt = createdAt
|
||||
}
|
||||
|
||||
func (a *BaseAttribute) SetUpdatedAt(updatedAt time.Time) {
|
||||
a.updatedAt = updatedAt
|
||||
}
|
||||
|
||||
func NewBaseAttribute(name string, valueType ValueType, value any) *BaseAttribute {
|
||||
return &BaseAttribute{
|
||||
name: name,
|
||||
valueType: valueType,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
var _ Attribute = &BaseAttribute{}
|
||||
|
||||
func HasAttribute(res Resource, name string, valueType ValueType) bool {
|
||||
return GetAttribute(res, name, valueType) != nil
|
||||
}
|
||||
|
||||
func GetAttribute(res Resource, name string, valueType ValueType) Attribute {
|
||||
for _, attr := range res.Attributes() {
|
||||
if attr.Name() != name {
|
||||
continue
|
||||
}
|
||||
|
||||
if attr.Type() == valueType {
|
||||
return attr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
13
pkg/module/share/sqlite/module_test.go
Normal file
13
pkg/module/share/sqlite/module_test.go
Normal file
@ -0,0 +1,13 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/share/testsuite"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func TestModule(t *testing.T) {
|
||||
logger.SetLevel(logger.LevelDebug)
|
||||
testsuite.TestModule(t, newTestRepo)
|
||||
}
|
429
pkg/module/share/sqlite/repository.go
Normal file
429
pkg/module/share/sqlite/repository.go
Normal file
@ -0,0 +1,429 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/share"
|
||||
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
getDB sqlite.GetDBFunc
|
||||
}
|
||||
|
||||
// DeleteAttributes implements share.Repository
|
||||
func (r *Repository) DeleteAttributes(ctx context.Context, origin app.ID, resourceID share.ResourceID, names ...string) error {
|
||||
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
||||
query := `
|
||||
DELETE FROM resources
|
||||
WHERE origin = $1 AND resource_id = $2
|
||||
`
|
||||
args := []any{origin, resourceID}
|
||||
criteria := ""
|
||||
|
||||
for idx, name := range names {
|
||||
if idx == 0 {
|
||||
criteria += " AND ("
|
||||
}
|
||||
|
||||
if idx != 0 {
|
||||
criteria += " OR "
|
||||
}
|
||||
|
||||
criteria += fmt.Sprintf(" name = $%d", len(args)+1)
|
||||
args = append(args, name)
|
||||
|
||||
if idx == len(names)-1 {
|
||||
criteria += " )"
|
||||
}
|
||||
}
|
||||
|
||||
query += criteria
|
||||
|
||||
logger.Debug(
|
||||
ctx, "executing query",
|
||||
logger.F("query", query),
|
||||
logger.F("args", args),
|
||||
)
|
||||
|
||||
res, err := tx.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if affected == 0 {
|
||||
return errors.WithStack(share.ErrNotFound)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteResource implements share.Repository
|
||||
func (r *Repository) DeleteResource(ctx context.Context, origin app.ID, resourceID share.ResourceID) error {
|
||||
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
||||
query := `
|
||||
DELETE FROM resources
|
||||
WHERE origin = $1 AND resource_id = $2
|
||||
`
|
||||
|
||||
args := []any{origin, resourceID}
|
||||
|
||||
logger.Debug(
|
||||
ctx, "executing query",
|
||||
logger.F("query", query),
|
||||
logger.F("args", args),
|
||||
)
|
||||
|
||||
res, err := tx.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if affected == 0 {
|
||||
return errors.WithStack(share.ErrNotFound)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// FindResources implements share.Repository
|
||||
func (r *Repository) FindResources(ctx context.Context, funcs ...share.FindResourcesOptionFunc) ([]share.Resource, error) {
|
||||
opts := share.FillFindResourcesOptions(funcs...)
|
||||
|
||||
var resources []share.Resource
|
||||
|
||||
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
||||
query := `
|
||||
SELECT
|
||||
main.origin, main.resource_id,
|
||||
main.name, main.type, main.value,
|
||||
main.created_at, main.updated_at
|
||||
FROM resources AS main
|
||||
JOIN resources AS sub ON
|
||||
main.resource_id = sub.resource_id
|
||||
AND main.origin = sub.origin
|
||||
`
|
||||
|
||||
criteria := " WHERE 1 = 1"
|
||||
preparedArgIndex := 1
|
||||
args := make([]any, 0)
|
||||
|
||||
if opts.Name != nil {
|
||||
criteria += fmt.Sprintf(" AND sub.name = $%d", preparedArgIndex)
|
||||
args = append(args, *opts.Name)
|
||||
preparedArgIndex++
|
||||
}
|
||||
|
||||
if opts.ValueType != nil {
|
||||
criteria += fmt.Sprintf(" AND sub.type = $%d", preparedArgIndex)
|
||||
args = append(args, *opts.ValueType)
|
||||
preparedArgIndex++
|
||||
}
|
||||
|
||||
query += criteria
|
||||
|
||||
logger.Debug(
|
||||
ctx, "executing query",
|
||||
logger.F("query", query),
|
||||
logger.F("args", args),
|
||||
)
|
||||
|
||||
rows, err := tx.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := rows.Close(); err != nil {
|
||||
logger.Error(ctx, "could not close rows", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
}()
|
||||
|
||||
indexedResources := make(map[string]*share.BaseResource)
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
origin string
|
||||
resourceID string
|
||||
name string
|
||||
valueType string
|
||||
value any
|
||||
updatedAt time.Time
|
||||
createdAt time.Time
|
||||
)
|
||||
|
||||
if err := rows.Scan(&origin, &resourceID, &name, &valueType, &value, &createdAt, &updatedAt); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
resourceKey := origin + resourceID
|
||||
resource, exists := indexedResources[resourceKey]
|
||||
if !exists {
|
||||
resource = share.NewBaseResource(app.ID(origin), share.ResourceID(resourceID))
|
||||
indexedResources[resourceKey] = resource
|
||||
}
|
||||
|
||||
attr := share.NewBaseAttribute(
|
||||
name,
|
||||
share.ValueType(valueType),
|
||||
value,
|
||||
)
|
||||
|
||||
attr.SetCreatedAt(createdAt)
|
||||
attr.SetUpdatedAt(updatedAt)
|
||||
|
||||
resource.SetAttribute(attr)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
resources = make([]share.Resource, 0, len(indexedResources))
|
||||
for _, res := range indexedResources {
|
||||
resources = append(resources, res)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
// GetResource implements share.Repository
|
||||
func (r *Repository) GetResource(ctx context.Context, origin app.ID, resourceID share.ResourceID) (share.Resource, error) {
|
||||
var (
|
||||
resource *share.BaseResource
|
||||
err error
|
||||
)
|
||||
|
||||
err = r.withTx(ctx, func(tx *sql.Tx) error {
|
||||
resource, err = r.getResourceWithinTx(ctx, tx, origin, resourceID)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
// UpdateAttributes implements share.Repository
|
||||
func (r *Repository) UpdateAttributes(ctx context.Context, origin app.ID, resourceID share.ResourceID, attributes ...share.Attribute) (share.Resource, error) {
|
||||
if len(attributes) == 0 {
|
||||
return nil, errors.WithStack(share.ErrAttributeRequired)
|
||||
}
|
||||
|
||||
var resource *share.BaseResource
|
||||
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
||||
query := `
|
||||
INSERT INTO resources (origin, resource_id, name, type, value, created_at, updated_at)
|
||||
VALUES($1, $2, $3, $4, $5, $6, $6)
|
||||
ON CONFLICT (origin, resource_id, name) DO UPDATE SET
|
||||
type = $4, value = $5, updated_at = $6
|
||||
`
|
||||
|
||||
stmt, err := tx.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := stmt.Close(); err != nil {
|
||||
logger.Error(ctx, "could not close statement", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
}()
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
for _, attr := range attributes {
|
||||
args := []any{
|
||||
string(origin), string(resourceID),
|
||||
attr.Name(), string(attr.Type()), attr.Value(),
|
||||
now, now,
|
||||
}
|
||||
|
||||
logger.Debug(
|
||||
ctx, "executing query",
|
||||
logger.F("query", query),
|
||||
logger.F("args", args),
|
||||
)
|
||||
|
||||
if _, err := stmt.ExecContext(ctx, args...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
resource, err = r.getResourceWithinTx(ctx, tx, origin, resourceID)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
func (r *Repository) getResourceWithinTx(ctx context.Context, tx *sql.Tx, origin app.ID, resourceID share.ResourceID) (*share.BaseResource, error) {
|
||||
query := `
|
||||
SELECT name, type, value, created_at, updated_at
|
||||
FROM resources
|
||||
WHERE origin = $1 AND resource_id = $2
|
||||
`
|
||||
|
||||
rows, err := tx.QueryContext(ctx, query, origin, resourceID)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := rows.Close(); err != nil {
|
||||
logger.Error(ctx, "could not close rows", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
}()
|
||||
|
||||
attributes := make([]share.Attribute, 0)
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
name string
|
||||
valueType string
|
||||
value any
|
||||
updatedAt time.Time
|
||||
createdAt time.Time
|
||||
)
|
||||
|
||||
if err := rows.Scan(&name, &valueType, &value, &createdAt, &updatedAt); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
attr := share.NewBaseAttribute(
|
||||
name,
|
||||
share.ValueType(valueType),
|
||||
value,
|
||||
)
|
||||
|
||||
attr.SetCreatedAt(createdAt)
|
||||
attr.SetUpdatedAt(updatedAt)
|
||||
|
||||
attributes = append(attributes, attr)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if len(attributes) == 0 {
|
||||
return nil, errors.WithStack(share.ErrNotFound)
|
||||
}
|
||||
|
||||
resource := share.NewBaseResource(origin, resourceID, attributes...)
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
func (r *Repository) withTx(ctx context.Context, fn func(tx *sql.Tx) error) error {
|
||||
var db *sql.DB
|
||||
|
||||
db, err := r.getDB(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := sqlite.WithTx(ctx, db, fn); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureTables(ctx context.Context, db *sql.DB) error {
|
||||
err := sqlite.WithTx(ctx, db, func(tx *sql.Tx) error {
|
||||
query := `
|
||||
CREATE TABLE IF NOT EXISTS resources (
|
||||
resource_id TEXT NOT NULL,
|
||||
origin TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
value TEXT,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP NOT NULL,
|
||||
UNIQUE(origin, resource_id, name) ON CONFLICT REPLACE
|
||||
);
|
||||
`
|
||||
if _, err := tx.ExecContext(ctx, query); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
query = `
|
||||
CREATE INDEX IF NOT EXISTS resource_idx ON resources (origin, resource_id, name);
|
||||
`
|
||||
if _, err := tx.ExecContext(ctx, query); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRepository(path string) *Repository {
|
||||
getDB := sqlite.NewGetDBFunc(path, ensureTables)
|
||||
|
||||
return &Repository{
|
||||
getDB: getDB,
|
||||
}
|
||||
}
|
||||
|
||||
func NewRepositoryWithDB(db *sql.DB) *Repository {
|
||||
getDB := sqlite.NewGetDBFuncFromDB(db, ensureTables)
|
||||
|
||||
return &Repository{
|
||||
getDB: getDB,
|
||||
}
|
||||
}
|
||||
|
||||
var _ share.Repository = &Repository{}
|
33
pkg/module/share/sqlite/repository_test.go
Normal file
33
pkg/module/share/sqlite/repository_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/share"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/share/testsuite"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func TestRepository(t *testing.T) {
|
||||
logger.SetLevel(logger.LevelDebug)
|
||||
testsuite.TestRepository(t, newTestRepo)
|
||||
}
|
||||
|
||||
func newTestRepo(testName string) (share.Repository, error) {
|
||||
filename := strings.ToLower(strings.ReplaceAll(testName, " ", "_"))
|
||||
file := fmt.Sprintf("./testdata/%s.sqlite", filename)
|
||||
|
||||
if err := os.Remove(file); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
dsn := fmt.Sprintf("%s?_pragma=foreign_keys(1)&_pragma=busy_timeout=%d", file, (60 * time.Second).Milliseconds())
|
||||
repo := NewRepository(dsn)
|
||||
|
||||
return repo, nil
|
||||
}
|
1
pkg/module/share/sqlite/testdata/.gitignore
vendored
Normal file
1
pkg/module/share/sqlite/testdata/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.sqlite*
|
47
pkg/module/share/testsuite/module.go
Normal file
47
pkg/module/share/testsuite/module.go
Normal file
@ -0,0 +1,47 @@
|
||||
package testsuite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
"testing"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/share"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func TestModule(t *testing.T, newRepo NewTestRepoFunc) {
|
||||
logger.SetLevel(logger.LevelDebug)
|
||||
|
||||
repo, err := newRepo("module")
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
server := app.NewServer(
|
||||
module.ContextModuleFactory(),
|
||||
module.ConsoleModuleFactory(),
|
||||
share.ModuleFactory("test.app.edge", repo),
|
||||
)
|
||||
|
||||
data, err := fs.ReadFile(testData, "testdata/share.js")
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
if err := server.Load("testdata/share.js", string(data)); err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
if err := server.Start(); err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
if _, err := server.ExecFuncByName(context.Background(), "testModule"); err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
server.Stop()
|
||||
}
|
16
pkg/module/share/testsuite/repository.go
Normal file
16
pkg/module/share/testsuite/repository.go
Normal file
@ -0,0 +1,16 @@
|
||||
package testsuite
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/share"
|
||||
)
|
||||
|
||||
type NewTestRepoFunc func(testname string) (share.Repository, error)
|
||||
|
||||
func TestRepository(t *testing.T, newRepo NewTestRepoFunc) {
|
||||
t.Run("Cases", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
runRepositoryTests(t, newRepo)
|
||||
})
|
||||
}
|
344
pkg/module/share/testsuite/repository_cases.go
Normal file
344
pkg/module/share/testsuite/repository_cases.go
Normal file
@ -0,0 +1,344 @@
|
||||
package testsuite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"forge.cadoles.com/arcad/edge/pkg/module/share"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type repositoryTestCase struct {
|
||||
Name string
|
||||
Skip bool
|
||||
Run func(ctx context.Context, t *testing.T, repo share.Repository) error
|
||||
}
|
||||
|
||||
var repositoryTestCases = []repositoryTestCase{
|
||||
{
|
||||
Name: "Update resource attributes",
|
||||
Skip: false,
|
||||
Run: func(ctx context.Context, t *testing.T, repo share.Repository) error {
|
||||
origin := app.ID("test")
|
||||
resourceID := share.ResourceID("test")
|
||||
|
||||
// Try to create resource without attributes
|
||||
_, err := repo.UpdateAttributes(ctx, origin, resourceID)
|
||||
if err == nil {
|
||||
return errors.New("err should not be nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, share.ErrAttributeRequired) {
|
||||
return errors.Errorf("err: expected share.ErrAttributeRequired, got '%+v'", err)
|
||||
}
|
||||
|
||||
attributes := []share.Attribute{
|
||||
share.NewBaseAttribute("my_text_attr", share.TypeText, "foo"),
|
||||
share.NewBaseAttribute("my_number_attr", share.TypeNumber, 5),
|
||||
share.NewBaseAttribute("my_path_attr", share.TypePath, "/my/path"),
|
||||
share.NewBaseAttribute("my_bool_attr", share.TypeBool, true),
|
||||
}
|
||||
|
||||
resource, err := repo.UpdateAttributes(ctx, origin, resourceID, attributes...)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
isNil := reflect.ValueOf(resource).IsNil()
|
||||
if isNil {
|
||||
return errors.New("resource should not be nil")
|
||||
}
|
||||
|
||||
if e, g := resourceID, resource.ID(); e != g {
|
||||
return errors.Errorf("resource.ID(): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
if e, g := origin, resource.Origin(); e != g {
|
||||
return errors.Errorf("resource.Origin(): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
if e, g := 4, len(resource.Attributes()); e != g {
|
||||
return errors.Errorf("len(resource.Attributes()): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Find resources by attribute name",
|
||||
Skip: false,
|
||||
Run: func(ctx context.Context, t *testing.T, repo share.Repository) error {
|
||||
if err := loadTestData(ctx, "testdata/find_resources_by_attribute_name.json", repo); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
resources, err := repo.FindResources(ctx, share.WithName("my_number"))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
isNil := reflect.ValueOf(resources).IsNil()
|
||||
if isNil {
|
||||
return errors.New("resources should not be nil")
|
||||
}
|
||||
|
||||
if e, g := 2, len(resources); e != g {
|
||||
return errors.Errorf("len(resources): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Find resources by attribute type",
|
||||
Skip: false,
|
||||
Run: func(ctx context.Context, t *testing.T, repo share.Repository) error {
|
||||
if err := loadTestData(ctx, "testdata/find_resources_by_attribute_type.json", repo); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
resources, err := repo.FindResources(ctx, share.WithType(share.TypePath))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
isNil := reflect.ValueOf(resources).IsNil()
|
||||
if isNil {
|
||||
return errors.New("resources should not be nil")
|
||||
}
|
||||
|
||||
if e, g := 1, len(resources); e != g {
|
||||
return errors.Errorf("len(resources): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Find resources by attribute type and name",
|
||||
Skip: false,
|
||||
Run: func(ctx context.Context, t *testing.T, repo share.Repository) error {
|
||||
if err := loadTestData(ctx, "testdata/find_resources_by_attribute_type_and_name.json", repo); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
resources, err := repo.FindResources(ctx, share.WithType(share.TypeText), share.WithName("my_attr"))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
isNil := reflect.ValueOf(resources).IsNil()
|
||||
if isNil {
|
||||
return errors.New("resources should not be nil")
|
||||
}
|
||||
|
||||
if e, g := 1, len(resources); e != g {
|
||||
return errors.Errorf("len(resources): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Get resource",
|
||||
Skip: false,
|
||||
Run: func(ctx context.Context, t *testing.T, repo share.Repository) error {
|
||||
if err := loadTestData(ctx, "testdata/get_resource.json", repo); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
origin := app.ID("app1.edge.app")
|
||||
resourceID := share.ResourceID("res-1")
|
||||
|
||||
resource, err := repo.GetResource(ctx, origin, resourceID)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
isNil := reflect.ValueOf(resource).IsNil()
|
||||
if isNil {
|
||||
return errors.New("resources should not be nil")
|
||||
}
|
||||
|
||||
if e, g := origin, resource.Origin(); e != g {
|
||||
return errors.Errorf("resource.Origin(): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
if e, g := resourceID, resource.ID(); e != g {
|
||||
return errors.Errorf("resource.ID(): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
resource, err = repo.GetResource(ctx, origin, "unexistant-id")
|
||||
if err == nil {
|
||||
return errors.New("err should not be nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, share.ErrNotFound) {
|
||||
return errors.Errorf("err: expected share.ErrNotFound, got '%+v'", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Delete resource",
|
||||
Skip: false,
|
||||
Run: func(ctx context.Context, t *testing.T, repo share.Repository) error {
|
||||
if err := loadTestData(ctx, "testdata/delete_resource.json", repo); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
origin := app.ID("app1.edge.app")
|
||||
resourceID := share.ResourceID("res-1")
|
||||
|
||||
// It should delete an existing resource
|
||||
if err := repo.DeleteResource(ctx, origin, resourceID); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
_, err := repo.GetResource(ctx, origin, resourceID)
|
||||
if err == nil {
|
||||
return errors.New("err should not be nil")
|
||||
}
|
||||
|
||||
// The resource should be deleted
|
||||
if !errors.Is(err, share.ErrNotFound) {
|
||||
return errors.Errorf("err: expected share.ErrNotFound, got '%+v'", err)
|
||||
}
|
||||
|
||||
// It should not delete an unexistant resource
|
||||
err = repo.DeleteResource(ctx, origin, resourceID)
|
||||
if err == nil {
|
||||
return errors.New("err should not be nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, share.ErrNotFound) {
|
||||
return errors.Errorf("err: expected share.ErrNotFound, got '%+v'", err)
|
||||
}
|
||||
|
||||
otherOrigin := app.ID("app2.edge.app")
|
||||
|
||||
// It should not delete a resource with the same id and another origin
|
||||
resource, err := repo.GetResource(ctx, otherOrigin, resourceID)
|
||||
if err != nil {
|
||||
return errors.New("err should not be nil")
|
||||
}
|
||||
|
||||
if e, g := otherOrigin, resource.Origin(); e != g {
|
||||
return errors.Errorf("resource.Origin(): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Delete attributes",
|
||||
Skip: false,
|
||||
Run: func(ctx context.Context, t *testing.T, repo share.Repository) error {
|
||||
if err := loadTestData(ctx, "testdata/delete_attributes.json", repo); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
origin := app.ID("app1.edge.app")
|
||||
resourceID := share.ResourceID("res-1")
|
||||
|
||||
// It should delete specified attributes
|
||||
if err := repo.DeleteAttributes(ctx, origin, resourceID, "my_text", "my_bool"); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
resource, err := repo.GetResource(ctx, origin, resourceID)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if e, g := 1, len(resource.Attributes()); e != g {
|
||||
return errors.Errorf("len(resource.Attributes()): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
|
||||
attr := share.GetAttribute(resource, "my_number", share.TypeNumber)
|
||||
if attr == nil {
|
||||
return errors.New("attr shoudl not be nil")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runRepositoryTests(t *testing.T, newRepo NewTestRepoFunc) {
|
||||
for _, tc := range repositoryTestCases {
|
||||
func(tc repositoryTestCase) {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if tc.Skip {
|
||||
t.SkipNow()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
repo, err := newRepo(tc.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
if err := tc.Run(ctx, t, repo); err != nil {
|
||||
t.Errorf("%+v", errors.WithStack(err))
|
||||
}
|
||||
})
|
||||
}(tc)
|
||||
}
|
||||
}
|
||||
|
||||
type jsonResource struct {
|
||||
ID string `json:"id"`
|
||||
Origin string `json:"origin"`
|
||||
Attributes []jsonAttribute `json:"attributes"`
|
||||
}
|
||||
|
||||
type jsonAttribute struct {
|
||||
Name string `json:"name"`
|
||||
Type share.ValueType `json:"type"`
|
||||
Value any `json:"value"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func loadTestData(ctx context.Context, jsonFile string, repo share.Repository) error {
|
||||
data, err := testData.ReadFile(jsonFile)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
var resources []jsonResource
|
||||
|
||||
if err := json.Unmarshal(data, &resources); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
for _, res := range resources {
|
||||
attributes := make([]share.Attribute, len(res.Attributes))
|
||||
|
||||
for idx, attr := range res.Attributes {
|
||||
attributes[idx] = share.NewBaseAttribute(
|
||||
attr.Name,
|
||||
attr.Type,
|
||||
attr.Value,
|
||||
)
|
||||
}
|
||||
|
||||
_, err := repo.UpdateAttributes(ctx, app.ID(res.Origin), share.ResourceID(res.ID), attributes...)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
6
pkg/module/share/testsuite/testdata.go
Normal file
6
pkg/module/share/testsuite/testdata.go
Normal file
@ -0,0 +1,6 @@
|
||||
package testsuite
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed testdata/*
|
||||
var testData embed.FS
|
11
pkg/module/share/testsuite/testdata/delete_attributes.json
vendored
Normal file
11
pkg/module/share/testsuite/testdata/delete_attributes.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"id": "res-1",
|
||||
"origin": "app1.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"my_text", "type": "text", "value": "bar" },
|
||||
{ "name":"my_bool", "type": "bool", "value": true },
|
||||
{ "name":"my_number", "type": "number", "value": 5 }
|
||||
]
|
||||
}
|
||||
]
|
16
pkg/module/share/testsuite/testdata/delete_resource.json
vendored
Normal file
16
pkg/module/share/testsuite/testdata/delete_resource.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"id": "res-1",
|
||||
"origin": "app1.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"my_text", "type": "text", "value": "bar" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "res-1",
|
||||
"origin": "app2.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"my_text", "type": "text", "value": "foo" }
|
||||
]
|
||||
}
|
||||
]
|
29
pkg/module/share/testsuite/testdata/find_resources_by_attribute_name.json
vendored
Normal file
29
pkg/module/share/testsuite/testdata/find_resources_by_attribute_name.json
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
[
|
||||
{
|
||||
"id": "res-1",
|
||||
"origin": "app1.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"my_text", "type": "text", "value": "bar" },
|
||||
{ "name":"my_number", "type": "number", "value": 5 },
|
||||
{ "name":"my_bool", "type": "bool", "value": true },
|
||||
{ "name":"my_path", "type": "path", "value": "/my/icon.png" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "res-2",
|
||||
"origin": "app1.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"other_text", "type": "text", "value": "foo" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "res-1",
|
||||
"origin": "app2.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"my_text", "type": "text", "value": "bar" },
|
||||
{ "name":"my_number", "type": "number", "value": 5 },
|
||||
{ "name":"my_bool", "type": "bool", "value": true },
|
||||
{ "name":"my_path", "type": "path", "value": "/my/icon.png" }
|
||||
]
|
||||
}
|
||||
]
|
28
pkg/module/share/testsuite/testdata/find_resources_by_attribute_type.json
vendored
Normal file
28
pkg/module/share/testsuite/testdata/find_resources_by_attribute_type.json
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
[
|
||||
{
|
||||
"id": "res-1",
|
||||
"origin": "app1.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"my_text", "type": "text", "value": "bar" },
|
||||
{ "name":"my_number", "type": "number", "value": 5 },
|
||||
{ "name":"my_bool", "type": "bool", "value": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "res-2",
|
||||
"origin": "app1.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"other_text", "type": "text", "value": "foo" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "res-1",
|
||||
"origin": "app2.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"my_text", "type": "text", "value": "bar" },
|
||||
{ "name":"my_number", "type": "number", "value": 5 },
|
||||
{ "name":"my_bool", "type": "bool", "value": true },
|
||||
{ "name":"my_path", "type": "path", "value": "/my/icon.png" }
|
||||
]
|
||||
}
|
||||
]
|
23
pkg/module/share/testsuite/testdata/find_resources_by_attribute_type_and_name.json
vendored
Normal file
23
pkg/module/share/testsuite/testdata/find_resources_by_attribute_type_and_name.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
[
|
||||
{
|
||||
"id": "res-1",
|
||||
"origin": "app1.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"my_attr", "type": "text", "value": "bar" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "res-2",
|
||||
"origin": "app1.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"my_attr", "type": "bool", "value": true }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "res-1",
|
||||
"origin": "app2.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"my_attr", "type": "number", "value": 5 }
|
||||
]
|
||||
}
|
||||
]
|
16
pkg/module/share/testsuite/testdata/get_resource.json
vendored
Normal file
16
pkg/module/share/testsuite/testdata/get_resource.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"id": "res-1",
|
||||
"origin": "app1.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"my_text", "type": "text", "value": "bar" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "res-1",
|
||||
"origin": "app2.edge.app",
|
||||
"attributes": [
|
||||
{ "name":"my_text", "type": "text", "value": "foo" }
|
||||
]
|
||||
}
|
||||
]
|
82
pkg/module/share/testsuite/testdata/share.js
vendored
Normal file
82
pkg/module/share/testsuite/testdata/share.js
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
|
||||
function testModule() {
|
||||
var ctx = context.new();
|
||||
var resourceId = "my-first-res";
|
||||
var attributes = [
|
||||
{ name: "my_text", type: share.TYPE_TEXT, value: "my_text" },
|
||||
{ name: "my_number", type: share.TYPE_NUMBER, value: 5 },
|
||||
{ name: "my_path", type: share.TYPE_PATH, value: "/my/path" },
|
||||
{ name: "my_bool", type: share.TYPE_BOOL, value: true },
|
||||
]
|
||||
|
||||
// Create resource with attributes
|
||||
|
||||
var resource = share.upsertResource(
|
||||
ctx, resourceId,
|
||||
attributes[0],
|
||||
attributes[1],
|
||||
attributes[2],
|
||||
attributes[3]
|
||||
);
|
||||
|
||||
if (resource.id != resourceId) {
|
||||
throw new Error("resource.id: expected '"+resourceId+"', got '"+resource.id+"'")
|
||||
}
|
||||
|
||||
if (resource.origin != "test.app.edge") {
|
||||
throw new Error("resource.origin: expected 'test.app.edge', got '"+resource.origin+"'")
|
||||
}
|
||||
|
||||
if (resource.attributes.length != 4) {
|
||||
throw new Error("resource.attributes.length: expected '1', got '"+resource.attributes.length+"'")
|
||||
}
|
||||
|
||||
for(var attr, i = 0;( attr = attributes[i] ); i++) {
|
||||
var exists = resource.has(attr.name, attr.type);
|
||||
if (!exists) {
|
||||
throw new Error("resource.has('"+attr.name+"'): expected 'true', got '"+hasAttr+"'")
|
||||
}
|
||||
|
||||
var value = resource.get(attr.name, attr.type);
|
||||
if (value != attr.value) {
|
||||
throw new Error("value: expected '"+attr.value+"', got '"+value+"'")
|
||||
}
|
||||
}
|
||||
|
||||
// Test acces of unexistant attribute
|
||||
|
||||
var unexistantAttr = "unexistant_attr"
|
||||
|
||||
var exists = resource.has(unexistantAttr, share.TYPE_TEXT);
|
||||
if (exists) {
|
||||
throw new Error("attr '"+unexistantAttr+"' should not exist")
|
||||
}
|
||||
|
||||
var expected = "foo"
|
||||
var value = resource.get(unexistantAttr, share.TYPE_TEXT, expected);
|
||||
if (value != expected) {
|
||||
throw new Error("resource.get('"+attr.name+"', share.TYPE_TEXT, '"+expected+"'): expected '"+expected+"', got '"+value+"'")
|
||||
}
|
||||
|
||||
// Search resources
|
||||
|
||||
// With any attribute
|
||||
var results = share.findResources(ctx, share.ANY_NAME, share.ANY_TYPE);
|
||||
if (results.length != 1) {
|
||||
throw new Error("results.length: expected '1', got '"+results.length+"'")
|
||||
}
|
||||
|
||||
// With an unexistant attribute
|
||||
var results = share.findResources(ctx, unexistantAttr, share.ANY_TYPE);
|
||||
if (results.length != 0) {
|
||||
throw new Error("results.length: expected '0', got '"+results.length+"'")
|
||||
}
|
||||
|
||||
// With a wrong type
|
||||
var results = share.findResources(ctx, "my_text", share.TYPE_NUMBER);
|
||||
if (results.length != 0) {
|
||||
throw new Error("results.length: expected '0', got '"+results.length+"'")
|
||||
}
|
||||
}
|
||||
|
||||
|
86
pkg/module/share/value_type.go
Normal file
86
pkg/module/share/value_type.go
Normal file
@ -0,0 +1,86 @@
|
||||
package share
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ValueType string
|
||||
|
||||
const (
|
||||
TypeText ValueType = "text"
|
||||
TypePath ValueType = "path"
|
||||
TypeNumber ValueType = "number"
|
||||
TypeBool ValueType = "bool"
|
||||
)
|
||||
|
||||
func AssertType(value any, valueType ValueType) error {
|
||||
switch valueType {
|
||||
case TypeText:
|
||||
if err := AssertTypeText(value); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
case TypeNumber:
|
||||
if err := AssertTypeNumber(value); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
case TypeBool:
|
||||
if err := AssertTypeBool(value); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
case TypePath:
|
||||
if err := AssertTypePath(value); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
default:
|
||||
return errors.Errorf("value type '%s' does not exist", valueType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AssertTypeText(value any) error {
|
||||
_, ok := value.(string)
|
||||
if !ok {
|
||||
return errors.Errorf("invalid value for type '%s': '%v'", TypeText, value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AssertTypeNumber(value any) error {
|
||||
switch value.(type) {
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
return nil
|
||||
|
||||
default:
|
||||
return errors.Errorf("invalid value for type '%s': '%v'", TypeNumber, value)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertTypeBool(value any) error {
|
||||
_, ok := value.(bool)
|
||||
if !ok {
|
||||
return errors.Errorf("invalid value for type '%s': '%v'", TypeBool, value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AssertTypePath(value any) error {
|
||||
path, ok := value.(string)
|
||||
if !ok {
|
||||
return errors.Errorf("invalid value for type '%s': '%v'", TypePath, value)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
return errors.Errorf("value '%s' should start with a '/'", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
88
pkg/module/share/value_type_test.go
Normal file
88
pkg/module/share/value_type_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
package share
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type valueTypeTestCase struct {
|
||||
Name string
|
||||
Value any
|
||||
Type ValueType
|
||||
ShouldFail bool
|
||||
}
|
||||
|
||||
var valueTypeTestCases = []valueTypeTestCase{
|
||||
{
|
||||
Name: "Valid text",
|
||||
Value: "my_text",
|
||||
Type: TypeText,
|
||||
},
|
||||
{
|
||||
Name: "Invalid text",
|
||||
Value: 0,
|
||||
Type: TypeText,
|
||||
ShouldFail: true,
|
||||
},
|
||||
{
|
||||
Name: "Valid number",
|
||||
Value: 5.6,
|
||||
Type: TypeNumber,
|
||||
},
|
||||
{
|
||||
Name: "Invalid number",
|
||||
Value: "5",
|
||||
Type: TypeNumber,
|
||||
ShouldFail: true,
|
||||
},
|
||||
{
|
||||
Name: "Valid bool",
|
||||
Value: false,
|
||||
Type: TypeBool,
|
||||
},
|
||||
{
|
||||
Name: "Invalid bool",
|
||||
Value: "yes",
|
||||
Type: TypeBool,
|
||||
ShouldFail: true,
|
||||
},
|
||||
{
|
||||
Name: "Valid path",
|
||||
Value: "/foo/bar",
|
||||
Type: TypePath,
|
||||
},
|
||||
{
|
||||
Name: "Invalid path",
|
||||
Value: true,
|
||||
Type: TypePath,
|
||||
ShouldFail: true,
|
||||
},
|
||||
{
|
||||
Name: "Missing slash",
|
||||
Value: "missing/slash",
|
||||
Type: TypePath,
|
||||
ShouldFail: true,
|
||||
},
|
||||
}
|
||||
|
||||
func TestAssertType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range valueTypeTestCases {
|
||||
func(tc valueTypeTestCase) {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := AssertType(tc.Value, tc.Type)
|
||||
if tc.ShouldFail && err == nil {
|
||||
t.Errorf("err should not be nil")
|
||||
}
|
||||
|
||||
if !tc.ShouldFail && err != nil {
|
||||
t.Errorf("err: expected nil, got '%+v'", errors.WithStack(err))
|
||||
}
|
||||
})
|
||||
}(tc)
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package util
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -26,3 +27,15 @@ func AssertObject(v goja.Value, r *goja.Runtime) map[string]any {
|
||||
func AssertString(v goja.Value, r *goja.Runtime) string {
|
||||
return AssertType[string](v, r)
|
||||
}
|
||||
|
||||
func AssertAppID(v goja.Value, r *goja.Runtime) app.ID {
|
||||
value := v.Export()
|
||||
switch typ := value.(type) {
|
||||
case string:
|
||||
return app.ID(typ)
|
||||
case app.ID:
|
||||
return typ
|
||||
default:
|
||||
panic(r.ToValue(errors.Errorf("expected value to be a string or app.ID, got '%T'", value)))
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import (
|
||||
|
||||
type BlobBucket struct {
|
||||
name string
|
||||
getDB getDBFunc
|
||||
getDB GetDBFunc
|
||||
closed bool
|
||||
}
|
||||
|
||||
@ -236,7 +236,7 @@ func (b *BlobBucket) withTx(ctx context.Context, fn func(tx *sql.Tx) error) erro
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := withTx(ctx, db, fn); err != nil {
|
||||
if err := WithTx(ctx, db, fn); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
@ -246,7 +246,7 @@ func (b *BlobBucket) withTx(ctx context.Context, fn func(tx *sql.Tx) error) erro
|
||||
type blobWriterCloser struct {
|
||||
id storage.BlobID
|
||||
bucket string
|
||||
getDB getDBFunc
|
||||
getDB GetDBFunc
|
||||
buf bytes.Buffer
|
||||
closed bool
|
||||
}
|
||||
@ -335,7 +335,7 @@ func (wbc *blobWriterCloser) withTx(ctx context.Context, fn func(tx *sql.Tx) err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := withTx(ctx, db, fn); err != nil {
|
||||
if err := WithTx(ctx, db, fn); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
@ -345,7 +345,7 @@ func (wbc *blobWriterCloser) withTx(ctx context.Context, fn func(tx *sql.Tx) err
|
||||
type blobReaderCloser struct {
|
||||
id storage.BlobID
|
||||
bucket string
|
||||
getDB getDBFunc
|
||||
getDB GetDBFunc
|
||||
reader bytes.Reader
|
||||
once sync.Once
|
||||
closed bool
|
||||
@ -444,7 +444,7 @@ func (brc *blobReaderCloser) withTx(ctx context.Context, fn func(tx *sql.Tx) err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := withTx(ctx, db, fn); err != nil {
|
||||
if err := WithTx(ctx, db, fn); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type BlobStore struct {
|
||||
getDB getDBFunc
|
||||
getDB GetDBFunc
|
||||
}
|
||||
|
||||
// DeleteBucket implements storage.BlobStore
|
||||
@ -81,7 +81,7 @@ func (s *BlobStore) OpenBucket(ctx context.Context, name string) (storage.BlobBu
|
||||
func ensureBlobTables(ctx context.Context, db *sql.DB) error {
|
||||
logger.Debug(ctx, "creating blobs table")
|
||||
|
||||
err := withTx(ctx, db, func(tx *sql.Tx) error {
|
||||
err := WithTx(ctx, db, func(tx *sql.Tx) error {
|
||||
query := `
|
||||
CREATE TABLE IF NOT EXISTS blobs (
|
||||
id TEXT,
|
||||
@ -114,7 +114,7 @@ func (s *BlobStore) withTx(ctx context.Context, fn func(tx *sql.Tx) error) error
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := withTx(ctx, db, fn); err != nil {
|
||||
if err := WithTx(ctx, db, fn); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
@ -122,13 +122,13 @@ func (s *BlobStore) withTx(ctx context.Context, fn func(tx *sql.Tx) error) error
|
||||
}
|
||||
|
||||
func NewBlobStore(dsn string) *BlobStore {
|
||||
getDB := newGetDBFunc(dsn, ensureBlobTables)
|
||||
getDB := NewGetDBFunc(dsn, ensureBlobTables)
|
||||
|
||||
return &BlobStore{getDB}
|
||||
}
|
||||
|
||||
func NewBlobStoreWithDB(db *sql.DB) *BlobStore {
|
||||
getDB := newGetDBFuncFromDB(db, ensureBlobTables)
|
||||
getDB := NewGetDBFuncFromDB(db, ensureBlobTables)
|
||||
|
||||
return &BlobStore{getDB}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
type DocumentStore struct {
|
||||
getDB getDBFunc
|
||||
getDB GetDBFunc
|
||||
}
|
||||
|
||||
// Delete implements storage.DocumentStore
|
||||
@ -269,7 +269,7 @@ func (s *DocumentStore) withTx(ctx context.Context, fn func(tx *sql.Tx) error) e
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := withTx(ctx, db, fn); err != nil {
|
||||
if err := WithTx(ctx, db, fn); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
@ -277,7 +277,7 @@ func (s *DocumentStore) withTx(ctx context.Context, fn func(tx *sql.Tx) error) e
|
||||
}
|
||||
|
||||
func ensureTables(ctx context.Context, db *sql.DB) error {
|
||||
err := withTx(ctx, db, func(tx *sql.Tx) error {
|
||||
err := WithTx(ctx, db, func(tx *sql.Tx) error {
|
||||
query := `
|
||||
CREATE TABLE IF NOT EXISTS documents (
|
||||
id TEXT PRIMARY KEY,
|
||||
@ -344,7 +344,7 @@ func withLimitOffsetClause(query string, args []any, limit int, offset int) (str
|
||||
}
|
||||
|
||||
func NewDocumentStore(path string) *DocumentStore {
|
||||
getDB := newGetDBFunc(path, ensureTables)
|
||||
getDB := NewGetDBFunc(path, ensureTables)
|
||||
|
||||
return &DocumentStore{
|
||||
getDB: getDB,
|
||||
@ -352,7 +352,7 @@ func NewDocumentStore(path string) *DocumentStore {
|
||||
}
|
||||
|
||||
func NewDocumentStoreWithDB(db *sql.DB) *DocumentStore {
|
||||
getDB := newGetDBFuncFromDB(db, ensureTables)
|
||||
getDB := NewGetDBFuncFromDB(db, ensureTables)
|
||||
|
||||
return &DocumentStore{
|
||||
getDB: getDB,
|
||||
|
@ -22,7 +22,7 @@ func Open(path string) (*sql.DB, error) {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func withTx(ctx context.Context, db *sql.DB, fn func(tx *sql.Tx) error) error {
|
||||
func WithTx(ctx context.Context, db *sql.DB, fn func(tx *sql.Tx) error) error {
|
||||
var tx *sql.Tx
|
||||
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
@ -70,9 +70,9 @@ func withTx(ctx context.Context, db *sql.DB, fn func(tx *sql.Tx) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type getDBFunc func(ctx context.Context) (*sql.DB, error)
|
||||
type GetDBFunc func(ctx context.Context) (*sql.DB, error)
|
||||
|
||||
func newGetDBFunc(dsn string, initFunc func(ctx context.Context, db *sql.DB) error) getDBFunc {
|
||||
func NewGetDBFunc(dsn string, initFunc func(ctx context.Context, db *sql.DB) error) GetDBFunc {
|
||||
var (
|
||||
db *sql.DB
|
||||
mutex sync.RWMutex
|
||||
@ -110,7 +110,7 @@ func newGetDBFunc(dsn string, initFunc func(ctx context.Context, db *sql.DB) err
|
||||
}
|
||||
}
|
||||
|
||||
func newGetDBFuncFromDB(db *sql.DB, initFunc func(ctx context.Context, db *sql.DB) error) getDBFunc {
|
||||
func NewGetDBFuncFromDB(db *sql.DB, initFunc func(ctx context.Context, db *sql.DB) error) GetDBFunc {
|
||||
var err error
|
||||
|
||||
initOnce := &sync.Once{}
|
||||
|
Reference in New Issue
Block a user